diff options
-rw-r--r-- | src/lib.rs | 239 | ||||
-rw-r--r-- | src/request.rs | 235 |
2 files changed, 239 insertions, 235 deletions
@@ -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<T, E = RequestError> = 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 - }, -} - - -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(), - }) - } - - 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, 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 - } -} - pub struct Server; impl Server { pub fn start<H: Handler + 'static + Sync>(handler: H) -> io::Result<Server> { 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<T, E = RequestError> = 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(), + }) + } + + 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, 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 + } +} |