New version of jsonrpc.
This commit is contained in:
@@ -19,7 +19,6 @@ use unicase::UniCase;
|
||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||
use hyper::header;
|
||||
use hyper::method::Method;
|
||||
use hyper::header::AccessControlAllowOrigin;
|
||||
|
||||
use api::types::{App, ApiError};
|
||||
use api::response;
|
||||
@@ -27,23 +26,20 @@ use apps::fetcher::Fetcher;
|
||||
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
use jsonrpc_http_server::cors;
|
||||
use jsonrpc_http_server;
|
||||
use jsonrpc_server_utils::cors;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
|
||||
cors_domains: Option<Vec<cors::AccessControlAllowOrigin>>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetcher: Arc<Fetcher>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
pub fn new(cors_domains: Vec<cors::AccessControlAllowOrigin>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
|
||||
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
|
||||
"null" => AccessControlAllowOrigin::Null,
|
||||
other => AccessControlAllowOrigin::Value(other.into()),
|
||||
}).collect()),
|
||||
cors_domains: Some(cors_domains),
|
||||
endpoints: endpoints,
|
||||
fetcher: fetcher,
|
||||
})
|
||||
@@ -64,7 +60,7 @@ impl Endpoint for RestApi {
|
||||
|
||||
struct RestApiRouter {
|
||||
api: RestApi,
|
||||
origin: Option<String>,
|
||||
cors_header: Option<header::AccessControlAllowOrigin>,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
@@ -74,7 +70,7 @@ impl RestApiRouter {
|
||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||
RestApiRouter {
|
||||
path: Some(path),
|
||||
origin: None,
|
||||
cors_header: None,
|
||||
control: Some(control),
|
||||
api: api,
|
||||
handler: response::as_json_error(&ApiError {
|
||||
@@ -95,21 +91,22 @@ impl RestApiRouter {
|
||||
}
|
||||
|
||||
/// Returns basic headers for a response (it may be overwritten by the handler)
|
||||
fn response_headers(&self) -> header::Headers {
|
||||
fn response_headers(cors_header: Option<header::AccessControlAllowOrigin>) -> header::Headers {
|
||||
let mut headers = header::Headers::new();
|
||||
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()),
|
||||
]));
|
||||
|
||||
if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -120,7 +117,7 @@ impl RestApiRouter {
|
||||
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
self.origin = cors::read_origin(&request);
|
||||
self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains);
|
||||
|
||||
if let Method::Options = *request.method() {
|
||||
self.handler = response::empty();
|
||||
@@ -164,7 +161,7 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
*res.headers_mut() = self.response_headers();
|
||||
*res.headers_mut() = Self::response_headers(self.cors_header.take());
|
||||
self.handler.on_response(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,25 +20,28 @@
|
||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||
|
||||
extern crate base32;
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
extern crate time;
|
||||
extern crate url as url_lib;
|
||||
extern crate unicase;
|
||||
extern crate linked_hash_map;
|
||||
extern crate mime_guess;
|
||||
extern crate rand;
|
||||
extern crate rustc_serialize;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate time;
|
||||
extern crate unicase;
|
||||
extern crate url as url_lib;
|
||||
extern crate zip;
|
||||
extern crate rand;
|
||||
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
extern crate mime_guess;
|
||||
extern crate rustc_serialize;
|
||||
extern crate jsonrpc_server_utils;
|
||||
|
||||
extern crate ethcore_rpc;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate parity_hash_fetch as hash_fetch;
|
||||
extern crate linked_hash_map;
|
||||
extern crate fetch;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
extern crate futures;
|
||||
extern crate parity_hash_fetch as hash_fetch;
|
||||
extern crate parity_reactor;
|
||||
|
||||
#[macro_use]
|
||||
@@ -68,17 +71,19 @@ mod web;
|
||||
mod tests;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ethcore_rpc::{Metadata};
|
||||
use jsonrpc_core::{Middleware, MetaIoHandler};
|
||||
use jsonrpc_http_server::tokio_core::reactor::Remote as TokioRemote;
|
||||
pub use jsonrpc_http_server::{DomainsValidation, Host, AccessControlAllowOrigin};
|
||||
|
||||
use ethcore_rpc::Metadata;
|
||||
use fetch::{Fetch, Client as FetchClient};
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use jsonrpc_core::Middleware;
|
||||
use jsonrpc_core::reactor::RpcHandler;
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use parity_reactor::Remote;
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
|
||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
|
||||
@@ -110,8 +115,8 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
extra_cors: Option<Vec<String>>,
|
||||
allowed_hosts: Option<Vec<Host>>,
|
||||
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
|
||||
remote: Remote,
|
||||
fetch: Option<T>,
|
||||
}
|
||||
@@ -172,15 +177,15 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
/// Change allowed hosts.
|
||||
/// `None` - All hosts are allowed
|
||||
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
|
||||
pub fn allowed_hosts(mut self, allowed_hosts: Option<Vec<String>>) -> Self {
|
||||
self.allowed_hosts = allowed_hosts;
|
||||
pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation<Host>) -> Self {
|
||||
self.allowed_hosts = allowed_hosts.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Extra cors headers.
|
||||
/// `None` - no additional CORS URLs
|
||||
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
|
||||
self.extra_cors = cors;
|
||||
pub fn extra_cors_headers(mut self, cors: DomainsValidation<AccessControlAllowOrigin>) -> Self {
|
||||
self.extra_cors = cors.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -192,7 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
|
||||
/// Asynchronously start server with no authentication,
|
||||
/// returns result with `Server` handle on success or an error.
|
||||
pub fn start_unsecured_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
|
||||
pub fn start_unsecured_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, handler: MetaIoHandler<Metadata, S>, tokio_remote: TokioRemote) -> Result<Server, ServerError> {
|
||||
let fetch = self.fetch_client()?;
|
||||
Server::start_http(
|
||||
addr,
|
||||
@@ -207,13 +212,14 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
self.sync_status,
|
||||
self.web_proxy_tokens,
|
||||
self.remote,
|
||||
tokio_remote,
|
||||
fetch,
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||
/// return result with `Server` handle on success or an error.
|
||||
pub fn start_basic_auth_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, username: &str, password: &str, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
|
||||
pub fn start_basic_auth_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, username: &str, password: &str, handler: MetaIoHandler<Metadata, S>, tokio_remote: TokioRemote) -> Result<Server, ServerError> {
|
||||
let fetch = self.fetch_client()?;
|
||||
Server::start_http(
|
||||
addr,
|
||||
@@ -228,6 +234,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
self.sync_status,
|
||||
self.web_proxy_tokens,
|
||||
self.remote,
|
||||
tokio_remote,
|
||||
fetch,
|
||||
)
|
||||
}
|
||||
@@ -243,12 +250,11 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
server: Option<hyper::server::Listening>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Returns a list of allowed hosts or `None` if all hosts are allowed.
|
||||
fn allowed_hosts(hosts: Option<Vec<String>>, bind_address: String) -> Option<Vec<String>> {
|
||||
fn allowed_hosts(hosts: Option<Vec<Host>>, bind_address: String) -> Option<Vec<Host>> {
|
||||
let mut allowed = Vec::new();
|
||||
|
||||
match hosts {
|
||||
@@ -263,16 +269,19 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
|
||||
fn cors_domains(
|
||||
signer_address: Option<(String, u16)>,
|
||||
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
|
||||
) -> Vec<AccessControlAllowOrigin> {
|
||||
let basic_cors = match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
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![],
|
||||
};
|
||||
|
||||
@@ -284,10 +293,10 @@ impl Server {
|
||||
|
||||
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
extra_cors: Option<Vec<String>>,
|
||||
hosts: Option<Vec<Host>>,
|
||||
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
|
||||
authorization: A,
|
||||
handler: RpcHandler<Metadata, T>,
|
||||
handler: MetaIoHandler<Metadata, T>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
@@ -295,9 +304,9 @@ impl Server {
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
tokio_remote: TokioRemote,
|
||||
fetch: F,
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
@@ -318,7 +327,7 @@ impl Server {
|
||||
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, tokio_remote, cors_domains.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Api,
|
||||
@@ -346,17 +355,11 @@ impl Server {
|
||||
|
||||
Server {
|
||||
server: Some(l),
|
||||
panic_handler: panic_handler,
|
||||
}
|
||||
})
|
||||
.map_err(ServerError::from)
|
||||
}
|
||||
|
||||
/// Set callback for panics.
|
||||
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
|
||||
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns address that this server is bound to.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
@@ -408,6 +411,7 @@ fn address(address: &(String, u16)) -> String {
|
||||
#[cfg(test)]
|
||||
mod util_tests {
|
||||
use super::Server;
|
||||
use jsonrpc_http_server::AccessControlAllowOrigin;
|
||||
|
||||
#[test]
|
||||
fn should_return_allowed_hosts() {
|
||||
@@ -432,18 +436,18 @@ mod util_tests {
|
||||
// when
|
||||
let none = Server::cors_domains(None, None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
|
||||
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
|
||||
let extra = Server::cors_domains(None, Some(vec!["all".into()]));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
assert_eq!(none, Vec::<AccessControlAllowOrigin>::new());
|
||||
assert_eq!(some, vec![
|
||||
"http://parity.web3.site".to_owned(),
|
||||
"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()
|
||||
"https://127.0.0.1:18180".into(),
|
||||
]);
|
||||
assert_eq!(extra, vec!["all".to_owned()]);
|
||||
assert_eq!(extra, vec![AccessControlAllowOrigin::Any]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +19,13 @@ use apps::DAPPS_DOMAIN;
|
||||
use hyper::{server, header, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
use jsonrpc_http_server::{is_host_header_valid};
|
||||
use handlers::ContentHandler;
|
||||
use jsonrpc_http_server;
|
||||
use jsonrpc_server_utils::hosts;
|
||||
|
||||
pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String], endpoints: Vec<String>) -> bool {
|
||||
let mut endpoints = endpoints.iter()
|
||||
.map(|endpoint| format!("{}{}", endpoint, DAPPS_DOMAIN))
|
||||
.collect::<Vec<String>>();
|
||||
endpoints.extend_from_slice(allowed_hosts);
|
||||
|
||||
let header_valid = is_host_header_valid(request, &endpoints);
|
||||
|
||||
match (header_valid, request.headers().get::<header::Host>()) {
|
||||
pub fn is_valid(req: &server::Request<HttpStream>, allowed_hosts: &Option<Vec<hosts::Host>>) -> bool {
|
||||
let header_valid = jsonrpc_http_server::is_host_allowed(req, allowed_hosts);
|
||||
match (header_valid, req.headers().get::<header::Host>()) {
|
||||
(true, _) => true,
|
||||
(_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN),
|
||||
_ => false,
|
||||
|
||||
@@ -24,14 +24,16 @@ use address;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
use jsonrpc_server_utils::hosts;
|
||||
|
||||
use apps::{self, DAPPS_DOMAIN};
|
||||
use apps::fetcher::Fetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use handlers::{self, Redirection, ContentHandler};
|
||||
use self::auth::{Authorization, Authorized};
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||
@@ -42,18 +44,18 @@ pub enum SpecialEndpoint {
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Router<A: Authorization + 'static> {
|
||||
pub struct Router<A: auth::Authorization + 'static> {
|
||||
control: Option<Control>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
allowed_hosts: Option<Vec<hosts::Host>>,
|
||||
handler: Box<server::Handler<HttpStream> + Send>,
|
||||
}
|
||||
|
||||
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
impl<A: auth::Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
// Choose proper handler depending on path / domain
|
||||
@@ -66,20 +68,18 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
// Validate Host header
|
||||
if let Some(ref hosts) = self.allowed_hosts {
|
||||
trace!(target: "dapps", "Validating host headers against: {:?}", hosts);
|
||||
let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect());
|
||||
if !is_valid {
|
||||
debug!(target: "dapps", "Rejecting invalid host header.");
|
||||
self.handler = host_validation::host_invalid_response();
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
trace!(target: "dapps", "Validating host headers against: {:?}", self.allowed_hosts);
|
||||
let is_valid = is_utils || host_validation::is_valid(&req, &self.allowed_hosts);
|
||||
if !is_valid {
|
||||
debug!(target: "dapps", "Rejecting invalid host header.");
|
||||
self.handler = host_validation::host_invalid_response();
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
|
||||
trace!(target: "dapps", "Checking authorization.");
|
||||
// Check authorization
|
||||
let auth = self.authorization.is_authorized(&req);
|
||||
if let Authorized::No(handler) = auth {
|
||||
if let auth::Authorized::No(handler) = auth {
|
||||
debug!(target: "dapps", "Authorization denied.");
|
||||
self.handler = handler;
|
||||
return self.handler.on_request(req);
|
||||
@@ -181,7 +181,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Authorization> Router<A> {
|
||||
impl<A: auth::Authorization> Router<A> {
|
||||
pub fn new(
|
||||
control: Control,
|
||||
signer_address: Option<(String, u16)>,
|
||||
@@ -189,7 +189,7 @@ impl<A: Authorization> Router<A> {
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
allowed_hosts: Option<Vec<hosts::Host>>,
|
||||
) -> Self {
|
||||
|
||||
let handler = special.get(&SpecialEndpoint::Utils)
|
||||
|
||||
@@ -14,46 +14,57 @@
|
||||
// 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, Mutex};
|
||||
use std::sync::Arc;
|
||||
use hyper;
|
||||
|
||||
use ethcore_rpc::{Metadata, Origin};
|
||||
use jsonrpc_core::Middleware;
|
||||
use jsonrpc_core::reactor::RpcHandler;
|
||||
use jsonrpc_http_server::{Rpc, ServerHandler, PanicHandler, AccessControlAllowOrigin, HttpMetaExtractor};
|
||||
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: RpcHandler<Metadata, T>,
|
||||
cors_domains: Vec<String>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
handler: MetaIoHandler<Metadata, T>,
|
||||
remote: Remote,
|
||||
cors_domains: Vec<AccessControlAllowOrigin>,
|
||||
) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: handler,
|
||||
handler: Arc::new(handler),
|
||||
remote: remote,
|
||||
meta_extractor: Arc::new(MetadataExtractor),
|
||||
panic_handler: panic_handler,
|
||||
cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
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: RpcHandler<Metadata, T>,
|
||||
handler: Arc<MetaIoHandler<Metadata, T>>,
|
||||
remote: Remote,
|
||||
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
allowed_hosts: Option<Vec<http::Host>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NoopMiddleware;
|
||||
impl http::RequestMiddleware for NoopMiddleware {
|
||||
fn on_request(&self, _request: &hyper::server::Request<hyper::net::HttpStream>) -> http::RequestMiddlewareAction {
|
||||
http::RequestMiddlewareAction::Proceed
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
|
||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
||||
Box::new(ServerHandler::new(
|
||||
Rpc::new(self.handler.clone(), self.meta_extractor.clone()),
|
||||
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(),
|
||||
panic_handler,
|
||||
Arc::new(NoopMiddleware),
|
||||
control,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ fn should_return_error() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#));
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -56,8 +56,8 @@ fn should_serve_apps() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
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);
|
||||
}
|
||||
@@ -79,8 +79,8 @@ fn should_handle_ping() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -102,7 +102,7 @@ fn should_try_to_resolve_dapp() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -125,12 +125,8 @@ fn should_return_signer_port_cors_headers() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"),
|
||||
"CORS header for signer missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -151,12 +147,8 @@ fn should_return_signer_port_cors_headers_for_home_parity() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://parity.web3.site"),
|
||||
"CORS header for parity.web3.site missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site");
|
||||
}
|
||||
|
||||
|
||||
@@ -178,12 +170,8 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_https() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: https://parity.web3.site"),
|
||||
"CORS header for parity.web3.site missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "https://parity.web3.site");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -204,12 +192,8 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://parity.web3.site:18180"),
|
||||
"CORS header for parity.web3.site missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,13 +21,12 @@ use std::sync::Arc;
|
||||
use env_logger::LogBuilder;
|
||||
use ethcore_rpc::Metadata;
|
||||
use jsonrpc_core::MetaIoHandler;
|
||||
use jsonrpc_core::reactor::RpcEventLoop;
|
||||
|
||||
use ServerBuilder;
|
||||
use Server;
|
||||
use fetch::Fetch;
|
||||
use devtools::http_client;
|
||||
use parity_reactor::Remote;
|
||||
use parity_reactor::{EventLoop, Remote};
|
||||
|
||||
mod registrar;
|
||||
mod fetch;
|
||||
@@ -48,7 +47,7 @@ fn init_logger() {
|
||||
|
||||
pub struct ServerLoop {
|
||||
pub server: Server,
|
||||
pub event_loop: RpcEventLoop,
|
||||
pub event_loop: EventLoop,
|
||||
}
|
||||
|
||||
impl Deref for ServerLoop {
|
||||
@@ -70,13 +69,12 @@ pub fn init_server<F, B>(process: F, io: MetaIoHandler<Metadata>, remote: Remote
|
||||
|
||||
// TODO [ToDr] When https://github.com/ethcore/jsonrpc/issues/26 is resolved
|
||||
// this additional EventLoop wouldn't be needed, we should be able to re-use remote.
|
||||
let event_loop = RpcEventLoop::spawn();
|
||||
let handler = event_loop.handler(Arc::new(io));
|
||||
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(), handler).unwrap();
|
||||
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io, event_loop.raw_remote()).unwrap();
|
||||
(
|
||||
ServerLoop { server: server, event_loop: event_loop },
|
||||
registrar,
|
||||
@@ -89,12 +87,12 @@ pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
|
||||
let event_loop = RpcEventLoop::spawn();
|
||||
let handler = event_loop.handler(Arc::new(MetaIoHandler::default()));
|
||||
let server = ServerBuilder::new(&dapps_path, registrar, Remote::new(event_loop.remote()))
|
||||
let event_loop = EventLoop::spawn();
|
||||
let io = MetaIoHandler::default();
|
||||
let server = ServerBuilder::new(&dapps_path, registrar, event_loop.remote())
|
||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||
.allowed_hosts(None)
|
||||
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), user, pass, handler).unwrap();
|
||||
.allowed_hosts(None.into())
|
||||
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), user, pass, io, event_loop.raw_remote()).unwrap();
|
||||
ServerLoop {
|
||||
server: server,
|
||||
event_loop: event_loop,
|
||||
@@ -102,26 +100,28 @@ pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop {
|
||||
}
|
||||
|
||||
pub fn serve_with_rpc(io: MetaIoHandler<Metadata>) -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(None), io, Remote::new_sync()).0
|
||||
init_server(|builder| builder.allowed_hosts(None.into()), io, Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
|
||||
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_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
|
||||
let extra_cors = extra_cors.map(|cors| cors.into_iter().map(Into::into).collect());
|
||||
init_server(|builder| builder.allowed_hosts(None.into()).extra_cors_headers(extra_cors.into()), Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
|
||||
init_server(|builder| builder.allowed_hosts(None.into()), Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| {
|
||||
builder
|
||||
.sync_status(Arc::new(|| true))
|
||||
.allowed_hosts(None)
|
||||
.allowed_hosts(None.into())
|
||||
}, Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, reg) = init_server(move |builder| {
|
||||
builder.allowed_hosts(None).fetch(f.clone())
|
||||
builder.allowed_hosts(None.into()).fetch(f.clone())
|
||||
}, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
|
||||
|
||||
(server, fetch, reg)
|
||||
@@ -144,7 +144,7 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
|
||||
let f = fetch.clone();
|
||||
let (server, _) = init_server(move |builder| {
|
||||
builder
|
||||
.allowed_hosts(None)
|
||||
.allowed_hosts(None.into())
|
||||
.fetch(f.clone())
|
||||
.web_proxy_tokens(Arc::new(move |token| &token == web_token))
|
||||
}, Default::default(), Remote::new_sync());
|
||||
@@ -153,7 +153,7 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
|
||||
}
|
||||
|
||||
pub fn serve() -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()).0
|
||||
init_server(|builder| builder.allowed_hosts(None.into()), Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn request(server: ServerLoop, request: &str) -> http_client::Response {
|
||||
|
||||
@@ -32,7 +32,7 @@ fn should_redirect_to_home() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ fn should_redirect_to_home_for_users_with_cached_redirection() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ fn should_display_404_on_invalid_dapp() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ fn should_serve_rpc() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -156,8 +156,8 @@ fn should_serve_rpc_at_slash_rpc() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ fn should_serve_proxy_pac() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
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_security_headers(&response.headers);
|
||||
}
|
||||
@@ -200,7 +200,7 @@ fn should_serve_utils() {
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body.contains("function(){"), true);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user