// 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) | |
} | |
} |