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 {
*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
}