1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::task::{Context as TaskContext, Poll};
6
7use anyhow::{Context, Result};
8use axum::Router;
9use axum::body::{Body, Bytes};
10use axum::extract::{Request, State};
11use axum::http::header::HOST;
12use axum::http::uri::Authority;
13use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
14use axum::response::IntoResponse;
15use axum::routing::any;
16use futures::Stream;
17use futures::StreamExt;
18use futures::future::BoxFuture;
19use http_body_util::LengthLimitError;
20use tokio_stream::wrappers::UnboundedReceiverStream;
21use tokio_util::sync::CancellationToken;
22use tower_http::services::ServeDir;
23
24use crate::ResponseChunk;
25use crate::serverless::{CoreServerlessRuntime, ServerlessRequest, ServerlessResponse};
26
27#[derive(Clone)]
28pub struct ListenerConfig {
29 pub host: Option<String>,
31 pub port: u16,
32 pub public_dir: Option<PathBuf>,
33 pub application: Option<ApplicationFetch>,
35}
36
37#[derive(Debug)]
38pub struct ApplicationRequest {
39 pub method: String,
40 pub url: String,
41 pub headers: HashMap<String, String>,
42 pub body: Vec<u8>,
43 pub cancel_token: CancellationToken,
44}
45
46#[derive(Debug)]
47pub struct ApplicationResponse {
48 pub status: u16,
49 pub headers: HashMap<String, String>,
50 pub body: ApplicationResponseBody,
51}
52
53#[derive(Debug)]
54pub enum ApplicationResponseBody {
55 Buffered(Vec<u8>),
56 Stream(tokio::sync::mpsc::Receiver<ResponseChunk>),
57}
58
59pub type ApplicationFetch = Arc<
60 dyn Fn(ApplicationRequest) -> BoxFuture<'static, Result<ApplicationResponse>> + Send + Sync,
61>;
62
63#[derive(Clone)]
64struct AppState {
65 runtime: CoreServerlessRuntime,
66 application: Option<ApplicationFetch>,
67 shutdown_token: CancellationToken,
68}
69
70#[derive(Clone)]
71struct ApplicationState {
72 application: ApplicationFetch,
73 shutdown_token: CancellationToken,
74}
75
76pub async fn serve(
78 runtime: CoreServerlessRuntime,
79 listener: ListenerConfig,
80 shutdown: CancellationToken,
81) -> Result<()> {
82 let host = listener.host.as_deref().unwrap_or("0.0.0.0");
83 let port = listener.port;
84
85 let state = AppState {
86 runtime,
87 application: listener.application.clone(),
88 shutdown_token: shutdown.clone(),
89 };
90
91 let forward_service = any(forward_request).with_state(state);
92
93 let router = match listener.public_dir.as_ref() {
94 Some(dir) => Router::new().fallback_service(
95 ServeDir::new(dir)
96 .call_fallback_on_method_not_allowed(true)
97 .fallback(forward_service),
98 ),
99 None => Router::new().fallback_service(forward_service),
100 };
101
102 let tcp = tokio::net::TcpListener::bind((host, port))
103 .await
104 .with_context(|| format!("bind tcp listener on {host}:{port}"))?;
105 let bound = tcp
106 .local_addr()
107 .context("read local address of bound listener")?;
108 tracing::info!(host = %bound.ip(), port = bound.port(), "rivetkit server listening");
109
110 let shutdown_fut = {
111 let shutdown = shutdown.clone();
112 async move { shutdown.cancelled().await }
113 };
114
115 axum::serve(tcp, router.into_make_service())
116 .with_graceful_shutdown(shutdown_fut)
117 .await
118 .context("axum::serve returned an error")?;
119
120 Ok(())
121}
122
123pub async fn serve_application(
128 listener: ListenerConfig,
129 application: ApplicationFetch,
130 max_body_bytes: usize,
131 shutdown: CancellationToken,
132) -> Result<()> {
133 let host = listener.host.as_deref().unwrap_or("0.0.0.0");
134 let port = listener.port;
135 let forward_service = any(forward_application_request).with_state((
136 ApplicationState {
137 application,
138 shutdown_token: shutdown.clone(),
139 },
140 max_body_bytes,
141 ));
142 let router = match listener.public_dir.as_ref() {
143 Some(dir) => Router::new().fallback_service(
144 ServeDir::new(dir)
145 .call_fallback_on_method_not_allowed(true)
146 .fallback(forward_service),
147 ),
148 None => Router::new().fallback_service(forward_service),
149 };
150 let tcp = tokio::net::TcpListener::bind((host, port))
151 .await
152 .with_context(|| format!("bind application tcp listener on {host}:{port}"))?;
153 let bound = tcp
154 .local_addr()
155 .context("read application listener local address")?;
156 tracing::info!(host = %bound.ip(), port = bound.port(), "application server listening");
157
158 axum::serve(tcp, router.into_make_service())
159 .with_graceful_shutdown(async move { shutdown.cancelled().await })
160 .await
161 .context("application axum::serve returned an error")?;
162 Ok(())
163}
164
165async fn forward_application_request(
166 State((state, max_body_bytes)): State<(ApplicationState, usize)>,
167 request: Request,
168) -> axum::response::Response {
169 let (parts, body) = request.into_parts();
170 let request_token = state.shutdown_token.child_token();
171 let body = match axum::body::to_bytes(body, max_body_bytes).await {
172 Ok(body) => body,
173 Err(error) if is_length_limit_error(&error) => {
174 tracing::warn!(max_body_bytes, "application request body exceeded limit");
175 return (
176 StatusCode::PAYLOAD_TOO_LARGE,
177 [("content-type", "text/plain; charset=utf-8")],
178 "Payload Too Large",
179 )
180 .into_response();
181 }
182 Err(error) => {
183 tracing::warn!(?error, "failed to read application request body");
184 return (
185 StatusCode::BAD_REQUEST,
186 [("content-type", "text/plain; charset=utf-8")],
187 "Bad Request",
188 )
189 .into_response();
190 }
191 };
192 let request = application_request_from_parts(parts, body, request_token.clone());
193 match (state.application)(request).await {
194 Ok(response) => into_application_response(response, request_token),
195 Err(error) => {
196 tracing::error!(?error, "application request handler failed");
197 request_token.cancel();
199 (
200 StatusCode::INTERNAL_SERVER_ERROR,
201 [("content-type", "text/plain; charset=utf-8")],
202 "Internal Server Error",
203 )
204 .into_response()
205 }
206 }
207}
208
209async fn forward_request(
210 State(state): State<AppState>,
211 request: Request,
212) -> axum::response::Response {
213 let (parts, body) = request.into_parts();
214 let body_limit = state.runtime.max_request_body_bytes();
215 let request_token = state.shutdown_token.child_token();
216 let body_bytes = match axum::body::to_bytes(body, body_limit).await {
217 Ok(bytes) => bytes,
218 Err(error) if is_length_limit_error(&error) => {
219 tracing::warn!(body_limit, "request body exceeded limit");
220 return into_axum_response(state.runtime.incoming_too_long_response(), request_token);
221 }
222 Err(error) => {
223 tracing::warn!(?error, "failed to read request body");
224 return into_axum_response(
225 state
226 .runtime
227 .invalid_request_response("failed to read request body"),
228 request_token,
229 );
230 }
231 };
232
233 let application_request =
234 application_request_from_parts(parts, body_bytes, request_token.clone());
235 let req = ServerlessRequest {
236 method: application_request.method,
237 url: application_request.url,
238 headers: application_request.headers,
239 body: application_request.body,
240 cancel_token: request_token.clone(),
241 };
242
243 if state.runtime.handles_listener_request(&req.url) || state.application.is_none() {
244 return into_axum_response(state.runtime.handle_request(req).await, request_token);
245 }
246
247 let application = state
248 .application
249 .as_ref()
250 .expect("application checked above");
251 match application(ApplicationRequest {
252 method: req.method,
253 url: req.url,
254 headers: req.headers,
255 body: req.body,
256 cancel_token: request_token.clone(),
257 })
258 .await
259 {
260 Ok(response) => into_application_response(response, request_token),
261 Err(error) => {
262 tracing::error!(?error, "application request handler failed");
263 request_token.cancel();
265 (
266 StatusCode::INTERNAL_SERVER_ERROR,
267 [("content-type", "text/plain; charset=utf-8")],
268 "Internal Server Error",
269 )
270 .into_response()
271 }
272 }
273}
274
275fn application_request_from_parts(
276 parts: axum::http::request::Parts,
277 body: Bytes,
278 cancel_token: CancellationToken,
279) -> ApplicationRequest {
280 let path_and_query = parts
281 .uri
282 .path_and_query()
283 .map(|pq| pq.as_str())
284 .unwrap_or("/");
285 let forwarded_proto = parts
286 .headers
287 .get("x-forwarded-proto")
288 .and_then(|value| value.to_str().ok())
289 .and_then(|value| value.split(',').next())
290 .map(str::trim)
291 .filter(|value| matches!(*value, "http" | "https"));
292 let forwarded_authority = parts
293 .headers
294 .get("x-forwarded-host")
295 .and_then(|value| value.to_str().ok())
296 .and_then(|value| value.split(',').next())
297 .map(str::trim)
298 .and_then(|value| value.parse::<Authority>().ok());
299 let authority = parts
300 .uri
301 .authority()
302 .cloned()
303 .or(forwarded_authority)
304 .or_else(|| {
305 parts
306 .headers
307 .get(HOST)
308 .and_then(|value| value.to_str().ok())
309 .and_then(|value| value.parse::<Authority>().ok())
310 });
311 let scheme = forwarded_proto
312 .or_else(|| parts.uri.scheme_str())
313 .unwrap_or("http");
314 let url = authority.map_or_else(
315 || format!("http://internal{path_and_query}"),
316 |authority| format!("{scheme}://{authority}{path_and_query}"),
317 );
318
319 let mut headers: HashMap<String, String> = HashMap::new();
321 for (name, value) in parts.headers.iter() {
322 let Ok(value_str) = value.to_str() else {
323 continue;
324 };
325 let key = name.as_str().to_ascii_lowercase();
326 headers
327 .entry(key)
328 .and_modify(|existing| {
329 existing.push_str(", ");
330 existing.push_str(value_str);
331 })
332 .or_insert_with(|| value_str.to_owned());
333 }
334
335 ApplicationRequest {
336 method: parts.method.as_str().to_owned(),
337 url,
338 headers,
339 body: body.to_vec(),
340 cancel_token,
341 }
342}
343
344fn into_application_response(
345 response: ApplicationResponse,
346 request_token: CancellationToken,
347) -> axum::response::Response {
348 let status = StatusCode::from_u16(response.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
349 let mut header_map = HeaderMap::with_capacity(response.headers.len());
350 for (name, value) in response.headers {
351 if let (Ok(name), Ok(value)) = (
352 HeaderName::try_from(name.as_str()),
353 HeaderValue::from_str(&value),
354 ) {
355 header_map.append(name, value);
356 }
357 }
358 let body = match response.body {
359 ApplicationResponseBody::Buffered(body) => Body::from(body),
360 ApplicationResponseBody::Stream(receiver) => {
361 let stream_token = request_token.clone();
362 let stream =
363 futures::stream::unfold((receiver, false), move |(mut receiver, finished)| {
364 let stream_token = stream_token.clone();
365 async move {
366 if finished {
367 return None;
368 }
369 let next = tokio::select! {
370 _ = stream_token.cancelled() => return None,
371 next = receiver.recv() => next,
372 };
373 match next {
374 Some(ResponseChunk::Data { data, finish }) => {
375 if finish && data.is_empty() {
376 None
377 } else {
378 Some((
379 Ok::<Bytes, std::io::Error>(Bytes::from(data)),
380 (receiver, finish),
381 ))
382 }
383 }
384 Some(ResponseChunk::Error(message)) => {
385 Some((Err(std::io::Error::other(message)), (receiver, true)))
386 }
387 None => None,
388 }
389 }
390 });
391 Body::from_stream(stream)
392 }
393 };
394 let guarded = CancelOnDropStream {
395 inner: body.into_data_stream(),
396 _guard: CancelOnDrop {
397 token: request_token,
398 },
399 };
400 (status, header_map, Body::from_stream(guarded)).into_response()
401}
402
403fn into_axum_response(
404 response: ServerlessResponse,
405 request_token: CancellationToken,
406) -> axum::response::Response {
407 let status = StatusCode::from_u16(response.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
408 let mut header_map = HeaderMap::with_capacity(response.headers.len());
409 for (name, value) in response.headers {
410 if let (Ok(name), Ok(value)) = (
411 HeaderName::try_from(name.as_str()),
412 HeaderValue::from_str(&value),
413 ) {
414 header_map.append(name, value);
415 }
416 }
417
418 let stream = UnboundedReceiverStream::new(response.body).map(|chunk| match chunk {
419 Ok(bytes) => Ok::<Bytes, std::io::Error>(Bytes::from(bytes)),
420 Err(error) => {
421 tracing::warn!(?error, "serverless stream error");
422 Err(std::io::Error::other(format!(
423 "{}.{}: {}",
424 error.group, error.code, error.message
425 )))
426 }
427 });
428
429 let guarded = CancelOnDropStream {
431 inner: stream,
432 _guard: CancelOnDrop {
433 token: request_token,
434 },
435 };
436
437 (status, header_map, Body::from_stream(guarded)).into_response()
438}
439
440fn is_length_limit_error(error: &axum::Error) -> bool {
441 let mut source: Option<&dyn std::error::Error> = Some(error);
442 while let Some(err) = source {
443 if err.is::<LengthLimitError>() {
444 return true;
445 }
446 source = err.source();
447 }
448 false
449}
450
451struct CancelOnDrop {
452 token: CancellationToken,
453}
454
455impl Drop for CancelOnDrop {
456 fn drop(&mut self) {
457 self.token.cancel();
458 }
459}
460
461struct CancelOnDropStream<S> {
462 inner: S,
463 _guard: CancelOnDrop,
464}
465
466impl<S: Stream + Unpin> Stream for CancelOnDropStream<S> {
467 type Item = S::Item;
468
469 fn poll_next(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
470 Pin::new(&mut self.inner).poll_next(cx)
471 }
472}