Skip to main content

karyon_jsonrpc/server/http/
h3.rs

1//! HTTP/3 over QUIC via h3 / h3-quinn.
2//!
3//! One request per stream. A subscription keeps its stream open:
4//! notifications go out on the response, and the client sends the
5//! unsubscribe request on the same request body.
6
7use std::{fmt::Display, sync::Arc};
8
9use async_channel::bounded;
10use bytes::Bytes;
11use h3::server::{Connection as H3Connection, RequestResolver, RequestStream};
12use h3_quinn::{Connection, RecvStream, SendStream};
13use hyper::{header::CONTENT_TYPE, Method, Request, Response, StatusCode};
14use log::{debug, error};
15use serde_json::{json, to_value, Value};
16
17use karyon_core::async_util::{select, Backoff, Either};
18
19use karyon_net::{
20    quic::{QuicConn, QuicEndpoint, QuicIncoming},
21    ByteBuffer, Endpoint,
22};
23
24use crate::{
25    codec::{encode_msg, extend_buf, JsonRpcCodec, DEFAULT_CONTENT_TYPE},
26    error::{Error, Result},
27    message,
28    server::{
29        channel::Channel,
30        dispatch::{sanity_check, Handler, NewRequest, SanityCheckResult},
31        http::{
32            encode_response, parse_error_response, ERR_BODY_TOO_LARGE, ERR_METHOD_NOT_ALLOWED,
33            MAX_HTTP_BODY_SIZE,
34        },
35        Server, CHANNEL_SUBSCRIPTION_BUFFER_SIZE,
36    },
37};
38
39type H3Resolver = RequestResolver<Connection, Bytes>;
40type H3Send = RequestStream<SendStream<Bytes>, Bytes>;
41type H3Recv = RequestStream<RecvStream, Bytes>;
42
43pub(super) async fn accept_h3<C: JsonRpcCodec>(
44    server: &Arc<Server>,
45    quic_ep: &QuicEndpoint,
46    codec: &C,
47    backoff: &Backoff,
48) {
49    // Only the accept runs here; the handshake runs in the spawned task.
50    let incoming = match quic_ep.accept_incoming().await {
51        Ok(incoming) => incoming,
52        Err(err) => {
53            error!("Accept QUIC for HTTP/3: {err}");
54            backoff.sleep().await;
55            return;
56        }
57    };
58    backoff.reset();
59
60    server
61        .task_group
62        .spawn(serve_incoming_task(server.clone(), incoming, codec.clone()));
63}
64
65async fn serve_incoming_task<C: JsonRpcCodec>(
66    server: Arc<Server>,
67    incoming: QuicIncoming,
68    codec: C,
69) -> Result<()> {
70    let peer = incoming.peer_endpoint();
71    match incoming.handshake().await {
72        Ok(quic_conn) => serve_conn_task(server, quic_conn, codec).await,
73        Err(err) => {
74            debug!("QUIC handshake with {peer} failed: {err}");
75            Ok(())
76        }
77    }
78}
79
80async fn serve_conn_task<C: JsonRpcCodec>(
81    server: Arc<Server>,
82    quic_conn: QuicConn,
83    codec: C,
84) -> Result<()> {
85    let peer = quic_conn
86        .peer_endpoint()
87        .map(|e| e.to_string())
88        .unwrap_or_default();
89    if let Err(err) = serve_conn(server, quic_conn, codec).await {
90        debug!("HTTP/3 from {peer} closed: {err}");
91    }
92    Ok(())
93}
94
95async fn serve_conn<C: JsonRpcCodec>(
96    server: Arc<Server>,
97    quic_conn: QuicConn,
98    codec: C,
99) -> Result<()> {
100    let h3_conn = Connection::new(quic_conn.inner().clone());
101    let mut h3_server: H3Connection<Connection, Bytes> = H3Connection::new(h3_conn)
102        .await
103        .map_err(|e| Error::HttpError(format!("H3 handshake: {e}")))?;
104
105    let peer = quic_conn.peer_endpoint().ok();
106
107    loop {
108        match h3_server.accept().await {
109            // Headers are read in the request task, so a slow stream
110            // does not block the other streams of this connection.
111            Ok(Some(resolver)) => {
112                server.task_group.spawn(handle_request_task(
113                    server.clone(),
114                    resolver,
115                    peer.clone(),
116                    codec.clone(),
117                ));
118            }
119            Ok(None) => break,
120            Err(err) => {
121                debug!("HTTP/3 connection error: {err}");
122                break;
123            }
124        }
125    }
126
127    Ok(())
128}
129
130async fn handle_request_task<C: JsonRpcCodec>(
131    server: Arc<Server>,
132    resolver: H3Resolver,
133    peer: Option<Endpoint>,
134    codec: C,
135) -> Result<()> {
136    let (req, stream) = match resolver.resolve_request().await {
137        Ok(v) => v,
138        Err(err) => {
139            debug!("Resolve HTTP/3 request: {err}");
140            return Ok(());
141        }
142    };
143
144    let (send, recv) = stream.split();
145    if let Err(err) = handle_h3_request(server, req, send, recv, peer, codec).await {
146        error!("Handle HTTP/3 request: {err}");
147    }
148    Ok(())
149}
150
151async fn handle_h3_request<C: JsonRpcCodec>(
152    server: Arc<Server>,
153    req: Request<()>,
154    mut send: H3Send,
155    recv: H3Recv,
156    peer: Option<Endpoint>,
157    codec: C,
158) -> Result<()> {
159    let content_type = server.config.http_config.content_type.clone();
160
161    if req.method() != Method::POST {
162        let body = ERR_METHOD_NOT_ALLOWED.as_bytes();
163        return h3_send(
164            &mut send,
165            StatusCode::METHOD_NOT_ALLOWED,
166            DEFAULT_CONTENT_TYPE,
167            body,
168        )
169        .await;
170    }
171
172    // Read only the first message; a subscription keeps the body open.
173    let mut reader = BodyReader {
174        recv,
175        buf: ByteBuffer::new(),
176        codec: codec.clone(),
177    };
178    let msg = match reader.next().await {
179        Ok(Some(msg)) => msg,
180        Err(Error::BufferFull(_)) => {
181            let body = ERR_BODY_TOO_LARGE.as_bytes();
182            return h3_send(
183                &mut send,
184                StatusCode::PAYLOAD_TOO_LARGE,
185                DEFAULT_CONTENT_TYPE,
186                body,
187            )
188            .await;
189        }
190        Err(Error::HttpError(err)) => return Err(Error::HttpError(err)),
191        Ok(None) | Err(_) => {
192            let body = encode_response(&codec, &parse_error_response())?;
193            return h3_send(&mut send, StatusCode::OK, &content_type, &body).await;
194        }
195    };
196
197    let req = match sanity_check(msg) {
198        SanityCheckResult::NewReq(req) => req,
199        SanityCheckResult::ErrRes(res) => {
200            let body = encode_response(&codec, &res)?;
201            return h3_send(&mut send, StatusCode::OK, &content_type, &body).await;
202        }
203    };
204
205    // Pubsub is detected by the resolved handler, not the method name.
206    // TODO: match on the handler and pass it on. Now it is resolved here,
207    // then the request is re-encoded, checked and resolved again in
208    // `handle_request`.
209    let is_pubsub = matches!(
210        server.resolve_handler(&req.srvc_name, &req.method_name, true),
211        Handler::Pubsub(_)
212    );
213    if is_pubsub {
214        return handle_h3_subscription(server, req, send, reader, peer, codec).await;
215    }
216
217    let msg = to_value(&req.msg)?;
218    let response = server.handle_request(None, msg).await;
219    debug!("--> {response}");
220    let body = encode_response(&codec, &response)?;
221    h3_send(&mut send, StatusCode::OK, &content_type, &body).await
222}
223
224/// Pubsub over HTTP/3: the subscription gets its own `Channel`.
225/// Notifications stream on the response while the request body waits
226/// for the unsubscribe request.
227async fn handle_h3_subscription<C: JsonRpcCodec>(
228    server: Arc<Server>,
229    req: NewRequest,
230    mut send: H3Send,
231    mut reader: BodyReader<C>,
232    peer: Option<Endpoint>,
233    codec: C,
234) -> Result<()> {
235    let content_type = server.config.http_config.content_type.clone();
236    let (ch_tx, ch_rx) = bounded(CHANNEL_SUBSCRIPTION_BUFFER_SIZE);
237    let channel = Channel::new(ch_tx, peer);
238
239    let method = match server.resolve_handler(&req.srvc_name, &req.method_name, true) {
240        Handler::Pubsub(m) => m,
241        _ => unreachable!("pubsub method presence checked by caller"),
242    };
243
244    let params = req.msg.params.unwrap_or(json!(()));
245    let response = match method(channel.clone(), req.msg.method, params).await {
246        Ok(res) => message::Response {
247            result: Some(res),
248            id: Some(req.msg.id),
249            ..Default::default()
250        },
251        Err(err) => {
252            let response = err.to_response(Some(req.msg.id), None);
253            let body = encode_response(&codec, &response)?;
254            return h3_send(&mut send, StatusCode::OK, &content_type, &body).await;
255        }
256    };
257
258    debug!("--> {response}");
259    send.send_response(ok_response(&content_type)?)
260        .await
261        .map_err(h3_err)?;
262    let body = encode_response(&codec, &response)?;
263    send.send_data(body).await.map_err(h3_err)?;
264
265    let encoder = server.config.notification_encoder;
266    let result = loop {
267        match select(ch_rx.recv(), reader.next()).await {
268            Either::Left(Ok(nt)) => {
269                let notification = encoder(nt);
270                debug!("--> {notification}");
271                let body = encode_msg(&codec, &json!(notification))?;
272                if let Err(err) = send.send_data(body).await {
273                    break Err(h3_err(err));
274                }
275            }
276            Either::Left(Err(err)) => break Err(err.into()),
277            // The unsubscribe request, handled with the subscription's
278            // own channel.
279            Either::Right(Ok(Some(msg))) => {
280                let response = server.handle_request(Some(channel.clone()), msg).await;
281                debug!("--> {response}");
282                let body = encode_response(&codec, &response)?;
283                break send.send_data(body).await.map_err(h3_err);
284            }
285            Either::Right(_) => break Ok(()),
286        }
287    };
288
289    channel.close();
290    let _ = send.finish().await;
291    result
292}
293
294/// Reads codec frames from an HTTP/3 request body.
295struct BodyReader<C> {
296    recv: H3Recv,
297    buf: ByteBuffer,
298    codec: C,
299}
300
301impl<C: JsonRpcCodec> BodyReader<C> {
302    /// Next message, or `None` when the body ends.
303    async fn next(&mut self) -> Result<Option<Value>> {
304        loop {
305            if let Some((n, msg)) = self.codec.decode(&mut self.buf)? {
306                self.buf.advance(n);
307                return Ok(Some(msg));
308            }
309            if self.buf.len() as u64 > MAX_HTTP_BODY_SIZE {
310                return Err(Error::BufferFull("request body too large".into()));
311            }
312            match self.recv.recv_data().await.map_err(h3_err)? {
313                Some(chunk) => extend_buf(&mut self.buf, chunk),
314                None => return Ok(None),
315            }
316        }
317    }
318}
319
320fn ok_response(content_type: &str) -> Result<Response<()>> {
321    Response::builder()
322        .status(StatusCode::OK)
323        .header(CONTENT_TYPE, content_type)
324        .body(())
325        .map_err(|e| Error::HttpError(e.to_string()))
326}
327
328async fn h3_send(
329    send: &mut H3Send,
330    status: StatusCode,
331    content_type: &str,
332    body: &[u8],
333) -> Result<()> {
334    let resp = Response::builder()
335        .status(status)
336        .header(CONTENT_TYPE, content_type)
337        .body(())
338        .map_err(|e| Error::HttpError(e.to_string()))?;
339    send.send_response(resp).await.map_err(h3_err)?;
340    send.send_data(Bytes::from(body.to_vec()))
341        .await
342        .map_err(h3_err)?;
343    send.finish().await.map_err(h3_err)?;
344    Ok(())
345}
346
347fn h3_err(e: impl Display) -> Error {
348    Error::HttpError(e.to_string())
349}