server_starter_listener/
lib.rs

1//! Get Server::Starter listeners for rust application
2//!
3//! This crate providers [start_server](https://github.com/lestrrat-go/server-starter) / [start_server](https://metacpan.org/pod/start_server) listeners for rust server applications.
4//!
5//! # Examples
6//!
7//! ```no_run
8//! use actix_web::{HttpServer, App};
9//! use server_starter_listener::{listeners, ServerStarterListener};
10//!
11//! let listener = listeners().unwrap().pop().unwrap();
12//! match listener {
13//!   ServerStarterListener::Tcp(listener) => {
14//!     HttpServer::new(|| App::new()).listen(listener).unwrap().run().unwrap();
15//!   }
16//!   _ => unimplemented!(),
17//! }
18//! ```
19//!
20//! You need to start application using [start_server](https://github.com/lestrrat-go/server-starter) / [start_server](https://metacpan.org/pod/start_server).
21//!
22//! ```sh
23//! > start_server --port=80 -- your_server_binary
24//! ```
25//!
26//! Now you can do hot-deploy by send `SIGHUP` to `start_server` process.
27//! `start_server` share file descriptor to new process and send `SIGTERM` to old process.
28//!
29
30#[macro_use]
31extern crate failure;
32#[macro_use]
33extern crate lazy_static;
34
35use std::net::TcpListener;
36use std::os::unix::io::{FromRawFd, RawFd};
37use std::os::unix::net::UnixListener;
38
39use regex::Regex;
40
41const SERVER_STARTER_PORT_ENV: &str = "SERVER_STARTER_PORT";
42
43lazy_static! {
44    static ref HOST_PORT_REGEX: Regex = Regex::new("^[^:]+:\\d+$").unwrap();
45    static ref PORT_REGEX: Regex = Regex::new("^\\d+$").unwrap();
46}
47
48///
49/// Kind of server starter listener
50///
51#[derive(Debug)]
52pub enum ServerStarterListener {
53    Tcp(TcpListener),
54    Uds(UnixListener),
55}
56
57impl ServerStarterListener {
58    fn tcp(fd: RawFd) -> ServerStarterListener {
59        ServerStarterListener::Tcp(unsafe { TcpListener::from_raw_fd(fd) })
60    }
61
62    fn uds(fd: RawFd) -> ServerStarterListener {
63        ServerStarterListener::Uds(unsafe { UnixListener::from_raw_fd(fd) })
64    }
65}
66
67///
68/// A server starter listener error
69///
70#[derive(Fail, Debug)]
71pub enum ListenerError {
72    #[fail(display = "server starter port env var not found.")]
73    ServerStarterPortEnvNotFound,
74    #[fail(display = "cannot parse server starter port: {}", _0)]
75    InvalidServerStarterPortSpec(String),
76}
77
78///
79/// Get server starter listening listeners.
80///
81/// There are tcp and unix domain socket listeners.
82///
83/// # Errors
84///
85/// Returns as `ListenerError` if `SERVER_STARTER_PORT` env var is not found or invalid format.
86///
87pub fn listeners() -> Result<Vec<ServerStarterListener>, ListenerError> {
88    let specs = match std::env::var(SERVER_STARTER_PORT_ENV) {
89        Ok(specs) => specs,
90        Err(_) => return Err(ListenerError::ServerStarterPortEnvNotFound),
91    };
92
93    let specs: Vec<&str> = specs.split(';').collect();
94    let mut results = vec![];
95    for spec in specs {
96        let pair: Vec<&str> = spec.split('=').collect();
97        if pair.len() != 2 {
98            return Err(ListenerError::InvalidServerStarterPortSpec(spec.into()));
99        }
100
101        let (left, fd) = (pair[0], pair[1]);
102        let fd: i32 = match fd.parse() {
103            Ok(fd) => fd,
104            Err(_) => return Err(ListenerError::InvalidServerStarterPortSpec(spec.into())),
105        };
106
107        let listener = if HOST_PORT_REGEX.find(left).is_some() || PORT_REGEX.find(left).is_some() {
108            ServerStarterListener::tcp(fd)
109        } else {
110            ServerStarterListener::uds(fd)
111        };
112        results.push(listener)
113    }
114    Ok(results)
115}
116
117#[cfg(test)]
118mod tests {
119    use std::os::unix::io::AsRawFd;
120
121    use crate::{listeners, ServerStarterListener};
122
123    #[test]
124    fn listeners_tcp() {
125        let assert_tcp_listener = |var, fd| {
126            std::env::set_var("SERVER_STARTER_PORT", var);
127            let results = listeners();
128            match results {
129                Ok(results) => {
130                    assert_eq!(1, results.len());
131                    let listener = results.first().unwrap();
132                    match listener {
133                        ServerStarterListener::Tcp(tcp_listener) => {
134                            assert_eq!(fd, tcp_listener.as_raw_fd());
135                        }
136                        ServerStarterListener::Uds(_) => {
137                            assert!(false, "not tcp listener {:?}", listener)
138                        }
139                    }
140                }
141                Err(_) => assert!(false, "results not ok {:?}", results),
142            }
143        };
144
145        assert_tcp_listener("80=2", 2);
146        assert_tcp_listener("127.0.0.1:8080=3", 3);
147        assert_tcp_listener("localhost:8080=4", 4);
148    }
149
150    #[test]
151    fn listeners_uds() {
152        let assert_uds_listener = |var, fd| {
153            std::env::set_var("SERVER_STARTER_PORT", var);
154            let results = listeners();
155            match results {
156                Ok(results) => {
157                    assert_eq!(1, results.len());
158                    let listener = results.first().unwrap();
159                    match listener {
160                        ServerStarterListener::Tcp(_) => {
161                            assert!(false, "not uds listener {:?}", listener)
162                        }
163                        ServerStarterListener::Uds(uds_listener) => {
164                            assert_eq!(fd, uds_listener.as_raw_fd());
165                        }
166                    }
167                }
168                Err(_) => assert!(false, "results not ok {:?}", results),
169            }
170        };
171
172        assert_uds_listener("/tmp/server-starter-listener/server.sock=2", 2);
173    }
174
175    #[test]
176    fn listeners_without_env() {
177        std::env::remove_var("SERVER_STARTER_PORT");
178        assert!(listeners().is_err());
179    }
180
181    #[test]
182    fn listeners_invalid_env() {
183        std::env::set_var("SERVER_STARTER_PORT", "80=a");
184        assert!(listeners().is_err());
185    }
186}