UI server refactoring (#5580)
* Full API in Authenticated WS server. * Replacing UI server with Hyper. * Solving CLI, RPCs and tests. * Porting signer tests. * Fixing origin recognition for dapps/rpc. * Fixing tests. Adding parity-rpc-client to test. * Dapps exposed as RPC method. * JS code to support new connection scheme. * Fixing dapps tests. * Updating allowed origins/hosts to support web3.site. * Fixing tests, fixing UI. * Fixing tests. * Removing invalid tests. * Fixing merge. * 404 fallback for UI * Improve ContentFetcher constructor readability. * Naming. * Update .gitlab-ci.yml fix CI lint error * Fixing tests and linting issues. * Fixing new tests. * UI hosts. * Submodules fix.
This commit is contained in:
committed by
Arkadiy Paronyan
parent
7499efecf6
commit
cbcc369a2d
@@ -16,42 +16,27 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use unicase::UniCase;
|
||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||
use hyper::header;
|
||||
use hyper::method::Method;
|
||||
|
||||
use api::types::{App, ApiError};
|
||||
use api::types::ApiError;
|
||||
use api::response;
|
||||
use apps::fetcher::Fetcher;
|
||||
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
use jsonrpc_http_server::{self, AccessControlAllowOrigin};
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
// TODO [ToDr] cors_domains should be handled by the server to avoid duplicated logic.
|
||||
// RequestMiddleware should be able to tell that cors headers should be included.
|
||||
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
|
||||
apps: Vec<App>,
|
||||
fetcher: Arc<Fetcher>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(cors_domains: Vec<AccessControlAllowOrigin>, endpoints: &Endpoints, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
pub fn new(fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
cors_domains: Some(cors_domains),
|
||||
apps: Self::list_apps(endpoints),
|
||||
fetcher: fetcher,
|
||||
})
|
||||
}
|
||||
|
||||
fn list_apps(endpoints: &Endpoints) -> Vec<App> {
|
||||
endpoints.iter().filter_map(|(ref k, ref e)| {
|
||||
e.info().map(|ref info| App::from_info(k, info))
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for RestApi {
|
||||
@@ -62,7 +47,6 @@ impl Endpoint for RestApi {
|
||||
|
||||
struct RestApiRouter {
|
||||
api: RestApi,
|
||||
cors_header: Option<header::AccessControlAllowOrigin>,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
@@ -72,7 +56,6 @@ impl RestApiRouter {
|
||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||
RestApiRouter {
|
||||
path: Some(path),
|
||||
cors_header: None,
|
||||
control: Some(control),
|
||||
api: api,
|
||||
handler: response::as_json_error(&ApiError {
|
||||
@@ -92,35 +75,10 @@ impl RestApiRouter {
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns basic headers for a response (it may be overwritten by the handler)
|
||||
fn response_headers(cors_header: Option<header::AccessControlAllowOrigin>) -> header::Headers {
|
||||
let mut headers = header::Headers::new();
|
||||
|
||||
if let Some(cors_header) = cors_header {
|
||||
headers.set(header::AccessControlAllowCredentials);
|
||||
headers.set(header::AccessControlAllowMethods(vec![
|
||||
Method::Options,
|
||||
Method::Post,
|
||||
Method::Get,
|
||||
]));
|
||||
headers.set(header::AccessControlAllowHeaders(vec![
|
||||
UniCase("origin".to_owned()),
|
||||
UniCase("content-type".to_owned()),
|
||||
UniCase("accept".to_owned()),
|
||||
]));
|
||||
|
||||
headers.set(cors_header);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains).into();
|
||||
|
||||
if let Method::Options = *request.method() {
|
||||
self.handler = response::empty();
|
||||
return Next::write();
|
||||
@@ -144,7 +102,6 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
|
||||
|
||||
let handler = endpoint.and_then(|v| match v {
|
||||
"apps" => Some(response::as_json(&self.api.apps)),
|
||||
"ping" => Some(response::ping()),
|
||||
"content" => self.resolve_content(hash, path, control),
|
||||
_ => None
|
||||
@@ -163,7 +120,6 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
*res.headers_mut() = Self::response_headers(self.cors_header.take());
|
||||
self.handler.on_response(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ mod response;
|
||||
mod types;
|
||||
|
||||
pub use self::api::RestApi;
|
||||
pub use self::types::App;
|
||||
|
||||
@@ -23,12 +23,6 @@ pub fn empty() -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
|
||||
@@ -14,46 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct App {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
#[serde(rename="iconUrl")]
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates `App` instance from `EndpointInfo` and `id`.
|
||||
pub fn from_info(id: &str, info: &EndpointInfo) -> Self {
|
||||
App {
|
||||
id: id.to_owned(),
|
||||
name: info.name.to_owned(),
|
||||
description: info.description.to_owned(),
|
||||
version: info.version.to_owned(),
|
||||
author: info.author.to_owned(),
|
||||
icon_url: info.icon_url.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<EndpointInfo> for App {
|
||||
fn into(self) -> EndpointInfo {
|
||||
EndpointInfo {
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
version: self.version,
|
||||
author: self.author,
|
||||
icon_url: self.icon_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApiError {
|
||||
|
||||
55
dapps/src/apps/app.rs
Normal file
55
dapps/src/apps/app.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct App {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
#[serde(rename="iconUrl")]
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates `App` instance from `EndpointInfo` and `id`.
|
||||
pub fn from_info(id: &str, info: &EndpointInfo) -> Self {
|
||||
App {
|
||||
id: id.to_owned(),
|
||||
name: info.name.to_owned(),
|
||||
description: info.description.to_owned(),
|
||||
version: info.version.to_owned(),
|
||||
author: info.author.to_owned(),
|
||||
icon_url: info.icon_url.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<EndpointInfo> for App {
|
||||
fn into(self) -> EndpointInfo {
|
||||
EndpointInfo {
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
version: self.version,
|
||||
author: self.author,
|
||||
icon_url: self.icon_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
only_content: bool,
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
@@ -66,7 +67,12 @@ impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
|
||||
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
|
||||
pub fn new(
|
||||
resolver: R,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
@@ -75,12 +81,23 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_on: embeddable_on,
|
||||
embeddable_on: None,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
only_content: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow_dapps(mut self, dapps: bool) -> Self {
|
||||
self.only_content = !dapps;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn embeddable_on(mut self, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
self.embeddable_on = embeddable_on;
|
||||
self
|
||||
}
|
||||
|
||||
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
@@ -91,6 +108,16 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
))
|
||||
}
|
||||
|
||||
fn dapps_disabled(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Network Dapps Not Available",
|
||||
"This interface doesn't support network dapps for security reasons.",
|
||||
None,
|
||||
address,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
||||
self.cache.lock().insert(content_id.to_owned(), status);
|
||||
@@ -163,6 +190,9 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
||||
},
|
||||
Some(URLHintResult::Dapp(_)) if self.only_content => {
|
||||
(None, Self::dapps_disabled(self.embeddable_on.clone()))
|
||||
},
|
||||
Some(URLHintResult::Dapp(dapp)) => {
|
||||
let handler = ContentFetcherHandler::new(
|
||||
dapp.url(),
|
||||
@@ -254,7 +284,8 @@ mod tests {
|
||||
fn should_true_if_contains_the_app() {
|
||||
// given
|
||||
let path = env::temp_dir();
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap());
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), Remote::new_sync(), Client::new().unwrap())
|
||||
.allow_dapps(true);
|
||||
let handler = LocalPageEndpoint::new(path, EndpointInfo {
|
||||
name: "fake".into(),
|
||||
description: "".into(),
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use endpoint::{Endpoint, EndpointInfo};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
|
||||
struct LocalDapp {
|
||||
@@ -85,8 +86,8 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
|
||||
|
||||
/// Returns endpoints for Local Dapps found for given filesystem path.
|
||||
/// Scans the directory and collects `LocalPageEndpoints`.
|
||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
|
||||
let mut pages = Endpoints::new();
|
||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> {
|
||||
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
|
||||
for dapp in local_dapps(dapps_path.as_ref()) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde_json;
|
||||
pub use api::App as Manifest;
|
||||
pub use apps::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use endpoint::{Endpoints, Endpoint};
|
||||
use page::PageEndpoint;
|
||||
use proxypac::ProxyPac;
|
||||
@@ -23,17 +25,19 @@ use web::Web;
|
||||
use fetch::Fetch;
|
||||
use parity_dapps::WebApp;
|
||||
use parity_reactor::Remote;
|
||||
use parity_ui;
|
||||
use {WebProxyTokens};
|
||||
|
||||
mod app;
|
||||
mod cache;
|
||||
mod fs;
|
||||
mod ui;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
|
||||
extern crate parity_ui;
|
||||
pub use self::app::App;
|
||||
|
||||
pub const HOME_PAGE: &'static str = "parity";
|
||||
pub const DAPPS_DOMAIN: &'static str = ".web3.site";
|
||||
pub const HOME_PAGE: &'static str = "home";
|
||||
pub const RPC_PATH: &'static str = "rpc";
|
||||
pub const API_PATH: &'static str = "api";
|
||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
||||
@@ -44,18 +48,27 @@ pub fn utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
pub fn ui() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
|
||||
}
|
||||
|
||||
pub fn ui_redirection(ui_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
Box::new(ui::Redirection::new(ui_address))
|
||||
}
|
||||
|
||||
pub fn all_endpoints<F: Fetch>(
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
ui_address: Option<(String, u16)>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Endpoints {
|
||||
// fetch fs dapps at first to avoid overwriting builtins
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
|
||||
let mut pages = fs::local_endpoints(dapps_path, ui_address.clone());
|
||||
for path in extra_dapps {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), ui_address.clone()) {
|
||||
pages.insert(id, endpoint);
|
||||
} else {
|
||||
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
|
||||
@@ -63,14 +76,14 @@ pub fn all_endpoints<F: Fetch>(
|
||||
}
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone()));
|
||||
pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(ui_address.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain));
|
||||
pages.insert(WEB_PATH.into(), Web::boxed(ui_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
||||
|
||||
pages
|
||||
Arc::new(pages)
|
||||
}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpoint>>, id: &str, embed_at: Embeddable) {
|
||||
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
||||
Embeddable::No => PageEndpoint::new(T::default()),
|
||||
|
||||
55
dapps/src/apps/ui.rs
Normal file
55
dapps/src/apps/ui.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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/>.
|
||||
|
||||
//! UI redirections
|
||||
|
||||
use hyper::{Control, StatusCode};
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use {address, handlers};
|
||||
|
||||
/// Redirection to UI server.
|
||||
pub struct Redirection {
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new(
|
||||
signer_address: Option<(String, u16)>,
|
||||
) -> Self {
|
||||
Redirection {
|
||||
signer_address: signer_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for Redirection {
|
||||
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
|
||||
if let Some(ref signer_address) = self.signer_address {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
handlers::Redirection::boxed(&format!("http://{}", address(signer_address)))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(handlers::ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
//! URL Endpoint traits
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hyper::{self, server, net};
|
||||
@@ -38,7 +39,7 @@ pub struct EndpointInfo {
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
|
||||
pub type Endpoints = Arc<BTreeMap<String, Box<Endpoint>>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
pub trait Endpoint : Send + Sync {
|
||||
|
||||
139
dapps/src/lib.rs
139
dapps/src/lib.rs
@@ -40,6 +40,7 @@ extern crate fetch;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
extern crate parity_hash_fetch as hash_fetch;
|
||||
extern crate parity_reactor;
|
||||
extern crate parity_ui;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -70,7 +71,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use jsonrpc_http_server::{self as http, hyper, AccessControlAllowOrigin};
|
||||
use jsonrpc_http_server::{self as http, hyper};
|
||||
|
||||
use fetch::Fetch;
|
||||
use parity_reactor::Remote;
|
||||
@@ -97,18 +98,74 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
||||
fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) }
|
||||
}
|
||||
|
||||
/// Current supported endpoints.
|
||||
pub struct Endpoints {
|
||||
endpoints: endpoint::Endpoints,
|
||||
}
|
||||
|
||||
impl Endpoints {
|
||||
/// Returns a current list of app endpoints.
|
||||
pub fn list(&self) -> Vec<apps::App> {
|
||||
self.endpoints.iter().filter_map(|(ref k, ref e)| {
|
||||
e.info().map(|ref info| apps::App::from_info(k, info))
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Dapps server as `jsonrpc-http-server` request middleware.
|
||||
pub struct Middleware {
|
||||
router: router::Router,
|
||||
endpoints: endpoint::Endpoints,
|
||||
}
|
||||
|
||||
impl Middleware {
|
||||
/// Creates new Dapps server middleware.
|
||||
pub fn new<F: Fetch + Clone>(
|
||||
/// Get local endpoints handle.
|
||||
pub fn endpoints(&self) -> Endpoints {
|
||||
Endpoints {
|
||||
endpoints: self.endpoints.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new middleware for UI server.
|
||||
pub fn ui<F: Fetch + Clone>(
|
||||
remote: Remote,
|
||||
signer_address: Option<(String, u16)>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
fetch: F,
|
||||
dapps_domain: String,
|
||||
) -> Self {
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status,
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
).embeddable_on(None).allow_dapps(false));
|
||||
let special = {
|
||||
let mut special = special_endpoints(content_fetcher.clone());
|
||||
special.insert(router::SpecialEndpoint::Home, Some(apps::ui()));
|
||||
special
|
||||
};
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
None,
|
||||
special,
|
||||
None,
|
||||
dapps_domain,
|
||||
);
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new Dapps server middleware.
|
||||
pub fn dapps<F: Fetch + Clone>(
|
||||
remote: Remote,
|
||||
ui_address: Option<(String, u16)>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
dapps_domain: String,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
@@ -117,45 +174,36 @@ impl Middleware {
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status,
|
||||
signer_address.clone(),
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
));
|
||||
).embeddable_on(ui_address.clone()).allow_dapps(true));
|
||||
let endpoints = apps::all_endpoints(
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
signer_address.clone(),
|
||||
dapps_domain.clone(),
|
||||
ui_address.clone(),
|
||||
web_proxy_tokens,
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
);
|
||||
|
||||
let cors_domains = cors_domains(signer_address.clone());
|
||||
|
||||
let special = {
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, None);
|
||||
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Api,
|
||||
Some(api::RestApi::new(
|
||||
cors_domains.clone(),
|
||||
&endpoints,
|
||||
content_fetcher.clone()
|
||||
)),
|
||||
);
|
||||
let mut special = special_endpoints(content_fetcher.clone());
|
||||
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone())));
|
||||
special
|
||||
};
|
||||
|
||||
let router = router::Router::new(
|
||||
signer_address,
|
||||
content_fetcher,
|
||||
endpoints,
|
||||
Some(endpoints.clone()),
|
||||
special,
|
||||
ui_address,
|
||||
dapps_domain,
|
||||
);
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: endpoints,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,21 +214,12 @@ impl http::RequestMiddleware for Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<AccessControlAllowOrigin> {
|
||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
|
||||
match signer_address {
|
||||
Some(signer_address) => [
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||
format!("http://{}", address(&signer_address)),
|
||||
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||
format!("https://{}", address(&signer_address)),
|
||||
].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(),
|
||||
None => vec![],
|
||||
}
|
||||
fn special_endpoints(content_fetcher: Arc<apps::fetcher::Fetcher>) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, None);
|
||||
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
|
||||
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(content_fetcher)));
|
||||
special
|
||||
}
|
||||
|
||||
fn address(address: &(String, u16)) -> String {
|
||||
@@ -193,29 +232,3 @@ fn random_filename() -> String {
|
||||
let mut rng = ::rand::OsRng::new().unwrap();
|
||||
rng.gen_ascii_chars().take(12).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod util_tests {
|
||||
use super::cors_domains;
|
||||
use jsonrpc_http_server::AccessControlAllowOrigin;
|
||||
|
||||
#[test]
|
||||
fn should_return_cors_domains() {
|
||||
// given
|
||||
|
||||
// when
|
||||
let none = cors_domains(None);
|
||||
let some = cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<AccessControlAllowOrigin>::new());
|
||||
assert_eq!(some, vec![
|
||||
"http://parity.web3.site".into(),
|
||||
"http://parity.web3.site:18180".into(),
|
||||
"http://127.0.0.1:18180".into(),
|
||||
"https://parity.web3.site".into(),
|
||||
"https://parity.web3.site:18180".into(),
|
||||
"https://127.0.0.1:18180".into(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Safe to be loaded in frame by other origin. (use wisely!)
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
info: EndpointInfo,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
@@ -38,6 +39,20 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `PageEndpoint` for builtin (compile time) Dapp.
|
||||
/// Instead of returning 404 this endpoint will always server index.html.
|
||||
pub fn with_fallback_to_index(app: T) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +66,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +80,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +93,7 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
Box::new(handler::PageHandler {
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
app: BuiltinDapp::new(self.app.clone(), self.fallback_to_index_html),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||
@@ -100,12 +117,14 @@ impl From<Info> for EndpointInfo {
|
||||
|
||||
struct BuiltinDapp<T: WebApp + 'static> {
|
||||
app: Arc<T>,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
||||
fn new(app: Arc<T>) -> Self {
|
||||
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
|
||||
BuiltinDapp {
|
||||
app: app,
|
||||
fallback_to_index_html: fallback_to_index_html,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,13 +133,19 @@ impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
|
||||
type DappFile = BuiltinDappFile<T>;
|
||||
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
||||
self.app.file(path).map(|_| {
|
||||
let file = |path| self.app.file(path).map(|_| {
|
||||
BuiltinDappFile {
|
||||
app: self.app.clone(),
|
||||
path: path.into(),
|
||||
write_pos: 0,
|
||||
}
|
||||
})
|
||||
});
|
||||
let res = file(path);
|
||||
if self.fallback_to_index_html {
|
||||
res.or_else(|| file("index.html"))
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,19 @@
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
use apps::HOME_PAGE;
|
||||
use address;
|
||||
|
||||
pub struct ProxyPac {
|
||||
signer_address: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
}
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>, dapps_domain: String) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac {
|
||||
signer_address: signer_address
|
||||
signer_address: signer_address,
|
||||
dapps_domain: dapps_domain,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,12 +45,12 @@ impl Endpoint for ProxyPac {
|
||||
let content = format!(
|
||||
r#"
|
||||
function FindProxyForURL(url, host) {{
|
||||
if (shExpMatch(host, "{0}{1}"))
|
||||
if (shExpMatch(host, "{0}.{1}"))
|
||||
{{
|
||||
return "PROXY {4}";
|
||||
}}
|
||||
|
||||
if (shExpMatch(host, "*{1}"))
|
||||
if (shExpMatch(host, "*.{1}"))
|
||||
{{
|
||||
return "PROXY {2}:{3}";
|
||||
}}
|
||||
@@ -56,7 +58,7 @@ function FindProxyForURL(url, host) {{
|
||||
return "DIRECT";
|
||||
}}
|
||||
"#,
|
||||
HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer);
|
||||
HOME_PAGE, self.dapps_domain, path.host, path.port, signer);
|
||||
|
||||
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||
}
|
||||
|
||||
@@ -17,20 +17,19 @@
|
||||
//! Router implementation
|
||||
//! Dispatch requests to proper application.
|
||||
|
||||
use address;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, header, Control, StatusCode};
|
||||
use hyper::{self, server, header, Control};
|
||||
use hyper::net::HttpStream;
|
||||
use jsonrpc_http_server as http;
|
||||
|
||||
use apps::{self, DAPPS_DOMAIN};
|
||||
use apps;
|
||||
use apps::fetcher::Fetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
|
||||
use handlers::{self, Redirection, ContentHandler};
|
||||
use handlers;
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||
@@ -38,26 +37,28 @@ pub enum SpecialEndpoint {
|
||||
Rpc,
|
||||
Api,
|
||||
Utils,
|
||||
Home,
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Router {
|
||||
signer_address: Option<(String, u16)>,
|
||||
endpoints: Endpoints,
|
||||
endpoints: Option<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
}
|
||||
|
||||
impl http::RequestMiddleware for Router {
|
||||
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = handlers::extract_url(req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
let referer = extract_referer_endpoint(req);
|
||||
let endpoint = extract_endpoint(&url, &self.dapps_domain);
|
||||
let referer = extract_referer_endpoint(req, &self.dapps_domain);
|
||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
||||
let is_dapps_domain = endpoint.0.as_ref().map(|endpoint| endpoint.using_dapps_domains).unwrap_or(false);
|
||||
let is_origin_set = req.headers().get::<http::hyper::header::Origin>().is_some();
|
||||
let is_origin_set = req.headers().get::<header::Origin>().is_some();
|
||||
let is_get_request = *req.method() == hyper::Method::Get;
|
||||
let is_head_request = *req.method() == hyper::Method::Head;
|
||||
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
@@ -67,7 +68,7 @@ impl http::RequestMiddleware for Router {
|
||||
// Handle invalid web requests that we can recover from
|
||||
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
||||
if referer.app_id == apps::WEB_PATH
|
||||
&& self.endpoints.contains_key(apps::WEB_PATH)
|
||||
&& self.endpoints.as_ref().map(|ep| ep.contains_key(apps::WEB_PATH)).unwrap_or(false)
|
||||
&& !is_web_endpoint(path)
|
||||
=>
|
||||
{
|
||||
@@ -75,7 +76,7 @@ impl http::RequestMiddleware for Router {
|
||||
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
|
||||
let base = referer_url.path[..len].join("/");
|
||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
||||
Some(Redirection::boxed(&format!("/{}/{}", base, requested)))
|
||||
Some(handlers::Redirection::boxed(&format!("/{}/{}", base, requested)))
|
||||
},
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
|
||||
@@ -86,9 +87,12 @@ impl http::RequestMiddleware for Router {
|
||||
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
(Some(ref path), _, _) if self.endpoints.as_ref().map(|ep| ep.contains_key(&path.app_id)).unwrap_or(false) => {
|
||||
trace!(target: "dapps", "Resolving to local/builtin dapp.");
|
||||
Some(self.endpoints.get(&path.app_id)
|
||||
Some(self.endpoints
|
||||
.as_ref()
|
||||
.expect("endpoints known to be set; qed")
|
||||
.get(&path.app_id)
|
||||
.expect("endpoints known to contain key; qed")
|
||||
.to_async_handler(path.clone(), control))
|
||||
},
|
||||
@@ -97,36 +101,28 @@ impl http::RequestMiddleware for Router {
|
||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||
Some(self.fetch.to_async_handler(path.clone(), control))
|
||||
},
|
||||
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached
|
||||
// (in the past we used 301 instead of 302)
|
||||
// It should be safe to remove it in (near) future.
|
||||
//
|
||||
// 404 for non-existent content
|
||||
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
|
||||
// 404 for non-existent content (only if serving endpoints and not homepage)
|
||||
(Some(ref path), _, _)
|
||||
if (is_get_request || is_head_request)
|
||||
&& self.endpoints.is_some()
|
||||
&& path.app_id != apps::HOME_PAGE
|
||||
=>
|
||||
{
|
||||
trace!(target: "dapps", "Resolving to 404.");
|
||||
Some(Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
Some(Box::new(handlers::ContentHandler::error(
|
||||
hyper::StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested content was not found.",
|
||||
None,
|
||||
self.signer_address.clone(),
|
||||
self.embeddable_on.clone(),
|
||||
)))
|
||||
},
|
||||
// Redirect any other GET request to signer.
|
||||
_ if is_get_request => {
|
||||
if let Some(ref signer_address) = self.signer_address {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
Some(Redirection::boxed(&format!("http://{}", address(signer_address))))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Some(Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
|
||||
self.signer_address.clone(),
|
||||
)))
|
||||
}
|
||||
// Any other GET|HEAD requests to home page.
|
||||
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
|
||||
self.special.get(&SpecialEndpoint::Home)
|
||||
.expect("special known to contain key; qed")
|
||||
.as_ref()
|
||||
.map(|special| special.to_async_handler(Default::default(), control))
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
@@ -137,7 +133,7 @@ impl http::RequestMiddleware for Router {
|
||||
|
||||
match handler {
|
||||
Some(handler) => http::RequestMiddlewareAction::Respond {
|
||||
should_validate_hosts: !(is_utils || is_dapps_domain),
|
||||
should_validate_hosts: !is_utils,
|
||||
handler: handler,
|
||||
},
|
||||
None => http::RequestMiddlewareAction::Proceed {
|
||||
@@ -149,16 +145,18 @@ impl http::RequestMiddleware for Router {
|
||||
|
||||
impl Router {
|
||||
pub fn new(
|
||||
signer_address: Option<(String, u16)>,
|
||||
content_fetcher: Arc<Fetcher>,
|
||||
endpoints: Endpoints,
|
||||
endpoints: Option<Endpoints>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
dapps_domain: String,
|
||||
) -> Self {
|
||||
Router {
|
||||
signer_address: signer_address,
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
embeddable_on: embeddable_on,
|
||||
dapps_domain: format!(".{}", dapps_domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,19 +168,19 @@ fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>) -> Option<(EndpointPath, Url)> {
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
|
||||
let referer = req.headers().get::<header::Referer>();
|
||||
|
||||
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
||||
url.and_then(|url| {
|
||||
let option = Some(url);
|
||||
extract_url_referer_endpoint(&option).or_else(|| {
|
||||
extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
||||
extract_url_referer_endpoint(&option, dapps_domain).or_else(|| {
|
||||
extract_endpoint(&option, dapps_domain).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)> {
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
|
||||
let query = url.as_ref().and_then(|url| url.query.as_ref());
|
||||
match (url, query) {
|
||||
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
|
||||
@@ -190,7 +188,7 @@ fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)
|
||||
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
|
||||
|
||||
let referer_url = Url::parse(&referer_url).ok();
|
||||
extract_endpoint(&referer_url).0.map(|endpoint| {
|
||||
extract_endpoint(&referer_url, dapps_domain).0.map(|endpoint| {
|
||||
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
|
||||
})
|
||||
},
|
||||
@@ -198,7 +196,7 @@ fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn extract_endpoint(url: &Option<Url>, dapps_domain: &str) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
return SpecialEndpoint::None;
|
||||
@@ -208,14 +206,15 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
|
||||
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
||||
apps::API_PATH => SpecialEndpoint::Api,
|
||||
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
||||
apps::HOME_PAGE => SpecialEndpoint::Home,
|
||||
_ => SpecialEndpoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
match *url {
|
||||
Some(ref url) => match url.host {
|
||||
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
||||
let id = &domain[0..(domain.len() - DAPPS_DOMAIN.len())];
|
||||
Host::Domain(ref domain) if domain.ends_with(dapps_domain) => {
|
||||
let id = &domain[0..(domain.len() - dapps_domain.len())];
|
||||
let (id, params) = if let Some(split) = id.rfind('.') {
|
||||
let (params, id) = id.split_at(split);
|
||||
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
|
||||
@@ -249,11 +248,12 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
|
||||
|
||||
#[test]
|
||||
fn should_extract_endpoint() {
|
||||
assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None));
|
||||
let dapps_domain = ".web3.site";
|
||||
assert_eq!(extract_endpoint(&None, dapps_domain), (None, SpecialEndpoint::None));
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["index.html".to_owned()],
|
||||
@@ -265,7 +265,7 @@ fn should_extract_endpoint() {
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "rpc".to_owned(),
|
||||
app_params: vec!["".to_owned()],
|
||||
@@ -276,7 +276,7 @@ fn should_extract_endpoint() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok()),
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
|
||||
@@ -288,7 +288,7 @@ fn should_extract_endpoint() {
|
||||
|
||||
// By Subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok()),
|
||||
extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["test.html".to_owned()],
|
||||
@@ -300,7 +300,7 @@ fn should_extract_endpoint() {
|
||||
|
||||
// RPC by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok()),
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
|
||||
@@ -312,7 +312,7 @@ fn should_extract_endpoint() {
|
||||
|
||||
// API by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok()),
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "api".into(), "".into()],
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 std::sync::Arc;
|
||||
use hyper;
|
||||
|
||||
use parity_rpc::{Metadata, Origin};
|
||||
use jsonrpc_core::{Middleware, MetaIoHandler};
|
||||
use jsonrpc_http_server::{self as http, AccessControlAllowOrigin, HttpMetaExtractor};
|
||||
use jsonrpc_http_server::tokio_core::reactor::Remote;
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
|
||||
pub fn rpc<T: Middleware<Metadata>>(
|
||||
handler: MetaIoHandler<Metadata, T>,
|
||||
remote: Remote,
|
||||
cors_domains: Vec<AccessControlAllowOrigin>,
|
||||
) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: Arc::new(handler),
|
||||
remote: remote,
|
||||
meta_extractor: Arc::new(MetadataExtractor),
|
||||
cors_domain: Some(cors_domains),
|
||||
// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
|
||||
allowed_hosts: None,
|
||||
})
|
||||
}
|
||||
|
||||
struct RpcEndpoint<T: Middleware<Metadata>> {
|
||||
handler: Arc<MetaIoHandler<Metadata, T>>,
|
||||
remote: Remote,
|
||||
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<http::Host>>,
|
||||
}
|
||||
|
||||
|
||||
impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
|
||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
Box::new(http::ServerHandler::new(
|
||||
http::Rpc {
|
||||
handler: self.handler.clone(),
|
||||
remote: self.remote.clone(),
|
||||
extractor: self.meta_extractor.clone(),
|
||||
},
|
||||
self.cors_domain.clone(),
|
||||
self.allowed_hosts.clone(),
|
||||
Arc::new(NoopMiddleware),
|
||||
control,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NoopMiddleware;
|
||||
impl http::RequestMiddleware for NoopMiddleware {
|
||||
fn on_request(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>, _control: &http::hyper::Control) -> http::RequestMiddlewareAction {
|
||||
http::RequestMiddlewareAction::Proceed {
|
||||
should_continue_on_invalid_cors: request.headers().get::<http::hyper::header::Origin>().is_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MetadataExtractor;
|
||||
impl HttpMetaExtractor<Metadata> for MetadataExtractor {
|
||||
fn read_metadata(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>) -> Metadata {
|
||||
let dapp_id = request.headers().get::<http::hyper::header::Origin>()
|
||||
.map(|origin| format!("{}://{}", origin.scheme, origin.host))
|
||||
.or_else(|| {
|
||||
// fallback to custom header, but only if origin is null
|
||||
request.headers().get_raw("origin")
|
||||
.and_then(|raw| raw.one())
|
||||
.and_then(|raw| if raw == "null".as_bytes() {
|
||||
request.headers().get_raw("x-parity-origin")
|
||||
.and_then(|raw| raw.one())
|
||||
.map(|raw| String::from_utf8_lossy(raw).into_owned())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
});
|
||||
Metadata {
|
||||
origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,29 +39,6 @@ fn should_return_error() {
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_apps() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/apps HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert!(response.body.contains("Parity UI"), response.body);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_ping() {
|
||||
// given
|
||||
@@ -106,92 +83,3 @@ fn should_try_to_resolve_dapp() {
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://127.0.0.1:18180\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers_for_home_parity() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://parity.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers_for_home_parity_with_https() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: https://parity.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "https://parity.web3.site");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://parity.web3.site:18180\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site:18180");
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use jsonrpc_http_server::{self as http, Host, DomainsValidation};
|
||||
use devtools::http_client;
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use fetch::{Fetch, Client as FetchClient};
|
||||
use parity_reactor::{EventLoop, Remote};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use {Middleware, SyncStatus, WebProxyTokens};
|
||||
|
||||
@@ -47,20 +47,7 @@ fn init_logger() {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerLoop {
|
||||
pub server: Server,
|
||||
pub event_loop: EventLoop,
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for ServerLoop {
|
||||
type Target = Server;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.server
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
|
||||
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||
B: Fetch,
|
||||
{
|
||||
@@ -69,44 +56,41 @@ pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (ServerLo
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
|
||||
// TODO [ToDr] When https://github.com/paritytech/jsonrpc/issues/26 is resolved
|
||||
// this additional EventLoop wouldn't be needed, we should be able to re-use remote.
|
||||
let event_loop = EventLoop::spawn();
|
||||
let server = process(ServerBuilder::new(
|
||||
&dapps_path, registrar.clone(), remote,
|
||||
))
|
||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
|
||||
(
|
||||
ServerLoop { server: server, event_loop: event_loop },
|
||||
server,
|
||||
registrar,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serve_with_rpc(io: IoHandler) -> ServerLoop {
|
||||
pub fn serve_with_rpc(io: IoHandler) -> Server {
|
||||
init_server(|builder| builder, io, Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
|
||||
let hosts = hosts.map(|hosts| hosts.into_iter().map(Into::into).collect());
|
||||
init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| {
|
||||
builder.sync_status(Arc::new(|| true))
|
||||
}, Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch() -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
serve_with_registrar_and_fetch_and_threads(false)
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
|
||||
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, reg) = init_server(move |builder| {
|
||||
@@ -116,7 +100,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv
|
||||
(server, fetch, reg)
|
||||
}
|
||||
|
||||
pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
|
||||
pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, _) = init_server(move |builder| {
|
||||
@@ -128,11 +112,11 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
|
||||
(server, fetch)
|
||||
}
|
||||
|
||||
pub fn serve() -> ServerLoop {
|
||||
pub fn serve() -> Server {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn request(server: ServerLoop, request: &str) -> http_client::Response {
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
http_client::request(server.addr(), request)
|
||||
}
|
||||
|
||||
@@ -240,6 +224,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
const DAPPS_DOMAIN: &'static str = "web3.site";
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
@@ -260,19 +245,27 @@ impl Server {
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Result<Server, http::Error> {
|
||||
let middleware = Middleware::new(
|
||||
let middleware = Middleware::dapps(
|
||||
remote,
|
||||
signer_address,
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
DAPPS_DOMAIN.into(),
|
||||
registrar,
|
||||
sync_status,
|
||||
web_proxy_tokens,
|
||||
fetch,
|
||||
);
|
||||
|
||||
let mut allowed_hosts: Option<Vec<Host>> = allowed_hosts.into();
|
||||
allowed_hosts.as_mut().map(|mut hosts| {
|
||||
hosts.push(format!("http://*.{}:*", DAPPS_DOMAIN).into());
|
||||
hosts.push(format!("http://*.{}", DAPPS_DOMAIN).into());
|
||||
});
|
||||
|
||||
http::ServerBuilder::new(io)
|
||||
.request_middleware(middleware)
|
||||
.allowed_hosts(allowed_hosts)
|
||||
.allowed_hosts(allowed_hosts.into())
|
||||
.cors(http::DomainsValidation::Disabled)
|
||||
.start_http(addr)
|
||||
.map(|server| Server {
|
||||
|
||||
@@ -37,15 +37,15 @@ fn should_redirect_to_home() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
fn should_redirect_to_home_with_domain() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /app HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: home.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -57,14 +57,14 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_for_users_with_cached_redirection() {
|
||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /home/ HTTP/1.1\r\n\
|
||||
GET /app HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
@@ -179,7 +179,7 @@ fn should_serve_proxy_pac() {
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, "DD\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"parity.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||
assert_eq!(response.body, "DB\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user