rouille/cgi.rs
1// Copyright (c) 2016 The Rouille developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! Allows you to let an external process handle the request through CGI.
11//!
12//! This module provides a trait named `CgiRun` which is implemented on `std::process::Command`.
13//! In order to dispatch a request, simply start building a `Command` object and call `start_cgi`
14//! on it.
15//!
16//! ## Example
17//!
18//! ```no_run
19//! use std::process::Command;
20//! use rouille::cgi::CgiRun;
21//!
22//! rouille::start_server("localhost:8080", move |request| {
23//! Command::new("php-cgi").start_cgi(request).unwrap()
24//! });
25//! ```
26//!
27//! # About the Result returned by start_cgi
28//!
29//! The `start_cgi` method returns a `Result<Response, std::io::Error>`. This object will contain
30//! an error if and only if there was a problem executing the command (for example if it fails to
31//! start, or starts then crashes, ...).
32//!
33//! If the process returns an error 400 or an error 404 for example, then the result will contain
34//! `Ok`.
35//!
36//! It is therefore appropriate to simply call `.unwrap()` on that result. Any panic will be turned
37//! into an error 500 and add an entry to the logs, which is probably what you want when your
38//! server is misconfigured.
39
40use std::error;
41use std::fmt;
42use std::io;
43use std::io::BufRead;
44use std::io::Error as IoError;
45use std::io::Read;
46use std::process::Command;
47use std::process::Stdio;
48
49use Request;
50use Response;
51use ResponseBody;
52
53/// Error that can happen when parsing the JSON input.
54#[derive(Debug)]
55pub enum CgiError {
56 /// Can't pass through the body of the request because it was already extracted.
57 BodyAlreadyExtracted,
58
59 /// Could not read the body from the request, or could not execute the CGI program.
60 IoError(IoError),
61}
62
63impl From<IoError> for CgiError {
64 fn from(err: IoError) -> CgiError {
65 CgiError::IoError(err)
66 }
67}
68
69impl error::Error for CgiError {
70 #[inline]
71 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
72 match *self {
73 CgiError::IoError(ref e) => Some(e),
74 _ => None,
75 }
76 }
77}
78
79impl fmt::Display for CgiError {
80 #[inline]
81 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
82 let description = match *self {
83 CgiError::BodyAlreadyExtracted => "the body of the request was already extracted",
84 CgiError::IoError(_) => {
85 "could not read the body from the request, or could not execute the CGI program"
86 }
87 };
88
89 write!(fmt, "{}", description)
90 }
91}
92
93pub trait CgiRun {
94 /// Dispatches a request to the process.
95 ///
96 /// This function modifies the `Command` to add all the required environment variables
97 /// and the request's body, then executes the command and waits until the child process has
98 /// returned all the headers of the response. Once the headers have been sent back, this
99 /// function returns.
100 ///
101 /// The body of the returned `Response` will hold a handle to the child's stdout output. This
102 /// means that the child can continue running in the background and send data to the client,
103 /// even after you have finished handling the request.
104 fn start_cgi(self, request: &Request) -> Result<Response, CgiError>;
105}
106
107impl CgiRun for Command {
108 fn start_cgi(mut self, request: &Request) -> Result<Response, CgiError> {
109 self.env("SERVER_SOFTWARE", "rouille")
110 .env("SERVER_NAME", "localhost") // FIXME:
111 .env("GATEWAY_INTERFACE", "CGI/1.1")
112 .env("SERVER_PROTOCOL", "HTTP/1.1") // FIXME:
113 .env("SERVER_PORT", "80") // FIXME:
114 .env("REQUEST_METHOD", request.method())
115 .env("PATH_INFO", &request.url()) // TODO: incorrect + what about PATH_TRANSLATED?
116 .env("SCRIPT_NAME", "") // FIXME:
117 .env("QUERY_STRING", request.raw_query_string())
118 .env("REMOTE_ADDR", &request.remote_addr().to_string())
119 .env("AUTH_TYPE", "") // FIXME:
120 .env("REMOTE_USER", "") // FIXME:
121 .env(
122 "CONTENT_TYPE",
123 &request.header("Content-Type").unwrap_or(""),
124 )
125 .env(
126 "CONTENT_LENGTH",
127 &request.header("Content-Length").unwrap_or(""),
128 )
129 .stdout(Stdio::piped())
130 .stderr(Stdio::inherit())
131 .stdin(Stdio::piped());
132
133 // TODO: `HTTP_` env vars with the headers
134
135 let mut child = self.spawn()?;
136
137 if let Some(mut body) = request.data() {
138 io::copy(&mut body, child.stdin.as_mut().unwrap())?;
139 } else {
140 return Err(CgiError::BodyAlreadyExtracted);
141 }
142
143 let response = {
144 let mut stdout = io::BufReader::new(child.stdout.take().unwrap());
145
146 let mut headers = Vec::new();
147 let mut status_code = 200;
148 for header in stdout.by_ref().lines() {
149 let header = header?;
150 if header.is_empty() {
151 break;
152 }
153
154 let (header, val) = header.split_once(':').unwrap();
155 let val = &val[1..];
156
157 if header == "Status" {
158 status_code = val[0..3]
159 .parse()
160 .expect("Status returned by CGI program is invalid");
161 } else {
162 headers.push((header.to_owned().into(), val.to_owned().into()));
163 }
164 }
165
166 Response {
167 status_code,
168 headers,
169 data: ResponseBody::from_reader(stdout),
170 upgrade: None,
171 }
172 };
173
174 Ok(response)
175 }
176}