From a99b8b984180d7089006ef9f92259984be4f74ef Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 17:15:10 +0200 Subject: Create a wrapper struct for `fastcgi::Request` Trying out the Conduit API, as it seems like nice interface that I can plug the FastCGI server into. To do this, I need a server type, and some way to convert a `fastcgi::Request` into a `conduit::Request`. Doing this with a new local type that I can use to implement `conduit::RequestExt`. --- src/lib.rs | 133 +++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 73 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 790a8f6..6253bf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,84 +1,97 @@ +extern crate conduit; extern crate fastcgi; extern crate http; +use std::io; use std::io::{BufReader, Write}; -use http::{Request, Response}; -use http::request; +use conduit::Handler; + use inflector::cases::traincase::to_train_case; +use snafu::{ResultExt, Snafu}; -pub fn run(handler: F) -where F: Fn(Request<()>) -> Response + Send + Sync + 'static -{ - fastcgi::run(move |mut req| { - let r: http::request::Builder = From::from(&req); - handler(r.body(()).unwrap()); +#[derive(Debug, Snafu)] +pub enum RequestError { + #[snafu(display("{}", source))] + InvalidMethod { source: http::method::InvalidMethod }, +} - let params = req.params() - .map(|(k, v)| k + ": " + &v) - .collect::>() - .join("\n"); +pub type RequestResult = std::result::Result; - write!( - &mut req.stdout(), - "Content-Type: text/plain\n\n{}", - params - ) - .unwrap_or(()); - }); -} -trait From: Sized { - fn from(_: T) -> Self; +struct FastCgiRequest<'a> { + request: &'a fastcgi::Request, + method: conduit::Method, } -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()), - ); +impl<'a> FastCgiRequest<'a> { + pub fn new(request: &'a fastcgi::Request) -> RequestResult { + let method = Self::method(request) + .context(InvalidMethod)?; - let mut http_request = http::request::Builder::new() - .method(&*method) - .uri(&uri); + let r = Self { + request: request, + method: method, + }; - let headers = headers_from_params(request.params()); - for (k, v) in headers { - http_request = http_request.header(&k, &v); - } + r.parse(); - // TODO: Add request body + Ok(r) + } - http_request + fn parse(&self) { + let headers = Self::headers_from_params(self.request.params()); + } - // let body = BufReader::new(request.stdin()); - // - // http_request.body(body) + fn method( + request: &'a fastcgi::Request + ) -> Result { + conduit::Method::from_bytes( + request.param("REQUEST_METHOD") + .unwrap_or_default() + .as_bytes() + ) + } - // 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 key = key.get(5..).unwrap_or_default(); + let key = &key.replace("_", "-"); + let key = &to_train_case(&key); + + (key.to_owned(), value) + }) + .collect() } } -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() +// impl<'a> conduit::RequestExt for FastCgiRequest { +// fn http_version(&self) -> conduit::Version { todo!() } +// fn method(&self) -> &conduit::Method { +// self.method +// } +// fn scheme(&self) -> conduit::Scheme { todo!() } +// fn host(&'a self) -> conduit::Host<'a> { todo!() } +// fn virtual_root(&'a self) -> std::option::Option<&'a str> { todo!() } +// fn path(&'a self) -> &'a str { todo!() } +// fn query_string(&'a self) -> std::option::Option<&'a str> { todo!() } +// fn remote_addr(&self) -> std::net::SocketAddr { todo!() } +// fn content_length(&self) -> std::option::Option { todo!() } +// fn headers(&self) -> &conduit::HeaderMap { todo!() } +// fn body(&'a mut self) -> &'a mut (dyn std::io::Read + 'a) { todo!() } +// fn extensions(&'a self) -> &'a conduit::TypeMap { todo!() } +// fn mut_extensions(&'a mut self) -> &'a mut conduit::TypeMap { todo!() } +// } + + +struct Server; + +impl Server { + pub fn start(handler: H) -> io::Result { + Ok(Server{}) + } } -- cgit v1.2.3 From 13cc04731d01ce216b274f53c920f9e19d15e25e Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 18:32:41 +0200 Subject: FastCgiRequest: Parse headers to a `conduit::HeaderMap` --- src/lib.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 6253bf0..a8f55a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,12 @@ use snafu::{ResultExt, Snafu}; pub enum RequestError { #[snafu(display("{}", source))] InvalidMethod { source: http::method::InvalidMethod }, + + #[snafu(display("{}", source))] + InvalidHeaderName { source: conduit::header::InvalidHeaderName }, + + #[snafu(display("{}", source))] + InvalidHeaderValue { source: conduit::header::InvalidHeaderValue }, } pub type RequestResult = std::result::Result; @@ -24,6 +30,7 @@ pub type RequestResult = std::result::Result; struct FastCgiRequest<'a> { request: &'a fastcgi::Request, method: conduit::Method, + headers: conduit::HeaderMap, } impl<'a> FastCgiRequest<'a> { @@ -31,20 +38,17 @@ impl<'a> FastCgiRequest<'a> { let method = Self::method(request) .context(InvalidMethod)?; + let headers = Self::headers(request.params())?; + let r = Self { request: request, method: method, + headers: headers, }; - r.parse(); - Ok(r) } - fn parse(&self) { - let headers = Self::headers_from_params(self.request.params()); - } - fn method( request: &'a fastcgi::Request ) -> Result { @@ -55,6 +59,25 @@ impl<'a> FastCgiRequest<'a> { ) } + fn headers(params: fastcgi::Params) -> RequestResult { + 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_")) -- cgit v1.2.3 From a078391aad95978f11488040bb794f2b7622311f Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:05:53 +0200 Subject: FastCgiRequest: Add `conduit::Version` --- src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index a8f55a5..0229980 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ pub type RequestResult = std::result::Result; struct FastCgiRequest<'a> { request: &'a fastcgi::Request, + http_version: conduit::Version, method: conduit::Method, headers: conduit::HeaderMap, } @@ -42,6 +43,7 @@ impl<'a> FastCgiRequest<'a> { let r = Self { request: request, + http_version: Self::version(&request), method: method, headers: headers, }; @@ -49,6 +51,17 @@ impl<'a> FastCgiRequest<'a> { Ok(r) } + fn version(request: &'a 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 method( request: &'a fastcgi::Request ) -> Result { -- cgit v1.2.3 From 5fd74d9b6356a3feebd6c1bff134e2f411c7c1dd Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:06:34 +0200 Subject: FastCgiRequest: Add `conduit::Scheme` --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 0229980..16cfd02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,16 @@ impl<'a> FastCgiRequest<'a> { Ok(r) } + pub 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 version(request: &'a fastcgi::Request) -> conduit::Version { match request.param("SERVER_PROTOCOL").unwrap_or_default().as_str() { "HTTP/0.9" => conduit::Version::HTTP_09, -- cgit v1.2.3 From b8ac72f1fb4b6e3e0575e212bf6138e20bd9bdff Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:07:07 +0200 Subject: FastCgiRequest: Add HTTP host --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 16cfd02..d237bab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ pub type RequestResult = std::result::Result; struct FastCgiRequest<'a> { request: &'a fastcgi::Request, http_version: conduit::Version, + host: String, method: conduit::Method, headers: conduit::HeaderMap, } @@ -44,6 +45,7 @@ impl<'a> FastCgiRequest<'a> { let r = Self { request: request, http_version: Self::version(&request), + host: Self::host(&request), method: method, headers: headers, }; @@ -61,6 +63,10 @@ impl<'a> FastCgiRequest<'a> { } } + fn host(request: &'a fastcgi::Request) -> String { + request.param("HTTP_HOST").unwrap_or_default() + } + fn version(request: &'a fastcgi::Request) -> conduit::Version { match request.param("SERVER_PROTOCOL").unwrap_or_default().as_str() { "HTTP/0.9" => conduit::Version::HTTP_09, -- cgit v1.2.3 From 81402cc311ae075d036759cabab829f61fd216be Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:07:22 +0200 Subject: FastCgiRequest: Start implementing `conduit::RequestExt` Was getting this error about the lifetimes in the trait impl: error[E0308]: method not compatible with trait --> src/lib.rs:137:4 | 137 | fn host(&'a self) -> conduit::Host<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch | = note: expected fn pointer `fn(&'a FastCgiRequest<'static>) -> conduit::Host<'a>` found fn pointer `fn(&'a FastCgiRequest<'static>) -> conduit::Host<'a>` note: the lifetime `'a` as defined on the method body at 137:4... --> src/lib.rs:137:4 | 137 | fn host(&'a self) -> conduit::Host<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: ...does not necessarily outlive the lifetime `'a` as defined on the impl at 124:6 --> src/lib.rs:124:6 | 124 | impl<'a> conduit::RequestExt for FastCgiRequest { | ^^ error[E0308]: method not compatible with trait --> src/lib.rs:137:4 | 137 | fn host(&'a self) -> conduit::Host<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch | = note: expected fn pointer `fn(&'a FastCgiRequest<'static>) -> conduit::Host<'a>` found fn pointer `fn(&'a FastCgiRequest<'static>) -> conduit::Host<'a>` note: the lifetime `'a` as defined on the impl at 124:6... --> src/lib.rs:124:6 | 124 | impl<'a> conduit::RequestExt for FastCgiRequest { | ^^ note: ...does not necessarily outlive the lifetime `'a` as defined on the method body at 137:4 --> src/lib.rs:137:4 | 137 | fn host(&'a self) -> conduit::Host<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Learned from these resources that the problem arose because the `impl` uses an `'a` lifetime, but the lifetime on the `host()` method need to be a different one: https://github.com/rust-lang/rust/issues/56423 https://stackoverflow.com/questions/24847331/rust-lifetime-error-expected-concrete-lifetime-but-found-bound-lifetime/24848424#24848424 --- src/lib.rs | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index d237bab..98dcba7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,23 +121,34 @@ impl<'a> FastCgiRequest<'a> { } } -// impl<'a> conduit::RequestExt for FastCgiRequest { -// fn http_version(&self) -> conduit::Version { todo!() } -// fn method(&self) -> &conduit::Method { -// self.method -// } -// fn scheme(&self) -> conduit::Scheme { todo!() } -// fn host(&'a self) -> conduit::Host<'a> { todo!() } -// fn virtual_root(&'a self) -> std::option::Option<&'a str> { todo!() } -// fn path(&'a self) -> &'a str { todo!() } -// fn query_string(&'a self) -> std::option::Option<&'a str> { todo!() } -// fn remote_addr(&self) -> std::net::SocketAddr { todo!() } -// fn content_length(&self) -> std::option::Option { todo!() } -// fn headers(&self) -> &conduit::HeaderMap { todo!() } -// fn body(&'a mut self) -> &'a mut (dyn std::io::Read + 'a) { todo!() } -// fn extensions(&'a self) -> &'a conduit::TypeMap { todo!() } -// fn mut_extensions(&'a mut self) -> &'a mut conduit::TypeMap { todo!() } -// } +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<'b>(&'b self) -> conduit::Host<'b> { + conduit::Host::Name(&self.host) + } + + fn virtual_root<'b>(&'b self) -> std::option::Option<&'b str> { todo!() } + + fn path<'b>(&'b self) -> &'b str { todo!() } + fn query_string<'b>(&'b self) -> std::option::Option<&'b str> { todo!() } + fn remote_addr<'b>(&self) -> std::net::SocketAddr { todo!() } + fn content_length<'b>(&self) -> std::option::Option { todo!() } + fn headers<'b>(&self) -> &conduit::HeaderMap { todo!() } + fn body<'b>(&'b mut self) -> &'b mut (dyn std::io::Read + 'b) { todo!() } + fn extensions<'b>(&'b self) -> &'b conduit::TypeMap { todo!() } + fn mut_extensions<'b>(&'b mut self) -> &'b mut conduit::TypeMap { todo!() } +} struct Server; -- cgit v1.2.3 From 21bbc6274117cd4b9a15dda63de284e8f9bad34d Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:45:07 +0200 Subject: FastCgiRequest: Remove elidable `'b` lifetime in `conduit::RequestExt` Turns out I don't need this `'b` lifetime in the `conduit::RequestExt` `impl`. I had just assumed it was necessary because I copied it as `'a` from the "missing trait methods" error message. --- src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 98dcba7..21ecf31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,20 +134,20 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { self.scheme() } - fn host<'b>(&'b self) -> conduit::Host<'b> { + fn host(&self) -> conduit::Host<'_> { conduit::Host::Name(&self.host) } - fn virtual_root<'b>(&'b self) -> std::option::Option<&'b str> { todo!() } + fn virtual_root(&self) -> std::option::Option<&str> { todo!() } - fn path<'b>(&'b self) -> &'b str { todo!() } - fn query_string<'b>(&'b self) -> std::option::Option<&'b str> { todo!() } - fn remote_addr<'b>(&self) -> std::net::SocketAddr { todo!() } - fn content_length<'b>(&self) -> std::option::Option { todo!() } - fn headers<'b>(&self) -> &conduit::HeaderMap { todo!() } - fn body<'b>(&'b mut self) -> &'b mut (dyn std::io::Read + 'b) { todo!() } - fn extensions<'b>(&'b self) -> &'b conduit::TypeMap { todo!() } - fn mut_extensions<'b>(&'b mut self) -> &'b mut conduit::TypeMap { todo!() } + fn path(&self) -> &str { todo!() } + fn query_string(&self) -> std::option::Option<&str> { todo!() } + fn remote_addr(&self) -> std::net::SocketAddr { todo!() } + fn content_length(&self) -> std::option::Option { todo!() } + fn headers(&self) -> &conduit::HeaderMap { todo!() } + fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } + fn extensions(&self) -> &conduit::TypeMap { todo!() } + fn mut_extensions(&mut self) -> &mut conduit::TypeMap { todo!() } } -- cgit v1.2.3 From 298d4555f9f5a0a322681e380a1d686972bfbf27 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 20:47:04 +0200 Subject: FastCgiRequest: Use a `None` `virtual_root` Looks like we don't need to implement this hopefully. --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 21ecf31..ca0fb83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,9 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { conduit::Host::Name(&self.host) } - fn virtual_root(&self) -> std::option::Option<&str> { todo!() } + fn virtual_root(&self) -> std::option::Option<&str> { + None + } fn path(&self) -> &str { todo!() } fn query_string(&self) -> std::option::Option<&str> { todo!() } -- cgit v1.2.3 From cb1ffca05c679f41490a09fc9e107da707858068 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 21:08:58 +0200 Subject: FastCgiRequest: Add request path --- src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index ca0fb83..f481f59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ struct FastCgiRequest<'a> { host: String, method: conduit::Method, headers: conduit::HeaderMap, + path: String, } impl<'a> FastCgiRequest<'a> { @@ -48,6 +49,7 @@ impl<'a> FastCgiRequest<'a> { host: Self::host(&request), method: method, headers: headers, + path: Self::path(&request), }; Ok(r) @@ -119,6 +121,13 @@ impl<'a> FastCgiRequest<'a> { }) .collect() } + + fn path(request: &'a fastcgi::Request) -> String { + match request.param("SCRIPT_NAME") { + Some(p) => p, + None => "/".to_owned(), + } + } } impl<'a> conduit::RequestExt for FastCgiRequest<'a> { @@ -142,7 +151,10 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { None } - fn path(&self) -> &str { todo!() } + fn path(&self) -> &str { + &self.path + } + fn query_string(&self) -> std::option::Option<&str> { todo!() } fn remote_addr(&self) -> std::net::SocketAddr { todo!() } fn content_length(&self) -> std::option::Option { todo!() } -- cgit v1.2.3 From 0be4106a600b2e038170d4c49136f50ad54af6b7 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 27 Jun 2020 21:17:14 +0200 Subject: FastCgiRequest: Add query string --- src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index f481f59..5571525 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ struct FastCgiRequest<'a> { method: conduit::Method, headers: conduit::HeaderMap, path: String, + query: Option, } impl<'a> FastCgiRequest<'a> { @@ -50,6 +51,7 @@ impl<'a> FastCgiRequest<'a> { method: method, headers: headers, path: Self::path(&request), + query: Self::query(&request), }; Ok(r) @@ -128,6 +130,10 @@ impl<'a> FastCgiRequest<'a> { None => "/".to_owned(), } } + + fn query(request: &'a fastcgi::Request) -> Option { + request.param("QUERY_STRING") + } } impl<'a> conduit::RequestExt for FastCgiRequest<'a> { @@ -155,7 +161,11 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { &self.path } - fn query_string(&self) -> std::option::Option<&str> { todo!() } + fn query_string(&self) -> std::option::Option<&str> { + self.query.as_ref() + .map(|p| p.as_str()) + } + fn remote_addr(&self) -> std::net::SocketAddr { todo!() } fn content_length(&self) -> std::option::Option { todo!() } fn headers(&self) -> &conduit::HeaderMap { todo!() } -- cgit v1.2.3 From 030ba89d5621ba5bb770d519eb68980b9da85b89 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 01:27:42 +0200 Subject: FastCgiRequest: Add remote addr Seemed to make sense to add a new error type to group the parse errors together. --- src/lib.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 5571525..fcfec03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate http; use std::io; use std::io::{BufReader, Write}; +use std::net::SocketAddr; use conduit::Handler; @@ -22,10 +23,28 @@ pub enum RequestError { #[snafu(display("{}", source))] InvalidHeaderValue { source: conduit::header::InvalidHeaderValue }, + + #[snafu(display("{}", source))] + InvalidRemoteAddr { source: RemoteAddrError }, } pub type RequestResult = std::result::Result; +#[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 + }, +} + struct FastCgiRequest<'a> { request: &'a fastcgi::Request, @@ -35,6 +54,7 @@ struct FastCgiRequest<'a> { headers: conduit::HeaderMap, path: String, query: Option, + remote_addr: SocketAddr, } impl<'a> FastCgiRequest<'a> { @@ -52,6 +72,7 @@ impl<'a> FastCgiRequest<'a> { headers: headers, path: Self::path(&request), query: Self::query(&request), + remote_addr: Self::remote_addr(&request).context(InvalidRemoteAddr)?, }; Ok(r) @@ -134,6 +155,18 @@ impl<'a> FastCgiRequest<'a> { fn query(request: &'a fastcgi::Request) -> Option { request.param("QUERY_STRING") } + + fn remote_addr(request: &'a fastcgi::Request) -> Result { + 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 })?, + ) + ) + } } impl<'a> conduit::RequestExt for FastCgiRequest<'a> { @@ -166,7 +199,10 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { .map(|p| p.as_str()) } - fn remote_addr(&self) -> std::net::SocketAddr { todo!() } + fn remote_addr(&self) -> std::net::SocketAddr { + self.remote_addr + } + fn content_length(&self) -> std::option::Option { todo!() } fn headers(&self) -> &conduit::HeaderMap { todo!() } fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } -- cgit v1.2.3 From 5007e55f291e71abff3a5bc6305b40bd8ad27af1 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 01:52:14 +0200 Subject: FastCgiRequest: Add content length --- src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index fcfec03..a27b305 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ struct FastCgiRequest<'a> { path: String, query: Option, remote_addr: SocketAddr, + content_length: Option, } impl<'a> FastCgiRequest<'a> { @@ -73,6 +74,7 @@ impl<'a> FastCgiRequest<'a> { path: Self::path(&request), query: Self::query(&request), remote_addr: Self::remote_addr(&request).context(InvalidRemoteAddr)?, + content_length: Self::content_length(&request), }; Ok(r) @@ -167,6 +169,10 @@ impl<'a> FastCgiRequest<'a> { ) ) } + + fn content_length(request: &'a fastcgi::Request) -> Option { + request.param("CONTENT_LENGTH").and_then(|l| l.parse().ok()) + } } impl<'a> conduit::RequestExt for FastCgiRequest<'a> { @@ -203,7 +209,10 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { self.remote_addr } - fn content_length(&self) -> std::option::Option { todo!() } + fn content_length(&self) -> std::option::Option { + self.content_length + } + fn headers(&self) -> &conduit::HeaderMap { todo!() } fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } fn extensions(&self) -> &conduit::TypeMap { todo!() } -- cgit v1.2.3 From 3ae94ad6923ff53fd66f428cd340a85679033598 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 01:53:32 +0200 Subject: FastCgiRequest: Add headers to `conduit::RequestExt` --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index a27b305..73e30b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,7 +213,10 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { self.content_length } - fn headers(&self) -> &conduit::HeaderMap { todo!() } + fn headers(&self) -> &conduit::HeaderMap { + &self.headers + } + fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } fn extensions(&self) -> &conduit::TypeMap { todo!() } fn mut_extensions(&mut self) -> &mut conduit::TypeMap { todo!() } -- cgit v1.2.3 From 2504f6c2db528a348dc6cdb2e48b1f9d8d30dda3 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 02:03:29 +0200 Subject: FastCgiRequest: Add empty extensions map to impls --- src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 73e30b2..9903c1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,7 @@ struct FastCgiRequest<'a> { query: Option, remote_addr: SocketAddr, content_length: Option, + extensions: conduit::Extensions, } impl<'a> FastCgiRequest<'a> { @@ -75,6 +76,7 @@ impl<'a> FastCgiRequest<'a> { query: Self::query(&request), remote_addr: Self::remote_addr(&request).context(InvalidRemoteAddr)?, content_length: Self::content_length(&request), + extensions: conduit::TypeMap::new(), }; Ok(r) @@ -218,8 +220,14 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { } fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } - fn extensions(&self) -> &conduit::TypeMap { todo!() } - fn mut_extensions(&mut self) -> &mut conduit::TypeMap { todo!() } + + fn extensions(&self) -> &conduit::Extensions { + &self.extensions + } + + fn mut_extensions(&mut self) -> &mut conduit::Extensions { + &mut self.extensions + } } -- cgit v1.2.3 From a2156b678a23d49a710ca5ff1345bee1c90254ec Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 11:26:16 +0200 Subject: FastCgiRequest: Implement `conduit::RequestExt` `body` method Needed to change some things around to be able to use a mutable borrow for the `fastcgi::Stdin` body and an immutable borrow of `request` for all the other fields. Passed `fastcgi::Stdin` over through a `Read` implementation, because returning `&mut self.request.stdin()` was impossible, since the caller owns the `fastcgi::Stdin` reference. --- src/lib.rs | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 9903c1a..ff0dce5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ extern crate fastcgi; extern crate http; use std::io; -use std::io::{BufReader, Write}; +use std::io::{BufReader, Read, Write}; use std::net::SocketAddr; use conduit::Handler; @@ -47,7 +47,7 @@ pub enum RemoteAddrError { struct FastCgiRequest<'a> { - request: &'a fastcgi::Request, + request: &'a mut fastcgi::Request, http_version: conduit::Version, host: String, method: conduit::Method, @@ -60,22 +60,26 @@ struct FastCgiRequest<'a> { } impl<'a> FastCgiRequest<'a> { - pub fn new(request: &'a fastcgi::Request) -> RequestResult { - let method = Self::method(request) - .context(InvalidMethod)?; - + pub fn new(request: &'a mut fastcgi::Request) -> RequestResult { + 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); let r = Self { request: request, - http_version: Self::version(&request), - host: Self::host(&request), + http_version: version, + host: host, method: method, headers: headers, - path: Self::path(&request), - query: Self::query(&request), - remote_addr: Self::remote_addr(&request).context(InvalidRemoteAddr)?, - content_length: Self::content_length(&request), + path: path, + query: query, + remote_addr: remote_addr, + content_length: content_length, extensions: conduit::TypeMap::new(), }; @@ -92,11 +96,11 @@ impl<'a> FastCgiRequest<'a> { } } - fn host(request: &'a fastcgi::Request) -> String { + fn host(request: &fastcgi::Request) -> String { request.param("HTTP_HOST").unwrap_or_default() } - fn version(request: &'a fastcgi::Request) -> conduit::Version { + 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, @@ -108,7 +112,7 @@ impl<'a> FastCgiRequest<'a> { } fn method( - request: &'a fastcgi::Request + request: &fastcgi::Request ) -> Result { conduit::Method::from_bytes( request.param("REQUEST_METHOD") @@ -149,18 +153,18 @@ impl<'a> FastCgiRequest<'a> { .collect() } - fn path(request: &'a fastcgi::Request) -> String { + fn path(request: &fastcgi::Request) -> String { match request.param("SCRIPT_NAME") { Some(p) => p, None => "/".to_owned(), } } - fn query(request: &'a fastcgi::Request) -> Option { + fn query(request: &fastcgi::Request) -> Option { request.param("QUERY_STRING") } - fn remote_addr(request: &'a fastcgi::Request) -> Result { + fn remote_addr(request: &fastcgi::Request) -> Result { let addr = request.param("REMOTE_ADDR").unwrap_or_default(); let port = request.param("REMOTE_PORT").unwrap_or_default(); @@ -172,11 +176,17 @@ impl<'a> FastCgiRequest<'a> { ) } - fn content_length(request: &'a fastcgi::Request) -> Option { + fn content_length(request: &fastcgi::Request) -> Option { 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 { + self.request.stdin().read(buf) + } +} + impl<'a> conduit::RequestExt for FastCgiRequest<'a> { fn http_version(&self) -> conduit::Version { self.http_version @@ -219,7 +229,9 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { &self.headers } - fn body(&mut self) -> &mut (dyn std::io::Read) { todo!() } + fn body(&mut self) -> &mut (dyn std::io::Read) { + self + } fn extensions(&self) -> &conduit::Extensions { &self.extensions -- cgit v1.2.3 From ed3ab289d13e6024fb504a2d1cfb3852f2327220 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 12:00:59 +0200 Subject: FastCgiRequest: Reorder `host` method implementation To match the struct field order. --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index ff0dce5..ebb024b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,10 +96,6 @@ impl<'a> FastCgiRequest<'a> { } } - fn host(request: &fastcgi::Request) -> String { - request.param("HTTP_HOST").unwrap_or_default() - } - fn version(request: &fastcgi::Request) -> conduit::Version { match request.param("SERVER_PROTOCOL").unwrap_or_default().as_str() { "HTTP/0.9" => conduit::Version::HTTP_09, @@ -111,6 +107,10 @@ impl<'a> FastCgiRequest<'a> { } } + fn host(request: &fastcgi::Request) -> String { + request.param("HTTP_HOST").unwrap_or_default() + } + fn method( request: &fastcgi::Request ) -> Result { -- cgit v1.2.3 From 0529b3763ca263f6c45fb632f24cf5d3c5ea0271 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 12:02:28 +0200 Subject: FastCgiRequest::new: Remove unnecessary variable --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index ebb024b..0da0f58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ impl<'a> FastCgiRequest<'a> { let remote_addr = Self::remote_addr(request).context(InvalidRemoteAddr)?; let content_length = Self::content_length(request); - let r = Self { + Ok(Self { request: request, http_version: version, host: host, @@ -81,9 +81,7 @@ impl<'a> FastCgiRequest<'a> { remote_addr: remote_addr, content_length: content_length, extensions: conduit::TypeMap::new(), - }; - - Ok(r) + }) } pub fn scheme(&self) -> conduit::Scheme { -- cgit v1.2.3 From a613320c5f69089feba14ef36de58bdc21dd7e0b Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 18:22:18 +0200 Subject: Server: Working `start()` implementation Got a draft version of the server working. Change the example to use the new Conduit server. Got a working HTML fragment response. Need to handle `conduit::Body::File` as well as unwrapped errors. --- src/lib.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 0da0f58..5d26a98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,10 +241,33 @@ impl<'a> conduit::RequestExt for FastCgiRequest<'a> { } -struct Server; +pub struct Server; impl Server { pub fn start(handler: H) -> io::Result { + fastcgi::run(move |mut raw_request| { + let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); + let response = handler.call(&mut request); + + let mut stdout = raw_request.stdout(); + + let (head, body) = response.unwrap().into_parts(); + + for (name, value) in head.headers.iter() { + write!(&mut stdout, "{}: ", name).unwrap(); + stdout.write(value.as_bytes()).unwrap(); + stdout.write(b"\r\n").unwrap(); + } + + stdout.write(b"\r\n").unwrap(); + + match body { + conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), + conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), + conduit::Body::File(file) => (), + }; + }); + Ok(Server{}) } } -- cgit v1.2.3 From 8ac4bfa0c941acefedf0d384685e8b422fc4b426 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 19:54:01 +0200 Subject: Server::start(): Write HTTP version and status code --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 5d26a98..558460f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,6 +253,13 @@ impl Server { let (head, body) = response.unwrap().into_parts(); + write!( + &mut stdout, + "HTTP/1.1 {} {}\r\n", + head.status.as_str(), + head.status.canonical_reason().unwrap_or("UNKNOWN"), + ); + for (name, value) in head.headers.iter() { write!(&mut stdout, "{}: ", name).unwrap(); stdout.write(value.as_bytes()).unwrap(); -- cgit v1.2.3 From 13fd1f2619df452dba2ab6ca8340de32c996d268 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 28 Jun 2020 19:56:45 +0200 Subject: Server::start: Implement `conduit::Body::File` response --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 558460f..78be10d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -271,7 +271,7 @@ impl Server { match body { conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), - conduit::Body::File(file) => (), + conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), }; }); -- cgit v1.2.3 From 4d24b2ce62f7f011297fe2b61358c9124af129e5 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 00:13:28 +0200 Subject: Move request code to `request.rs` Want to separate request and server code. --- src/lib.rs | 239 +-------------------------------------------------------- src/request.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 235 deletions(-) create mode 100644 src/request.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 78be10d..315417e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,251 +2,20 @@ extern crate conduit; extern crate fastcgi; extern crate http; +mod request; + use std::io; -use std::io::{BufReader, Read, Write}; -use std::net::SocketAddr; +use std::io::Write; use conduit::Handler; -use inflector::cases::traincase::to_train_case; - -use snafu::{ResultExt, Snafu}; - - -#[derive(Debug, Snafu)] -pub enum RequestError { - #[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 = std::result::Result; - -#[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 - }, -} - - -struct FastCgiRequest<'a> { - request: &'a mut fastcgi::Request, - http_version: conduit::Version, - host: String, - method: conduit::Method, - headers: conduit::HeaderMap, - path: String, - query: Option, - remote_addr: SocketAddr, - content_length: Option, - extensions: conduit::Extensions, -} - -impl<'a> FastCgiRequest<'a> { - pub fn new(request: &'a mut fastcgi::Request) -> RequestResult { - 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(), - }) - } - - pub 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 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 host(request: &fastcgi::Request) -> String { - request.param("HTTP_HOST").unwrap_or_default() - } - - fn method( - request: &fastcgi::Request - ) -> Result { - conduit::Method::from_bytes( - request.param("REQUEST_METHOD") - .unwrap_or_default() - .as_bytes() - ) - } - - fn headers(params: fastcgi::Params) -> RequestResult { - 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 { - request.param("QUERY_STRING") - } - - fn remote_addr(request: &fastcgi::Request) -> Result { - 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 { - 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 { - 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 { - 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 - } -} - pub struct Server; impl Server { pub fn start(handler: H) -> io::Result { fastcgi::run(move |mut raw_request| { - let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); + let mut request = request::FastCgiRequest::new(&mut raw_request).unwrap(); let response = handler.call(&mut request); let mut stdout = raw_request.stdout(); diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..8138d47 --- /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 RequestError { + #[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 = std::result::Result; + +#[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, + remote_addr: SocketAddr, + content_length: Option, + extensions: conduit::Extensions, +} + +impl<'a> FastCgiRequest<'a> { + pub fn new(request: &'a mut fastcgi::Request) -> RequestResult { + 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(), + }) + } + + pub 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 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 host(request: &fastcgi::Request) -> String { + request.param("HTTP_HOST").unwrap_or_default() + } + + fn method( + request: &fastcgi::Request + ) -> Result { + conduit::Method::from_bytes( + request.param("REQUEST_METHOD") + .unwrap_or_default() + .as_bytes() + ) + } + + fn headers(params: fastcgi::Params) -> RequestResult { + 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 { + request.param("QUERY_STRING") + } + + fn remote_addr(request: &fastcgi::Request) -> Result { + 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 { + 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 { + 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 { + 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 + } +} -- cgit v1.2.3 From b66e4dcdc7322a6e96ddcd422414ac6c5708bdbf Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 00:15:16 +0200 Subject: FastCgiRequest: Make `scheme()` method private Turns out I didn't need to make it public as we only need to use it within the module. --- src/request.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/request.rs b/src/request.rs index 8138d47..e86e690 100644 --- a/src/request.rs +++ b/src/request.rs @@ -78,16 +78,6 @@ impl<'a> FastCgiRequest<'a> { }) } - pub 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 version(request: &fastcgi::Request) -> conduit::Version { match request.param("SERVER_PROTOCOL").unwrap_or_default().as_str() { "HTTP/0.9" => conduit::Version::HTTP_09, @@ -99,6 +89,16 @@ impl<'a> FastCgiRequest<'a> { } } + 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() } -- cgit v1.2.3 From d0066b2e10c9c23650253b8a47db4da2ea9573f6 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 00:25:51 +0200 Subject: Move `Server` code into a `server.rs` module --- src/lib.rs | 45 ++------------------------------------------- src/server.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 src/server.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 315417e..ab40fa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,47 +3,6 @@ extern crate fastcgi; extern crate http; mod request; +mod server; -use std::io; -use std::io::Write; - -use conduit::Handler; - - -pub struct Server; - -impl Server { - pub fn start(handler: H) -> io::Result { - fastcgi::run(move |mut raw_request| { - let mut request = request::FastCgiRequest::new(&mut raw_request).unwrap(); - let response = handler.call(&mut request); - - let mut stdout = raw_request.stdout(); - - let (head, body) = response.unwrap().into_parts(); - - write!( - &mut stdout, - "HTTP/1.1 {} {}\r\n", - head.status.as_str(), - head.status.canonical_reason().unwrap_or("UNKNOWN"), - ); - - for (name, value) in head.headers.iter() { - write!(&mut stdout, "{}: ", name).unwrap(); - stdout.write(value.as_bytes()).unwrap(); - stdout.write(b"\r\n").unwrap(); - } - - stdout.write(b"\r\n").unwrap(); - - match body { - conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), - conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), - conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), - }; - }); - - Ok(Server{}) - } -} +pub use server::Server; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..dad6813 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,45 @@ +use std::io; +use std::io::Write; + +use conduit::Handler; + +use crate::request::FastCgiRequest; + + +pub struct Server; + +impl Server { + pub fn start(handler: H) -> io::Result { + fastcgi::run(move |mut raw_request| { + let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); + let response = handler.call(&mut request); + + let mut stdout = raw_request.stdout(); + + let (head, body) = response.unwrap().into_parts(); + + write!( + &mut stdout, + "HTTP/1.1 {} {}\r\n", + head.status.as_str(), + head.status.canonical_reason().unwrap_or("UNKNOWN"), + ); + + for (name, value) in head.headers.iter() { + write!(&mut stdout, "{}: ", name).unwrap(); + stdout.write(value.as_bytes()).unwrap(); + stdout.write(b"\r\n").unwrap(); + } + + stdout.write(b"\r\n").unwrap(); + + match body { + conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), + conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), + conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), + }; + }); + + Ok(Server{}) + } +} -- cgit v1.2.3 From 7b4e04adbcc2a7892a45ce537871a2efcb826dd4 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 01:15:00 +0200 Subject: Server::start: Extract `fastcgi::run` request handler to function Make a separate function that can return a result to make it easier to handle errors from the handler. --- src/server.rs | 63 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index dad6813..297e555 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,35 +11,46 @@ pub struct Server; impl Server { pub fn start(handler: H) -> io::Result { fastcgi::run(move |mut raw_request| { - let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); - let response = handler.call(&mut request); - - let mut stdout = raw_request.stdout(); - - let (head, body) = response.unwrap().into_parts(); + handle_request(&mut raw_request, &handler); + }); - write!( - &mut stdout, - "HTTP/1.1 {} {}\r\n", - head.status.as_str(), - head.status.canonical_reason().unwrap_or("UNKNOWN"), - ); + Ok(Server{}) + } +} - for (name, value) in head.headers.iter() { - write!(&mut stdout, "{}: ", name).unwrap(); - stdout.write(value.as_bytes()).unwrap(); - stdout.write(b"\r\n").unwrap(); - } +fn handle_request( + mut raw_request: &mut fastcgi::Request, + handler: &H, +) -> Result<(), ()> +where H: Handler + 'static + Sync +{ + let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); + let response = handler.call(&mut request); + + let mut stdout = raw_request.stdout(); + + let (head, body) = response.unwrap().into_parts(); + + write!( + &mut stdout, + "HTTP/1.1 {} {}\r\n", + head.status.as_str(), + head.status.canonical_reason().unwrap_or("UNKNOWN"), + ); + + for (name, value) in head.headers.iter() { + write!(&mut stdout, "{}: ", name).unwrap(); + stdout.write(value.as_bytes()).unwrap(); + stdout.write(b"\r\n").unwrap(); + } - stdout.write(b"\r\n").unwrap(); + stdout.write(b"\r\n").unwrap(); - match body { - conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), - conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), - conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), - }; - }); + match body { + conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), + conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), + conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), + }; - Ok(Server{}) - } + Ok(()) } -- cgit v1.2.3 From f630e894ea3a4d789e802bf569e1056e46510faf Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 01:42:01 +0200 Subject: server::handle_request: Return errors Add a new error type that we can use to collect and match errors from `handle_request()`. --- src/server.rs | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index 297e555..0dd6d70 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,7 +3,22 @@ use std::io::Write; use conduit::Handler; -use crate::request::FastCgiRequest; +use snafu::{ResultExt, Snafu}; + +use crate::request; + + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("{}", source))] + Io { source: io::Error }, + + #[snafu(display("Couldn't build request: {}", source))] + RequestBuilder { source: request::RequestError }, + + #[snafu(display("Couldn't parse response: {}", source))] + ConduitResponse { source: conduit::BoxError }, +} pub struct Server; @@ -21,35 +36,39 @@ impl Server { fn handle_request( mut raw_request: &mut fastcgi::Request, handler: &H, -) -> Result<(), ()> +) -> Result<(), Error> where H: Handler + 'static + Sync { - let mut request = FastCgiRequest::new(&mut raw_request).unwrap(); + 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.unwrap().into_parts(); + let (head, body) = response + .context(ConduitResponse)? + .into_parts(); write!( &mut stdout, "HTTP/1.1 {} {}\r\n", head.status.as_str(), head.status.canonical_reason().unwrap_or("UNKNOWN"), - ); + ) + .context(Io)?; for (name, value) in head.headers.iter() { - write!(&mut stdout, "{}: ", name).unwrap(); - stdout.write(value.as_bytes()).unwrap(); - stdout.write(b"\r\n").unwrap(); + write!(&mut stdout, "{}: ", name).context(Io)?; + stdout.write(value.as_bytes()).context(Io)?; + stdout.write(b"\r\n").context(Io)?; } - stdout.write(b"\r\n").unwrap(); + stdout.write(b"\r\n").context(Io)?; match body { - conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).unwrap(), - conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).unwrap(), - conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).unwrap(), + conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).context(Io)?, + conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).context(Io)?, + conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).context(Io)?, }; Ok(()) -- cgit v1.2.3 From 0d5761aee5af893c90585d375e9bf2545b60d080 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 29 Jun 2020 01:50:33 +0200 Subject: Rename `request::RequestError` to `request::Error` Now that we have a `request` module, the "Request" part of the name is redundant. --- src/request.rs | 4 ++-- src/server.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/request.rs b/src/request.rs index e86e690..2d90ea9 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use snafu::{ResultExt, Snafu}; #[derive(Debug, Snafu)] -pub enum RequestError { +pub enum Error { #[snafu(display("{}", source))] InvalidMethod { source: http::method::InvalidMethod }, @@ -22,7 +22,7 @@ pub enum RequestError { InvalidRemoteAddr { source: RemoteAddrError }, } -pub type RequestResult = std::result::Result; +pub type RequestResult = std::result::Result; #[derive(Debug, Snafu)] pub enum RemoteAddrError { diff --git a/src/server.rs b/src/server.rs index 0dd6d70..9416d9c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -14,7 +14,7 @@ pub enum Error { Io { source: io::Error }, #[snafu(display("Couldn't build request: {}", source))] - RequestBuilder { source: request::RequestError }, + RequestBuilder { source: request::Error }, #[snafu(display("Couldn't parse response: {}", source))] ConduitResponse { source: conduit::BoxError }, -- cgit v1.2.3 From 6360e9f37a0a65fbc65d9e923db9ef8f5b77ff83 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 02:29:07 +0200 Subject: Server::start: Handle errors with a 500 response If we get errors building the request or Conduit errors, respond with a 500 error. Otherwise, it's a write error, and we should do nothing (ideally log the error), because this means the client closed the connection or there are bigger problems. --- src/server.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index 9416d9c..0f45f5e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,6 +8,9 @@ use snafu::{ResultExt, Snafu}; use crate::request; +const HTTP_VERSION: &'static str = "HTTP/1.1"; + + #[derive(Debug, Snafu)] pub enum Error { #[snafu(display("{}", source))] @@ -26,7 +29,19 @@ pub struct Server; impl Server { pub fn start(handler: H) -> io::Result { fastcgi::run(move |mut raw_request| { - handle_request(&mut raw_request, &handler); + match handle_request(&mut raw_request, &handler) { + Ok(_) => (), + + // TODO: log + // Ignore write errors as clients will have closed the + // connection by this point. + Err(Error::Io { .. }) => (), + + Err(Error::RequestBuilder { .. }) => + internal_server_error(&mut raw_request.stdout()), + Err(Error::ConduitResponse { .. }) => + internal_server_error(&mut raw_request.stdout()), + } }); Ok(Server{}) @@ -51,7 +66,8 @@ where H: Handler + 'static + Sync write!( &mut stdout, - "HTTP/1.1 {} {}\r\n", + "{} {} {}\r\n", + HTTP_VERSION, head.status.as_str(), head.status.canonical_reason().unwrap_or("UNKNOWN"), ) @@ -73,3 +89,17 @@ where H: Handler + 'static + Sync Ok(()) } + +fn internal_server_error(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(()) +} -- cgit v1.2.3 From cdad0271c909c90e800a7e9dd95658b143475d15 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 02:46:01 +0200 Subject: server: Rename `Error::Io` to `Error::WriteError` Make this specifically about errors writing instead of a generic I/O error. Name the variant `WriteError` instead of `Write` because Snafu will generate context selector structs with these names and we already have a `Write` identifier imported. --- src/server.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index 0f45f5e..e3e5d9a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -14,7 +14,7 @@ const HTTP_VERSION: &'static str = "HTTP/1.1"; #[derive(Debug, Snafu)] pub enum Error { #[snafu(display("{}", source))] - Io { source: io::Error }, + WriteError { source: io::Error }, #[snafu(display("Couldn't build request: {}", source))] RequestBuilder { source: request::Error }, @@ -35,7 +35,7 @@ impl Server { // TODO: log // Ignore write errors as clients will have closed the // connection by this point. - Err(Error::Io { .. }) => (), + Err(Error::WriteError { .. }) => (), Err(Error::RequestBuilder { .. }) => internal_server_error(&mut raw_request.stdout()), @@ -71,20 +71,20 @@ where H: Handler + 'static + Sync head.status.as_str(), head.status.canonical_reason().unwrap_or("UNKNOWN"), ) - .context(Io)?; + .context(WriteError)?; for (name, value) in head.headers.iter() { - write!(&mut stdout, "{}: ", name).context(Io)?; - stdout.write(value.as_bytes()).context(Io)?; - stdout.write(b"\r\n").context(Io)?; + write!(&mut stdout, "{}: ", name).context(WriteError)?; + stdout.write(value.as_bytes()).context(WriteError)?; + stdout.write(b"\r\n").context(WriteError)?; } - stdout.write(b"\r\n").context(Io)?; + stdout.write(b"\r\n").context(WriteError)?; match body { - conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).context(Io)?, - conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).context(Io)?, - conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).context(Io)?, + conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).context(WriteError)?, + conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).context(WriteError)?, + conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).context(WriteError)?, }; Ok(()) -- cgit v1.2.3 From 3914b2189069783e6d538f2d742525284c8951c2 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 02:55:33 +0200 Subject: server: Rename `Error::WriteError` to `Error::Write` and remove context Since we don't actually need any additional context here, we can remove it. Since that allows us to avoid calling `context()` with the `WriteError` context selector, we can then safely rename `WriteError` to `Write`. --- src/server.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index e3e5d9a..2b70c0d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,8 +13,8 @@ const HTTP_VERSION: &'static str = "HTTP/1.1"; #[derive(Debug, Snafu)] pub enum Error { - #[snafu(display("{}", source))] - WriteError { source: io::Error }, + #[snafu(context(false))] + Write { source: io::Error }, #[snafu(display("Couldn't build request: {}", source))] RequestBuilder { source: request::Error }, @@ -35,7 +35,7 @@ impl Server { // TODO: log // Ignore write errors as clients will have closed the // connection by this point. - Err(Error::WriteError { .. }) => (), + Err(Error::Write { .. }) => (), Err(Error::RequestBuilder { .. }) => internal_server_error(&mut raw_request.stdout()), @@ -70,21 +70,20 @@ where H: Handler + 'static + Sync HTTP_VERSION, head.status.as_str(), head.status.canonical_reason().unwrap_or("UNKNOWN"), - ) - .context(WriteError)?; + )?; for (name, value) in head.headers.iter() { - write!(&mut stdout, "{}: ", name).context(WriteError)?; - stdout.write(value.as_bytes()).context(WriteError)?; - stdout.write(b"\r\n").context(WriteError)?; + write!(&mut stdout, "{}: ", name)?; + stdout.write(value.as_bytes())?; + stdout.write(b"\r\n")?; } - stdout.write(b"\r\n").context(WriteError)?; + stdout.write(b"\r\n")?; match body { - conduit::Body::Static(slice) => stdout.write(slice).map(|_| ()).context(WriteError)?, - conduit::Body::Owned(vec) => stdout.write(&vec).map(|_| ()).context(WriteError)?, - conduit::Body::File(mut file) => io::copy(&mut file, &mut stdout).map(|_| ()).context(WriteError)?, + 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(()) -- cgit v1.2.3 From 4498c8fdf70b67bc65d2d44a32afa901f1594e51 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 03:01:02 +0200 Subject: server::handle_request: Wrap lines --- src/server.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index 2b70c0d..9bbba00 100644 --- a/src/server.rs +++ b/src/server.rs @@ -81,9 +81,12 @@ where H: Handler + 'static + Sync 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(|_| ())?, + 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(()) -- cgit v1.2.3 From ab8b50d5b98ab071c5f70d929f1ca76f3faf71ca Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 03:28:02 +0200 Subject: server: Log errors Provide a mechanism to get detailed error messages when 500 internal server errors happen. We don't want to expose any detailed error information to clients, but it would be useful for administrators to know the cause of any errors. --- src/lib.rs | 1 + src/server.rs | 36 +++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index ab40fa9..2ffe77e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ extern crate conduit; extern crate fastcgi; extern crate http; +extern crate log; mod request; mod server; diff --git a/src/server.rs b/src/server.rs index 9bbba00..22bf244 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,6 +3,8 @@ use std::io::Write; use conduit::Handler; +use log::error; + use snafu::{ResultExt, Snafu}; use crate::request; @@ -31,16 +33,22 @@ impl Server { fastcgi::run(move |mut raw_request| { match handle_request(&mut raw_request, &handler) { Ok(_) => (), - - // TODO: log - // Ignore write errors as clients will have closed the - // connection by this point. - Err(Error::Write { .. }) => (), - - Err(Error::RequestBuilder { .. }) => - internal_server_error(&mut raw_request.stdout()), - Err(Error::ConduitResponse { .. }) => - internal_server_error(&mut raw_request.stdout()), + 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()) + }, + } } }); @@ -95,13 +103,15 @@ where H: Handler + 'static + Sync fn internal_server_error(mut w: W) { let code = conduit::StatusCode::INTERNAL_SERVER_ERROR; - write!( + match write!( w, "{} {} {}\r\n{}\r\n\r\n", HTTP_VERSION, code, code.canonical_reason().unwrap_or_default(), "Content-Length: 0", - ) - .unwrap_or(()) + ) { + Ok(_) => (), + Err(e) => error!("Write error: {}", e), + } } -- cgit v1.2.3 From d125b25f4245df3d0eb80aa726c2a7039945f3de Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 4 Jul 2020 03:44:48 +0200 Subject: server::internal_server_error: Change `match` to `unwrap_or_else` Reduce a few lines. --- src/server.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/server.rs b/src/server.rs index 22bf244..948a470 100644 --- a/src/server.rs +++ b/src/server.rs @@ -103,15 +103,13 @@ where H: Handler + 'static + Sync fn internal_server_error(mut w: W) { let code = conduit::StatusCode::INTERNAL_SERVER_ERROR; - match write!( + write!( w, "{} {} {}\r\n{}\r\n\r\n", HTTP_VERSION, code, code.canonical_reason().unwrap_or_default(), "Content-Length: 0", - ) { - Ok(_) => (), - Err(e) => error!("Write error: {}", e), - } + ) + .unwrap_or_else(|e| error!("Write error: {}", e)) } -- cgit v1.2.3