| // Copyright (c) 2016 The Rouille developers | |
| // Licensed under the Apache License, Version 2.0 | |
| // <LICENSE-APACHE or | |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT | |
| // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, | |
| // at your option. All files in the project carrying such | |
| // notice may not be copied, modified, or distributed except | |
| // according to those terms. | |
| //! Allows you to let an external process handle the request through CGI. | |
| //! | |
| //! This module provides a trait named `CgiRun` which is implemented on `std::process::Command`. | |
| //! In order to dispatch a request, simply start building a `Command` object and call `start_cgi` | |
| //! on it. | |
| //! | |
| //! ## Example | |
| //! | |
| //! ```no_run | |
| //! use std::process::Command; | |
| //! use rouille::cgi::CgiRun; | |
| //! | |
| //! rouille::start_server("localhost:8080", move |request| { | |
| //! Command::new("php-cgi").start_cgi(request).unwrap() | |
| //! }); | |
| //! ``` | |
| //! | |
| //! # About the Result returned by start_cgi | |
| //! | |
| //! The `start_cgi` method returns a `Result<Response, std::io::Error>`. This object will contain | |
| //! an error if and only if there was a problem executing the command (for example if it fails to | |
| //! start, or starts then crashes, ...). | |
| //! | |
| //! If the process returns an error 400 or an error 404 for example, then the result will contain | |
| //! `Ok`. | |
| //! | |
| //! It is therefore appropriate to simply call `.unwrap()` on that result. Any panic will be turned | |
| //! into an error 500 and add an entry to the logs, which is probably what you want when your | |
| //! server is misconfigured. | |
| use std::error; | |
| use std::fmt; | |
| use std::io; | |
| use std::io::Error as IoError; | |
| use std::io::BufRead; | |
| use std::io::Read; | |
| use std::process::Command; | |
| use std::process::Stdio; | |
| use Request; | |
| use Response; | |
| use ResponseBody; | |
| /// Error that can happen when parsing the JSON input. | |
| #[derive(Debug)] | |
| pub enum CgiError { | |
| /// Can't pass through the body of the request because it was already extracted. | |
| BodyAlreadyExtracted, | |
| /// Could not read the body from the request, or could not execute the CGI program. | |
| IoError(IoError), | |
| } | |
| impl From<IoError> for CgiError { | |
| fn from(err: IoError) -> CgiError { | |
| CgiError::IoError(err) | |
| } | |
| } | |
| impl error::Error for CgiError { | |
| #[inline] | |
| fn description(&self) -> &str { | |
| match *self { | |
| CgiError::BodyAlreadyExtracted => { | |
| "the body of the request was already extracted" | |
| }, | |
| CgiError::IoError(_) => { | |
| "could not read the body from the request, or could not execute the CGI program" | |
| }, | |
| } | |
| } | |
| #[inline] | |
| fn cause(&self) -> Option<&error::Error> { | |
| match *self { | |
| CgiError::IoError(ref e) => Some(e), | |
| _ => None | |
| } | |
| } | |
| } | |
| impl fmt::Display for CgiError { | |
| #[inline] | |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
| write!(fmt, "{}", error::Error::description(self)) | |
| } | |
| } | |
| pub trait CgiRun { | |
| /// Dispatches a request to the process. | |
| /// | |
| /// This function modifies the `Command` to add all the required environment variables | |
| /// and the request's body, then executes the command and waits until the child process has | |
| /// returned all the headers of the response. Once the headers have been sent back, this | |
| /// function returns. | |
| /// | |
| /// The body of the returned `Response` will hold a handle to the child's stdout output. This | |
| /// means that the child can continue running in the background and send data to the client, | |
| /// even after you have finished handling the request. | |
| fn start_cgi(self, request: &Request) -> Result<Response, CgiError>; | |
| } | |
| impl CgiRun for Command { | |
| fn start_cgi(mut self, request: &Request) -> Result<Response, CgiError> { | |
| self.env("SERVER_SOFTWARE", "rouille") | |
| .env("SERVER_NAME", "localhost") // FIXME: | |
| .env("GATEWAY_INTERFACE", "CGI/1.1") | |
| .env("SERVER_PROTOCOL", "HTTP/1.1") // FIXME: | |
| .env("SERVER_PORT", "80") // FIXME: | |
| .env("REQUEST_METHOD", request.method()) | |
| .env("PATH_INFO", &request.url()) // TODO: incorrect + what about PATH_TRANSLATED? | |
| .env("SCRIPT_NAME", "") // FIXME: | |
| .env("QUERY_STRING", request.raw_query_string()) | |
| .env("REMOTE_ADDR", &request.remote_addr().to_string()) | |
| .env("AUTH_TYPE", "") // FIXME: | |
| .env("REMOTE_USER", "") // FIXME: | |
| .env("CONTENT_TYPE", &request.header("Content-Type").unwrap_or("")) | |
| .env("CONTENT_LENGTH", &request.header("Content-Length").unwrap_or("")) | |
| .stdout(Stdio::piped()) | |
| .stderr(Stdio::inherit()) | |
| .stdin(Stdio::piped()); | |
| // TODO: `HTTP_` env vars with the headers | |
| let mut child = try!(self.spawn()); | |
| if let Some(mut body) = request.data() { | |
| try!(io::copy(&mut body, child.stdin.as_mut().unwrap())); | |
| } else { | |
| return Err(CgiError::BodyAlreadyExtracted); | |
| } | |
| let response = { | |
| let mut stdout = io::BufReader::new(child.stdout.take().unwrap()); | |
| let mut headers = Vec::new(); | |
| let mut status_code = 200; | |
| for header in stdout.by_ref().lines() { | |
| let header = try!(header); | |
| if header.is_empty() { break; } | |
| let mut splits = header.splitn(2, ':'); | |
| let header = splits.next().unwrap(); // TODO: return Err instead? | |
| let val = splits.next().unwrap(); // TODO: return Err instead? | |
| let val = &val[1..]; | |
| if header == "Status" { | |
| status_code = val[0..3].parse().expect("Status returned by CGI program is invalid"); | |
| } else { | |
| headers.push((header.to_owned().into(), val.to_owned().into())); | |
| } | |
| } | |
| Response { | |
| status_code, | |
| headers, | |
| data: ResponseBody::from_reader(stdout), | |
| upgrade: None, | |
| } | |
| }; | |
| Ok(response) | |
| } | |
| } |