Skip to main content

alux_http_actix/
server.rs

1use crate::ActixRoute;
2use actix_web::dev::ServerHandle;
3use actix_web::{App, HttpServer};
4use alux_http::{HttpServerAlg, HttpServerSetup};
5use tokio::task::JoinHandle;
6
7type ActixServerError = Box<dyn std::error::Error + Send + Sync>;
8
9/// Bounds how long the workers drain connections before they stop anyway, in seconds.
10///
11/// Actix Web waits 30 seconds by default, which is long enough to look like a close that never
12/// returns. This is the same bound the other interpretations state.
13const DRAIN: u64 = 5;
14
15/// Serves an actix-web route at the address its setup names.
16#[derive(Debug, Default)]
17pub struct ActixServer;
18
19/// Carries an open actix-web server that is accepting requests.
20pub struct ActixOpen {
21    handle: ServerHandle,
22    task: JoinHandle<Result<(), ActixServerError>>,
23}
24
25impl Drop for ActixOpen {
26    fn drop(&mut self) {
27        // `stop` sends its command before the future it returns is awaited, so the workers holding
28        // the listener are reached even though a drop cannot wait for them.
29        let stopping = self.handle.stop(true);
30        drop(stopping);
31        self.task.abort();
32    }
33}
34
35impl HttpServerAlg for ActixServer {
36    type Program = ActixRoute;
37    type Open = ActixOpen;
38    type Error = ActixServerError;
39
40    async fn open(&mut self, setup: HttpServerSetup<Self::Program>) -> Result<Self::Open, Self::Error> {
41        let (bind, route) = setup.into_parts();
42        let configure = route.into_actix();
43        // Actix accepts from its own per-worker runtimes and wants a std listener. Binding here
44        // makes a taken address fail like it does in the other crates. Binding does not block.
45        let listener = std::net::TcpListener::bind(bind.address())?;
46        let server = HttpServer::new(move || App::new().configure(configure.clone()))
47            .shutdown_timeout(DRAIN)
48            // Whoever opened this server decides when it closes. Left on, actix installs
49            // process-wide handlers and answers Ctrl-C and SIGTERM itself, which stops an
50            // application from ever seeing them.
51            .disable_signals()
52            .listen(listener)?
53            .run();
54        let handle = server.handle();
55        let task = actix_web::rt::spawn(async move { server.await.map_err(Into::into) });
56
57        Ok(ActixOpen { handle, task })
58    }
59
60    async fn close(&mut self, open: &mut Self::Open) -> Result<(), Self::Error> {
61        // Aborting the task does not reach actix's workers, which hold the listener, so ask the
62        // server to stop instead. Graceful, so connections already accepted are answered first.
63        open.handle.stop(true).await;
64        if !open.task.is_finished() {
65            let _ = (&mut open.task).await;
66        }
67
68        Ok(())
69    }
70
71    // Nothing here can tell the address being released from the drain being over, because both
72    // happen inside the framework's own shutdown. So ending is closing, which satisfies both.
73    async fn end(&mut self, open: &mut Self::Open) -> Result<(), Self::Error> {
74        self.close(open).await
75    }
76}