1pub mod actions;
3pub mod wire;
4use crate::llm::{action_helper::call_llm, ActionResult};
5use crate::logging::emit::Log;
6use crate::protocol::{Event, SpawnContext};
7use crate::server::connection::ConnectionId;
8use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
9use crate::utils::quic::*;
10use actions::{Http3Protocol, HTTP3_REQUEST_EVENT};
11use anyhow::{bail, ensure, Result};
12use bytes::{Buf, Bytes};
13use futures::{stream::FuturesUnordered, StreamExt};
14use serde_json::json;
15use std::{net::SocketAddr, sync::Arc, time::Duration};
16use wire::*;
17#[derive(Clone)]
18struct Settings {
19 handshake: Duration,
20 exchange: Duration,
21 idle: Duration,
22 connections: usize,
23 streams: usize,
24}
25impl Settings {
26 fn read(p: Option<&crate::protocol::StartupParams>) -> Result<Self> {
27 Ok(Self {
28 handshake: Duration::from_secs(bounded_parameter(p, "handshake_timeout_secs", 10, 60)?),
29 exchange: Duration::from_secs(bounded_parameter(p, "exchange_timeout_secs", 30, 300)?),
30 idle: Duration::from_secs(bounded_parameter(p, "idle_timeout_secs", 300, 3600)?),
31 connections: bounded_parameter(p, "max_connections", 64, 256)? as usize,
32 streams: bounded_parameter(p, "max_streams", 32, 32)? as usize,
33 })
34 }
35}
36pub struct Http3Server;
37impl Http3Server {
38 pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
39 let settings = Settings::read(ctx.startup_params.as_ref())?;
40 let cert = ctx
41 .startup_params
42 .as_ref()
43 .map(|p| p.get_optional_string("cert_path"))
44 .transpose()?
45 .flatten();
46 let key = ctx
47 .startup_params
48 .as_ref()
49 .map(|p| p.get_optional_string("key_path"))
50 .transpose()?
51 .flatten();
52 let _ = rustls::crypto::ring::default_provider().install_default();
53 let tls = match (cert, key) {
54 (Some(c), Some(k)) => {
55 crate::server::tls_cert_manager::load_tls_config_from_files(&c, &k)?
56 }
57 (None, None) => crate::server::tls_cert_manager::generate_default_tls_config()?,
58 _ => bail!("cert_path and key_path must be supplied together"),
59 };
60 let mut tls = (*tls).clone();
61 tls.alpn_protocols = vec![b"h3".to_vec()];
62 tls.max_early_data_size = 0;
63 let mut config = quinn::ServerConfig::with_crypto(Arc::new(
64 quinn::crypto::rustls::QuicServerConfig::try_from(tls)?,
65 ));
66 config.transport_config(transport(settings.idle, settings.streams as u32, 4));
67 config.migration(false);
68 let endpoint = EndpointOwner(EndpointGuard(quinn::Endpoint::server(
69 config,
70 ctx.legacy_listen_addr(),
71 )?));
72 let local = endpoint.local_addr()?;
73 Log::new(Some(&ctx.status_tx)).info(format!("HTTP3 listening on {local}"));
74 let state = ctx.state.clone();
75 let id = ctx.server_id;
76 let task = tokio::spawn(async move {
77 let mut sessions = FuturesUnordered::new();
78 loop {
79 tokio::select! {
80 incoming=endpoint.accept()=> { let Some(incoming)=incoming else { break; }; if sessions.len()>=settings.connections { incoming.refuse(); continue; } sessions.push(Self::session(incoming,ctx.clone(),settings.clone(),local)); },
81 _=sessions.next(),if !sessions.is_empty()=>{}
82 }
83 }
84 });
85 state.register_server_task(id, task).await;
86 Ok(local)
87 }
88 async fn session(
89 incoming: quinn::Incoming,
90 ctx: SpawnContext,
91 settings: Settings,
92 local: SocketAddr,
93 ) {
94 let connection = match tokio::time::timeout(settings.handshake, incoming).await {
95 Ok(Ok(c)) => ConnectionOwner(ConnectionGuard(c)),
96 _ => return,
97 };
98 let mut h3 = match h3::server::builder()
99 .send_grease(false)
100 .max_field_section_size(MAX_HEADERS as u64)
101 .build(h3_quinn::Connection::new(connection.clone()))
102 .await
103 {
104 Ok(c) => c,
105 Err(_) => return,
106 };
107 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
108 let now = crate::utils::clock::Instant::now();
109 ctx.state
110 .add_connection_to_server(
111 ctx.server_id,
112 ConnectionState {
113 id,
114 remote_addr: connection.remote_address(),
115 local_addr: local,
116 bytes_sent: 0,
117 bytes_received: 0,
118 packets_sent: 0,
119 packets_received: 0,
120 last_activity: now,
121 status: ConnectionStatus::Active,
122 status_changed_at: now,
123 protocol_info: ProtocolConnectionInfo::new(json!({"alpn":"h3"})),
124 },
125 )
126 .await;
127 let mut requests = FuturesUnordered::new();
128 loop {
129 tokio::select! {
130 resolver=h3.accept()=> { match resolver {
131 Ok(Some(resolver))=> { if requests.len()>=settings.streams { connection.close(0x107u32.into(),b"request capacity"); break; } requests.push(Self::request(resolver,ctx.clone(),id,settings.exchange,connection.remote_address())); },
132 _=>break
133 } },
134 _=requests.next(),if !requests.is_empty()=>{},
135 _=connection.closed()=>break,
136 }
137 }
138 drop(requests);
139 ctx.state
140 .close_connection_on_server(ctx.server_id, id)
141 .await;
142 let _ = ctx.status_tx.send("__UPDATE_UI__".into());
143 }
144 async fn request(
145 resolver: h3::server::RequestResolver<h3_quinn::Connection, Bytes>,
146 ctx: SpawnContext,
147 id: ConnectionId,
148 deadline: Duration,
149 peer: SocketAddr,
150 ) {
151 let end = tokio::time::Instant::now() + deadline;
152 let (request, stream) = match tokio::time::timeout_at(end, resolver.resolve_request()).await
153 {
154 Ok(Ok(r)) => r,
155 _ => return,
156 };
157 let mut guard = ServerStreamGuard {
158 stream,
159 done: false,
160 };
161 let exchange = async {
162 check_request_headers(request.headers())?;
163 let mut body = Vec::new();
164 while let Some(mut chunk) = guard.stream.recv_data().await? {
165 ensure!(
166 chunk.remaining() <= MAX_BODY.saturating_sub(body.len()),
167 "HTTP3 request body exceeds 8 MiB"
168 );
169 let count = chunk.remaining();
170 body.extend_from_slice(&chunk.copy_to_bytes(count));
171 }
172 let trailers = guard.stream.recv_trailers().await?.unwrap_or_default();
173 check_headers(&trailers)?;
174 validate_length(request.headers(), body.len())?;
175 let bytes = body.len();
176 let text = String::from_utf8(body)?;
177 ctx.state
178 .update_connection_stats(ctx.server_id, id, Some(bytes as u64), None, Some(1), None)
179 .await;
180 let event = Event::new(
181 &HTTP3_REQUEST_EVENT,
182 json!({
183 "method":request.method().as_str(),
184 "path":request.uri().path_and_query().map(|p|p.as_str()).unwrap_or("/"),
185 "headers":request_header_json(request.headers())?, "body":text,
186 "trailers":header_json(&trailers)?, "stream_id":guard.stream.id().index(), "peer_addr":peer.to_string(),
187 }),
188 );
189 Log::new(Some(&ctx.status_tx)).debug(format!(
190 "HTTP3 {} {}",
191 request.method(),
192 request.uri()
193 ));
194 Log::new(Some(&ctx.status_tx)).trace(format!("HTTP3 request: {}", event.data));
195 let log = Log::new(Some(&ctx.status_tx));
196 let response = match call_llm(
197 &ctx.llm_client,
198 &ctx.state,
199 ctx.server_id,
200 Some(id),
201 &event,
202 &Http3Protocol,
203 )
204 .await
205 {
206 Ok(result) => {
207 for message in result.messages {
208 log.info(message);
209 }
210 if !result.failures.is_empty() {
211 log.error("HTTP3 decision=fail_closed_action_error; answering 500");
212 json!({"status":500,"body":"Response could not be generated"})
213 } else {
214 let mut response = None;
215 for action in result.protocol_results {
216 match action {
217 ActionResult::Custom { name, data } if name == "http3_response" => {
218 ensure!(response.is_none(), "Multiple final HTTP3 responses");
219 response = Some(data);
220 }
221 ActionResult::CloseConnection => {
222 log.info(
223 "HTTP3 decision=model_reject; request explicitly cancelled",
224 );
225 return Ok(false);
226 }
227 _ => {}
228 }
229 }
230 if let Some(response) = response {
231 let decision = if matches!(response["status"].as_u64(), Some(401 | 403))
232 {
233 "model_reject"
234 } else {
235 "model_answer"
236 };
237 log.info(format!(
238 "HTTP3 decision={decision} status={}",
239 response["status"]
240 ));
241 response
242 } else {
243 log.info("HTTP3 decision=model_silent; answering 500");
244 json!({"status":500,"body":"No response provided"})
245 }
246 }
247 }
248 Err(error) => {
249 let category = if crate::utils::WireFailure::classify(&error).is_overloaded() {
250 "overloaded"
251 } else {
252 "unavailable"
253 };
254 log.error(format!("HTTP3 decision=fail_closed_llm_error category={category}; answering 503: {error}"));
255 json!({"status":503,"body":"Service unavailable"})
256 }
257 };
258 let mut reply = http::Response::builder()
259 .status(response["status"].as_u64().unwrap_or(500) as u16)
260 .body(())?;
261 *reply.headers_mut() = parse_headers(&response["headers"])?;
262 let body = response["body"].as_str().unwrap_or("");
263 guard.stream.send_response(reply).await?;
264 let body = if request.method() == http::Method::HEAD {
265 ""
266 } else {
267 body
268 };
269 if !body.is_empty() {
270 guard
271 .stream
272 .send_data(Bytes::copy_from_slice(body.as_bytes()))
273 .await?;
274 }
275 let trailers = parse_headers(&response["trailers"])?;
276 if !trailers.is_empty() {
277 guard.stream.send_trailers(trailers).await?;
278 }
279 guard.stream.finish().await?;
280 ctx.state
281 .update_connection_stats(
282 ctx.server_id,
283 id,
284 None,
285 Some(body.len() as u64),
286 None,
287 Some(1),
288 )
289 .await;
290 Ok::<_, anyhow::Error>(true)
291 };
292 match tokio::time::timeout_at(end, exchange).await {
293 Ok(Ok(true)) => guard.done = true,
294 Ok(Ok(false)) => {}
295 Ok(Err(error)) => Log::new(Some(&ctx.status_tx)).error(format!(
296 "HTTP3 decision=fail_closed_request_error; resetting request: {error}"
297 )),
298 Err(_) => Log::new(Some(&ctx.status_tx))
299 .error("HTTP3 decision=fail_closed_timeout; resetting request"),
300 }
301 }
302}
303struct ServerStreamGuard {
304 stream: h3::server::RequestStream<h3_quinn::BidiStream<Bytes>, Bytes>,
305 done: bool,
306}
307impl Drop for ServerStreamGuard {
308 fn drop(&mut self) {
309 if !self.done {
310 self.stream
311 .stop_sending(h3::error::Code::H3_REQUEST_CANCELLED);
312 self.stream
313 .stop_stream(h3::error::Code::H3_REQUEST_CANCELLED);
314 }
315 }
316}