tachyon_web/server/
http.rs1use crate::http::response::Body;
2#[cfg(feature = "tls")]
3use crate::server::TLS_HANDSHAKE_TIMEOUT;
4use crate::server::{IS_LOCAL_WORKER, REQUEST_TIMEOUT, Server};
5use bytes::Bytes;
6use hyper::body::{Body as HyperBody, Frame, SizeHint};
7use hyper::service::service_fn;
8use hyper::{Request, Response};
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12use std::task::{Context, Poll};
13use tokio::net::TcpListener;
14#[cfg(feature = "tls")]
15use tokio_rustls::TlsAcceptor;
16
17pin_project_lite::pin_project! {
18 struct DeadlineBody {
27 #[pin]
28 inner: hyper::body::Incoming,
29 #[pin]
30 deadline: tokio::time::Sleep,
31 }
32}
33
34impl HyperBody for DeadlineBody {
35 type Data = Bytes;
36 type Error = crate::http::error::Error;
37
38 fn poll_frame(
39 self: Pin<&mut Self>,
40 cx: &mut Context<'_>,
41 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
42 let this = self.project();
43 if this.deadline.poll(cx).is_ready() {
44 return Poll::Ready(Some(Err(crate::http::error::Error::Rejection {
45 status: hyper::StatusCode::REQUEST_TIMEOUT,
46 message: "Timed out reading request body".to_string(),
47 })));
48 }
49 match this.inner.poll_frame(cx) {
50 Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok(frame))),
51 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e.into()))),
52 Poll::Ready(None) => Poll::Ready(None),
53 Poll::Pending => Poll::Pending,
54 }
55 }
56
57 fn is_end_stream(&self) -> bool {
58 self.inner.is_end_stream()
59 }
60
61 fn size_hint(&self) -> SizeHint {
62 self.inner.size_hint()
63 }
64}
65
66#[cfg(feature = "http2")]
67#[derive(Clone, Copy, Debug)]
68struct LocalExecutor;
69
70#[cfg(feature = "http2")]
71impl<F> hyper::rt::Executor<F> for LocalExecutor
72where
73 F: Future + Send + 'static,
74 F::Output: Send + 'static,
75{
76 fn execute(&self, fut: F) {
77 IS_LOCAL_WORKER.with(|flag| {
78 if flag.get() {
79 drop(tokio::task::spawn_local(fut));
80 } else {
81 drop(tokio::spawn(fut));
82 }
83 });
84 }
85}
86
87impl<S> Server<S>
88where
89 S: Clone + Send + Sync + 'static,
90{
91 pub async fn serve_http(self, listener: TcpListener) -> Result<(), std::io::Error> {
113 crate::server::enforce_fips_compliance()?;
114 let state = Arc::new(self);
115 let connection_semaphore = Arc::new(tokio::sync::Semaphore::new(state.max_connections));
116
117 #[cfg(all(feature = "http1", feature = "http2"))]
121 let builder = {
122 let mut b = hyper_util::server::conn::auto::Builder::new(LocalExecutor);
125 let _ = b
126 .http1()
127 .timer(hyper_util::rt::TokioTimer::new())
128 .header_read_timeout(REQUEST_TIMEOUT)
129 .keep_alive(true)
130 .max_buf_size(8192)
131 .writev(true);
132 let _ = b
133 .http2()
134 .timer(hyper_util::rt::TokioTimer::new())
135 .initial_stream_window_size(65535)
136 .initial_connection_window_size(1024 * 1024)
137 .max_frame_size(16384)
138 .max_concurrent_streams(200)
139 .keep_alive_timeout(REQUEST_TIMEOUT);
140 b
141 };
142 #[cfg(all(feature = "http1", not(feature = "http2")))]
143 let builder = {
144 let mut b = hyper::server::conn::http1::Builder::new();
146 let _ = b
147 .timer(hyper_util::rt::TokioTimer::new())
148 .header_read_timeout(REQUEST_TIMEOUT)
149 .keep_alive(true)
150 .max_buf_size(8192)
151 .writev(true);
152 b
153 };
154 #[cfg(all(feature = "http2", not(feature = "http1")))]
155 let builder = {
156 let mut b = hyper::server::conn::http2::Builder::new(LocalExecutor);
159 let _ = b
160 .timer(hyper_util::rt::TokioTimer::new())
161 .initial_stream_window_size(65535)
162 .initial_connection_window_size(1024 * 1024)
163 .max_frame_size(16384)
164 .max_concurrent_streams(200)
165 .keep_alive_timeout(REQUEST_TIMEOUT);
166 b
167 };
168
169 loop {
170 let Ok(permit) = connection_semaphore.clone().acquire_owned().await else {
171 break;
172 };
173
174 let (stream, peer) = match listener.accept().await {
175 Ok(c) => c,
176 Err(e) => {
177 drop(permit);
178 tracing::error!("[http] Accept error: {}", e);
179 if crate::server::is_resource_exhaustion(&e) {
180 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
181 }
182 continue;
183 }
184 };
185 let _ = stream.set_nodelay(true);
186 #[cfg(target_os = "linux")]
187 {
188 let sock_ref = socket2::SockRef::from(&stream);
189 let _ = sock_ref.set_tcp_quickack(true);
190 }
191 let state = state.clone();
192 let builder = builder.clone();
193
194 let serve_fut = async move {
195 let io = hyper_util::rt::TokioIo::new(stream);
196 let svc = service_fn(move |req| hyper_handler(state.clone(), req, peer));
197 #[cfg(all(feature = "http1", feature = "http2"))]
198 let result = builder.serve_connection_with_upgrades(io, svc).await;
199 #[cfg(all(feature = "http1", not(feature = "http2")))]
200 let result = builder.serve_connection(io, svc).with_upgrades().await;
201 #[cfg(all(feature = "http2", not(feature = "http1")))]
202 let result = builder.serve_connection(io, svc).await;
203 if let Err(e) = result {
204 tracing::debug!("[http] Connection error: {}", e);
205 }
206 drop(permit);
207 };
208
209 IS_LOCAL_WORKER.with(|flag| {
210 if flag.get() {
211 drop(tokio::task::spawn_local(serve_fut));
212 } else {
213 drop(tokio::spawn(serve_fut));
214 }
215 });
216 }
217 Ok(())
218 }
219
220 #[cfg(feature = "tls")]
228 pub async fn serve_https(
229 self,
230 listener: TcpListener,
231 acceptor: TlsAcceptor,
232 ) -> Result<(), std::io::Error> {
233 crate::server::enforce_fips_compliance()?;
234 let state = Arc::new(self);
235 let connection_semaphore = Arc::new(tokio::sync::Semaphore::new(state.max_connections));
236
237 loop {
238 let Ok(permit) = connection_semaphore.clone().acquire_owned().await else {
239 break;
240 };
241
242 let (tcp_stream, peer) = match listener.accept().await {
243 Ok(c) => c,
244 Err(e) => {
245 drop(permit);
246 tracing::error!("[https] Accept error: {}", e);
247 if crate::server::is_resource_exhaustion(&e) {
248 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
249 }
250 continue;
251 }
252 };
253 let _ = tcp_stream.set_nodelay(true);
254 #[cfg(target_os = "linux")]
255 {
256 let sock_ref = socket2::SockRef::from(&tcp_stream);
257 let _ = sock_ref.set_tcp_quickack(true);
258 }
259 let acceptor = acceptor.clone();
260 let state = state.clone();
261
262 let serve_fut = async move {
263 let tls_stream =
264 match tokio::time::timeout(TLS_HANDSHAKE_TIMEOUT, acceptor.accept(tcp_stream))
265 .await
266 {
267 Ok(Ok(stream)) => stream,
268 Ok(Err(e)) => {
269 tracing::debug!("[https] TLS handshake error: {}", e);
270 drop(permit);
271 return;
272 }
273 Err(_) => {
274 tracing::debug!("[https] TLS handshake timed out");
275 drop(permit);
276 return;
277 }
278 };
279
280 #[cfg(feature = "http2")]
283 let is_h2 = {
284 let (_, connection) = tls_stream.get_ref();
285 connection.alpn_protocol() == Some(b"h2")
286 };
287
288 let io = hyper_util::rt::TokioIo::new(tls_stream);
289 let svc = service_fn(move |req| hyper_handler(state.clone(), req, peer));
290
291 #[cfg(feature = "http2")]
292 if is_h2 {
293 let mut builder = hyper::server::conn::http2::Builder::new(LocalExecutor);
295 let _ = builder
296 .timer(hyper_util::rt::TokioTimer::new())
297 .initial_stream_window_size(65535)
298 .initial_connection_window_size(1024 * 1024)
299 .max_frame_size(16384)
300 .max_concurrent_streams(200)
301 .keep_alive_timeout(REQUEST_TIMEOUT);
302
303 if let Err(e) = builder.serve_connection(io, svc).await {
304 tracing::debug!("[https] HTTP/2 Connection error: {}", e);
305 }
306 drop(permit);
307 return;
308 }
309
310 #[cfg(feature = "http1")]
317 {
318 let mut builder = hyper::server::conn::http1::Builder::new();
320 let _ = builder
321 .timer(hyper_util::rt::TokioTimer::new())
322 .header_read_timeout(REQUEST_TIMEOUT)
323 .keep_alive(true)
324 .max_buf_size(8192);
325
326 if let Err(e) = builder.serve_connection(io, svc).with_upgrades().await {
327 tracing::debug!("[https] HTTP/1.1 Connection error: {}", e);
328 }
329 }
330 drop(permit);
331 };
332
333 IS_LOCAL_WORKER.with(|flag| {
334 if flag.get() {
335 drop(tokio::task::spawn_local(serve_fut));
336 } else {
337 drop(tokio::spawn(serve_fut));
338 }
339 });
340 }
341 Ok(())
342 }
343
344 #[cfg(feature = "tls")]
352 pub async fn serve_https_config(
353 self,
354 listener: TcpListener,
355 config: rustls::ServerConfig,
356 ) -> Result<(), std::io::Error> {
357 crate::server::enforce_fips_compliance()?;
358 let acceptor = TlsAcceptor::from(Arc::new(config));
359 self.serve_https(listener, acceptor).await
360 }
361}
362
363pub(super) async fn hyper_handler<S>(
364 state: Arc<Server<S>>,
365 req: Request<hyper::body::Incoming>,
366 peer: std::net::SocketAddr,
367) -> Result<Response<Body>, std::io::Error>
368where
369 S: Clone + Send + Sync + 'static,
370{
371 let (parts, incoming_body) = req.into_parts();
372
373 let body = Body::stream(DeadlineBody {
374 inner: incoming_body,
375 deadline: tokio::time::sleep(REQUEST_TIMEOUT),
376 });
377
378 let mut rebuild_req = Request::from_parts(parts, body);
379 #[cfg(feature = "original-uri")]
380 {
381 let orig_uri = rebuild_req.uri().clone();
382 rebuild_req
383 .extensions_mut()
384 .insert(crate::routing::extract::OriginalUri(orig_uri));
385 }
386 rebuild_req
387 .extensions_mut()
388 .insert(crate::routing::extract::ConnectInfo(peer));
389 rebuild_req
390 .extensions_mut()
391 .insert(crate::routing::extract::MaxBodySize(state.max_body_size));
392
393 let resp = state.router.handle_request(rebuild_req).await;
394
395 if let Some((min, max)) = state.response_jitter {
396 tokio::time::sleep(crate::server::jittered_delay(min, max)).await;
397 }
398
399 Ok(resp)
400}