diff options
Diffstat (limited to 'src/server.rs')
-rw-r--r-- | src/server.rs | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..948a470 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,115 @@ +use std::io; +use std::io::Write; + +use conduit::Handler; + +use log::error; + +use snafu::{ResultExt, Snafu}; + +use crate::request; + + +const HTTP_VERSION: &'static str = "HTTP/1.1"; + + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(context(false))] + Write { source: io::Error }, + + #[snafu(display("Couldn't build request: {}", source))] + RequestBuilder { source: request::Error }, + + #[snafu(display("Couldn't parse response: {}", source))] + ConduitResponse { source: conduit::BoxError }, +} + + +pub struct Server; + +impl Server { + pub fn start<H: Handler + 'static + Sync>(handler: H) -> io::Result<Server> { + fastcgi::run(move |mut raw_request| { + match handle_request(&mut raw_request, &handler) { + Ok(_) => (), + Err(e) => match e { + // Ignore write errors as clients will have closed the + // connection by this point. + Error::Write { .. } => error!("Write error: {}", e), + + Error::RequestBuilder { .. } => { + error!("Unable to build request: {}", e); + + internal_server_error(&mut raw_request.stdout()) + }, + Error::ConduitResponse { .. } => { + error!("Error getting response: {}", e); + + internal_server_error(&mut raw_request.stdout()) + }, + } + } + }); + + Ok(Server{}) + } +} + +fn handle_request<H>( + mut raw_request: &mut fastcgi::Request, + handler: &H, +) -> Result<(), Error> +where H: Handler + 'static + Sync +{ + let mut request = request::FastCgiRequest::new(&mut raw_request) + .context(RequestBuilder)?; + let response = handler.call(&mut request); + + let mut stdout = raw_request.stdout(); + + let (head, body) = response + .context(ConduitResponse)? + .into_parts(); + + write!( + &mut stdout, + "{} {} {}\r\n", + HTTP_VERSION, + head.status.as_str(), + head.status.canonical_reason().unwrap_or("UNKNOWN"), + )?; + + for (name, value) in head.headers.iter() { + write!(&mut stdout, "{}: ", name)?; + stdout.write(value.as_bytes())?; + stdout.write(b"\r\n")?; + } + + stdout.write(b"\r\n")?; + + match body { + conduit::Body::Static(slice) => + stdout.write(slice).map(|_| ())?, + conduit::Body::Owned(vec) => + stdout.write(&vec).map(|_| ())?, + conduit::Body::File(mut file) => + io::copy(&mut file, &mut stdout).map(|_| ())?, + }; + + Ok(()) +} + +fn internal_server_error<W: Write>(mut w: W) { + let code = conduit::StatusCode::INTERNAL_SERVER_ERROR; + + write!( + w, + "{} {} {}\r\n{}\r\n\r\n", + HTTP_VERSION, + code, + code.canonical_reason().unwrap_or_default(), + "Content-Length: 0", + ) + .unwrap_or_else(|e| error!("Write error: {}", e)) +} |