Much nicer error pages

This commit is contained in:
Tomasz Drwięga 2016-08-31 16:53:22 +02:00
parent 25e6a4e45f
commit 2789824a51
7 changed files with 108 additions and 40 deletions

View File

@ -98,13 +98,11 @@ impl<R: URLHint> AppFetcher<R> {
}, },
// App is already being fetched // App is already being fetched
Some(&mut ContentStatus::Fetching(_)) => { Some(&mut ContentStatus::Fetching(_)) => {
(None, Box::new(ContentHandler::html( (None, Box::new(ContentHandler::error_with_refresh(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
format!( "Download In Progress",
"<html><head>{}</head><body>{}</body></html>", "This dapp is already being downloaded. Please wait...",
"<meta http-equiv=\"refresh\" content=\"1\">", None,
"<h1>This dapp is already being downloaded.</h1><h2>Please wait...</h2>",
)
)) as Box<Handler>) )) as Box<Handler>)
}, },
// We need to start fetching app // We need to start fetching app

22
dapps/src/error_tpl.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
{meta}
<title>{title}</title>
<link rel="stylesheet" href="/parity-utils/styles.css">
</head>
<body>
<div class="parity-navbar">
</div>
<div class="parity-box">
<h1>{title}</h1>
<h3>{message}</h3>
<p><code>{details}</code></p>
</div>
<div class="parity-status">
{version}
</div>
</body>
</html>

View File

@ -21,6 +21,8 @@ use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream; use hyper::net::HttpStream;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use util::version;
pub struct ContentHandler { pub struct ContentHandler {
code: StatusCode, code: StatusCode,
content: String, content: String,
@ -38,15 +40,6 @@ impl ContentHandler {
} }
} }
pub fn forbidden(content: String, mimetype: String) -> Self {
ContentHandler {
code: StatusCode::Forbidden,
content: content,
mimetype: mimetype,
write_pos: 0
}
}
pub fn not_found(content: String, mimetype: String) -> Self { pub fn not_found(content: String, mimetype: String) -> Self {
ContentHandler { ContentHandler {
code: StatusCode::NotFound, code: StatusCode::NotFound,
@ -60,6 +53,28 @@ impl ContentHandler {
Self::new(code, content, "text/html".into()) Self::new(code, content, "text/html".into())
} }
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
title=title,
meta="",
message=message,
details=details.unwrap_or_else(|| ""),
version=version(),
))
}
pub fn error_with_refresh(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
title=title,
meta="<meta http-equiv=\"refresh\" content=\"1\">",
message=message,
details=details.unwrap_or_else(|| ""),
version=version(),
))
}
pub fn new(code: StatusCode, content: String, mimetype: String) -> Self { pub fn new(code: StatusCode, content: String, mimetype: String) -> Self {
ContentHandler { ContentHandler {
code: code, code: code,

View File

@ -130,16 +130,20 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT), deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
receiver: receiver, receiver: receiver,
}, },
Err(e) => FetchState::Error(ContentHandler::html( Err(e) => FetchState::Error(ContentHandler::error(
StatusCode::BadGateway, StatusCode::BadGateway,
format!("<h1>Error starting dapp download.</h1><pre>{}</pre>", e), "Unable To Start Dapp Download",
"Could not initialize download of the dapp. It might be a problem with remote server.",
Some(&format!("{}", e)),
)), )),
} }
}, },
// or return error // or return error
_ => FetchState::Error(ContentHandler::html( _ => FetchState::Error(ContentHandler::error(
StatusCode::MethodNotAllowed, StatusCode::MethodNotAllowed,
"<h1>Only <code>GET</code> requests are allowed.</h1>".into(), "Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
)), )),
}) })
} else { None }; } else { None };
@ -156,10 +160,12 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
// Request may time out // Request may time out
FetchState::InProgress { ref deadline, .. } if *deadline < Instant::now() => { FetchState::InProgress { ref deadline, .. } if *deadline < Instant::now() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout."); trace!(target: "dapps", "Fetching dapp failed because of timeout.");
let timeout = ContentHandler::html( let timeout = ContentHandler::error(
StatusCode::GatewayTimeout, StatusCode::GatewayTimeout,
format!("<h1>Could not fetch app bundle within {} seconds.</h1>", FETCH_TIMEOUT), "Download Timeout",
); &format!("Could not fetch dapp bundle within {} seconds.", FETCH_TIMEOUT),
None
);
Self::close_client(&mut self.client); Self::close_client(&mut self.client);
(Some(FetchState::Error(timeout)), Next::write()) (Some(FetchState::Error(timeout)), Next::write())
}, },
@ -175,9 +181,11 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
let state = match self.dapp.validate_and_install(path.clone()) { let state = match self.dapp.validate_and_install(path.clone()) {
Err(e) => { Err(e) => {
trace!(target: "dapps", "Error while validating dapp: {:?}", e); trace!(target: "dapps", "Error while validating dapp: {:?}", e);
FetchState::Error(ContentHandler::html( FetchState::Error(ContentHandler::error(
StatusCode::BadGateway, StatusCode::BadGateway,
format!("<h1>Downloaded bundle does not contain valid app.</h1><pre>{:?}</pre>", e), "Invalid Dapp",
"Downloaded bundle does not contain a valid dapp.",
Some(&format!("{:?}", e))
)) ))
}, },
Ok(manifest) => FetchState::Done(manifest) Ok(manifest) => FetchState::Done(manifest)
@ -188,9 +196,11 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
}, },
Ok(Err(e)) => { Ok(Err(e)) => {
warn!(target: "dapps", "Unable to fetch new dapp: {:?}", e); warn!(target: "dapps", "Unable to fetch new dapp: {:?}", e);
let error = ContentHandler::html( let error = ContentHandler::error(
StatusCode::BadGateway, StatusCode::BadGateway,
"<h1>There was an error when fetching the dapp.</h1>".into(), "Download Error",
"There was an error when fetching the dapp.",
Some(&format!("{:?}", e)),
); );
(Some(FetchState::Error(error)), Next::write()) (Some(FetchState::Error(error)), Next::write())
}, },

View File

@ -55,10 +55,9 @@ impl Authorization for HttpBasicAuth {
match auth { match auth {
Access::Denied => { Access::Denied => {
Authorized::No(Box::new(ContentHandler::new( Authorized::No(Box::new(ContentHandler::error(
status::StatusCode::Unauthorized, status::StatusCode::Unauthorized,
"<h1>Unauthorized</h1>".into(), "Unauthorized", "You need to provide valid credentials to access this page.", None
"text/html".into(),
))) )))
}, },
Access::AuthRequired => { Access::AuthRequired => {

View File

@ -16,7 +16,7 @@
use DAPPS_DOMAIN; use DAPPS_DOMAIN;
use hyper::{server, header}; use hyper::{server, header, StatusCode};
use hyper::net::HttpStream; use hyper::net::HttpStream;
use jsonrpc_http_server::{is_host_header_valid}; use jsonrpc_http_server::{is_host_header_valid};
@ -38,11 +38,9 @@ pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String],
} }
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> { pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
Box::new(ContentHandler::forbidden( Box::new(ContentHandler::error(StatusCode::Forbidden,
r#" "Current host is disallowed",
<h1>Request with disallowed <code>Host</code> header has been blocked.</h1> "You are trying to access your node using incorrect address.",
<p>Check the URL in your browser address bar.</p> Some("Use allowed URL or specify different <code>hosts</code> CLI options.")
"#.into(),
"text/html".into()
)) ))
} }

View File

@ -22,7 +22,7 @@ use std::path::{PathBuf, Path};
use std::sync::Arc; use std::sync::Arc;
use std::str::FromStr; use std::str::FromStr;
use jsonrpc_core::IoHandler; use jsonrpc_core::IoHandler;
use util::H256; use util::{H256, version};
#[cfg(feature = "ui")] #[cfg(feature = "ui")]
mod signer { mod signer {
@ -112,7 +112,12 @@ impl ws::Handler for Session {
if !self.skip_origin_validation { if !self.skip_origin_validation {
if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) { if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) {
warn!(target: "signer", "Blocked connection to Signer API from untrusted origin."); warn!(target: "signer", "Blocked connection to Signer API from untrusted origin.");
return Ok(ws::Response::forbidden(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin))); return Ok(error(
ErrorType::Forbidden,
"URL Blocked",
"You are not allowed to access Trusted Signer using this URL.",
Some(&format!("Use: http://{}", self.self_origin)),
));
} }
} }
@ -121,7 +126,7 @@ impl ws::Handler for Session {
// Check authorization // Check authorization
if !auth_is_valid(&self.authcodes_path, req.protocols()) { if !auth_is_valid(&self.authcodes_path, req.protocols()) {
info!(target: "signer", "Unauthorized connection to Signer API blocked."); info!(target: "signer", "Unauthorized connection to Signer API blocked.");
return Ok(ws::Response::forbidden("You are not authorized.".into())); return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None));
} }
let protocols = req.protocols().expect("Existence checked by authorization."); let protocols = req.protocols().expect("Existence checked by authorization.");
@ -137,7 +142,7 @@ impl ws::Handler for Session {
Ok(signer::handle(req.resource()) Ok(signer::handle(req.resource())
.map_or_else( .map_or_else(
// return 404 not found // return 404 not found
|| add_headers(ws::Response::not_found("Not found".into()), "text/plain"), || error(ErrorType::NotFound, "Not found", "Requested file was not found.", None),
// or serve the file // or serve the file
|f| add_headers(ws::Response::ok(f.content.into()), &f.mime) |f| add_headers(ws::Response::ok(f.content.into()), &f.mime)
)) ))
@ -185,3 +190,24 @@ impl ws::Factory for Factory {
} }
} }
} }
enum ErrorType {
NotFound,
Forbidden,
}
fn error(error: ErrorType, title: &str, message: &str, details: Option<&str>) -> ws::Response {
let content = format!(
include_str!("../../../dapps/src/error_tpl.html"),
title=title,
meta="",
message=message,
details=details.unwrap_or(""),
version=version(),
);
let res = match error {
ErrorType::NotFound => ws::Response::not_found(content),
ErrorType::Forbidden => ws::Response::forbidden(content),
};
add_headers(res, "text/html")
}