mirror of https://github.com/rwf2/Rocket.git
Enable configurable 'ctrl-c' shutdown by default.
This removes the 'ctrl_c_shutdown' feature opting instead for a 'ctrlc' configuration option. To avoid further merge conflicts with the master branch, the option is currently read as an extra. Co-authored-by: Jeb Rosen <jeb@jebrosen.com>
This commit is contained in:
parent
9277ddafdf
commit
824de061c3
|
@ -24,7 +24,7 @@ pub trait Listener {
|
|||
fn local_addr(&self) -> Option<SocketAddr>;
|
||||
|
||||
/// Try to accept an incoming Connection if ready
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<Self::Connection, io::Error>>;
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>>;
|
||||
}
|
||||
|
||||
/// A 'Connection' represents an open connection to a client
|
||||
|
@ -125,7 +125,10 @@ impl<L: Listener + Unpin> Accept for Incoming<L> {
|
|||
type Conn = L::Connection;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll_accept(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
fn poll_accept(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>
|
||||
) -> Poll<Option<io::Result<Self::Conn>>> {
|
||||
self.poll_next(cx).map(Some)
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +157,8 @@ impl<L: fmt::Debug> fmt::Debug for Incoming<L> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn bind_tcp(address: SocketAddr) -> Pin<Box<dyn Future<Output=Result<TcpListener, io::Error>> + Send>> {
|
||||
Box::pin(async move {
|
||||
Ok(TcpListener::bind(address).await?)
|
||||
})
|
||||
pub async fn bind_tcp(address: SocketAddr) -> io::Result<TcpListener> {
|
||||
Ok(TcpListener::bind(address).await?)
|
||||
}
|
||||
|
||||
impl Listener for TcpListener {
|
||||
|
@ -167,7 +168,7 @@ impl Listener for TcpListener {
|
|||
self.local_addr().ok()
|
||||
}
|
||||
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<Self::Connection, io::Error>> {
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>> {
|
||||
self.poll_accept(cx).map_ok(|(stream, _addr)| stream)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,28 +113,24 @@ impl Listener for TlsListener {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn bind_tls(address: SocketAddr, cert_chain: Vec<Certificate>, key: PrivateKey)
|
||||
-> Pin<Box<dyn Future<Output=Result<TlsListener, io::Error>> + Send>>
|
||||
{
|
||||
Box::pin(async move {
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
pub async fn bind_tls(
|
||||
address: SocketAddr,
|
||||
cert_chain: Vec<Certificate>,
|
||||
key: PrivateKey
|
||||
) -> io::Result<TlsListener> {
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
|
||||
let client_auth = rustls::NoClientAuth::new();
|
||||
let mut tls_config = ServerConfig::new(client_auth);
|
||||
let cache = rustls::ServerSessionMemoryCache::new(1024);
|
||||
tls_config.set_persistence(cache);
|
||||
tls_config.ticketer = rustls::Ticketer::new();
|
||||
tls_config.set_single_cert(cert_chain, key).expect("invalid key");
|
||||
let client_auth = rustls::NoClientAuth::new();
|
||||
let mut tls_config = ServerConfig::new(client_auth);
|
||||
let cache = rustls::ServerSessionMemoryCache::new(1024);
|
||||
tls_config.set_persistence(cache);
|
||||
tls_config.ticketer = rustls::Ticketer::new();
|
||||
tls_config.set_single_cert(cert_chain, key).expect("invalid key");
|
||||
|
||||
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
|
||||
let state = TlsListenerState::Listening;
|
||||
|
||||
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
|
||||
|
||||
Ok(TlsListener {
|
||||
listener,
|
||||
acceptor,
|
||||
state: TlsListenerState::Listening,
|
||||
})
|
||||
})
|
||||
Ok(TlsListener { listener, acceptor, state })
|
||||
}
|
||||
|
||||
impl Connection for TlsStream<TcpStream> {
|
||||
|
|
|
@ -19,16 +19,14 @@ edition = "2018"
|
|||
all-features = true
|
||||
|
||||
[features]
|
||||
default = ["private-cookies", "ctrl_c_shutdown"]
|
||||
default = ["private-cookies"]
|
||||
tls = ["rocket_http/tls"]
|
||||
private-cookies = ["rocket_http/private-cookies"]
|
||||
ctrl_c_shutdown = ["tokio/signal"]
|
||||
|
||||
[dependencies]
|
||||
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
|
||||
rocket_http = { version = "0.5.0-dev", path = "../http" }
|
||||
futures = "0.3.0"
|
||||
tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] }
|
||||
yansi = "0.5"
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
toml = "0.4.7"
|
||||
|
@ -42,6 +40,10 @@ atty = "0.2"
|
|||
async-trait = "0.1"
|
||||
ref-cast = "1.0"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "0.2.9"
|
||||
features = ["fs", "io-std", "io-util", "rt-threaded", "sync", "signal"]
|
||||
|
||||
[build-dependencies]
|
||||
yansi = "0.5"
|
||||
version_check = "0.9.1"
|
||||
|
|
|
@ -523,7 +523,9 @@ impl Rocket {
|
|||
#[derive(Clone)]
|
||||
struct TokioExecutor;
|
||||
|
||||
impl<Fut> hyper::Executor<Fut> for TokioExecutor where Fut: Future + Send + 'static, Fut::Output: Send {
|
||||
impl<Fut> hyper::Executor<Fut> for TokioExecutor
|
||||
where Fut: Future + Send + 'static, Fut::Output: Send
|
||||
{
|
||||
fn execute(&self, fut: Fut) {
|
||||
tokio::spawn(fut);
|
||||
}
|
||||
|
@ -945,9 +947,9 @@ impl Rocket {
|
|||
|
||||
/// Returns a `Future` that drives the server, listening for and dispatching
|
||||
/// requests to mounted routes and catchers. The `Future` completes when the
|
||||
/// server is shut down, via a [`ShutdownHandle`], or encounters a fatal
|
||||
/// error. If the `ctrl_c_shutdown` feature is enabled, the server will
|
||||
/// also shut down once `Ctrl-C` is pressed.
|
||||
/// server is shut down via a [`ShutdownHandle`], encounters a fatal error,
|
||||
/// or if the the `ctrlc` configuration option is set, when `Ctrl+C` is
|
||||
/// pressed.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
|
@ -969,6 +971,7 @@ impl Rocket {
|
|||
/// ```
|
||||
pub async fn launch(mut self) -> Result<(), crate::error::Error> {
|
||||
use std::net::ToSocketAddrs;
|
||||
use futures::future::Either;
|
||||
use crate::error::Error::Launch;
|
||||
|
||||
self.prelaunch_check().await.map_err(crate::error::Error::Launch)?;
|
||||
|
@ -979,70 +982,53 @@ impl Rocket {
|
|||
Err(e) => return Err(Launch(e.into())),
|
||||
};
|
||||
|
||||
#[cfg(feature = "ctrl_c_shutdown")]
|
||||
let (
|
||||
shutdown_handle,
|
||||
(cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver)
|
||||
) = (
|
||||
self.shutdown_handle.clone(),
|
||||
oneshot::channel(),
|
||||
);
|
||||
// FIXME: Make `ctrlc` a known `Rocket` config option.
|
||||
// If `ctrl-c` shutdown is enabled, we `select` on `the ctrl-c` signal
|
||||
// and server. Otherwise, we only wait on the `server`, hence `pending`.
|
||||
let shutdown_handle = self.shutdown_handle.clone();
|
||||
let shutdown_signal = match self.config.get_bool("ctrlc") {
|
||||
Ok(false) => futures::future::pending().boxed(),
|
||||
_ => tokio::signal::ctrl_c().boxed(),
|
||||
};
|
||||
|
||||
let server = {
|
||||
macro_rules! listen_on {
|
||||
($expr:expr) => {{
|
||||
let listener = match $expr {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err)))),
|
||||
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err))))
|
||||
};
|
||||
self.listen_on(listener)
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
{
|
||||
#[cfg(feature = "tls")] {
|
||||
if let Some(tls) = self.config.tls.clone() {
|
||||
listen_on!(crate::http::tls::bind_tls(addr, tls.certs, tls.key).await).boxed()
|
||||
} else {
|
||||
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
{
|
||||
listen_on!(crate::http::private::bind_tcp(addr).await)
|
||||
#[cfg(not(feature = "tls"))] {
|
||||
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "ctrl_c_shutdown")]
|
||||
let server = server.inspect(|_| {
|
||||
let _ = cancel_ctrl_c_listener_sender.send(());
|
||||
});
|
||||
|
||||
#[cfg(feature = "ctrl_c_shutdown")]
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
use futures::future::{select, Either};
|
||||
|
||||
let either = select(
|
||||
tokio::signal::ctrl_c().boxed(),
|
||||
cancel_ctrl_c_listener_receiver,
|
||||
).await;
|
||||
|
||||
match either {
|
||||
Either::Left((Ok(()), _)) | Either::Right((_, _)) => shutdown_handle.shutdown(),
|
||||
Either::Left((Err(err), _)) => {
|
||||
// Signal handling isn't strictly necessary, so we can skip it
|
||||
// if necessary. It's a good idea to let the user know we're
|
||||
// doing so in case they are expecting certain behavior.
|
||||
let message = "Not listening for shutdown keybinding.";
|
||||
warn!("{}", Paint::yellow(message));
|
||||
info_!("Error: {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
match futures::future::select(shutdown_signal, server).await {
|
||||
Either::Left((Ok(()), server)) => {
|
||||
// Ctrl-was pressed. Signal shutdown, wait for the server.
|
||||
shutdown_handle.shutdown();
|
||||
server.await
|
||||
}
|
||||
Either::Left((Err(err), server)) => {
|
||||
// Error setting up ctrl-c signal. Let the user know.
|
||||
warn!("Failed to enable `ctrl+c` graceful signal shutdown.");
|
||||
info_!("Error: {}", err);
|
||||
server.await
|
||||
}
|
||||
// Server shut down before Ctrl-C; return the result.
|
||||
Either::Right((result, _)) => result,
|
||||
}
|
||||
|
||||
server.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue