Skip to main content

karyon_jsonrpc/server/http/
mod.rs

1//! HTTP server: HTTP/1.1 + HTTP/2 over TCP, optional HTTP/3 over QUIC.
2
3mod h1h2;
4#[cfg(feature = "http3")]
5mod h3;
6
7use std::{net::SocketAddr, sync::Arc};
8
9use async_trait::async_trait;
10use bytes::Bytes;
11use http_body_util::Full;
12use hyper::{header::CONTENT_TYPE, Response, StatusCode};
13use serde_json::to_value;
14
15use karyon_core::async_runtime::net::TcpListener;
16
17#[cfg(feature = "http3")]
18use karyon_core::async_util::select;
19
20use karyon_net::Endpoint;
21
22pub(super) use crate::hyper_exec::HyperExecutor;
23
24use crate::{
25    codec::{encode_msg, JsonRpcCodec, DEFAULT_CONTENT_TYPE},
26    error::{Error, Result},
27    message,
28    server::{accept_backoff, Server, FAILED_TO_PARSE_ERROR_MSG},
29};
30
31/// HTTP settings for `http://` endpoints, set with
32/// `ServerBuilder::http_config`.
33#[derive(Clone, Debug)]
34pub struct HttpConfig {
35    /// `Content-Type` of the responses. Set it to match a custom codec.
36    /// Defaults to `application/json`.
37    pub content_type: String,
38}
39
40impl Default for HttpConfig {
41    fn default() -> Self {
42        Self {
43            content_type: DEFAULT_CONTENT_TYPE.to_string(),
44        }
45    }
46}
47
48/// Max HTTP request body size (1 MB).
49pub(super) const MAX_HTTP_BODY_SIZE: u64 = 1024 * 1024;
50
51// Pre-encoded JSON-RPC errors returned before a message can be parsed.
52pub(super) const ERR_METHOD_NOT_ALLOWED: &str = r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Only POST method is accepted"},"id":null}"#;
53pub(super) const ERR_BODY_TOO_LARGE: &str =
54    r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Request body too large"},"id":null}"#;
55pub(super) const ERR_READ_BODY: &str = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Failed to read request body"},"id":null}"#;
56pub(super) const ERR_ENCODE_RESPONSE: &str =
57    r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Failed to encode response"},"id":null}"#;
58
59/// Runs an HTTP server. Hides the codec type, like `AsyncAcceptor`
60/// does for the stream transports.
61#[async_trait]
62pub(crate) trait HttpAcceptor: Send + Sync {
63    /// Run the accept loop.
64    async fn accept_loop(&self, server: Arc<Server>) -> Result<()>;
65    fn local_endpoint(&self) -> Result<Endpoint>;
66}
67
68/// HTTP server (TCP for h1/h2, optional QUIC for h3).
69pub(crate) struct HttpServer<C> {
70    tcp_listener: TcpListener,
71    /// Encodes and decodes all HTTP bodies and HTTP/3 streams.
72    codec: C,
73    #[cfg(feature = "http3")]
74    quic_endpoint: Option<karyon_net::quic::QuicEndpoint>,
75}
76
77impl<C: JsonRpcCodec> HttpServer<C> {
78    /// Create HTTP/1.1 + HTTP/2 server.
79    pub(crate) async fn new(endpoint: &Endpoint, codec: C) -> Result<Self> {
80        let addr = SocketAddr::try_from(endpoint.clone())?;
81        let tcp_listener = TcpListener::bind(addr).await?;
82        Ok(Self {
83            tcp_listener,
84            codec,
85            #[cfg(feature = "http3")]
86            quic_endpoint: None,
87        })
88    }
89
90    /// Create HTTP/1.1 + HTTP/2 + HTTP/3 server.
91    #[cfg(feature = "http3")]
92    pub(crate) async fn new_h3(
93        endpoint: &Endpoint,
94        quic_config: karyon_net::quic::ServerQuicConfig,
95        codec: C,
96    ) -> Result<Self> {
97        let addr = SocketAddr::try_from(endpoint.clone())?;
98        let tcp_listener = TcpListener::bind(addr).await?;
99        let actual_addr = tcp_listener.local_addr()?;
100        let quic_ep_addr = Endpoint::new_quic_addr(actual_addr);
101        let quic_endpoint =
102            karyon_net::quic::QuicEndpoint::listen(&quic_ep_addr, quic_config).await?;
103        Ok(Self {
104            tcp_listener,
105            codec,
106            quic_endpoint: Some(quic_endpoint),
107        })
108    }
109}
110
111#[async_trait]
112impl<C: JsonRpcCodec> HttpAcceptor for HttpServer<C> {
113    async fn accept_loop(&self, server: Arc<Server>) -> Result<()> {
114        let codec = &self.codec;
115        // Back off on accept errors, so the loop does not spin.
116        let tcp_backoff = accept_backoff();
117
118        #[cfg(feature = "http3")]
119        if let Some(ref quic_ep) = self.quic_endpoint {
120            let quic_backoff = accept_backoff();
121            loop {
122                select(
123                    h1h2::accept_tcp(&server, &self.tcp_listener, codec, &tcp_backoff),
124                    h3::accept_h3(&server, quic_ep, codec, &quic_backoff),
125                )
126                .await;
127            }
128        }
129
130        loop {
131            h1h2::accept_tcp(&server, &self.tcp_listener, codec, &tcp_backoff).await;
132        }
133    }
134
135    fn local_endpoint(&self) -> Result<Endpoint> {
136        let addr = self.tcp_listener.local_addr()?;
137        format!("http://{addr}/")
138            .parse()
139            .map_err(|e: karyon_net::Error| Error::HttpError(e.to_string()))
140    }
141}
142
143// -- Shared helpers --
144
145/// Response with one of the pre-encoded JSON error bodies (`ERR_*`).
146pub(super) fn json_response(status: StatusCode, body: &str) -> Response<Full<Bytes>> {
147    json_response_bytes(status, DEFAULT_CONTENT_TYPE, Bytes::from(body.to_string()))
148}
149
150/// Response with a codec-encoded body.
151pub(super) fn json_response_bytes(
152    status: StatusCode,
153    content_type: &str,
154    body: Bytes,
155) -> Response<Full<Bytes>> {
156    Response::builder()
157        .status(status)
158        .header(CONTENT_TYPE, content_type)
159        .body(Full::new(body))
160        .unwrap()
161}
162
163/// Encode a JSON-RPC response with the server codec.
164pub(super) fn encode_response<C: JsonRpcCodec>(
165    codec: &C,
166    resp: &message::Response,
167) -> Result<Bytes> {
168    encode_msg(codec, &to_value(resp)?)
169}
170
171/// Build the JSON-RPC parse error response.
172pub(super) fn parse_error_response() -> message::Response {
173    message::Response {
174        error: Some(message::Error {
175            code: message::PARSE_ERROR_CODE,
176            message: FAILED_TO_PARSE_ERROR_MSG.to_string(),
177            data: None,
178        }),
179        ..Default::default()
180    }
181}