1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::any::Any;
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;
use async_trait::async_trait;
use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
use hyper::body::{Bytes, Incoming};
use hyper::{Method, Request, Response, StatusCode};
use dce_router::protocol::RoutableProtocol;
use dce_router::request::{RawRequest, RequestContext, Request as DceRequest};
use dce_router::router::Router;
use dce_router::serializer::Serialized;
use dce_util::mixed::{DceErr, DceResult};
use crate::request::{HttpMethodGetter, HttpRawRequest};

pub type HttpRaw = DceRequest<HttpRawRequest<HyperHttpProtocol>, (), (), (), ()>;
pub type HttpGet<Dto> = DceRequest<HttpRawRequest<HyperHttpProtocol>, (), (), Dto, Dto>;
pub type HttpSame<Dto> = DceRequest<HttpRawRequest<HyperHttpProtocol>, Dto, Dto, Dto, Dto>;
pub type HttpNoConvert<Req, Resp> = DceRequest<HttpRawRequest<HyperHttpProtocol>, Req, Req, Resp, Resp>;
pub type Http<ReqDto, Req, Resp, RespDto> = DceRequest<HttpRawRequest<HyperHttpProtocol>, ReqDto, Req, Resp, RespDto>;

#[derive(Debug)]
pub struct HyperHttpProtocol {
    req: Option<Request<Incoming>>,
    resp: Option<Response<BoxBody<Bytes, Infallible>>>,
}

impl HyperHttpProtocol {
    pub async fn route(
        self,
        router: Arc<Router<HttpRawRequest<Self>>>,
        context_data: HashMap<String, Box<dyn Any + Send>>,
    ) -> Result<Response<BoxBody<Bytes, Infallible>>, Infallible> {
        Ok(HyperHttpProtocol { req: None, resp: None }
            .handle_result(HttpRawRequest::route(RequestContext::new(router, HttpRawRequest::new(self)).set_data(context_data)).await).unwrap())
    }
}

impl From<Request<Incoming>> for HyperHttpProtocol {
    fn from(value: Request<Incoming>) -> Self {
        Self { req: Some(value), resp: None }
    }
}

impl Into<Response<BoxBody<Bytes, Infallible>>> for HyperHttpProtocol {
    fn into(self) -> Response<BoxBody<Bytes, Infallible>> {
        self.resp.unwrap()
    }
}

#[async_trait]
impl RoutableProtocol for HyperHttpProtocol {
    type Req = Request<Incoming>;
    type Resp = Response<BoxBody<Bytes, Infallible>>;

    fn path(&self) -> &str {
        let Some(req) = &self.req else { unreachable!() };
        req.uri().path().trim_start_matches('/')
    }

    async fn body(&mut self) -> DceResult<Serialized> {
        assert!(matches!(self.req, Some(_)), "req can only take once.");
        let Some(req) = self.req.take() else { unreachable!() };
        Ok(Serialized::Bytes(req.collect().await.or_else(|e| Err(DceErr::closed(0, e.to_string())))?.to_bytes()))
    }

    fn pack_resp(self, serialized: Serialized) -> Self::Resp {
        Response::new(match serialized {
            Serialized::String(str) => Full::from(str).boxed(),
            Serialized::Bytes(bytes) => Full::from(bytes).boxed(),
        })
    }

    fn err_into(self, code: isize, message: String) -> Self::Resp {
        Response::builder().status(code as u16)
            .body(if message.is_empty() { Empty::new().boxed() } else { Full::from(message).boxed() })
            .expect("failed to build a response")
    }

    fn handle_result(self, (_, resp): (Option<bool>, DceResult<Option<Self::Resp>>)) -> Option<Self::Resp> {
        Self::try_print_err(&resp);
        Some(match resp {
            Ok(resp) => resp.unwrap_or_else(|| Response::new(Empty::new().boxed())),
            Err(DceErr::Openly(err)) => self.err_into(if err.code > 0 && err.code < 600 { err.code as u16 } else { StatusCode::SERVICE_UNAVAILABLE.as_u16() } as isize, err.message),
            Err(DceErr::Closed(_)) => self.err_into(StatusCode::SERVICE_UNAVAILABLE.as_u16() as isize, "".to_owned())
        })
    }
}

impl HttpMethodGetter for HyperHttpProtocol {
    fn method(&self) -> &Method {
        let Some(req) = &self.req else { unreachable!() };
        req.method()
    }
}