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/request.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/request.rs (limited to 'src/request.rs') 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