pulse_http 0.2.0

Async HTTP/1.1 framework on Tokio — raw TCP, routing, middleware, no Hyper
Documentation
use crate::{
    middleware::{Middleware, Next},
    request::{Method, Request},
    response::Response,
    router::BoxFuture,
};

pub struct Cors {
    pub origins: Vec<String>,
    pub allowed_methods: String,
    pub allowed_headers: String,
}

impl Cors {
    pub fn new() -> Self {
        Self {
            origins: vec!["http://localhost:3000".into()],
            allowed_methods: "GET, POST, PUT, DELETE, HEAD, OPTIONS".into(),
            allowed_headers: "content-type, x-request-id".into(),
        }
    }

    pub fn origins(mut self, origins: Vec<String>) -> Self {
        self.origins = origins;
        self
    }

    pub fn allowed_methods(mut self, methods: impl Into<String>) -> Self {
        self.allowed_methods = methods.into();
        self
    }

    pub fn allowed_headers(mut self, headers: impl Into<String>) -> Self {
        self.allowed_headers = headers.into();
        self
    }

    fn is_origin_allowed(&self, origin: &str) -> bool {
        self.origins.iter().any(|allowed| allowed == origin)
    }

    fn apply_cors_headers(&self, response: &mut Response, origin: &str) {
        response
            .headers
            .insert("access-control-allow-origin".into(), origin.to_string());
        response.headers.insert(
            "access-control-allow-methods".into(),
            self.allowed_methods.clone(),
        );
        response.headers.insert(
            "access-control-allow-headers".into(),
            self.allowed_headers.clone(),
        );
        response.headers.insert("vary".into(), "Origin".into());
    }
}

impl Default for Cors {
    fn default() -> Self {
        Self::new()
    }
}

impl Middleware for Cors {
    fn handle(&self, request: Request, next: Next) -> BoxFuture<Response> {
        let origins = self.origins.clone();
        let allowed_methods = self.allowed_methods.clone();
        let allowed_headers = self.allowed_headers.clone();

        Box::pin(async move {
            let cors = Cors {
                origins,
                allowed_methods,
                allowed_headers,
            };

            let origin = request.headers.get("origin").cloned();

            if request.method == Method::Options {
                let mut response = Response::no_content();
                if let Some(origin) = origin.as_deref() {
                    if cors.is_origin_allowed(origin) {
                        cors.apply_cors_headers(&mut response, origin);
                        response
                            .headers
                            .insert("access-control-max-age".into(), "86400".into());
                    }
                }
                return response;
            }

            let mut response = next.run(request).await;

            if let Some(origin) = origin.as_deref() {
                if cors.is_origin_allowed(origin) {
                    cors.apply_cors_headers(&mut response, origin);
                }
            }

            response
        })
    }
}