Skip to main content

objectstore_server/web/
app.rs

1use std::net::SocketAddr;
2
3use anyhow::Result;
4use axum::ServiceExt;
5use axum::extract::Request;
6use objectstore_log::Level;
7use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer};
8use tokio::net::TcpListener;
9use tower::ServiceBuilder;
10use tower_http::catch_panic::CatchPanicLayer;
11use tower_http::trace::{DefaultOnFailure, TraceLayer};
12
13use crate::endpoints;
14use crate::state::ServiceState;
15use crate::web::middleware as m;
16
17/// The objectstore web server application.
18#[derive(Debug)]
19pub struct App {
20    router: axum::Router,
21    graceful_shutdown: bool,
22}
23
24impl App {
25    /// Creates a new application router for the given service state.
26    ///
27    /// The applications sets up middlewares and routes for the objectstore web API. Use
28    /// [`serve`](Self::serve) to run the server future.
29    pub fn new(state: ServiceState) -> Self {
30        // Build the router middleware into a single service which runs _after_ routing. Service
31        // builder order defines layers added first will be called first. This means:
32        //  - Requests go from top to bottom
33        //  - Responses go from bottom to top
34        let middleware = ServiceBuilder::new()
35            .layer(axum::middleware::from_fn(m::emit_request_metrics))
36            .layer(NewSentryLayer::new_from_top())
37            .layer(SentryHttpLayer::new().enable_transaction())
38            .layer(axum::middleware::from_fn(m::bind_sentry_body))
39            .layer(axum::middleware::from_fn_with_state(
40                state.request_counter.clone(),
41                m::limit_web_concurrency,
42            ))
43            .layer(state.request_counter.layer())
44            .layer(CatchPanicLayer::custom(m::handle_panic))
45            .layer(m::set_server_header())
46            .layer(
47                TraceLayer::new_for_http()
48                    .make_span_with(m::make_http_span)
49                    .on_failure(DefaultOnFailure::new().level(Level::DEBUG)),
50            );
51
52        let router = endpoints::routes()
53            .layer(middleware)
54            .with_state(state.clone());
55
56        App {
57            router,
58            graceful_shutdown: false,
59        }
60    }
61
62    /// Enables or disables graceful shutdown for the server.
63    ///
64    /// By default, graceful shutdown is disabled.
65    pub fn graceful_shutdown(mut self, enable: bool) -> Self {
66        self.graceful_shutdown = enable;
67        self
68    }
69
70    /// Runs the web server until graceful shutdown is triggered.
71    ///
72    /// This function creates a future that runs the server. The future must be spawned or awaited for
73    /// the server to continue running.
74    pub async fn serve(self, listener: TcpListener) -> Result<()> {
75        let Self {
76            router,
77            graceful_shutdown,
78        } = self;
79
80        let service =
81            ServiceExt::<Request>::into_make_service_with_connect_info::<SocketAddr>(router);
82
83        if graceful_shutdown {
84            let guard = elegant_departure::get_shutdown_guard();
85            axum::serve(listener, service)
86                .with_graceful_shutdown(guard.wait_owned())
87                .await?;
88        } else {
89            axum::serve(listener, service).await?;
90        }
91
92        Ok(())
93    }
94}