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::{HeaderMap, HeaderName, HeaderValue, StatusCode};
12use axum::response::IntoResponse;
13use axum::routing::any;
14use futures::Stream;
15use futures::StreamExt;
16use futures::future::BoxFuture;
17use http_body_util::LengthLimitError;
18use tokio_stream::wrappers::UnboundedReceiverStream;
19use tokio_util::sync::CancellationToken;
20use tower_http::services::ServeDir;
21
22use crate::serverless::{CoreServerlessRuntime, ServerlessRequest, ServerlessResponse};
23
24#[derive(Clone)]
25pub struct ListenerConfig {
26 pub host: Option<String>,
28 pub port: u16,
29 pub public_dir: Option<PathBuf>,
30 pub application: Option<ApplicationFetch>,
32}
33
34#[derive(Debug)]
35pub struct ApplicationRequest {
36 pub method: String,
37 pub url: String,
38 pub headers: HashMap<String, String>,
39 pub body: Vec<u8>,
40}
41
42#[derive(Debug)]
43pub struct ApplicationResponse {
44 pub status: u16,
45 pub headers: HashMap<String, String>,
46 pub body: Vec<u8>,
47}
48
49pub type ApplicationFetch = Arc<
50 dyn Fn(ApplicationRequest) -> BoxFuture<'static, Result<ApplicationResponse>> + Send + Sync,
51>;
52
53#[derive(Clone)]
54struct AppState {
55 runtime: CoreServerlessRuntime,
56 application: Option<ApplicationFetch>,
57 shutdown_token: CancellationToken,
58}
59
60#[derive(Clone)]
61struct ApplicationState {
62 application: ApplicationFetch,
63}
64
65pub async fn serve(
67 runtime: CoreServerlessRuntime,
68 listener: ListenerConfig,
69 shutdown: CancellationToken,
70) -> Result<()> {
71 let host = listener.host.as_deref().unwrap_or("0.0.0.0");
72 let port = listener.port;
73
74 let state = AppState {
75 runtime,
76 application: listener.application.clone(),
77 shutdown_token: shutdown.clone(),
78 };
79
80 let forward_service = any(forward_request).with_state(state);
81
82 let router = match listener.public_dir.as_ref() {
83 Some(dir) => Router::new().fallback_service(
84 ServeDir::new(dir)
85 .call_fallback_on_method_not_allowed(true)
86 .fallback(forward_service),
87 ),
88 None => Router::new().fallback_service(forward_service),
89 };
90
91 let tcp = tokio::net::TcpListener::bind((host, port))
92 .await
93 .with_context(|| format!("bind tcp listener on {host}:{port}"))?;
94 let bound = tcp
95 .local_addr()
96 .context("read local address of bound listener")?;
97 tracing::info!(host = %bound.ip(), port = bound.port(), "rivetkit server listening");
98
99 let shutdown_fut = {
100 let shutdown = shutdown.clone();
101 async move { shutdown.cancelled().await }
102 };
103
104 axum::serve(tcp, router.into_make_service())
105 .with_graceful_shutdown(shutdown_fut)
106 .await
107 .context("axum::serve returned an error")?;
108
109 Ok(())
110}
111
112pub async fn serve_application(
117 listener: ListenerConfig,
118 application: ApplicationFetch,
119 max_body_bytes: usize,
120 shutdown: CancellationToken,
121) -> Result<()> {
122 let host = listener.host.as_deref().unwrap_or("0.0.0.0");
123 let port = listener.port;
124 let forward_service = any(forward_application_request)
125 .with_state((ApplicationState { application }, max_body_bytes));
126 let router = match listener.public_dir.as_ref() {
127 Some(dir) => Router::new().fallback_service(
128 ServeDir::new(dir)
129 .call_fallback_on_method_not_allowed(true)
130 .fallback(forward_service),
131 ),
132 None => Router::new().fallback_service(forward_service),
133 };
134 let tcp = tokio::net::TcpListener::bind((host, port))
135 .await
136 .with_context(|| format!("bind application tcp listener on {host}:{port}"))?;
137 let bound = tcp
138 .local_addr()
139 .context("read application listener local address")?;
140 tracing::info!(host = %bound.ip(), port = bound.port(), "application server listening");
141
142 axum::serve(tcp, router.into_make_service())
143 .with_graceful_shutdown(async move { shutdown.cancelled().await })
144 .await
145 .context("application axum::serve returned an error")?;
146 Ok(())
147}
148
149async fn forward_application_request(
150 State((state, max_body_bytes)): State<(ApplicationState, usize)>,
151 request: Request,
152) -> axum::response::Response {
153 let (parts, body) = request.into_parts();
154 let body = match axum::body::to_bytes(body, max_body_bytes).await {
155 Ok(body) => body,
156 Err(error) if is_length_limit_error(&error) => {
157 tracing::warn!(max_body_bytes, "application request body exceeded limit");
158 return (
159 StatusCode::PAYLOAD_TOO_LARGE,
160 [("content-type", "text/plain; charset=utf-8")],
161 "Payload Too Large",
162 )
163 .into_response();
164 }
165 Err(error) => {
166 tracing::warn!(?error, "failed to read application request body");
167 return (
168 StatusCode::BAD_REQUEST,
169 [("content-type", "text/plain; charset=utf-8")],
170 "Bad Request",
171 )
172 .into_response();
173 }
174 };
175 let request = application_request_from_parts(parts, body);
176 match (state.application)(request).await {
177 Ok(response) => into_application_response(response),
178 Err(error) => {
179 tracing::error!(?error, "application request handler failed");
180 (
181 StatusCode::INTERNAL_SERVER_ERROR,
182 [("content-type", "text/plain; charset=utf-8")],
183 "Internal Server Error",
184 )
185 .into_response()
186 }
187 }
188}
189
190async fn forward_request(
191 State(state): State<AppState>,
192 request: Request,
193) -> axum::response::Response {
194 let (parts, body) = request.into_parts();
195 let body_limit = state.runtime.max_request_body_bytes();
196 let request_token = state.shutdown_token.child_token();
197 let body_bytes = match axum::body::to_bytes(body, body_limit).await {
198 Ok(bytes) => bytes,
199 Err(error) if is_length_limit_error(&error) => {
200 tracing::warn!(body_limit, "request body exceeded limit");
201 return into_axum_response(state.runtime.incoming_too_long_response(), request_token);
202 }
203 Err(error) => {
204 tracing::warn!(?error, "failed to read request body");
205 return into_axum_response(
206 state
207 .runtime
208 .invalid_request_response("failed to read request body"),
209 request_token,
210 );
211 }
212 };
213
214 let application_request = application_request_from_parts(parts, body_bytes);
215 let req = ServerlessRequest {
216 method: application_request.method,
217 url: application_request.url,
218 headers: application_request.headers,
219 body: application_request.body,
220 cancel_token: request_token.clone(),
221 };
222
223 if state.runtime.handles_listener_request(&req.url) || state.application.is_none() {
224 return into_axum_response(state.runtime.handle_request(req).await, request_token);
225 }
226
227 let application = state
228 .application
229 .as_ref()
230 .expect("application checked above");
231 match application(ApplicationRequest {
232 method: req.method,
233 url: req.url,
234 headers: req.headers,
235 body: req.body,
236 })
237 .await
238 {
239 Ok(response) => into_application_response(response),
240 Err(error) => {
241 tracing::error!(?error, "application request handler failed");
242 (
243 StatusCode::INTERNAL_SERVER_ERROR,
244 [("content-type", "text/plain; charset=utf-8")],
245 "Internal Server Error",
246 )
247 .into_response()
248 }
249 }
250}
251
252fn application_request_from_parts(
253 parts: axum::http::request::Parts,
254 body: Bytes,
255) -> ApplicationRequest {
256 let path_and_query = parts
257 .uri
258 .path_and_query()
259 .map(|pq| pq.as_str())
260 .unwrap_or("/");
261 let url = format!("http://internal{path_and_query}");
262
263 let mut headers: HashMap<String, String> = HashMap::new();
265 for (name, value) in parts.headers.iter() {
266 let Ok(value_str) = value.to_str() else {
267 continue;
268 };
269 let key = name.as_str().to_ascii_lowercase();
270 headers
271 .entry(key)
272 .and_modify(|existing| {
273 existing.push_str(", ");
274 existing.push_str(value_str);
275 })
276 .or_insert_with(|| value_str.to_owned());
277 }
278
279 ApplicationRequest {
280 method: parts.method.as_str().to_owned(),
281 url,
282 headers,
283 body: body.to_vec(),
284 }
285}
286
287fn into_application_response(response: ApplicationResponse) -> axum::response::Response {
288 let status = StatusCode::from_u16(response.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
289 let mut header_map = HeaderMap::with_capacity(response.headers.len());
290 for (name, value) in response.headers {
291 if let (Ok(name), Ok(value)) = (
292 HeaderName::try_from(name.as_str()),
293 HeaderValue::from_str(&value),
294 ) {
295 header_map.append(name, value);
296 }
297 }
298 (status, header_map, response.body).into_response()
299}
300
301fn into_axum_response(
302 response: ServerlessResponse,
303 request_token: CancellationToken,
304) -> axum::response::Response {
305 let status = StatusCode::from_u16(response.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
306 let mut header_map = HeaderMap::with_capacity(response.headers.len());
307 for (name, value) in response.headers {
308 if let (Ok(name), Ok(value)) = (
309 HeaderName::try_from(name.as_str()),
310 HeaderValue::from_str(&value),
311 ) {
312 header_map.append(name, value);
313 }
314 }
315
316 let stream = UnboundedReceiverStream::new(response.body).map(|chunk| match chunk {
317 Ok(bytes) => Ok::<Bytes, std::io::Error>(Bytes::from(bytes)),
318 Err(error) => {
319 tracing::warn!(?error, "serverless stream error");
320 Err(std::io::Error::other(format!(
321 "{}.{}: {}",
322 error.group, error.code, error.message
323 )))
324 }
325 });
326
327 let guarded = CancelOnDropStream {
329 inner: stream,
330 _guard: CancelOnDrop {
331 token: request_token,
332 },
333 };
334
335 (status, header_map, Body::from_stream(guarded)).into_response()
336}
337
338fn is_length_limit_error(error: &axum::Error) -> bool {
339 let mut source: Option<&dyn std::error::Error> = Some(error);
340 while let Some(err) = source {
341 if err.is::<LengthLimitError>() {
342 return true;
343 }
344 source = err.source();
345 }
346 false
347}
348
349struct CancelOnDrop {
350 token: CancellationToken,
351}
352
353impl Drop for CancelOnDrop {
354 fn drop(&mut self) {
355 self.token.cancel();
356 }
357}
358
359struct CancelOnDropStream<S> {
360 inner: S,
361 _guard: CancelOnDrop,
362}
363
364impl<S: Stream + Unpin> Stream for CancelOnDropStream<S> {
365 type Item = S::Item;
366
367 fn poll_next(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
368 Pin::new(&mut self.inner).poll_next(cx)
369 }
370}