use std::{ collections::*, convert::{TryFrom, TryInto}, }; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; // Hyper doesn't seem to make it easy to de/ser requests // and responses and stuff like that, so I do it by hand here. pub enum Error { UnsupportedMethod, InvalidHeaderName, } impl From for Error { fn from (_x: hyper::header::InvalidHeaderName) -> Self { Self::InvalidHeaderName } } #[derive (Debug, Deserialize, Serialize)] pub enum Method { Get, Head, Post, Put, } impl TryFrom for Method { type Error = Error; fn try_from (x: hyper::Method) -> Result { match x { hyper::Method::GET => Ok (Self::Get), hyper::Method::HEAD => Ok (Self::Head), _ => Err (Error::UnsupportedMethod), } } } #[derive (Deserialize, Serialize)] pub struct RequestParts { pub method: Method, // Technically URIs are subtle and complex, but I don't care pub uri: String, // Technically Hyper has headers in a multi-map // but I don't feel like doing that right now. // Headers are _kinda_ ASCII but they can also be binary. // HTTP is nutty. pub headers: HashMap >, } impl RequestParts { pub fn from_hyper ( method: hyper::Method, uri: String, headers: hyper::HeaderMap ) -> Result { use std::iter::FromIterator; let method = Method::try_from (method)?; let headers = HashMap::from_iter ( headers.into_iter () .filter_map (|(k, v)| k.map (|k| (k, v))) .map (|(k, v)| (String::from (k.as_str ()), v.as_bytes ().to_vec ())) ); Ok (Self { method, uri, headers, }) } } #[derive (Deserialize, Serialize)] pub struct WrappedRequest { pub id: String, pub req: RequestParts, } #[derive (Debug, Deserialize, Serialize, PartialEq)] pub enum StatusCode { Ok, // 200 NoContent, // 204 PartialContent, // 206 TemporaryRedirect, // 307 BadRequest, // 400 Forbidden, // 403 NotFound, // 404 MethodNotAllowed, // 405 RangeNotSatisfiable, // 416 } impl Default for StatusCode { fn default () -> Self { Self::Ok } } impl From for hyper::StatusCode { fn from (x: StatusCode) -> Self { match x { StatusCode::Ok => Self::OK, StatusCode::NoContent => Self::NO_CONTENT, StatusCode::PartialContent => Self::PARTIAL_CONTENT, StatusCode::TemporaryRedirect => Self::TEMPORARY_REDIRECT, StatusCode::BadRequest => Self::BAD_REQUEST, StatusCode::Forbidden => Self::FORBIDDEN, StatusCode::NotFound => Self::NOT_FOUND, StatusCode::MethodNotAllowed => Self::METHOD_NOT_ALLOWED, StatusCode::RangeNotSatisfiable => Self::RANGE_NOT_SATISFIABLE, } } } #[derive (Default, Deserialize, Serialize)] pub struct ResponseParts { pub status_code: StatusCode, // Technically Hyper has headers in a multi-map // but I don't feel like doing that right now. // We promise not to need this feature. pub headers: HashMap >, } // reqwest and hyper have different Body types for _some reason_ // so I have to do everything myself type Body = mpsc::Receiver , std::convert::Infallible>>; #[derive (Default)] pub struct Response { pub parts: ResponseParts, pub body: Option , // Needed to make Nginx happy pub content_length: Option , } impl Response { pub async fn into_bytes (self) -> Vec { let mut body = match self.body { None => return Vec::new (), Some (x) => x, }; let mut result = match self.content_length { None => Vec::new (), Some (x) => Vec::with_capacity (x.try_into ().unwrap ()), }; while let Some (Ok (mut chunk)) = body.recv ().await { result.append (&mut chunk); } result } pub fn status_code (&mut self, c: StatusCode) -> &mut Self { self.parts.status_code = c; self } pub fn header (&mut self, k: String, v: Vec ) -> &mut Self { self.parts.headers.insert (k, v); self } pub fn body (&mut self, b: Body) -> &mut Self { self.body = Some (b); self } pub fn body_bytes (&mut self, b: Vec ) -> &mut Self { self.content_length = b.len ().try_into ().ok (); self.header ("content-length".to_string (), b.len ().to_string ().into_bytes ()); let (mut tx, rx) = tokio::sync::mpsc::channel (1); tokio::spawn (async move { tx.send (Ok (b)).await.ok (); }); self.body = Some (rx); self } }