Displaying special page when syncing. 404 instead of redirection

This commit is contained in:
Tomasz Drwięga 2016-09-01 11:16:19 +02:00
parent 9c4d31f548
commit 89f1444c51
9 changed files with 157 additions and 43 deletions

View File

@ -30,6 +30,7 @@ use hyper::Control;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use random_filename; use random_filename;
use SyncStatus;
use util::{Mutex, H256}; use util::{Mutex, H256};
use util::sha3::sha3; use util::sha3::sha3;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
@ -44,6 +45,7 @@ const MAX_CACHED_DAPPS: usize = 10;
pub struct AppFetcher<R: URLHint = URLHintContract> { pub struct AppFetcher<R: URLHint = URLHintContract> {
dapps_path: PathBuf, dapps_path: PathBuf,
resolver: R, resolver: R,
sync: Arc<SyncStatus>,
dapps: Arc<Mutex<ContentCache>>, dapps: Arc<Mutex<ContentCache>>,
} }
@ -56,13 +58,14 @@ impl<R: URLHint> Drop for AppFetcher<R> {
impl<R: URLHint> AppFetcher<R> { impl<R: URLHint> AppFetcher<R> {
pub fn new(resolver: R) -> Self { pub fn new(resolver: R, sync_status: Arc<SyncStatus>) -> Self {
let mut dapps_path = env::temp_dir(); let mut dapps_path = env::temp_dir();
dapps_path.push(random_filename()); dapps_path.push(random_filename());
AppFetcher { AppFetcher {
dapps_path: dapps_path, dapps_path: dapps_path,
resolver: resolver, resolver: resolver,
sync: sync_status,
dapps: Arc::new(Mutex::new(ContentCache::default())), dapps: Arc::new(Mutex::new(ContentCache::default())),
} }
} }
@ -74,14 +77,20 @@ impl<R: URLHint> AppFetcher<R> {
pub fn contains(&self, app_id: &str) -> bool { pub fn contains(&self, app_id: &str) -> bool {
let mut dapps = self.dapps.lock(); let mut dapps = self.dapps.lock();
match dapps.get(app_id) { // Check if we already have the app
// Check if we already have the app if dapps.get(app_id).is_some() {
Some(_) => true, return true;
// fallback to resolver }
None => match app_id.from_hex() { // fallback to resolver
Ok(app_id) => self.resolver.resolve(app_id).is_some(), if let Ok(app_id) = app_id.from_hex() {
_ => false, // if app_id is valid, but we are syncing always return true.
}, if self.sync.is_major_syncing() {
return true;
}
// else try to resolve the app_id
self.resolver.resolve(app_id).is_some()
} else {
false
} }
} }
@ -89,6 +98,15 @@ impl<R: URLHint> AppFetcher<R> {
let mut dapps = self.dapps.lock(); let mut dapps = self.dapps.lock();
let app_id = path.app_id.clone(); let app_id = path.app_id.clone();
if self.sync.is_major_syncing() {
return Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync in progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
None
));
}
let (new_status, handler) = { let (new_status, handler) = {
let status = dapps.get(&app_id); let status = dapps.get(&app_id);
match status { match status {
@ -108,20 +126,32 @@ impl<R: URLHint> AppFetcher<R> {
// We need to start fetching app // We need to start fetching app
None => { None => {
let app_hex = app_id.from_hex().expect("to_handler is called only when `contains` returns true."); let app_hex = app_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let app = self.resolver.resolve(app_hex).expect("to_handler is called only when `contains` returns true."); let app = self.resolver.resolve(app_hex);
let abort = Arc::new(AtomicBool::new(false));
(Some(ContentStatus::Fetching(abort.clone())), Box::new(ContentFetcherHandler::new( if let Some(app) = app {
app, let abort = Arc::new(AtomicBool::new(false));
abort,
control, (Some(ContentStatus::Fetching(abort.clone())), Box::new(ContentFetcherHandler::new(
path.using_dapps_domains, app,
DappInstaller { abort,
dapp_id: app_id.clone(), control,
dapps_path: self.dapps_path.clone(), path.using_dapps_domains,
dapps: self.dapps.clone(), DappInstaller {
} dapp_id: app_id.clone(),
)) as Box<Handler>) dapps_path: self.dapps_path.clone(),
dapps: self.dapps.clone(),
}
)) as Box<Handler>)
} else {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None
)) as Box<Handler>)
}
}, },
} }
}; };
@ -274,6 +304,7 @@ impl ContentValidator for DappInstaller {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::env; use std::env;
use std::sync::Arc;
use util::Bytes; use util::Bytes;
use endpoint::EndpointInfo; use endpoint::EndpointInfo;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
@ -292,7 +323,7 @@ mod tests {
fn should_true_if_contains_the_app() { fn should_true_if_contains_the_app() {
// given // given
let path = env::temp_dir(); let path = env::temp_dir();
let fetcher = AppFetcher::new(FakeResolver); let fetcher = AppFetcher::new(FakeResolver, Arc::new(|| false));
let handler = LocalPageEndpoint::new(path, EndpointInfo { let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(), name: "fake".into(),
description: "".into(), description: "".into(),

View File

@ -88,11 +88,22 @@ use ethcore_rpc::Extendable;
static DAPPS_DOMAIN : &'static str = ".parity"; static DAPPS_DOMAIN : &'static str = ".parity";
/// Indicates sync status
pub trait SyncStatus: Send + Sync {
/// Returns true if there is a major sync happening.
fn is_major_syncing(&self) -> bool;
}
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
fn is_major_syncing(&self) -> bool { self() }
}
/// Webapps HTTP+RPC server build. /// Webapps HTTP+RPC server build.
pub struct ServerBuilder { pub struct ServerBuilder {
dapps_path: String, dapps_path: String,
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
} }
impl Extendable for ServerBuilder { impl Extendable for ServerBuilder {
@ -108,9 +119,15 @@ impl ServerBuilder {
dapps_path: dapps_path, dapps_path: dapps_path,
handler: Arc::new(IoHandler::new()), handler: Arc::new(IoHandler::new()),
registrar: registrar, registrar: registrar,
sync_status: Arc::new(|| false),
} }
} }
/// Change default sync status.
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
self.sync_status = status;
}
/// Asynchronously start server with no authentication, /// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error. /// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> { pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
@ -120,7 +137,8 @@ impl ServerBuilder {
NoAuth, NoAuth,
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.registrar.clone() self.registrar.clone(),
self.sync_status.clone(),
) )
} }
@ -133,7 +151,8 @@ impl ServerBuilder {
HttpBasicAuth::single_user(username, password), HttpBasicAuth::single_user(username, password),
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.registrar.clone() self.registrar.clone(),
self.sync_status.clone(),
) )
} }
} }
@ -167,10 +186,11 @@ impl Server {
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
dapps_path: String, dapps_path: String,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
) -> Result<Server, ServerError> { ) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None)); let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization); let authorization = Arc::new(authorization);
let apps_fetcher = Arc::new(apps::fetcher::AppFetcher::new(apps::urlhint::URLHintContract::new(registrar))); let apps_fetcher = Arc::new(apps::fetcher::AppFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status));
let endpoints = Arc::new(apps::all_endpoints(dapps_path)); let endpoints = Arc::new(apps::all_endpoints(dapps_path));
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();

View File

@ -24,12 +24,12 @@ use DAPPS_DOMAIN;
use std::sync::Arc; use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use url::{Url, Host}; use url::{Url, Host};
use hyper::{self, server, Next, Encoder, Decoder, Control}; use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode};
use hyper::net::HttpStream; use hyper::net::HttpStream;
use apps; use apps;
use apps::fetcher::AppFetcher; use apps::fetcher::AppFetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath}; use endpoint::{Endpoint, Endpoints, EndpointPath};
use handlers::{Redirection, extract_url}; use handlers::{Redirection, extract_url, ContentHandler};
use self::auth::{Authorization, Authorized}; use self::auth::{Authorization, Authorized};
/// Special endpoints are accessible on every domain (every dapp) /// Special endpoints are accessible on every domain (every dapp)
@ -94,7 +94,12 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
// Redirection to main page (maybe 404 instead?) // Redirection to main page (maybe 404 instead?)
(Some(ref path), _) if *req.method() == hyper::method::Method::Get => { (Some(ref path), _) if *req.method() == hyper::method::Method::Get => {
let address = apps::redirection_address(path.using_dapps_domains, self.main_page); let address = apps::redirection_address(path.using_dapps_domains, self.main_page);
Redirection::new(address.as_str()) Box::new(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Requested content was not found on a server.",
Some(&format!("Go back to the <a href=\"{}\">Home Page</a>.", address))
))
}, },
// Redirect any GET request to home. // Redirect any GET request to home.
_ if *req.method() == hyper::method::Method::Get => { _ if *req.method() == hyper::method::Method::Get => {

38
dapps/src/tests/fetch.rs Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_with_registrar, request};
#[test]
fn should_resolve_dapp() {
// given
let (server, registrar) = serve_with_registrar();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2);
}

View File

@ -58,22 +58,35 @@ impl ContractClient for FakeRegistrar {
} }
} }
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server { pub fn init_server(hosts: Option<Vec<String>>) -> (Server, Arc<FakeRegistrar>) {
let registrar = Arc::new(FakeRegistrar::new()); let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir(); let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); let builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap() (
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
registrar,
)
} }
pub fn serve_with_auth(user: &str, pass: &str) -> Server { pub fn serve_with_auth(user: &str, pass: &str) -> Server {
let registrar = Arc::new(FakeRegistrar::new()); let registrar = Arc::new(FakeRegistrar::new());
let builder = ServerBuilder::new(env::temp_dir().to_str().unwrap().into(), registrar); let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar);
builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
} }
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
init_server(hosts).0
}
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
init_server(None)
}
pub fn serve() -> Server { pub fn serve() -> Server {
serve_hosts(None) init_server(None).0
} }
pub fn request(server: Server, request: &str) -> http_client::Response { pub fn request(server: Server, request: &str) -> http_client::Response {

View File

@ -20,6 +20,7 @@ mod helpers;
mod api; mod api;
mod authorization; mod authorization;
mod fetch;
mod redirection; mod redirection;
mod validation; mod validation;

View File

@ -57,7 +57,7 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
} }
#[test] #[test]
fn should_redirect_to_home_on_invalid_dapp() { fn should_display_404_on_invalid_dapp() {
// given // given
let server = serve(); let server = serve();
@ -72,12 +72,12 @@ fn should_redirect_to_home_on_invalid_dapp() {
); );
// then // then
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Location: /home/"); assert!(response.body.contains("href=\"/home/"));
} }
#[test] #[test]
fn should_redirect_to_home_on_invalid_dapp_with_domain() { fn should_display_404_on_invalid_dapp_with_domain() {
// given // given
let server = serve(); let server = serve();
@ -92,8 +92,8 @@ fn should_redirect_to_home_on_invalid_dapp_with_domain() {
); );
// then // then
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Location: http://home.parity/"); assert!(response.body.contains("href=\"http://home.parity/"));
} }
#[test] #[test]

View File

@ -18,6 +18,7 @@ use std::sync::Arc;
use io::PanicHandler; use io::PanicHandler;
use rpc_apis; use rpc_apis;
use ethcore::client::Client; use ethcore::client::Client;
use ethsync::SyncProvider;
use helpers::replace_home; use helpers::replace_home;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -49,6 +50,7 @@ pub struct Dependencies {
pub panic_handler: Arc<PanicHandler>, pub panic_handler: Arc<PanicHandler>,
pub apis: Arc<rpc_apis::Dependencies>, pub apis: Arc<rpc_apis::Dependencies>,
pub client: Arc<Client>, pub client: Arc<Client>,
pub sync: Arc<SyncProvider>,
} }
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> { pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> {
@ -117,9 +119,12 @@ mod server {
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
use ethcore_dapps as dapps; use ethcore_dapps as dapps;
let server = dapps::ServerBuilder::new(dapps_path, Arc::new(Registrar { let mut server = dapps::ServerBuilder::new(
client: deps.client.clone(), dapps_path,
})); Arc::new(Registrar { client: deps.client.clone() })
);
let sync = deps.sync.clone();
server.with_sync_status(Arc::new(move || sync.status().is_major_syncing()));
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
let start_result = match auth { let start_result = match auth {
None => { None => {

View File

@ -224,6 +224,7 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
panic_handler: panic_handler.clone(), panic_handler: panic_handler.clone(),
apis: deps_for_rpc_apis.clone(), apis: deps_for_rpc_apis.clone(),
client: client.clone(), client: client.clone(),
sync: sync_provider.clone(),
}; };
// start dapps server // start dapps server