aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs85
-rw-r--r--src/request.rs235
-rw-r--r--src/server.rs115
3 files changed, 355 insertions, 80 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 790a8f6..2ffe77e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,84 +1,9 @@
+extern crate conduit;
extern crate fastcgi;
extern crate http;
+extern crate log;
-use std::io::{BufReader, Write};
+mod request;
+mod server;
-use http::{Request, Response};
-use http::request;
-use inflector::cases::traincase::to_train_case;
-
-
-pub fn run<F, T>(handler: F)
-where F: Fn(Request<()>) -> Response<T> + Send + Sync + 'static
-{
- fastcgi::run(move |mut req| {
- let r: http::request::Builder = From::from(&req);
-
- handler(r.body(()).unwrap());
-
- let params = req.params()
- .map(|(k, v)| k + ": " + &v)
- .collect::<Vec<String>>()
- .join("\n");
-
- write!(
- &mut req.stdout(),
- "Content-Type: text/plain\n\n{}",
- params
- )
- .unwrap_or(());
- });
-}
-
-trait From<T>: Sized {
- fn from(_: T) -> Self;
-}
-
-impl From<&fastcgi::Request> for http::request::Builder {
- fn from(request: &fastcgi::Request) -> Self {
- let method = request.param("REQUEST_METHOD")
- .unwrap_or("".to_owned());
-
- let uri = format!(
- "{}://{}{}",
- request.param("REQUEST_SCHEME").unwrap_or("".to_owned()),
- request.param("HTTP_HOST").unwrap_or("".to_owned()),
- request.param("REQUEST_URI").unwrap_or("".to_owned()),
- );
-
- let mut http_request = http::request::Builder::new()
- .method(&*method)
- .uri(&uri);
-
- let headers = headers_from_params(request.params());
- for (k, v) in headers {
- http_request = http_request.header(&k, &v);
- }
-
- // TODO: Add request body
-
- http_request
-
- // let body = BufReader::new(request.stdin());
- //
- // http_request.body(body)
-
- // HTTP_* params become headers
- }
-}
-
-fn headers_from_params(params: fastcgi::Params) -> Vec<(String, String)> {
- return params
- .filter(|(key, _)| key.starts_with("HTTP_"))
- .map(|(key, value)| {
- let mut key = key.get(5..).unwrap_or("").to_owned();
- key = key.replace("_", "-");
- key = to_train_case(&key);
-
- // Change _ to -
- // Uppercase each word
-
- (key, value)
- })
- .collect()
-}
+pub use server::Server;
diff --git a/src/request.rs b/src/request.rs
new file mode 100644
index 0000000..2d90ea9
--- /dev/null
+++ b/src/request.rs
@@ -0,0 +1,235 @@
+use std::io;
+use std::io::Read;
+use std::net::SocketAddr;
+
+use inflector::cases::traincase::to_train_case;
+
+use snafu::{ResultExt, Snafu};
+
+
+#[derive(Debug, Snafu)]
+pub enum Error {
+ #[snafu(display("{}", source))]
+ InvalidMethod { source: http::method::InvalidMethod },
+
+ #[snafu(display("{}", source))]
+ InvalidHeaderName { source: conduit::header::InvalidHeaderName },
+
+ #[snafu(display("{}", source))]
+ InvalidHeaderValue { source: conduit::header::InvalidHeaderValue },
+
+ #[snafu(display("{}", source))]
+ InvalidRemoteAddr { source: RemoteAddrError },
+}
+
+pub type RequestResult<T, E = Error> = std::result::Result<T, E>;
+
+#[derive(Debug, Snafu)]
+pub enum RemoteAddrError {
+ #[snafu(display("Could not parse address {}: {}", address, source))]
+ AddrParseError {
+ address: String,
+ source: std::net::AddrParseError,
+ },
+
+ #[snafu(display("Could not parse port {}: {}", port, source))]
+ PortParseError {
+ port: String,
+ source: std::num::ParseIntError
+ },
+}
+
+
+pub struct FastCgiRequest<'a> {
+ request: &'a mut fastcgi::Request,
+ http_version: conduit::Version,
+ host: String,
+ method: conduit::Method,
+ headers: conduit::HeaderMap,
+ path: String,
+ query: Option<String>,
+ remote_addr: SocketAddr,
+ content_length: Option<u64>,
+ extensions: conduit::Extensions,
+}
+
+impl<'a> FastCgiRequest<'a> {
+ pub fn new(request: &'a mut fastcgi::Request) -> RequestResult<Self> {
+ let version = Self::version(request);
+ let host = Self::host(request);
+ let method = Self::method(request).context(InvalidMethod)?;
+ let headers = Self::headers(request.params())?;
+ let path = Self::path(request);
+ let query = Self::query(request);
+ let remote_addr = Self::remote_addr(request).context(InvalidRemoteAddr)?;
+ let content_length = Self::content_length(request);
+
+ Ok(Self {
+ request: request,
+ http_version: version,
+ host: host,
+ method: method,
+ headers: headers,
+ path: path,
+ query: query,
+ remote_addr: remote_addr,
+ content_length: content_length,
+ extensions: conduit::TypeMap::new(),
+ })
+ }
+
+ fn version(request: &fastcgi::Request) -> conduit::Version {
+ match request.param("SERVER_PROTOCOL").unwrap_or_default().as_str() {
+ "HTTP/0.9" => conduit::Version::HTTP_09,
+ "HTTP/1.0" => conduit::Version::HTTP_10,
+ "HTTP/1.1" => conduit::Version::HTTP_11,
+ "HTTP/2.0" => conduit::Version::HTTP_2,
+ "HTTP/3.0" => conduit::Version::HTTP_3,
+ _ => conduit::Version::default(),
+ }
+ }
+
+ fn scheme(&self) -> conduit::Scheme {
+ let scheme = self.request.param("REQUEST_SCHEME").unwrap_or_default();
+
+ if scheme == "https" {
+ conduit::Scheme::Https
+ } else {
+ conduit::Scheme::Http
+ }
+ }
+
+ fn host(request: &fastcgi::Request) -> String {
+ request.param("HTTP_HOST").unwrap_or_default()
+ }
+
+ fn method(
+ request: &fastcgi::Request
+ ) -> Result<conduit::Method, http::method::InvalidMethod> {
+ conduit::Method::from_bytes(
+ request.param("REQUEST_METHOD")
+ .unwrap_or_default()
+ .as_bytes()
+ )
+ }
+
+ fn headers(params: fastcgi::Params) -> RequestResult<conduit::HeaderMap> {
+ let mut map = conduit::HeaderMap::new();
+ let headers = Self::headers_from_params(params);
+
+ for (name, value) in headers
+ .iter()
+ .map(|(name, value)| (name.as_bytes(), value.as_bytes()))
+ {
+ map.append(
+ conduit::header::HeaderName::from_bytes(name)
+ .context(InvalidHeaderName)?,
+ conduit::header::HeaderValue::from_bytes(value)
+ .context(InvalidHeaderValue)?,
+ );
+ }
+
+ Ok(map)
+ }
+
+ fn headers_from_params(params: fastcgi::Params) -> Vec<(String, String)> {
+ return params
+ .filter(|(key, _)| key.starts_with("HTTP_"))
+ .map(|(key, value)| {
+ let key = key.get(5..).unwrap_or_default();
+ let key = &key.replace("_", "-");
+ let key = &to_train_case(&key);
+
+ (key.to_owned(), value)
+ })
+ .collect()
+ }
+
+ fn path(request: &fastcgi::Request) -> String {
+ match request.param("SCRIPT_NAME") {
+ Some(p) => p,
+ None => "/".to_owned(),
+ }
+ }
+
+ fn query(request: &fastcgi::Request) -> Option<String> {
+ request.param("QUERY_STRING")
+ }
+
+ fn remote_addr(request: &fastcgi::Request) -> Result<SocketAddr, RemoteAddrError> {
+ let addr = request.param("REMOTE_ADDR").unwrap_or_default();
+ let port = request.param("REMOTE_PORT").unwrap_or_default();
+
+ Ok(
+ SocketAddr::new(
+ addr.parse().context(AddrParseError { address: addr })?,
+ port.parse().context(PortParseError { port })?,
+ )
+ )
+ }
+
+ fn content_length(request: &fastcgi::Request) -> Option<u64> {
+ request.param("CONTENT_LENGTH").and_then(|l| l.parse().ok())
+ }
+}
+
+impl<'a> Read for FastCgiRequest<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.request.stdin().read(buf)
+ }
+}
+
+impl<'a> conduit::RequestExt for FastCgiRequest<'a> {
+ fn http_version(&self) -> conduit::Version {
+ self.http_version
+ }
+
+ fn method(&self) -> &conduit::Method {
+ &self.method
+ }
+
+ fn scheme(&self) -> conduit::Scheme {
+ self.scheme()
+ }
+
+ fn host(&self) -> conduit::Host<'_> {
+ conduit::Host::Name(&self.host)
+ }
+
+ fn virtual_root(&self) -> std::option::Option<&str> {
+ None
+ }
+
+ fn path(&self) -> &str {
+ &self.path
+ }
+
+ fn query_string(&self) -> std::option::Option<&str> {
+ self.query.as_ref()
+ .map(|p| p.as_str())
+ }
+
+ fn remote_addr(&self) -> std::net::SocketAddr {
+ self.remote_addr
+ }
+
+ fn content_length(&self) -> std::option::Option<u64> {
+ self.content_length
+ }
+
+ fn headers(&self) -> &conduit::HeaderMap {
+ &self.headers
+ }
+
+ fn body(&mut self) -> &mut (dyn std::io::Read) {
+ self
+ }
+
+ fn extensions(&self) -> &conduit::Extensions {
+ &self.extensions
+ }
+
+ fn mut_extensions(&mut self) -> &mut conduit::Extensions {
+ &mut self.extensions
+ }
+}
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))
+}