// Copyright (c) 2020 Teddy Wing // // This file is part of FastCGI-Conduit. // // FastCGI-Conduit is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // FastCGI-Conduit is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with FastCGI-Conduit. If not, see . use std::io; use std::io::Write; use conduit::Handler; use log::error; use snafu::{ResultExt, Snafu}; use crate::request; /// Wraps server errors. #[derive(Debug, Snafu)] pub enum Error { /// I/O write errors during response output. #[snafu(context(false))] Write { source: io::Error }, /// Error building the request into a [`FastCgiRequest`][FastCgiRequest]. /// /// [FastCgiRequest]: ../request/struct.FastCgiRequest.html #[snafu(display("Couldn't build request: {}", source))] RequestBuilder { source: request::Error }, /// Error building a [`conduit::Response`][conduit::Response]. /// /// [conduit::Response]: ../../conduit/struct.Response.html #[snafu(display("Couldn't parse response: {}", source))] ConduitResponse { source: conduit::BoxError }, } /// The application server that interfaces with FastCGI. pub struct Server; impl Server { /// Start the server. /// /// Start the main [`fastcgi::run`][fastcgi::run] process to listen for /// requests and handle them using `handler`. /// /// [fastcgi::run]: ../../fastcgi/fn.run.html pub fn start(handler: H) -> 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()) }, } } }); Server{} } } /// Given a raw FastCGI request and a Conduit handler, get a response from the /// handler to write to a FastCGI response. fn handle_request( 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, "Status: {}\r\n", head.status.as_str(), )?; 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(()) } /// Write a 500 internal server error to `w`. fn internal_server_error(mut w: W) { let code = conduit::StatusCode::INTERNAL_SERVER_ERROR; write!( w, "Status: {}\r\n{}\r\n\r\n", code, "Content-Length: 0", ) .unwrap_or_else(|e| error!("Write error: {}", e)) }