brio 0.2.0

Async http server and web framework
Documentation
use crate::{Path, Receiver, Result};
use async_std::prelude::*;
use futures::{select, FutureExt};
use std::{collections::hash_map::HashMap, fmt};

pub struct Request {
    pub(crate) bytes: Vec<u8>,
    pub(crate) version: u8,
    pub(crate) method: Method,
    pub(crate) path: String,
    pub(crate) headers: HashMap<String, String>,
    pub(crate) trailers: Option<HashMap<String, String>>,
    pub(crate) stream: Option<Receiver<Chunk>>,
}

impl Request {
    pub(crate) fn from_parser(parser: httparse::Request) -> Result<Request> {
        let headers: HashMap<String, String> = parser
            .headers
            .iter()
            .map(|&x| {
                (
                    x.name.to_owned().to_lowercase(),
                    std::str::from_utf8(x.value).unwrap().to_owned(),
                )
            })
            .collect();
        Ok(Request {
            bytes: vec![],
            version: parser.version.unwrap(),
            path: parser.path.unwrap().to_owned(),
            method: parser.method.unwrap().to_lowercase().parse().unwrap(),
            headers,
            trailers: None,
            stream: None,
        })
    }

    pub fn stream(&mut self, receiver: Receiver<Chunk>) {
        self.stream = Some(receiver)
    }

    pub fn take_stream(&mut self) -> Option<Receiver<Chunk>> {
        self.stream.take()
    }

    pub fn route(&self) -> Path {
        Path::new(self.method, self.path.clone())
    }

    pub fn headers(&mut self) -> &HashMap<String, String> {
        &self.headers
    }

    pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
        &mut self.headers
    }

    pub fn content_len(&self) -> Option<usize> {
        self.headers
            .get("content-length")
            .and_then(|cl: &String| cl.parse().ok())
    }

    pub fn content_type(&self) -> Option<mime::Mime> {
        self.headers
            .get("content-type")
            .and_then(|ct: &String| ct.parse().ok())
    }

    pub fn is_keep_alive(&self) -> bool {
        match self.headers.get("connection") {
            Some(connection) => "keep-alive" == connection,
            None => self.version == 1,
        }
    }

    pub fn transfer_endcoding(&self) -> Encoding {
        match self.headers.get("transfer-encoding") {
            Some(encoding) => encoding.parse().unwrap_or(Encoding::Identity),
            None => Encoding::Identity,
        }
    }

    pub async fn json(&mut self) -> Result<serde_json::Value> {
        self.body().await?;
        Ok(serde_json::from_slice(&self.bytes.as_slice())?)
    }

    pub fn check_trailers(&self) -> Vec<String> {
        match self.headers.get("trailer") {
            Some(trailers) => trailers
                .chars()
                .filter(|c| !c.is_whitespace())
                .collect::<String>()
                .split(',')
                .map(|s| s.to_owned())
                .collect::<Vec<String>>(),
            None => vec![],
        }
    }

    pub async fn trailers(&mut self) -> &Option<HashMap<String, String>> {
        self.body().await.ok();
        &self.trailers
    }

    pub async fn bytes(&mut self) -> Result<&Vec<u8>> {
        self.body().await?;
        Ok(&self.bytes)
    }

    async fn body(&mut self) -> Result<()> {
        match self.stream.take() {
            Some(body) => {
                let mut stream = body.fuse();
                if self.transfer_endcoding() == Encoding::Chunked {
                    loop {
                        let chunk = select! {
                            chunk = stream.next().fuse() => match chunk {
                                None => break,
                                Some(chunk) => chunk,
                            },
                        };
                        match chunk {
                            Chunk::Body { buf, size } => {
                                self.bytes.extend_from_slice(&buf[..size]);
                            }
                            Chunk::Trailers { trailers } => {
                                self.trailers = Some(trailers);
                                break;
                            }
                        }
                    }
                } else {
                    let cl = self.content_len().unwrap();
                    while self.bytes.len() < cl as usize {
                        let chunk = select! {
                            chunk = stream.next().fuse() => match chunk {
                                None => break,
                                Some(chunk) => chunk,
                            },
                        };
                        match chunk {
                            Chunk::Body { buf, size } => {
                                self.bytes.extend_from_slice(&buf[..size]);
                            }
                            _ => {
                                return Err(Box::new(std::io::Error::new(
                                    std::io::ErrorKind::InvalidData,
                                    "trailers for identity",
                                )));
                            }
                        }
                    }
                }
            }
            None => {}
        }
        Ok(())
    }
}

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub enum Method {
    Get,
    Head,
    Post,
    Put,
    Delete,
    Connect,
    Options,
    Trace,
    Patch,
}

impl fmt::Display for Method {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

impl std::str::FromStr for Method {
    type Err = ();
    fn from_str(s: &str) -> std::result::Result<Self, ()> {
        match s {
            "get" => Ok(Method::Get),
            "head" => Ok(Method::Head),
            "post" => Ok(Method::Post),
            "put" => Ok(Method::Put),
            "delete" => Ok(Method::Delete),
            "connect" => Ok(Method::Connect),
            "options" => Ok(Method::Options),
            "trace" => Ok(Method::Trace),
            "patch" => Ok(Method::Patch),
            _ => Err(()),
        }
    }
}

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub enum Encoding {
    Chunked,
    Identity,
}

impl fmt::Display for Encoding {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

impl std::str::FromStr for Encoding {
    type Err = ();
    fn from_str(s: &str) -> std::result::Result<Self, ()> {
        match s {
            "identity" => Ok(Encoding::Identity),
            "chunked" => Ok(Encoding::Chunked),
            _ => Err(()),
        }
    }
}

pub enum Chunk {
    Body {
        buf: [u8; crate::BUF_LEN],
        size: usize,
    },
    Trailers {
        trailers: HashMap<String, String>,
    },
}