From 65da98896219674e2ea9356626d9936969cbb3b3 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 15 Mar 2017 22:10:09 -0700 Subject: [PATCH] Return a `LaunchError` from `launch` when launching fails. This is a (minor) breaking change. If `rocket.launch()` is the last expression in a function, the return type will change from `()` to `LaunchError`. A simple workaround that preserves the previous functionality is to simply add a semicolon after `launch()`: `rocket.launch();`. resolves #34 --- examples/config/src/main.rs | 2 +- examples/cookies/src/main.rs | 2 +- examples/errors/src/main.rs | 5 +- examples/extended_validation/src/main.rs | 2 +- examples/forms/src/main.rs | 2 +- examples/from_request/src/main.rs | 2 +- examples/optional_redirect/src/main.rs | 2 +- examples/pastebin/src/main.rs | 2 +- examples/query_params/src/main.rs | 2 +- examples/session/src/main.rs | 2 +- examples/testing/src/main.rs | 2 +- lib/src/catcher.rs | 2 +- lib/src/error.rs | 158 +++++++++++++++++++++++ lib/src/http/hyper.rs | 1 + lib/src/lib.rs | 6 +- lib/src/request/state.rs | 2 +- lib/src/response/flash.rs | 2 +- lib/src/rocket.rs | 35 ++--- 18 files changed, 199 insertions(+), 32 deletions(-) diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index 84583182..f53c6559 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -6,5 +6,5 @@ extern crate config; // This example's illustration is the Rocket.toml file. fn main() { - rocket::ignite().mount("/hello", routes![config::hello]).launch() + rocket::ignite().mount("/hello", routes![config::hello]).launch(); } diff --git a/examples/cookies/src/main.rs b/examples/cookies/src/main.rs index 243c1954..f4f114dc 100644 --- a/examples/cookies/src/main.rs +++ b/examples/cookies/src/main.rs @@ -37,5 +37,5 @@ fn index(cookies: Cookies) -> Template { } fn main() { - rocket::ignite().mount("/", routes![submit, index]).launch() + rocket::ignite().mount("/", routes![submit, index]).launch(); } diff --git a/examples/errors/src/main.rs b/examples/errors/src/main.rs index 47ebd736..cebdc34b 100644 --- a/examples/errors/src/main.rs +++ b/examples/errors/src/main.rs @@ -20,8 +20,11 @@ fn not_found(req: &rocket::Request) -> content::HTML { } fn main() { - rocket::ignite() + let e = rocket::ignite() .mount("/", routes![hello]) .catch(errors![not_found]) .launch(); + + println!("Whoops! Rocket didn't launch!"); + println!("This went wrong: {}", e); } diff --git a/examples/extended_validation/src/main.rs b/examples/extended_validation/src/main.rs index 20a8a2f1..347ac9da 100644 --- a/examples/extended_validation/src/main.rs +++ b/examples/extended_validation/src/main.rs @@ -85,5 +85,5 @@ fn rocket() -> rocket::Rocket { } fn main() { - rocket().launch() + rocket().launch(); } diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 0f2608cd..6ab3c601 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -48,5 +48,5 @@ fn rocket() -> rocket::Rocket { } fn main() { - rocket().launch() + rocket().launch(); } diff --git a/examples/from_request/src/main.rs b/examples/from_request/src/main.rs index 40134bea..f9d4d813 100644 --- a/examples/from_request/src/main.rs +++ b/examples/from_request/src/main.rs @@ -29,7 +29,7 @@ fn header_count(header_count: HeaderCount) -> String { } fn main() { - rocket::ignite().mount("/", routes![header_count]).launch() + rocket::ignite().mount("/", routes![header_count]).launch(); } #[cfg(test)] diff --git a/examples/optional_redirect/src/main.rs b/examples/optional_redirect/src/main.rs index 95ee9911..a25fcf08 100644 --- a/examples/optional_redirect/src/main.rs +++ b/examples/optional_redirect/src/main.rs @@ -26,5 +26,5 @@ fn login() -> &'static str { } fn main() { - rocket::ignite().mount("/", routes![root, user, login]).launch() + rocket::ignite().mount("/", routes![root, user, login]).launch(); } diff --git a/examples/pastebin/src/main.rs b/examples/pastebin/src/main.rs index e7c08a4e..b15cdf1b 100644 --- a/examples/pastebin/src/main.rs +++ b/examples/pastebin/src/main.rs @@ -53,5 +53,5 @@ fn index() -> &'static str { } fn main() { - rocket::ignite().mount("/", routes![index, upload, retrieve]).launch() + rocket::ignite().mount("/", routes![index, upload, retrieve]).launch(); } diff --git a/examples/query_params/src/main.rs b/examples/query_params/src/main.rs index 636904ab..37059bf6 100644 --- a/examples/query_params/src/main.rs +++ b/examples/query_params/src/main.rs @@ -21,5 +21,5 @@ fn hello(person: Person) -> String { } fn main() { - rocket::ignite().mount("/", routes![hello]).launch() + rocket::ignite().mount("/", routes![hello]).launch(); } diff --git a/examples/session/src/main.rs b/examples/session/src/main.rs index 7bb21eb2..a2733b5d 100644 --- a/examples/session/src/main.rs +++ b/examples/session/src/main.rs @@ -83,5 +83,5 @@ fn index() -> Redirect { fn main() { rocket::ignite() .mount("/", routes![index, user_index, login, logout, login_user, login_page]) - .launch() + .launch(); } diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 885316ce..0ca16f63 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -9,7 +9,7 @@ fn hello() -> &'static str { } fn main() { - rocket::ignite().mount("/", routes![hello]).launch() + rocket::ignite().mount("/", routes![hello]).launch(); } #[cfg(test)] diff --git a/lib/src/catcher.rs b/lib/src/catcher.rs index cc418ec2..ba4498d2 100644 --- a/lib/src/catcher.rs +++ b/lib/src/catcher.rs @@ -55,7 +55,7 @@ use term_painter::Color::*; /// /// fn main() { /// # if false { // We don't actually want to launch the server in an example. -/// rocket::ignite().catch(errors![internal_error, not_found]).launch() +/// rocket::ignite().catch(errors![internal_error, not_found]).launch(); /// # } /// } /// ``` diff --git a/lib/src/error.rs b/lib/src/error.rs index b141dcef..a9ce4c02 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -1,3 +1,10 @@ +//! Types representing various errors that can occur in a Rocket application. + +use std::{io, fmt}; +use std::sync::atomic::{Ordering, AtomicBool}; + +use http::hyper; + /// [unstable] Error type for Rocket. Likely to change. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Error { @@ -12,3 +19,154 @@ pub enum Error { /// The requested key/index does not exist. NoKey, } + +/// The kind of launch error that occured. +/// +/// In almost every instance, a launch error occurs because of an I/O error; +/// this represented by the `Io` variant. The `Unknown` variant captures all +/// other kinds of launch errors. +#[derive(Debug)] +pub enum LaunchErrorKind { + Io(io::Error), + Unknown(Box<::std::error::Error + Send + Sync>) +} + +/// An error that occurred during launch. +/// +/// A `LaunchError` is returned by +/// [rocket::launch](/rocket/struct.Rocket.html#method.launch) when launching an +/// application fails for some reason. +/// +/// # Panics +/// +/// A value of this type panics if it is dropped without first being inspected. +/// An _inspection_ occurs when any method is called. For instance, if +/// `println!("Error: {}", e)` is called, where `e: LaunchError`, the +/// `Display::fmt` method being called by `println!` results in `e` being marked +/// as inspected; a subsequent `drop` of the value will _not_ result in a panic. +/// The following snippet illustrates this: +/// +/// ```rust +/// # if false { +/// let error = rocket::ignite().launch(); +/// +/// // This line is only reached if launching failed. This "inspects" the error. +/// println!("Launch failed! Error: {}", error); +/// +/// // This call to drop (explicit here for demonstration) will do nothing. +/// drop(error); +/// # } +/// ``` +/// +/// When a value of this type panics, the corresponding error message is pretty +/// printed to the console. The following snippet illustrates this: +/// +/// ```rust +/// # if false { +/// let error = rocket::ignite().launch(); +/// +/// // This call to drop (explicit here for demonstration) will result in +/// // `error` being pretty-printed to the console along with a `panic!`. +/// drop(error); +/// # } +/// ``` +pub struct LaunchError { + handled: AtomicBool, + kind: LaunchErrorKind +} + +impl LaunchError { + #[inline(always)] + fn new(kind: LaunchErrorKind) -> LaunchError { + LaunchError { handled: AtomicBool::new(false), kind: kind } + } + + #[inline(always)] + fn was_handled(&self) -> bool { + self.handled.load(Ordering::Acquire) + } + + #[inline(always)] + fn mark_handled(&self) { + self.handled.store(true, Ordering::Release) + } + + /// Retrieve the `kind` of the launch error. + /// + /// # Example + /// + /// ```rust + /// # if false { + /// let error = rocket::ignite().launch(); + /// + /// // This line is only reached if launch failed. + /// let error_kind = error.kind(); + /// # } + /// ``` + #[inline] + pub fn kind(&self) -> &LaunchErrorKind { + self.mark_handled(); + &self.kind + } +} + +impl From for LaunchError { + fn from(error: hyper::Error) -> LaunchError { + match error { + hyper::Error::Io(e) => LaunchError::new(LaunchErrorKind::Io(e)), + e => LaunchError::new(LaunchErrorKind::Unknown(Box::new(e))) + } + } +} + +impl fmt::Display for LaunchErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + LaunchErrorKind::Io(ref e) => write!(f, "I/O error: {}", e), + LaunchErrorKind::Unknown(ref e) => write!(f, "unknown error: {}", e) + } + } +} + +impl fmt::Debug for LaunchError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.mark_handled(); + write!(f, "{:?}", self.kind()) + } +} + +impl fmt::Display for LaunchError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.mark_handled(); + write!(f, "{}", self.kind()) + } +} + +impl ::std::error::Error for LaunchError { + fn description(&self) -> &str { + self.mark_handled(); + match *self.kind() { + LaunchErrorKind::Io(_) => "an I/O error occured during launch", + LaunchErrorKind::Unknown(_) => "an unknown error occured during launch" + } + } +} + +impl Drop for LaunchError { + fn drop(&mut self) { + if self.was_handled() { + return + } + + match *self.kind() { + LaunchErrorKind::Io(ref e) => { + error!("Rocket failed to launch due to an I/O error."); + panic!("{}", e); + } + LaunchErrorKind::Unknown(ref e) => { + error!("Rocket failed to launch due to an unknown error."); + panic!("{}", e); + } + } + } +} diff --git a/lib/src/http/hyper.rs b/lib/src/http/hyper.rs index 504e9d67..54814df4 100644 --- a/lib/src/http/hyper.rs +++ b/lib/src/http/hyper.rs @@ -13,6 +13,7 @@ pub(crate) use hyper::net; pub(crate) use hyper::method::Method; pub(crate) use hyper::status::StatusCode; +pub(crate) use hyper::error::Error; pub(crate) use hyper::uri::RequestUri; pub(crate) use hyper::http::h1; pub(crate) use hyper::buffer; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6066853b..77240bca 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -74,7 +74,7 @@ //! //! fn main() { //! # if false { // We don't actually want to launch the server in an example. -//! rocket::ignite().mount("/", routes![hello]).launch() +//! rocket::ignite().mount("/", routes![hello]).launch(); //! # } //! } //! ``` @@ -117,8 +117,8 @@ pub mod outcome; pub mod config; pub mod data; pub mod handler; +pub mod error; -mod error; mod router; mod rocket; mod codegen; @@ -133,7 +133,7 @@ mod ext; #[doc(inline)] pub use data::Data; pub use router::Route; pub use request::{Request, State}; -pub use error::Error; +pub use error::{Error, LaunchError}; pub use catcher::Catcher; pub use rocket::Rocket; diff --git a/lib/src/request/state.rs b/lib/src/request/state.rs index 5fc37e24..c76d9fc3 100644 --- a/lib/src/request/state.rs +++ b/lib/src/request/state.rs @@ -46,7 +46,7 @@ use http::Status; /// rocket::ignite() /// .mount("/", routes![index, raw_config_value]) /// .manage(config) -/// .launch() +/// .launch(); /// # } /// } /// ``` diff --git a/lib/src/response/flash.rs b/lib/src/response/flash.rs index 14234cd4..ceb629e2 100644 --- a/lib/src/response/flash.rs +++ b/lib/src/response/flash.rs @@ -71,7 +71,7 @@ const FLASH_COOKIE_NAME: &'static str = "_flash"; /// /// fn main() { /// # if false { // We don't actually want to launch the server in an example. -/// rocket::ignite().mount("/", routes![login, index]).launch() +/// rocket::ignite().mount("/", routes![login, index]).launch(); /// # } /// } /// ``` diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index cc58ac8f..092785f7 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -18,7 +18,7 @@ use response::{Body, Response}; use router::{Router, Route}; use catcher::{self, Catcher}; use outcome::Outcome; -use error::Error; +use error::{Error, LaunchError}; use http::{Method, Status, Header, Session}; use http::hyper::{self, header}; @@ -391,7 +391,7 @@ impl Rocket { /// fn main() { /// # if false { // We don't actually want to launch the server in an example. /// rocket::ignite().mount("/hello", routes![hi]) - /// # .launch() + /// # .launch(); /// # } /// } /// ``` @@ -411,7 +411,7 @@ impl Rocket { /// /// # if false { // We don't actually want to launch the server in an example. /// rocket::ignite().mount("/hello", vec![Route::new(Get, "/world", hi)]) - /// # .launch() + /// # .launch(); /// # } /// ``` pub fn mount(mut self, base: &str, routes: Vec) -> Self { @@ -459,7 +459,7 @@ impl Rocket { /// fn main() { /// # if false { // We don't actually want to launch the server in an example. /// rocket::ignite().catch(errors![internal_error, not_found]) - /// # .launch() + /// # .launch(); /// # } /// } /// ``` @@ -514,7 +514,7 @@ impl Rocket { /// rocket::ignite() /// .mount("/", routes![index]) /// .manage(MyValue(10)) - /// .launch() + /// .launch(); /// # } /// } /// ``` @@ -530,19 +530,23 @@ impl Rocket { /// Starts the application server and begins listening for and dispatching /// requests to mounted routes and catchers. /// - /// # Panics + /// # Error /// - /// If the server could not be started, this method prints the reason and - /// then exits the process. + /// If there is a problem starting the application, a + /// [LaunchError](/rocket/struct.LaunchError.html) is returned. Note + /// that a value of type `LaunchError` panics if dropped without first being + /// inspected. See the [LaunchError + /// documentation](/rocket/struct.LaunchError.html) for more + /// information. /// /// # Examples /// /// ```rust /// # if false { - /// rocket::ignite().launch() + /// rocket::ignite().launch(); /// # } /// ``` - pub fn launch(self) { + pub fn launch(self) -> LaunchError { if self.router.has_collisions() { warn!("Route collisions detected!"); } @@ -550,10 +554,7 @@ impl Rocket { let full_addr = format!("{}:{}", self.config.address, self.config.port); let server = match hyper::Server::http(full_addr.as_str()) { Ok(hyper_server) => hyper_server, - Err(e) => { - error!("Failed to start server."); - panic!("{}", e); - } + Err(e) => return LaunchError::from(e) }; info!("🚀 {} {}{}...", @@ -562,6 +563,10 @@ impl Rocket { White.bold().paint(&full_addr)); let threads = self.config.workers as usize; - server.handle_threads(self, threads).unwrap(); + if let Err(e) = server.handle_threads(self, threads) { + return LaunchError::from(e); + } + + unreachable!("the call to `handle_threads` should block on success") } }