flyer 3.0.7

HTTP framework for rust
Documentation
use std::net::SocketAddr;

use anyhow::{Context, Result};
use bytes::{Buf, Bytes, BytesMut};
use h3::server::RequestStream;
use h3_quinn::BidiStream;

use crate::{
    cookies::Cookies,
    request::{form::Form, Request},
    response::Response,
    server::{protocol::UdpHandler, Server},
    session::Session,
    utils::{http::Headers, mem::Instance, url::parse_query, Values},
};

pub struct Http3 {
    server: Instance<Server>,
    addr: SocketAddr,
}

impl UdpHandler for Http3 {
    fn new(server: Instance<Server>, addr: SocketAddr) -> Self {
        Self { server, addr }
    }

    async fn handle(&mut self, mut server: h3::server::Connection<h3_quinn::Connection, Bytes>) -> Result<()> {
        while let Ok(Some(resolver)) = server.accept().await {
            let server = self.server.clone();
            let addr = self.addr;

            tokio::spawn(async move {
                if let Err(err) = Self::process_request(server, addr, resolver).await {
                    eprintln!("Error handling HTTP/3 request from {addr}: {err}");
                }
            });
        }

        Ok(())
    }
}

impl Http3 {
    async fn process_request(
        server: Instance<Server>,
        addr: SocketAddr,
        resolver: h3::server::RequestResolver<h3_quinn::Connection, Bytes>,
    ) -> Result<()> {
        let (request, mut stream) = resolver
            .resolve_request()
            .await
            .context("Failed to resolve HTTP/3 request")?;

        let req = Self::deserialize(server.clone(), addr, &request, &mut stream).await?;

        let (_, res) = server.as_mut().on_http(req, Response::new()).await;

        Self::write(&mut stream, res).await
    }

    async fn deserialize(
        server: Instance<Server>,
        addr: SocketAddr,
        request: &http::Request<()>,
        stream: &mut RequestStream<BidiStream<Bytes>, Bytes>,
    ) -> Result<Request> {
        let mut headers = Headers::new();

        for (k, v) in request.headers() {
            if let Ok(val_str) = v.to_str() {
                headers.insert(k.as_str().to_string(), val_str.to_string());
            }
        }

        let host = request
            .uri()
            .authority()
            .map(|a| a.as_str())
            .or_else(|| {
                request
                    .headers()
                    .get(http::header::HOST)
                    .and_then(|h| h.to_str().ok())
            })
            .unwrap_or_default()
            .to_string();

        let body = Self::read_body(stream).await.unwrap_or_default();

        let path = request.uri().path().to_string();
        let queries = parse_query(request.uri().query().unwrap_or(""));

        Ok(Request {
            server: server,
            addr: addr,
            protocol: "HTTP/3.0".into(),
            method: request.method().as_str().to_string(),
            path: path,
            queries: queries,
            host: host,
            headers: headers,
            cookies: Cookies::new(),
            session: Session::new(),
            body: body,
            parameters: Values::new(),
            form: Form::new(Default::default(), Default::default()),
        })
    }

    async fn read_body(stream: &mut RequestStream<BidiStream<Bytes>, Bytes>) -> Result<Bytes> {
        let mut buf = BytesMut::new();

        while let Some(mut chunk) = stream.recv_data().await? {
            while chunk.has_remaining() {
                let slice = chunk.chunk();
                buf.extend_from_slice(slice);
                chunk.advance(slice.len());
            }
        }

        Ok(buf.freeze())
    }

    pub async fn write(stream: &mut RequestStream<BidiStream<Bytes>, Bytes>, res: Response) -> Result<()> {
        let mut builder = http::Response::builder().status(res.status_code);

        for (k, v) in &res.headers {
            if !k.eq_ignore_ascii_case("content-length") {
                builder = builder.header(k, v);
            }
        }

        builder = builder.header("Content-Length", res.content.len().to_string());

        let response_head = builder
            .body(())
            .context("Failed to build HTTP/3 response")?;

        stream
            .send_response(response_head)
            .await
            .context("Failed to send HTTP/3 headers")?;

        if !res.content.is_empty() {
            stream
                .send_data(res.content)
                .await
                .context("Failed to send HTTP/3 body")?;
        }

        stream
            .finish()
            .await
            .context("Failed to finish HTTP/3 stream")?;

        Ok(())
    }
}