dce-hyper 1.8.0

A http routable protocol implementation for dce-router
Documentation
use std::any::Any;
use std::collections::HashMap;
use std::convert::Infallible;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, LazyLock};

use async_trait::async_trait;
use dce_router::api::{Api, Handler, Methods as ApiMethod, MARK_PATH_PART_SEPARATOR};
use dce_router::context::Context;
use dce_router::protocol::{RoutableProtocol, RpMeta};
use dce_router::router::{RouteMatch, Router};
use dce_util::result::{DceError, DceResult, SERVICE_UNAVAILABLE};
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full};
use hyper::body::{Body, Bytes, Incoming};
use hyper::header::{HeaderValue, COOKIE};
use hyper::{Method as HyperMethod, Request, Response, StatusCode};
use tokio::sync::RwLock;

const HEADER_CONTENT_TYPE: &str = "Content-Type";
const HEADER_SID_KEY: &str = "X-Session-Id";
const COOKIE_SID_KEY: &str = "Session-Id=";

pub struct HyperProtocol {
    meta: RpMeta<Request<Incoming>>,
    resp: Response<BoxBody<Bytes, Infallible>>,
}

impl Deref for HyperProtocol {
    type Target = RpMeta<Request<Incoming>>;

    fn deref(&self) -> &Self::Target {
        &self.meta
    }
}

impl DerefMut for HyperProtocol {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.meta
    }
}

impl HyperProtocol {
    pub fn sid(&self) -> Option<&str> {
        let headers = self.raw().headers();
        headers.get(HEADER_SID_KEY).and_then(|hv| hv.to_str().ok())
            .or_else(|| headers.get(COOKIE).and_then(|cv| cv.to_str().ok())

                .and_then(|cs| cs.split(';').find_map(|l| l.find(COOKIE_SID_KEY).map(|i| &l[i+COOKIE_SID_KEY.len()..]))))
    }

    fn new(req: Request<Incoming>, ctx_data: Option<HashMap<String, Box<dyn Any + Send>>>) -> Self {
        Self { meta: RpMeta::new(req, ctx_data), resp: Response::new(BoxBody::default()) }
    }

    fn response(mut ctx: Context<HyperProtocol>, err: Option<DceError>) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
        if let Some(hv) = ctx.ctx_data_remove(HEADER_CONTENT_TYPE)
            .and_then(|ct| ct.downcast_ref::<String>().and_then(|ct| HeaderValue::from_str(ct).ok())) {
            ctx.resp.headers_mut().insert(HEADER_CONTENT_TYPE, hv);
        }
        if let Some(hv) = ctx.resp_sid().and_then(|sid| HeaderValue::from_str(sid).ok()) {
            ctx.resp.headers_mut().insert(HEADER_SID_KEY, hv);
        }
        ctx.try_print_err(&err);
        if let Some(err) = err {
            // 只自动设置公开且合法的 HTTP 状态码
            *ctx.resp.status_mut() = StatusCode::from_u16(if err.public && err.code > 0 && err.code < 600 { err.code } else { SERVICE_UNAVAILABLE } as u16)
                .unwrap_or(StatusCode::SERVICE_UNAVAILABLE);
        }
        if ctx.resp.body().is_end_stream() {
            *ctx.resp.body_mut() = Full::from(Bytes::from_owner(ctx.clear_buffer())).boxed();
        }
        let HyperProtocol{ resp, ..} = ctx.into_rp();
        Ok(resp)
    }

    pub async fn handle(self, routed: DceResult<RouteMatch<'_, HyperProtocol>>) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
        let mut ctx = Context::new(self, routed.as_ref().ok().map(|r| r.api));
        let err = match routed {
            Ok(routed) => routed.api.handle(&mut ctx, routed).await.err(),
            err => err.err(),
        };
        Self::response(ctx, err)
    }
}

#[async_trait]
impl RoutableProtocol for HyperProtocol {
    type Req = Request<Incoming>;

    fn path(&self) ->  &str {
        self.raw().uri().path().trim_start_matches(MARK_PATH_PART_SEPARATOR)
    }

    async fn body(&mut self) -> DceResult<Bytes> {
        self.raw_mut().collect().await.map(|c| c.to_bytes()).map_err(DceError::priv_err0)
    }

    fn match_api(&self, apis: &Vec<&'static Api<Self>>) -> Option<&'static Api<Self>> {
        apis.iter().find(|a| {
            let method = hyper_to_api_method(self.raw().method());
            a.methods & method == method && {
                let hosts = a.hosts();
                hosts.is_empty() || hosts.iter().any(|h| self.raw().uri().host().is_some_and(|rh|
                    if h.contains(':') {
                        rh.eq_ignore_ascii_case(h)
                    } else if let Ok(port) = h.parse::<usize>() {
                        rh.ends_with(format!(":{}", port).as_str())
                    } else {
                        rh.starts_with(format!("{}:", h).as_str())
                    }

                ))
            }
        }).map(|a| *a)
    }
}

#[allow(non_snake_case, non_upper_case_globals)]
pub mod Method {
    use dce_router::api::Methods;

    pub const Get: Methods = Methods(1);
    pub const Post: Methods = Methods(2);
    pub const Put: Methods = Methods(4);
    pub const Delete: Methods = Methods(8);
    pub const Head: Methods = Methods(16);
    pub const Options: Methods = Methods(32);
    pub const Connect: Methods = Methods(64);
    pub const Patch: Methods = Methods(128);
    pub const Trace: Methods = Methods(256);
}

const MAPPING: &[(HyperMethod, ApiMethod)] = &[
    (HyperMethod::GET, Method::Get),
    (HyperMethod::POST, Method::Post),
    (HyperMethod::PUT, Method::Put),
    (HyperMethod::DELETE, Method::Delete),
    (HyperMethod::HEAD, Method::Head),
    (HyperMethod::OPTIONS, Method::Options),
    (HyperMethod::CONNECT, Method::Connect),
    (HyperMethod::PATCH, Method::Patch),
    (HyperMethod::TRACE, Method::Trace),
];

fn hyper_to_api_method(value: &HyperMethod) -> ApiMethod {
    MAPPING.iter().find(|(hm, _)| value.eq(hm))
        .map_or(Method::Get, |&(_, m)| m)
}

pub struct HttpRouter<Rp: RoutableProtocol + 'static>(Router<Rp>);

impl <Rp: RoutableProtocol> Deref for HttpRouter<Rp> {
    type Target = Router<Rp>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl <Rp: RoutableProtocol> DerefMut for HttpRouter<Rp> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl <Rp: RoutableProtocol> HttpRouter<Rp> {
    pub fn new() -> Self {
        Self(Router::new())
    }

    pub fn get(&mut self, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        self.push_method(Method::Get | Method::Head, path, handler)
    }
    
    pub fn post(&mut self, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        self.push_method(Method::Post|Method::Options, path, handler)
    }

    pub fn put(&mut self, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        self.push_method(Method::Put|Method::Options, path, handler)
    }

    pub fn patch(&mut self, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        self.push_method(Method::Patch|Method::Options, path, handler)
    }

    pub fn delete(&mut self, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        self.push_method(Method::Delete|Method::Options, path, handler)
    }

    fn push_method(&mut self, method: ApiMethod, path: &'static str, handler: Handler<Rp>) -> &mut Self {
        let api = Api::new(path).by_methods(method);
        self.0.bind_api(api, handler);
        self
    }
}

#[allow(non_snake_case, non_upper_case_globals)]
pub static HyperRouter: LazyLock<Arc<RwLock<HttpRouter<HyperProtocol>>>> = LazyLock::new(|| Arc::new(RwLock::new(HttpRouter::<HyperProtocol>::new())));

pub async fn hyper_route(req: Request<Incoming>, ctx_data: Option<HashMap<String, Box<dyn Any + Send>>>) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
    let router = HyperRouter.clone();
    let router = router.read().await;
    let rp = HyperProtocol::new(req, ctx_data);
    let routed = router.lookup(&rp);
    rp.handle(routed).await
}