ptth/src/http_serde.rs

199 lines
4.4 KiB
Rust

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 <hyper::header::InvalidHeaderName> for Error {
fn from (_x: hyper::header::InvalidHeaderName) -> Self {
Self::InvalidHeaderName
}
}
#[derive (Debug, Deserialize, Serialize)]
pub enum Method {
Get,
Head,
Post,
Put,
}
impl TryFrom <hyper::Method> for Method {
type Error = Error;
fn try_from (x: hyper::Method) -> Result <Self, Error> {
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 <String, Vec <u8>>,
}
impl RequestParts {
pub fn from_hyper (
method: hyper::Method,
uri: String,
headers: hyper::HeaderMap <hyper::header::HeaderValue>
) -> Result <Self, Error>
{
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 <StatusCode> 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 <String, Vec <u8>>,
}
// reqwest and hyper have different Body types for _some reason_
// so I have to do everything myself
type Body = mpsc::Receiver <Result <Vec <u8>, std::convert::Infallible>>;
#[derive (Default)]
pub struct Response {
pub parts: ResponseParts,
pub body: Option <Body>,
// Needed to make Nginx happy
pub content_length: Option <u64>,
}
impl Response {
pub async fn into_bytes (self) -> Vec <u8> {
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 <u8>) -> &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 <u8>) -> &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
}
}