dce_hyper/
protocol.rs

1use std::any::Any;
2use std::collections::{HashMap, HashSet};
3use std::convert::Infallible;
4use std::fmt::Debug;
5use std::ops::{Deref, DerefMut};
6use std::sync::Arc;
7use async_trait::async_trait;
8use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
9use hyper::body::{Bytes, Incoming};
10use hyper::{Method, Request, Response, StatusCode};
11#[allow(unused)]
12use hyper::header::{COOKIE, HeaderValue};
13use dce_router::protocol::{Meta, RoutableProtocol};
14use dce_router::request::{Context, Request as DceRequest, Response as DceResponse};
15use dce_router::router::Router;
16use dce_router::serializer::Serialized;
17use dce_util::mixed::{DceErr, DceResult};
18use dce_router::api::Method as DceMethod;
19
20pub type HttpRaw<'a> = DceRequest<'a, HyperHttpProtocol, (), ()>;
21pub type HttpGet<'a, Dto> = DceRequest<'a, HyperHttpProtocol, (), Dto>;
22pub type HttpSame<'a, Dto> = DceRequest<'a, HyperHttpProtocol, Dto, Dto>;
23pub type Http<'a, ReqDto, RespDto> = DceRequest<'a, HyperHttpProtocol, ReqDto, RespDto>;
24
25#[derive(Debug)]
26pub struct HyperHttpProtocol {
27    meta: Meta<Request<Incoming>, Response<BoxBody<Bytes, Infallible>>>,
28}
29
30impl HyperHttpProtocol {
31    pub async fn route(
32        self,
33        router: Arc<Router<Self>>,
34        context_data: HashMap<String, Box<dyn Any + Send>>,
35    ) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
36        Self::handle(self, router, context_data).await.ok_or_else(|| unreachable!("http route should always return Some(Resp)"))
37    }
38}
39
40impl From<Request<Incoming>> for HyperHttpProtocol {
41    fn from(value: Request<Incoming>) -> Self {
42        Self { meta: Meta::new(value, Default::default()) }
43    }
44}
45
46impl Into<Response<BoxBody<Bytes, Infallible>>> for HyperHttpProtocol {
47    fn into(mut self) -> Response<BoxBody<Bytes, Infallible>> {
48        let resp = self.resp_mut().take();
49        #[allow(unused_mut)]
50        let mut resp = match resp {
51            None => Response::new(Empty::new().boxed()),
52            Some(DceResponse::Serialized(sd)) => self.pack_resp(sd),
53            Some(DceResponse::Raw(rr)) => rr,
54        };
55        #[cfg(feature = "session")]
56        if let Some(resp_sid) = self.get_resp_sid() {
57            resp.headers_mut().insert("X-Session-Id", HeaderValue::from_str(resp_sid.as_str()).unwrap());
58        }
59        resp
60    }
61}
62
63impl Deref for HyperHttpProtocol {
64    type Target = Meta<Request<Incoming>, Response<BoxBody<Bytes, Infallible>>>;
65
66    fn deref(&self) -> &Self::Target {
67        &self.meta
68    }
69}
70
71impl DerefMut for HyperHttpProtocol {
72    fn deref_mut(&mut self) -> &mut Self::Target {
73        &mut self.meta
74    }
75}
76
77#[async_trait]
78impl RoutableProtocol for HyperHttpProtocol {
79    type Req = Request<Incoming>;
80    type Resp = Response<BoxBody<Bytes, Infallible>>;
81
82    async fn body(&mut self) -> DceResult<Serialized> {
83        let req = self.req_mut().take().ok_or_else(|| DceErr::closed0("Empty request"))?;
84        Ok(Serialized::Bytes(req.collect().await.or_else(DceErr::closed0_wrap)?.to_bytes()))
85    }
86
87    fn pack_resp(&self, serialized: Serialized) -> Self::Resp {
88        Response::new(match serialized {
89            Serialized::String(str) => Full::from(str).boxed(),
90            Serialized::Bytes(bytes) => Full::from(bytes).boxed(),
91        })
92    }
93
94    fn path(&self) -> &str {
95        self.req().map_or("", |r| r.uri().path().trim_start_matches('/'))
96    }
97
98    fn handle_result(self, result: DceResult<()>, _: &mut Context<Self>) -> Option<Self::Resp> {
99        Self::try_print_err(&result);
100        Some(match result {
101            Ok(_) => self.into(),
102            Err(err) => {
103                let code = err.value().code;
104                let is_openly = matches!(err, DceErr::Openly(_));
105                let mut resp = self.err_into(err);
106                if is_openly && code < 600 {
107                    *resp.status_mut() = StatusCode::from_u16(code as u16).unwrap_or(StatusCode::SERVICE_UNAVAILABLE);
108                }
109                resp
110            },
111        })
112    }
113
114    #[cfg(feature = "session")]
115    fn sid(&self) -> Option<&str> {
116        self.req().map_or(None, |r| r.headers().get("X-Session-Id").map(|v| v.to_str().unwrap())
117            .or_else(|| r.headers().get(COOKIE).iter().find_map(|v| {
118                let mut cookies = v.to_str().unwrap_or("").split(';');
119                cookies.find_map(|kv| if let Some(index) = kv.find("session_id=") { Some(&kv[index + 11 ..]) } else { None } )
120            })))
121    }
122
123    fn parse_api_method(prop_mapping: &mut HashMap<&'static str, Box<dyn Any + Send + Sync>>) -> Option<Box<dyn DceMethod<Self> + Send + Sync>> {
124        Self::parse_http_method(prop_mapping)
125    }
126}
127
128
129impl HttpProtocol for HyperHttpProtocol {}
130
131impl HttpMethodGetter for HyperHttpProtocol {
132    fn method(&self) -> &Method {
133        self.req().map_or_else(|_| &Method::GET, |r| r.method())
134    }
135}
136
137pub trait HttpProtocol: RoutableProtocol + HttpMethodGetter {
138    fn parse_http_method(prop_mapping: &mut HashMap<&'static str, Box<dyn Any + Send + Sync>>) -> Option<Box<dyn DceMethod<Self> + Send + Sync>> {
139        Some(Box::new(prop_mapping.remove("method").map(|ms| if ms.is::<Method>() {
140            ms.downcast::<Method>().map(|m| HashSet::from([*m])).ok()
141        } else {
142            ms.downcast::<Vec<Method>>().map(|m| m.into_iter().collect::<HashSet<_>>()).ok()
143        }).unwrap_or_else(|| Some(HashSet::from([Method::GET, Method::HEAD, Method::OPTIONS]))).map(HttpMethodSet)?))
144    }
145}
146
147#[allow(non_snake_case, non_upper_case_globals)]
148pub mod HttpMethod {
149    use hyper::Method;
150
151    pub const Get: Method = Method::GET;
152    pub const Post: Method = Method::POST;
153    pub const Put: Method = Method::PUT;
154    pub const Delete: Method = Method::DELETE;
155    pub const Head: Method = Method::HEAD;
156    pub const Options: Method = Method::OPTIONS;
157    pub const Connect: Method = Method::CONNECT;
158    pub const Patch: Method = Method::PATCH;
159    pub const Trace: Method = Method::TRACE;
160}
161
162#[derive(Debug)]
163pub struct HttpMethodSet(HashSet<Method>);
164
165impl<T: HttpMethodGetter> DceMethod<T> for HttpMethodSet {
166    fn to_string(&self) -> String {
167        format!("[{}]", self.0.iter().map(|m| m.to_string()).fold(String::new(), |a, b| format!("{a}, {b}")))
168    }
169
170    fn req_match(&self, raw: &T) -> bool {
171        self.0.contains(raw.method())
172    }
173}
174
175pub trait HttpMethodGetter {
176    fn method(&self) -> &Method;
177}