lino_rest_api/serve.rs
1//! Running an application over HTTP.
2//!
3//! The application itself knows nothing about sockets: it turns a [`RawRequest`]
4//! into [`crate::middleware::ResponseParts`]. This module is the shell that
5//! carries those over HTTP, built on axum, and it is the only place in the crate
6//! that depends on a server.
7
8use std::net::SocketAddr;
9use std::sync::Arc;
10
11use axum::Router;
12use axum::body::{Body, Bytes};
13use axum::extract::State;
14use axum::http::{HeaderName, HeaderValue, Method, Response, StatusCode, Uri};
15use tokio::net::TcpListener;
16
17use crate::app::{LinoApp, RawRequest};
18
19/// Build the axum router that serves an application.
20///
21/// Every method and every path reaches the application, which does its own
22/// routing and answers a `404` or a `405` itself, with problem details and an
23/// `Allow` header, as the specification requires.
24pub fn router(app: Arc<LinoApp>) -> Router {
25 Router::new().fallback(dispatch).with_state(app)
26}
27
28/// Serve one request through the application.
29async fn dispatch(
30 State(app): State<Arc<LinoApp>>,
31 method: Method,
32 uri: Uri,
33 headers: axum::http::HeaderMap,
34 body: Bytes,
35) -> Response<Body> {
36 let mut raw = RawRequest::new(method.as_str(), uri.path());
37 raw.query_string = uri.query().unwrap_or_default().to_string();
38 for (name, value) in &headers {
39 raw.headers
40 .insert(name.as_str(), value.to_str().unwrap_or_default());
41 }
42 raw.body = body.to_vec();
43
44 let parts = app.respond(raw).await;
45 let status = StatusCode::from_u16(parts.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
46 let mut response = Response::builder().status(status);
47 for (name, value) in parts.headers.iter() {
48 // A header the application could not have produced is dropped rather
49 // than allowed to fail the whole response.
50 if let (Ok(name), Ok(value)) = (
51 HeaderName::from_bytes(name.as_bytes()),
52 HeaderValue::from_str(value),
53 ) {
54 response = response.header(name, value);
55 }
56 }
57 response
58 .body(Body::from(parts.body))
59 .unwrap_or_else(|_| Response::new(Body::empty()))
60}
61
62/// An application bound to a port, before it starts serving.
63///
64/// Binding and serving are separate so that a test can bind port `0`, read the
65/// port that was chosen and then serve in the background.
66pub struct BoundServer {
67 /// The address the server listens on.
68 pub address: SocketAddr,
69 listener: TcpListener,
70 router: Router,
71}
72
73impl BoundServer {
74 /// Bind an application to an address.
75 ///
76 /// # Errors
77 ///
78 /// Whatever binding the socket reports.
79 pub async fn bind(app: LinoApp, address: &str) -> std::io::Result<Self> {
80 let listener = TcpListener::bind(address).await?;
81 Ok(Self {
82 address: listener.local_addr()?,
83 listener,
84 router: router(Arc::new(app)),
85 })
86 }
87
88 /// The base URL of the bound server, as a client needs it.
89 pub fn base_url(&self) -> String {
90 format!("http://{}", self.address)
91 }
92
93 /// Serve until the process ends.
94 ///
95 /// # Errors
96 ///
97 /// Whatever the server reports while accepting connections.
98 pub async fn serve(self) -> std::io::Result<()> {
99 axum::serve(self.listener, self.router).await
100 }
101}
102
103/// Bind an application to an address and serve it.
104///
105/// # Errors
106///
107/// Whatever binding the socket or accepting a connection reports.
108///
109/// # Examples
110///
111/// ```no_run
112/// use lino_rest_api::app::LinoApp;
113/// use lino_rest_api::serve::serve;
114///
115/// # async fn run() -> std::io::Result<()> {
116/// serve(LinoApp::new(), "0.0.0.0:8000").await
117/// # }
118/// ```
119pub async fn serve(app: LinoApp, address: &str) -> std::io::Result<()> {
120 BoundServer::bind(app, address).await?.serve().await
121}