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:
parent
7499efecf6
commit
cbcc369a2d
@ -564,7 +564,7 @@ test-windows:
|
|||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
script:
|
script:
|
||||||
- set RUST_BACKTRACE=1
|
- set RUST_BACKTRACE=1
|
||||||
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p parity-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
|
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p parity-dapps -p parity-rpc -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity-rpc-client -p parity %CARGOFLAGS% --verbose --release
|
||||||
tags:
|
tags:
|
||||||
- rust-windows
|
- rust-windows
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -609,26 +609,6 @@ dependencies = [
|
|||||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ethcore-signer"
|
|
||||||
version = "1.7.0"
|
|
||||||
dependencies = [
|
|
||||||
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ethcore-devtools 1.7.0",
|
|
||||||
"ethcore-io 1.7.0",
|
|
||||||
"ethcore-util 1.7.0",
|
|
||||||
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
|
|
||||||
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
|
|
||||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"parity-rpc 1.7.0",
|
|
||||||
"parity-ui 1.7.0",
|
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethcore-stratum"
|
name = "ethcore-stratum"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@ -1608,7 +1588,6 @@ dependencies = [
|
|||||||
"ethcore-light 1.7.0",
|
"ethcore-light 1.7.0",
|
||||||
"ethcore-logger 1.7.0",
|
"ethcore-logger 1.7.0",
|
||||||
"ethcore-secretstore 1.0.0",
|
"ethcore-secretstore 1.0.0",
|
||||||
"ethcore-signer 1.7.0",
|
|
||||||
"ethcore-stratum 1.7.0",
|
"ethcore-stratum 1.7.0",
|
||||||
"ethcore-util 1.7.0",
|
"ethcore-util 1.7.0",
|
||||||
"ethkey 0.2.0",
|
"ethkey 0.2.0",
|
||||||
@ -1797,18 +1776,18 @@ dependencies = [
|
|||||||
name = "parity-rpc-client"
|
name = "parity-rpc-client"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethcore-signer 1.7.0",
|
|
||||||
"ethcore-util 1.7.0",
|
"ethcore-util 1.7.0",
|
||||||
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
|
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
|
||||||
|
"jsonrpc-ws-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
|
||||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-rpc 1.7.0",
|
"parity-rpc 1.7.0",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2385,11 +2364,6 @@ name = "siphasher"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "git+https://github.com/carllerche/slab?rev=5476efcafb#5476efcafbc5ef4d7315b1bea3f756d8a1fe975e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -2823,25 +2797,10 @@ name = "winapi-build"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ws"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7#30415c17f1bec53b2dcabae5b8b887df75dcbe34"
|
|
||||||
dependencies = [
|
|
||||||
"bytes 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"mio 0.6.1 (git+https://github.com/paritytech/mio)",
|
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)",
|
|
||||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ws"
|
name = "ws"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/tomusdrw/ws-rs#3259e7ca906c848beae109eb32e492871f8f397d"
|
source = "git+https://github.com/tomusdrw/ws-rs#7f8e416b7f048880228005457e117128be38bf0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -3082,7 +3041,6 @@ dependencies = [
|
|||||||
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
|
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
|
||||||
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
|
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
|
||||||
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"
|
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"
|
||||||
"checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "<none>"
|
|
||||||
"checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4"
|
"checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4"
|
||||||
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
||||||
"checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410"
|
"checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410"
|
||||||
@ -3135,7 +3093,6 @@ dependencies = [
|
|||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
"checksum ws 0.5.3 (git+https://github.com/paritytech/ws-rs.git?branch=parity-1.7)" = "<none>"
|
|
||||||
"checksum ws 0.6.0 (git+https://github.com/tomusdrw/ws-rs)" = "<none>"
|
"checksum ws 0.6.0 (git+https://github.com/tomusdrw/ws-rs)" = "<none>"
|
||||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
"checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453"
|
"checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453"
|
||||||
|
@ -33,7 +33,6 @@ ethcore = { path = "ethcore" }
|
|||||||
ethcore-util = { path = "util" }
|
ethcore-util = { path = "util" }
|
||||||
ethcore-io = { path = "util/io" }
|
ethcore-io = { path = "util/io" }
|
||||||
ethcore-devtools = { path = "devtools" }
|
ethcore-devtools = { path = "devtools" }
|
||||||
ethcore-signer = { path = "signer" }
|
|
||||||
ethcore-ipc = { path = "ipc/rpc" }
|
ethcore-ipc = { path = "ipc/rpc" }
|
||||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||||
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
||||||
@ -75,17 +74,15 @@ default = ["ui-precompiled"]
|
|||||||
ui = [
|
ui = [
|
||||||
"dapps",
|
"dapps",
|
||||||
"parity-dapps/ui",
|
"parity-dapps/ui",
|
||||||
"ethcore-signer/ui",
|
|
||||||
]
|
]
|
||||||
ui-precompiled = [
|
ui-precompiled = [
|
||||||
"dapps",
|
"dapps",
|
||||||
"ethcore-signer/ui-precompiled",
|
|
||||||
"parity-dapps/ui-precompiled",
|
"parity-dapps/ui-precompiled",
|
||||||
]
|
]
|
||||||
dapps = ["parity-dapps"]
|
dapps = ["parity-dapps"]
|
||||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||||
jit = ["ethcore/jit"]
|
jit = ["ethcore/jit"]
|
||||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev", "ethcore-signer/dev"]
|
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev"]
|
||||||
json-tests = ["ethcore/json-tests"]
|
json-tests = ["ethcore/json-tests"]
|
||||||
test-heavy = ["ethcore/test-heavy"]
|
test-heavy = ["ethcore/test-heavy"]
|
||||||
ethkey-cli = ["ethcore/ethkey-cli"]
|
ethkey-cli = ["ethcore/ethkey-cli"]
|
||||||
|
@ -16,42 +16,27 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use unicase::UniCase;
|
|
||||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||||
use hyper::header;
|
|
||||||
use hyper::method::Method;
|
use hyper::method::Method;
|
||||||
|
|
||||||
use api::types::{App, ApiError};
|
use api::types::ApiError;
|
||||||
use api::response;
|
use api::response;
|
||||||
use apps::fetcher::Fetcher;
|
use apps::fetcher::Fetcher;
|
||||||
|
|
||||||
use handlers::extract_url;
|
use handlers::extract_url;
|
||||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||||
use jsonrpc_http_server::{self, AccessControlAllowOrigin};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RestApi {
|
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>,
|
fetcher: Arc<Fetcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RestApi {
|
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 {
|
Box::new(RestApi {
|
||||||
cors_domains: Some(cors_domains),
|
|
||||||
apps: Self::list_apps(endpoints),
|
|
||||||
fetcher: fetcher,
|
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 {
|
impl Endpoint for RestApi {
|
||||||
@ -62,7 +47,6 @@ impl Endpoint for RestApi {
|
|||||||
|
|
||||||
struct RestApiRouter {
|
struct RestApiRouter {
|
||||||
api: RestApi,
|
api: RestApi,
|
||||||
cors_header: Option<header::AccessControlAllowOrigin>,
|
|
||||||
path: Option<EndpointPath>,
|
path: Option<EndpointPath>,
|
||||||
control: Option<Control>,
|
control: Option<Control>,
|
||||||
handler: Box<Handler>,
|
handler: Box<Handler>,
|
||||||
@ -72,7 +56,6 @@ impl RestApiRouter {
|
|||||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||||
RestApiRouter {
|
RestApiRouter {
|
||||||
path: Some(path),
|
path: Some(path),
|
||||||
cors_header: None,
|
|
||||||
control: Some(control),
|
control: Some(control),
|
||||||
api: api,
|
api: api,
|
||||||
handler: response::as_json_error(&ApiError {
|
handler: response::as_json_error(&ApiError {
|
||||||
@ -92,35 +75,10 @@ impl RestApiRouter {
|
|||||||
_ => None
|
_ => 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 {
|
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
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() {
|
if let Method::Options = *request.method() {
|
||||||
self.handler = response::empty();
|
self.handler = response::empty();
|
||||||
return Next::write();
|
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() }
|
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
|
||||||
|
|
||||||
let handler = endpoint.and_then(|v| match v {
|
let handler = endpoint.and_then(|v| match v {
|
||||||
"apps" => Some(response::as_json(&self.api.apps)),
|
|
||||||
"ping" => Some(response::ping()),
|
"ping" => Some(response::ping()),
|
||||||
"content" => self.resolve_content(hash, path, control),
|
"content" => self.resolve_content(hash, path, control),
|
||||||
_ => None
|
_ => None
|
||||||
@ -163,7 +120,6 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
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)
|
self.handler.on_response(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,4 +21,3 @@ mod response;
|
|||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use self::api::RestApi;
|
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)))
|
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> {
|
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
|
||||||
let json = serde_json::to_string(val)
|
let json = serde_json::to_string(val)
|
||||||
.expect("serialization to string is infallible; qed");
|
.expect("serialization to string is infallible; qed");
|
||||||
|
@ -14,46 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// 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)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ApiError {
|
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)>,
|
embeddable_on: Option<(String, u16)>,
|
||||||
remote: Remote,
|
remote: Remote,
|
||||||
fetch: F,
|
fetch: F,
|
||||||
|
only_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
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> {
|
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();
|
let mut dapps_path = env::temp_dir();
|
||||||
dapps_path.push(random_filename());
|
dapps_path.push(random_filename());
|
||||||
|
|
||||||
@ -75,12 +81,23 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
|||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
sync: sync_status,
|
sync: sync_status,
|
||||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||||
embeddable_on: embeddable_on,
|
embeddable_on: None,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
fetch: fetch,
|
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> {
|
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
|
||||||
Box::new(ContentHandler::error(
|
Box::new(ContentHandler::error(
|
||||||
StatusCode::ServiceUnavailable,
|
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)]
|
#[cfg(test)]
|
||||||
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
||||||
self.cache.lock().insert(content_id.to_owned(), status);
|
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() => {
|
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
|
||||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
(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)) => {
|
Some(URLHintResult::Dapp(dapp)) => {
|
||||||
let handler = ContentFetcherHandler::new(
|
let handler = ContentFetcherHandler::new(
|
||||||
dapp.url(),
|
dapp.url(),
|
||||||
@ -254,7 +284,8 @@ mod tests {
|
|||||||
fn should_true_if_contains_the_app() {
|
fn should_true_if_contains_the_app() {
|
||||||
// given
|
// given
|
||||||
let path = env::temp_dir();
|
let path = env::temp_dir();
|
||||||
let fetcher = 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 {
|
let handler = LocalPageEndpoint::new(path, EndpointInfo {
|
||||||
name: "fake".into(),
|
name: "fake".into(),
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use page::{LocalPageEndpoint, PageCache};
|
use page::{LocalPageEndpoint, PageCache};
|
||||||
use endpoint::{Endpoints, EndpointInfo};
|
use endpoint::{Endpoint, EndpointInfo};
|
||||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||||
|
|
||||||
struct LocalDapp {
|
struct LocalDapp {
|
||||||
@ -85,8 +86,8 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
|
|||||||
|
|
||||||
/// Returns endpoints for Local Dapps found for given filesystem path.
|
/// Returns endpoints for Local Dapps found for given filesystem path.
|
||||||
/// Scans the directory and collects `LocalPageEndpoints`.
|
/// Scans the directory and collects `LocalPageEndpoints`.
|
||||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
|
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> BTreeMap<String, Box<Endpoint>> {
|
||||||
let mut pages = Endpoints::new();
|
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
|
||||||
for dapp in local_dapps(dapps_path.as_ref()) {
|
for dapp in local_dapps(dapps_path.as_ref()) {
|
||||||
pages.insert(
|
pages.insert(
|
||||||
dapp.id,
|
dapp.id,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
pub use api::App as Manifest;
|
pub use apps::App as Manifest;
|
||||||
|
|
||||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||||
|
|
||||||
|
@ -14,8 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use endpoint::{Endpoints, Endpoint};
|
use endpoint::{Endpoints, Endpoint};
|
||||||
use page::PageEndpoint;
|
use page::PageEndpoint;
|
||||||
use proxypac::ProxyPac;
|
use proxypac::ProxyPac;
|
||||||
@ -23,17 +25,19 @@ use web::Web;
|
|||||||
use fetch::Fetch;
|
use fetch::Fetch;
|
||||||
use parity_dapps::WebApp;
|
use parity_dapps::WebApp;
|
||||||
use parity_reactor::Remote;
|
use parity_reactor::Remote;
|
||||||
|
use parity_ui;
|
||||||
use {WebProxyTokens};
|
use {WebProxyTokens};
|
||||||
|
|
||||||
|
mod app;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod fs;
|
mod fs;
|
||||||
|
mod ui;
|
||||||
pub mod fetcher;
|
pub mod fetcher;
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
|
|
||||||
extern crate parity_ui;
|
pub use self::app::App;
|
||||||
|
|
||||||
pub const HOME_PAGE: &'static str = "parity";
|
pub const HOME_PAGE: &'static str = "home";
|
||||||
pub const DAPPS_DOMAIN: &'static str = ".web3.site";
|
|
||||||
pub const RPC_PATH: &'static str = "rpc";
|
pub const RPC_PATH: &'static str = "rpc";
|
||||||
pub const API_PATH: &'static str = "api";
|
pub const API_PATH: &'static str = "api";
|
||||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
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()))
|
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>(
|
pub fn all_endpoints<F: Fetch>(
|
||||||
dapps_path: PathBuf,
|
dapps_path: PathBuf,
|
||||||
extra_dapps: Vec<PathBuf>,
|
extra_dapps: Vec<PathBuf>,
|
||||||
signer_address: Option<(String, u16)>,
|
dapps_domain: String,
|
||||||
|
ui_address: Option<(String, u16)>,
|
||||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||||
remote: Remote,
|
remote: Remote,
|
||||||
fetch: F,
|
fetch: F,
|
||||||
) -> Endpoints {
|
) -> Endpoints {
|
||||||
// fetch fs dapps at first to avoid overwriting builtins
|
// 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 {
|
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);
|
pages.insert(id, endpoint);
|
||||||
} else {
|
} else {
|
||||||
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
|
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
|
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
|
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(ui_address.clone()));
|
||||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone()));
|
pages.insert("proxy".into(), ProxyPac::boxed(ui_address.clone(), dapps_domain));
|
||||||
pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
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 {
|
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||||
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
||||||
Embeddable::No => PageEndpoint::new(T::default()),
|
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
|
//! URL Endpoint traits
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use hyper::{self, server, net};
|
use hyper::{self, server, net};
|
||||||
@ -38,7 +39,7 @@ pub struct EndpointInfo {
|
|||||||
pub icon_url: String,
|
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 type Handler = server::Handler<net::HttpStream> + Send;
|
||||||
|
|
||||||
pub trait Endpoint : Send + Sync {
|
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_dapps_glue as parity_dapps;
|
||||||
extern crate parity_hash_fetch as hash_fetch;
|
extern crate parity_hash_fetch as hash_fetch;
|
||||||
extern crate parity_reactor;
|
extern crate parity_reactor;
|
||||||
|
extern crate parity_ui;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@ -70,7 +71,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::collections::HashMap;
|
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 fetch::Fetch;
|
||||||
use parity_reactor::Remote;
|
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()) }
|
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.
|
/// Dapps server as `jsonrpc-http-server` request middleware.
|
||||||
pub struct Middleware {
|
pub struct Middleware {
|
||||||
router: router::Router,
|
router: router::Router,
|
||||||
|
endpoints: endpoint::Endpoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Middleware {
|
impl Middleware {
|
||||||
/// Creates new Dapps server middleware.
|
/// Get local endpoints handle.
|
||||||
pub fn new<F: Fetch + Clone>(
|
pub fn endpoints(&self) -> Endpoints {
|
||||||
|
Endpoints {
|
||||||
|
endpoints: self.endpoints.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new middleware for UI server.
|
||||||
|
pub fn ui<F: Fetch + Clone>(
|
||||||
remote: Remote,
|
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,
|
dapps_path: PathBuf,
|
||||||
extra_dapps: Vec<PathBuf>,
|
extra_dapps: Vec<PathBuf>,
|
||||||
|
dapps_domain: String,
|
||||||
registrar: Arc<ContractClient>,
|
registrar: Arc<ContractClient>,
|
||||||
sync_status: Arc<SyncStatus>,
|
sync_status: Arc<SyncStatus>,
|
||||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||||
@ -117,45 +174,36 @@ impl Middleware {
|
|||||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||||
sync_status,
|
sync_status,
|
||||||
signer_address.clone(),
|
|
||||||
remote.clone(),
|
remote.clone(),
|
||||||
fetch.clone(),
|
fetch.clone(),
|
||||||
));
|
).embeddable_on(ui_address.clone()).allow_dapps(true));
|
||||||
let endpoints = apps::all_endpoints(
|
let endpoints = apps::all_endpoints(
|
||||||
dapps_path,
|
dapps_path,
|
||||||
extra_dapps,
|
extra_dapps,
|
||||||
signer_address.clone(),
|
dapps_domain.clone(),
|
||||||
|
ui_address.clone(),
|
||||||
web_proxy_tokens,
|
web_proxy_tokens,
|
||||||
remote.clone(),
|
remote.clone(),
|
||||||
fetch.clone(),
|
fetch.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let cors_domains = cors_domains(signer_address.clone());
|
|
||||||
|
|
||||||
let special = {
|
let special = {
|
||||||
let mut special = HashMap::new();
|
let mut special = special_endpoints(content_fetcher.clone());
|
||||||
special.insert(router::SpecialEndpoint::Rpc, None);
|
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_redirection(ui_address.clone())));
|
||||||
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
|
|
||||||
special.insert(
|
|
||||||
router::SpecialEndpoint::Api,
|
|
||||||
Some(api::RestApi::new(
|
|
||||||
cors_domains.clone(),
|
|
||||||
&endpoints,
|
|
||||||
content_fetcher.clone()
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
special
|
special
|
||||||
};
|
};
|
||||||
|
|
||||||
let router = router::Router::new(
|
let router = router::Router::new(
|
||||||
signer_address,
|
|
||||||
content_fetcher,
|
content_fetcher,
|
||||||
endpoints,
|
Some(endpoints.clone()),
|
||||||
special,
|
special,
|
||||||
|
ui_address,
|
||||||
|
dapps_domain,
|
||||||
);
|
);
|
||||||
|
|
||||||
Middleware {
|
Middleware {
|
||||||
router: router,
|
router: router,
|
||||||
|
endpoints: endpoints,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,21 +214,12 @@ impl http::RequestMiddleware for Middleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of CORS domains for API endpoint.
|
fn special_endpoints(content_fetcher: Arc<apps::fetcher::Fetcher>) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
|
||||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<AccessControlAllowOrigin> {
|
let mut special = HashMap::new();
|
||||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
special.insert(router::SpecialEndpoint::Rpc, None);
|
||||||
|
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
|
||||||
match signer_address {
|
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(content_fetcher)));
|
||||||
Some(signer_address) => [
|
special
|
||||||
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 address(address: &(String, u16)) -> String {
|
fn address(address: &(String, u16)) -> String {
|
||||||
@ -193,29 +232,3 @@ fn random_filename() -> String {
|
|||||||
let mut rng = ::rand::OsRng::new().unwrap();
|
let mut rng = ::rand::OsRng::new().unwrap();
|
||||||
rng.gen_ascii_chars().take(12).collect()
|
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 be loaded in frame by other origin. (use wisely!)
|
||||||
safe_to_embed_on: Option<(String, u16)>,
|
safe_to_embed_on: Option<(String, u16)>,
|
||||||
info: EndpointInfo,
|
info: EndpointInfo,
|
||||||
|
fallback_to_index_html: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: WebApp + 'static> PageEndpoint<T> {
|
impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||||
@ -38,6 +39,20 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
|||||||
prefix: None,
|
prefix: None,
|
||||||
safe_to_embed_on: None,
|
safe_to_embed_on: None,
|
||||||
info: EndpointInfo::from(info),
|
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),
|
prefix: Some(prefix),
|
||||||
safe_to_embed_on: None,
|
safe_to_embed_on: None,
|
||||||
info: EndpointInfo::from(info),
|
info: EndpointInfo::from(info),
|
||||||
|
fallback_to_index_html: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +80,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
|||||||
prefix: None,
|
prefix: None,
|
||||||
safe_to_embed_on: address,
|
safe_to_embed_on: address,
|
||||||
info: EndpointInfo::from(info),
|
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> {
|
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||||
Box::new(handler::PageHandler {
|
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(),
|
prefix: self.prefix.clone(),
|
||||||
path: path,
|
path: path,
|
||||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||||
@ -100,12 +117,14 @@ impl From<Info> for EndpointInfo {
|
|||||||
|
|
||||||
struct BuiltinDapp<T: WebApp + 'static> {
|
struct BuiltinDapp<T: WebApp + 'static> {
|
||||||
app: Arc<T>,
|
app: Arc<T>,
|
||||||
|
fallback_to_index_html: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
||||||
fn new(app: Arc<T>) -> Self {
|
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
|
||||||
BuiltinDapp {
|
BuiltinDapp {
|
||||||
app: app,
|
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>;
|
type DappFile = BuiltinDappFile<T>;
|
||||||
|
|
||||||
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
||||||
self.app.file(path).map(|_| {
|
let file = |path| self.app.file(path).map(|_| {
|
||||||
BuiltinDappFile {
|
BuiltinDappFile {
|
||||||
app: self.app.clone(),
|
app: self.app.clone(),
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
write_pos: 0,
|
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 endpoint::{Endpoint, Handler, EndpointPath};
|
||||||
use handlers::ContentHandler;
|
use handlers::ContentHandler;
|
||||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
use apps::HOME_PAGE;
|
||||||
use address;
|
use address;
|
||||||
|
|
||||||
pub struct ProxyPac {
|
pub struct ProxyPac {
|
||||||
signer_address: Option<(String, u16)>,
|
signer_address: Option<(String, u16)>,
|
||||||
|
dapps_domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProxyPac {
|
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 {
|
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!(
|
let content = format!(
|
||||||
r#"
|
r#"
|
||||||
function FindProxyForURL(url, host) {{
|
function FindProxyForURL(url, host) {{
|
||||||
if (shExpMatch(host, "{0}{1}"))
|
if (shExpMatch(host, "{0}.{1}"))
|
||||||
{{
|
{{
|
||||||
return "PROXY {4}";
|
return "PROXY {4}";
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if (shExpMatch(host, "*{1}"))
|
if (shExpMatch(host, "*.{1}"))
|
||||||
{{
|
{{
|
||||||
return "PROXY {2}:{3}";
|
return "PROXY {2}:{3}";
|
||||||
}}
|
}}
|
||||||
@ -56,7 +58,7 @@ function FindProxyForURL(url, host) {{
|
|||||||
return "DIRECT";
|
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)))
|
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||||
}
|
}
|
||||||
|
@ -17,20 +17,19 @@
|
|||||||
//! Router implementation
|
//! Router implementation
|
||||||
//! Dispatch requests to proper application.
|
//! Dispatch requests to proper application.
|
||||||
|
|
||||||
use address;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use url::{Url, Host};
|
use url::{Url, Host};
|
||||||
use hyper::{self, server, header, Control, StatusCode};
|
use hyper::{self, server, header, Control};
|
||||||
use hyper::net::HttpStream;
|
use hyper::net::HttpStream;
|
||||||
use jsonrpc_http_server as http;
|
use jsonrpc_http_server as http;
|
||||||
|
|
||||||
use apps::{self, DAPPS_DOMAIN};
|
use apps;
|
||||||
use apps::fetcher::Fetcher;
|
use apps::fetcher::Fetcher;
|
||||||
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
|
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
|
||||||
use handlers::{self, Redirection, ContentHandler};
|
use handlers;
|
||||||
|
|
||||||
/// Special endpoints are accessible on every domain (every dapp)
|
/// Special endpoints are accessible on every domain (every dapp)
|
||||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||||
@ -38,26 +37,28 @@ pub enum SpecialEndpoint {
|
|||||||
Rpc,
|
Rpc,
|
||||||
Api,
|
Api,
|
||||||
Utils,
|
Utils,
|
||||||
|
Home,
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Router {
|
pub struct Router {
|
||||||
signer_address: Option<(String, u16)>,
|
endpoints: Option<Endpoints>,
|
||||||
endpoints: Endpoints,
|
|
||||||
fetch: Arc<Fetcher>,
|
fetch: Arc<Fetcher>,
|
||||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||||
|
embeddable_on: Option<(String, u16)>,
|
||||||
|
dapps_domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl http::RequestMiddleware for Router {
|
impl http::RequestMiddleware for Router {
|
||||||
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
|
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
|
||||||
// Choose proper handler depending on path / domain
|
// Choose proper handler depending on path / domain
|
||||||
let url = handlers::extract_url(req);
|
let url = handlers::extract_url(req);
|
||||||
let endpoint = extract_endpoint(&url);
|
let endpoint = extract_endpoint(&url, &self.dapps_domain);
|
||||||
let referer = extract_referer_endpoint(req);
|
let referer = extract_referer_endpoint(req, &self.dapps_domain);
|
||||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
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::<header::Origin>().is_some();
|
||||||
let is_origin_set = req.headers().get::<http::hyper::header::Origin>().is_some();
|
|
||||||
let is_get_request = *req.method() == hyper::Method::Get;
|
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);
|
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
|
// Handle invalid web requests that we can recover from
|
||||||
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
||||||
if referer.app_id == apps::WEB_PATH
|
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)
|
&& !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 len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
|
||||||
let base = referer_url.path[..len].join("/");
|
let base = referer_url.path[..len].join("/");
|
||||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
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
|
// First check special endpoints
|
||||||
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
|
(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))
|
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
|
||||||
},
|
},
|
||||||
// Then delegate to dapp
|
// 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.");
|
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")
|
.expect("endpoints known to contain key; qed")
|
||||||
.to_async_handler(path.clone(), control))
|
.to_async_handler(path.clone(), control))
|
||||||
},
|
},
|
||||||
@ -97,36 +101,28 @@ impl http::RequestMiddleware for Router {
|
|||||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||||
Some(self.fetch.to_async_handler(path.clone(), control))
|
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
|
// 404 for non-existent content (only if serving endpoints and not homepage)
|
||||||
// (in the past we used 301 instead of 302)
|
(Some(ref path), _, _)
|
||||||
// It should be safe to remove it in (near) future.
|
if (is_get_request || is_head_request)
|
||||||
//
|
&& self.endpoints.is_some()
|
||||||
// 404 for non-existent content
|
&& path.app_id != apps::HOME_PAGE
|
||||||
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
|
=>
|
||||||
|
{
|
||||||
trace!(target: "dapps", "Resolving to 404.");
|
trace!(target: "dapps", "Resolving to 404.");
|
||||||
Some(Box::new(ContentHandler::error(
|
Some(Box::new(handlers::ContentHandler::error(
|
||||||
StatusCode::NotFound,
|
hyper::StatusCode::NotFound,
|
||||||
"404 Not Found",
|
"404 Not Found",
|
||||||
"Requested content was not found.",
|
"Requested content was not found.",
|
||||||
None,
|
None,
|
||||||
self.signer_address.clone(),
|
self.embeddable_on.clone(),
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
// Redirect any other GET request to signer.
|
// Any other GET|HEAD requests to home page.
|
||||||
_ if is_get_request => {
|
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
|
||||||
if let Some(ref signer_address) = self.signer_address {
|
self.special.get(&SpecialEndpoint::Home)
|
||||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
.expect("special known to contain key; qed")
|
||||||
Some(Redirection::boxed(&format!("http://{}", address(signer_address))))
|
.as_ref()
|
||||||
} else {
|
.map(|special| special.to_async_handler(Default::default(), control))
|
||||||
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(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// RPC by default
|
// RPC by default
|
||||||
_ => {
|
_ => {
|
||||||
@ -137,7 +133,7 @@ impl http::RequestMiddleware for Router {
|
|||||||
|
|
||||||
match handler {
|
match handler {
|
||||||
Some(handler) => http::RequestMiddlewareAction::Respond {
|
Some(handler) => http::RequestMiddlewareAction::Respond {
|
||||||
should_validate_hosts: !(is_utils || is_dapps_domain),
|
should_validate_hosts: !is_utils,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
},
|
},
|
||||||
None => http::RequestMiddlewareAction::Proceed {
|
None => http::RequestMiddlewareAction::Proceed {
|
||||||
@ -149,16 +145,18 @@ impl http::RequestMiddleware for Router {
|
|||||||
|
|
||||||
impl Router {
|
impl Router {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
signer_address: Option<(String, u16)>,
|
|
||||||
content_fetcher: Arc<Fetcher>,
|
content_fetcher: Arc<Fetcher>,
|
||||||
endpoints: Endpoints,
|
endpoints: Option<Endpoints>,
|
||||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||||
|
embeddable_on: Option<(String, u16)>,
|
||||||
|
dapps_domain: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Router {
|
Router {
|
||||||
signer_address: signer_address,
|
|
||||||
endpoints: endpoints,
|
endpoints: endpoints,
|
||||||
fetch: content_fetcher,
|
fetch: content_fetcher,
|
||||||
special: special,
|
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 referer = req.headers().get::<header::Referer>();
|
||||||
|
|
||||||
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
||||||
url.and_then(|url| {
|
url.and_then(|url| {
|
||||||
let option = Some(url);
|
let option = Some(url);
|
||||||
extract_url_referer_endpoint(&option).or_else(|| {
|
extract_url_referer_endpoint(&option, dapps_domain).or_else(|| {
|
||||||
extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
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());
|
let query = url.as_ref().and_then(|url| url.query.as_ref());
|
||||||
match (url, query) {
|
match (url, query) {
|
||||||
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
|
(&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);
|
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
|
||||||
|
|
||||||
let referer_url = Url::parse(&referer_url).ok();
|
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())
|
(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 {
|
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||||
if url.path.len() <= 1 {
|
if url.path.len() <= 1 {
|
||||||
return SpecialEndpoint::None;
|
return SpecialEndpoint::None;
|
||||||
@ -208,14 +206,15 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
|
|||||||
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
||||||
apps::API_PATH => SpecialEndpoint::Api,
|
apps::API_PATH => SpecialEndpoint::Api,
|
||||||
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
||||||
|
apps::HOME_PAGE => SpecialEndpoint::Home,
|
||||||
_ => SpecialEndpoint::None,
|
_ => SpecialEndpoint::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match *url {
|
match *url {
|
||||||
Some(ref url) => match url.host {
|
Some(ref url) => match url.host {
|
||||||
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
Host::Domain(ref domain) if domain.ends_with(dapps_domain) => {
|
||||||
let id = &domain[0..(domain.len() - DAPPS_DOMAIN.len())];
|
let id = &domain[0..(domain.len() - dapps_domain.len())];
|
||||||
let (id, params) = if let Some(split) = id.rfind('.') {
|
let (id, params) = if let Some(split) = id.rfind('.') {
|
||||||
let (params, id) = id.split_at(split);
|
let (params, id) = id.split_at(split);
|
||||||
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
|
(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]
|
#[test]
|
||||||
fn should_extract_endpoint() {
|
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
|
// With path prefix
|
||||||
assert_eq!(
|
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 {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
app_params: vec!["index.html".to_owned()],
|
app_params: vec!["index.html".to_owned()],
|
||||||
@ -265,7 +265,7 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// With path prefix
|
// With path prefix
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok(), dapps_domain),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "rpc".to_owned(),
|
app_id: "rpc".to_owned(),
|
||||||
app_params: vec!["".to_owned()],
|
app_params: vec!["".to_owned()],
|
||||||
@ -276,7 +276,7 @@ fn should_extract_endpoint() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
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 {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
|
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
|
||||||
@ -288,7 +288,7 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// By Subdomain
|
// By Subdomain
|
||||||
assert_eq!(
|
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 {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
app_params: vec!["test.html".to_owned()],
|
app_params: vec!["test.html".to_owned()],
|
||||||
@ -300,7 +300,7 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// RPC by subdomain
|
// RPC by subdomain
|
||||||
assert_eq!(
|
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 {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
|
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
|
||||||
@ -312,7 +312,7 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// API by subdomain
|
// API by subdomain
|
||||||
assert_eq!(
|
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 {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
app_params: vec!["my".to_owned(), "api".into(), "".into()],
|
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);
|
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]
|
#[test]
|
||||||
fn should_handle_ping() {
|
fn should_handle_ping() {
|
||||||
// given
|
// given
|
||||||
@ -106,92 +83,3 @@ fn should_try_to_resolve_dapp() {
|
|||||||
assert_eq!(registrar.calls.lock().len(), 2);
|
assert_eq!(registrar.calls.lock().len(), 2);
|
||||||
assert_security_headers(&response.headers);
|
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 devtools::http_client;
|
||||||
use hash_fetch::urlhint::ContractClient;
|
use hash_fetch::urlhint::ContractClient;
|
||||||
use fetch::{Fetch, Client as FetchClient};
|
use fetch::{Fetch, Client as FetchClient};
|
||||||
use parity_reactor::{EventLoop, Remote};
|
use parity_reactor::Remote;
|
||||||
|
|
||||||
use {Middleware, SyncStatus, WebProxyTokens};
|
use {Middleware, SyncStatus, WebProxyTokens};
|
||||||
|
|
||||||
@ -47,20 +47,7 @@ fn init_logger() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ServerLoop {
|
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||||
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
|
|
||||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||||
B: Fetch,
|
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();
|
let mut dapps_path = env::temp_dir();
|
||||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||||
|
|
||||||
// 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(
|
let server = process(ServerBuilder::new(
|
||||||
&dapps_path, registrar.clone(), remote,
|
&dapps_path, registrar.clone(), remote,
|
||||||
))
|
))
|
||||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||||
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
|
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
|
||||||
(
|
(
|
||||||
ServerLoop { server: server, event_loop: event_loop },
|
server,
|
||||||
registrar,
|
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
|
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());
|
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
|
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())
|
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| {
|
init_server(|builder| {
|
||||||
builder.sync_status(Arc::new(|| true))
|
builder.sync_status(Arc::new(|| true))
|
||||||
}, Default::default(), Remote::new_sync())
|
}, 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)
|
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 fetch = FakeFetch::default();
|
||||||
let f = fetch.clone();
|
let f = fetch.clone();
|
||||||
let (server, reg) = init_server(move |builder| {
|
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)
|
(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 fetch = FakeFetch::default();
|
||||||
let f = fetch.clone();
|
let f = fetch.clone();
|
||||||
let (server, _) = init_server(move |builder| {
|
let (server, _) = init_server(move |builder| {
|
||||||
@ -128,11 +112,11 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
|
|||||||
(server, fetch)
|
(server, fetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve() -> ServerLoop {
|
pub fn serve() -> Server {
|
||||||
init_server(|builder| builder, Default::default(), Remote::new_sync()).0
|
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)
|
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.
|
/// Webapps HTTP server.
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
@ -260,19 +245,27 @@ impl Server {
|
|||||||
remote: Remote,
|
remote: Remote,
|
||||||
fetch: F,
|
fetch: F,
|
||||||
) -> Result<Server, http::Error> {
|
) -> Result<Server, http::Error> {
|
||||||
let middleware = Middleware::new(
|
let middleware = Middleware::dapps(
|
||||||
remote,
|
remote,
|
||||||
signer_address,
|
signer_address,
|
||||||
dapps_path,
|
dapps_path,
|
||||||
extra_dapps,
|
extra_dapps,
|
||||||
|
DAPPS_DOMAIN.into(),
|
||||||
registrar,
|
registrar,
|
||||||
sync_status,
|
sync_status,
|
||||||
web_proxy_tokens,
|
web_proxy_tokens,
|
||||||
fetch,
|
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)
|
http::ServerBuilder::new(io)
|
||||||
.request_middleware(middleware)
|
.request_middleware(middleware)
|
||||||
.allowed_hosts(allowed_hosts)
|
.allowed_hosts(allowed_hosts.into())
|
||||||
.cors(http::DomainsValidation::Disabled)
|
.cors(http::DomainsValidation::Disabled)
|
||||||
.start_http(addr)
|
.start_http(addr)
|
||||||
.map(|server| Server {
|
.map(|server| Server {
|
||||||
|
@ -37,15 +37,15 @@ fn should_redirect_to_home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
fn should_redirect_to_home_with_domain() {
|
||||||
// given
|
// given
|
||||||
let server = serve();
|
let server = serve();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET /app HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 127.0.0.1:8080\r\n\
|
Host: home.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -57,14 +57,14 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_redirect_to_home_for_users_with_cached_redirection() {
|
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||||
// given
|
// given
|
||||||
let server = serve();
|
let server = serve();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let response = request(server,
|
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\
|
Host: 127.0.0.1:8080\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
@ -179,7 +179,7 @@ fn should_serve_proxy_pac() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
response.assert_status("HTTP/1.1 200 OK");
|
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);
|
assert_security_headers(&response.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,15 +90,14 @@ export default class Parity {
|
|||||||
.execute('parity_consensusCapability');
|
.execute('parity_consensusCapability');
|
||||||
}
|
}
|
||||||
|
|
||||||
dappsPort () {
|
dappsList () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_dappsPort')
|
.execute('parity_dappsList');
|
||||||
.then(outNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dappsInterface () {
|
dappsUrl () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_dappsInterface');
|
.execute('parity_dappsUrl');
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptMessage (address, data) {
|
decryptMessage (address, data) {
|
||||||
@ -530,12 +529,6 @@ export default class Parity {
|
|||||||
.execute('parity_setVaultMeta', vaultName, JSON.stringify(meta));
|
.execute('parity_setVaultMeta', vaultName, JSON.stringify(meta));
|
||||||
}
|
}
|
||||||
|
|
||||||
signerPort () {
|
|
||||||
return this._transport
|
|
||||||
.execute('parity_signerPort')
|
|
||||||
.then(outNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
signMessage (address, password, messageHash) {
|
signMessage (address, password, messageHash) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_signMessage', inAddress(address), password, inHex(messageHash));
|
.execute('parity_signMessage', inAddress(address), password, inHex(messageHash));
|
||||||
@ -567,4 +560,9 @@ export default class Parity {
|
|||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_versionInfo');
|
.execute('parity_versionInfo');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsUrl () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_wsUrl');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,8 @@ import Web3 from 'web3';
|
|||||||
const api = window.parent.secureApi;
|
const api = window.parent.secureApi;
|
||||||
let web3;
|
let web3;
|
||||||
|
|
||||||
Promise
|
api.parity.dappsUrl().then(url => {
|
||||||
.all([
|
web3 = new Web3(new Web3.providers.HttpProvider(`${window.location.protocol}//${url}/rpc/`));
|
||||||
api.parity.dappsInterface(),
|
|
||||||
api.parity.dappsPort()
|
|
||||||
]).then((res) => {
|
|
||||||
web3 = new Web3(new Web3.providers.HttpProvider(`http://${res.join(':')}/rpc/`));
|
|
||||||
window.web3 = web3;
|
window.web3 = web3;
|
||||||
|
|
||||||
// Usage example:
|
// Usage example:
|
||||||
@ -321,10 +317,10 @@ Promise
|
|||||||
|
|
||||||
web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A'));
|
web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.api = api;
|
window.api = api;
|
||||||
window.web3 = web3;
|
window.web3 = web3;
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { parityNode } from '../../../environment';
|
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
padding: '.5em',
|
padding: '.5em',
|
||||||
border: '1px solid #777'
|
border: '1px solid #777'
|
||||||
@ -34,7 +32,7 @@ export default (address) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={ `${parityNode}/api/content/${address.replace(/^0x/, '')}` }
|
src={ `/api/content/${address.replace(/^0x/, '')}` }
|
||||||
alt={ address }
|
alt={ address }
|
||||||
style={ styles }
|
style={ styles }
|
||||||
/>
|
/>
|
||||||
|
@ -30,7 +30,6 @@ import styles from './token.css';
|
|||||||
import { metaDataKeys } from '../../constants';
|
import { metaDataKeys } from '../../constants';
|
||||||
|
|
||||||
import { api } from '../../parity';
|
import { api } from '../../parity';
|
||||||
import { parityNode } from '../../../../environment';
|
|
||||||
|
|
||||||
export default class Token extends Component {
|
export default class Token extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -312,7 +311,7 @@ export default class Token extends Component {
|
|||||||
</span> meta-data:
|
</span> meta-data:
|
||||||
</p>
|
</p>
|
||||||
<div className={ styles['meta-image'] }>
|
<div className={ styles['meta-image'] }>
|
||||||
<img src={ `${parityNode}/api/content/${imageHash}/` } />
|
<img src={ `/api/content/${imageHash}/` } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -55,6 +55,9 @@ class FakeTransport {
|
|||||||
return Promise.reject('not connected');
|
return Promise.reject('not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMiddleware () {
|
||||||
|
}
|
||||||
|
|
||||||
on () {
|
on () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,4 @@
|
|||||||
|
|
||||||
import './tests';
|
import './tests';
|
||||||
|
|
||||||
const parityNode = (
|
export {};
|
||||||
process.env.PARITY_URL && `http://${process.env.PARITY_URL}`
|
|
||||||
) || (
|
|
||||||
process.env.NODE_ENV === 'production'
|
|
||||||
? 'http://127.0.0.1:8545'
|
|
||||||
: ''
|
|
||||||
);
|
|
||||||
|
|
||||||
export {
|
|
||||||
parityNode
|
|
||||||
};
|
|
||||||
|
@ -53,8 +53,7 @@ if (process.env.NODE_ENV === 'development') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AUTH_HASH = '#/auth?';
|
const AUTH_HASH = '#/auth?';
|
||||||
const parityUrl = process.env.PARITY_URL || window.location.host;
|
const parityUrl = process.env.PARITY_URL || '127.0.0.1:8546';
|
||||||
const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://';
|
|
||||||
|
|
||||||
let token = null;
|
let token = null;
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
|||||||
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
|
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = new SecureApi(`${urlScheme}${parityUrl}`, token);
|
const api = new SecureApi(parityUrl, token);
|
||||||
|
|
||||||
patchApi(api);
|
patchApi(api);
|
||||||
loadSender(api);
|
loadSender(api);
|
||||||
|
@ -143,25 +143,34 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
dappsPort: {
|
dappsList: {
|
||||||
section: SECTION_NODE,
|
subdoc: SUBDOC_SET,
|
||||||
desc: 'Returns the port the dapps are running on, error if not enabled.',
|
desc: 'Returns a list of available local dapps.',
|
||||||
params: [],
|
params: [],
|
||||||
returns: {
|
returns: {
|
||||||
type: Quantity,
|
type: Array,
|
||||||
desc: 'The port number',
|
desc: 'The list of dapps',
|
||||||
example: 8080
|
example: [
|
||||||
|
{
|
||||||
|
author: 'Parity Technologies Ltd',
|
||||||
|
description: 'A skeleton dapp',
|
||||||
|
iconUrl: 'title.png',
|
||||||
|
id: 'skeleton',
|
||||||
|
name: 'Skeleton',
|
||||||
|
version: '0.1'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
dappsInterface: {
|
dappsUrl: {
|
||||||
section: SECTION_NODE,
|
section: SECTION_NODE,
|
||||||
desc: 'Returns the interface the dapps are running on, error if not enabled.',
|
desc: 'Returns the hostname and the port of dapps/rpc server, error if not enabled.',
|
||||||
params: [],
|
params: [],
|
||||||
returns: {
|
returns: {
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'The interface',
|
desc: 'The hostname and port number',
|
||||||
example: '127.0.0.1'
|
example: 'localhost:8545'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -788,17 +797,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
signerPort: {
|
|
||||||
section: SECTION_NODE,
|
|
||||||
desc: 'Returns the port the signer is running on, error if not enabled',
|
|
||||||
params: [],
|
|
||||||
returns: {
|
|
||||||
type: Quantity,
|
|
||||||
desc: 'The port number',
|
|
||||||
example: 8180
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
transactionsLimit: {
|
transactionsLimit: {
|
||||||
section: SECTION_MINING,
|
section: SECTION_MINING,
|
||||||
desc: 'Changes limit for transactions in queue.',
|
desc: 'Changes limit for transactions in queue.',
|
||||||
@ -1916,6 +1914,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
wsUrl: {
|
||||||
|
section: SECTION_NODE,
|
||||||
|
desc: 'Returns the hostname and the port of WebSockets/Signer server, error if not enabled.',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: String,
|
||||||
|
desc: 'The hostname and port number',
|
||||||
|
example: 'localhost:8546'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
composeTransaction: {
|
composeTransaction: {
|
||||||
desc: 'Given partial transaction request produces transaction with all fields filled in. Such transaction can be then signed externally.',
|
desc: 'Given partial transaction request produces transaction with all fields filled in. Such transaction can be then signed externally.',
|
||||||
params: [
|
params: [
|
||||||
@ -1997,4 +2006,5 @@ export default {
|
|||||||
example: 'QmSbFjqjd6nFwNHqsBCC7SK8GShGcayLUEtysJjNGhZAnC'
|
example: 'QmSbFjqjd6nFwNHqsBCC7SK8GShGcayLUEtysJjNGhZAnC'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -27,21 +27,28 @@ export default class SecureApi extends Api {
|
|||||||
_needsToken = false;
|
_needsToken = false;
|
||||||
_tokens = [];
|
_tokens = [];
|
||||||
|
|
||||||
_dappsInterface = null;
|
_dappsUrl = null;
|
||||||
_dappsPort = 8545;
|
_wsUrl = null;
|
||||||
_signerPort = 8180;
|
|
||||||
|
|
||||||
static getTransport (url, sysuiToken) {
|
static getTransport (url, sysuiToken, protocol) {
|
||||||
return new Api.Transport.Ws(url, sysuiToken, false);
|
const proto = protocol() === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
|
||||||
|
return new Api.Transport.Ws(`${proto}//${url}`, sysuiToken, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (url, nextToken, getTransport = SecureApi.getTransport) {
|
// Returns a protocol with `:` at the end.
|
||||||
|
static protocol () {
|
||||||
|
return window.location.protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (url, nextToken, getTransport = SecureApi.getTransport, protocol = SecureApi.protocol) {
|
||||||
const sysuiToken = store.get('sysuiToken');
|
const sysuiToken = store.get('sysuiToken');
|
||||||
const transport = getTransport(url, sysuiToken);
|
const transport = getTransport(url, sysuiToken, protocol);
|
||||||
|
|
||||||
super(transport);
|
super(transport);
|
||||||
|
|
||||||
this._url = url;
|
this._wsUrl = url;
|
||||||
|
this.protocol = protocol;
|
||||||
// Try tokens from localStorage, from hash and 'initial'
|
// Try tokens from localStorage, from hash and 'initial'
|
||||||
this._tokens = uniq([sysuiToken, nextToken, 'initial'])
|
this._tokens = uniq([sysuiToken, nextToken, 'initial'])
|
||||||
.filter((token) => token)
|
.filter((token) => token)
|
||||||
@ -53,12 +60,30 @@ export default class SecureApi extends Api {
|
|||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _dappsAddress () {
|
||||||
|
if (!this._dappsUrl) {
|
||||||
|
return {
|
||||||
|
host: null,
|
||||||
|
port: 8545
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [host, port] = this._dappsUrl.split(':');
|
||||||
|
|
||||||
|
return {
|
||||||
|
host,
|
||||||
|
port: parseInt(port, 10)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get dappsPort () {
|
get dappsPort () {
|
||||||
return this._dappsPort;
|
return this._dappsAddress.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
get dappsUrl () {
|
get dappsUrl () {
|
||||||
return `http://${this.hostname}:${this.dappsPort}`;
|
const { port } = this._dappsAddress;
|
||||||
|
|
||||||
|
return `${this.protocol()}//${this.hostname}:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hostname () {
|
get hostname () {
|
||||||
@ -66,15 +91,13 @@ export default class SecureApi extends Api {
|
|||||||
return 'dapps.parity';
|
return 'dapps.parity';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
|
const { host } = this._dappsAddress;
|
||||||
|
|
||||||
|
if (!host || host === '0.0.0.0') {
|
||||||
return window.location.hostname;
|
return window.location.hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._dappsInterface;
|
return host;
|
||||||
}
|
|
||||||
|
|
||||||
get signerPort () {
|
|
||||||
return this._signerPort;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isConnecting () {
|
get isConnecting () {
|
||||||
@ -98,18 +121,18 @@ export default class SecureApi extends Api {
|
|||||||
* (`signerPort`, `dappsInterface`, `dappsPort`, ...)
|
* (`signerPort`, `dappsInterface`, `dappsPort`, ...)
|
||||||
*/
|
*/
|
||||||
configure (configuration) {
|
configure (configuration) {
|
||||||
const { dappsInterface, dappsPort, signerPort } = configuration;
|
const { dappsInterface, dappsPort, signerPort, wsPort } = configuration;
|
||||||
|
|
||||||
if (dappsInterface) {
|
if (dappsInterface) {
|
||||||
this._dappsInterface = dappsInterface;
|
this._dappsUrl = `${dappsInterface}:${this._dappsAddress.port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dappsPort) {
|
if (dappsPort) {
|
||||||
this._dappsPort = dappsPort;
|
this._dappsUrl = `${this.hostname}:${dappsPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signerPort) {
|
if (signerPort || wsPort) {
|
||||||
this._signerPort = signerPort;
|
this._wsUrl = `${this.hostname}:${signerPort || wsPort}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,9 +189,7 @@ export default class SecureApi extends Api {
|
|||||||
* otherwise (HEAD request to the Node)
|
* otherwise (HEAD request to the Node)
|
||||||
*/
|
*/
|
||||||
isNodeUp () {
|
isNodeUp () {
|
||||||
const url = this._url.replace(/wss?/, 'http');
|
return fetch(`${this.protocol()}//${this._wsUrl}`, { method: 'HEAD', mode: 'no-cors' })
|
||||||
|
|
||||||
return fetch(url, { method: 'HEAD' })
|
|
||||||
.then(
|
.then(
|
||||||
(r) => r.status === 200,
|
(r) => r.status === 200,
|
||||||
() => false
|
() => false
|
||||||
@ -297,14 +318,12 @@ export default class SecureApi extends Api {
|
|||||||
_fetchSettings () {
|
_fetchSettings () {
|
||||||
return Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
this.parity.dappsPort(),
|
this.parity.dappsUrl(),
|
||||||
this.parity.dappsInterface(),
|
this.parity.wsUrl()
|
||||||
this.parity.signerPort()
|
|
||||||
])
|
])
|
||||||
.then(([dappsPort, dappsInterface, signerPort]) => {
|
.then(([dappsUrl, wsUrl]) => {
|
||||||
this._dappsPort = dappsPort.toNumber();
|
this._dappsUrl = dappsUrl;
|
||||||
this._dappsInterface = dappsInterface;
|
this._wsUrl = dappsUrl;
|
||||||
this._signerPort = signerPort.toNumber();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,21 +25,6 @@ import builtinJson from '~/views/Dapps/builtin.json';
|
|||||||
|
|
||||||
const builtinApps = builtinJson.filter((app) => app.id);
|
const builtinApps = builtinJson.filter((app) => app.id);
|
||||||
|
|
||||||
function getHost (api) {
|
|
||||||
const host = process.env.DAPPS_URL ||
|
|
||||||
(
|
|
||||||
process.env.NODE_ENV === 'production'
|
|
||||||
? api.dappsUrl
|
|
||||||
: ''
|
|
||||||
);
|
|
||||||
|
|
||||||
if (host === '/') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function subscribeToChanges (api, dappReg, callback) {
|
export function subscribeToChanges (api, dappReg, callback) {
|
||||||
return dappReg
|
return dappReg
|
||||||
.getContract()
|
.getContract()
|
||||||
@ -105,12 +90,7 @@ export function fetchBuiltinApps () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchLocalApps (api) {
|
export function fetchLocalApps (api) {
|
||||||
return fetch(`${getHost(api)}/api/apps`)
|
return api.parity.dappsList()
|
||||||
.then((response) => {
|
|
||||||
return response.ok
|
|
||||||
? response.json()
|
|
||||||
: [];
|
|
||||||
})
|
|
||||||
.then((apps) => {
|
.then((apps) => {
|
||||||
return apps
|
return apps
|
||||||
.map((app) => {
|
.map((app) => {
|
||||||
@ -195,7 +175,7 @@ export function fetchManifest (api, manifestHash) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return fetch(
|
return fetch(
|
||||||
`${getHost(api)}/api/content/${manifestHash}/`,
|
`/api/content/${manifestHash}/`,
|
||||||
{ redirect: 'follow', mode: 'cors' }
|
{ redirect: 'follow', mode: 'cors' }
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -26,17 +26,11 @@ const APPID_DAPPREG = '0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5
|
|||||||
const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75';
|
const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75';
|
||||||
const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d';
|
const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d';
|
||||||
const APPID_TOKENDEPLOY = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f';
|
const APPID_TOKENDEPLOY = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f';
|
||||||
const FETCH_OK = {
|
|
||||||
ok: true,
|
|
||||||
status: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
let globalContractsGet;
|
let globalContractsGet;
|
||||||
let globalFetch;
|
|
||||||
|
|
||||||
function stubGlobals () {
|
function stubGlobals () {
|
||||||
globalContractsGet = Contracts.get;
|
globalContractsGet = Contracts.get;
|
||||||
globalFetch = global.fetch;
|
|
||||||
|
|
||||||
Contracts.get = () => {
|
Contracts.get = () => {
|
||||||
return {
|
return {
|
||||||
@ -50,31 +44,21 @@ function stubGlobals () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
global.fetch = (url) => {
|
|
||||||
switch (url) {
|
|
||||||
case '/api/apps':
|
|
||||||
return Promise.resolve(Object.assign({}, FETCH_OK, {
|
|
||||||
json: sinon.stub().resolves([]) // TODO: Local stubs in here
|
|
||||||
}));
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log('Unknown fetch stub endpoint', url);
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreGlobals () {
|
function restoreGlobals () {
|
||||||
Contracts.get = globalContractsGet;
|
Contracts.get = globalContractsGet;
|
||||||
global.fetch = globalFetch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let api;
|
let api;
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
function create () {
|
function create () {
|
||||||
api = {};
|
api = {
|
||||||
|
parity: {
|
||||||
|
dappsList: () => Promise.resolve([])
|
||||||
|
}
|
||||||
|
};
|
||||||
store = new Store(api);
|
store = new Store(api);
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
|
@ -34,8 +34,8 @@ let store;
|
|||||||
|
|
||||||
function createApi () {
|
function createApi () {
|
||||||
api = {
|
api = {
|
||||||
dappsPort: 8080,
|
dappsPort: 8545,
|
||||||
dappsUrl: 'http://home.web3.site:8080',
|
dappsUrl: 'http://home.web3.site:8545',
|
||||||
parity: {
|
parity: {
|
||||||
listRecentDapps: sinon.stub().resolves(TEST_HISTORY)
|
listRecentDapps: sinon.stub().resolves(TEST_HISTORY)
|
||||||
},
|
},
|
||||||
@ -159,7 +159,7 @@ describe('views/Web/Store', () => {
|
|||||||
it('encodes current', () => {
|
it('encodes current', () => {
|
||||||
store.setCurrentUrl(TEST_URL1);
|
store.setCurrentUrl(TEST_URL1);
|
||||||
expect(store.encodedPath).to.match(
|
expect(store.encodedPath).to.match(
|
||||||
/http:\/\/home\.web3\.site:8080\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/
|
/http:\/\/home\.web3\.site:8545\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -167,7 +167,7 @@ describe('views/Web/Store', () => {
|
|||||||
it('encodes current', () => {
|
it('encodes current', () => {
|
||||||
store.setCurrentUrl(TEST_URL1);
|
store.setCurrentUrl(TEST_URL1);
|
||||||
expect(store.encodedUrl).to.match(
|
expect(store.encodedUrl).to.match(
|
||||||
/^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8080\?t=[0-9]*$/
|
/^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8545\?t=[0-9]*$/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,26 +15,21 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// test only
|
// test only
|
||||||
/**
|
/**
|
||||||
* Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8180" NODE_ENV="production" npm run build`
|
* Run `DAPPS_URL="/" PARITY_URL="127.0.0.1:8546" NODE_ENV="production" npm run build`
|
||||||
* to build the project ; use this server to test that the minifed
|
* to build the project ; use this server to test that the minifed
|
||||||
* version is working (this is a simple proxy server)
|
* version is working (this is a simple proxy server)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var proxy = require('http-proxy-middleware');
|
|
||||||
|
|
||||||
var Shared = require('./shared');
|
var Shared = require('./shared');
|
||||||
|
|
||||||
var app = express();
|
var app = express();
|
||||||
var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true });
|
|
||||||
|
|
||||||
Shared.addProxies(app);
|
Shared.addProxies(app);
|
||||||
|
|
||||||
app.use(express.static('.build'));
|
app.use(express.static('.build'));
|
||||||
app.use(wsProxy);
|
|
||||||
|
|
||||||
var server = app.listen(process.env.PORT || 3000, function () {
|
var server = app.listen(process.env.PORT || 3000, function () {
|
||||||
console.log('Listening on port', server.address().port);
|
console.log('Listening on port', server.address().port);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('upgrade', wsProxy.upgrade);
|
|
||||||
|
@ -22,7 +22,6 @@ const webpackHotMiddleware = require('webpack-hot-middleware');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const ProgressBar = require('progress');
|
const ProgressBar = require('progress');
|
||||||
const proxy = require('http-proxy-middleware');
|
|
||||||
|
|
||||||
const webpackConfig = require('./app');
|
const webpackConfig = require('./app');
|
||||||
const Shared = require('./shared');
|
const Shared = require('./shared');
|
||||||
@ -84,18 +83,13 @@ app.use(webpackDevMiddleware(compiler, {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true });
|
|
||||||
|
|
||||||
// Add the dev proxies in the express App
|
// Add the dev proxies in the express App
|
||||||
Shared.addProxies(app);
|
Shared.addProxies(app);
|
||||||
|
|
||||||
app.use(express.static(webpackConfig.output.path));
|
app.use(express.static(webpackConfig.output.path));
|
||||||
app.use(wsProxy);
|
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
server.listen(process.env.PORT || 3000, function () {
|
server.listen(process.env.PORT || 3000, function () {
|
||||||
console.log('Listening on port', server.address().port);
|
console.log('Listening on port', server.address().port);
|
||||||
progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 });
|
progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 });
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('upgrade', wsProxy.upgrade);
|
|
||||||
|
@ -162,16 +162,8 @@ function getDappsEntry () {
|
|||||||
function addProxies (app) {
|
function addProxies (app) {
|
||||||
const proxy = require('http-proxy-middleware');
|
const proxy = require('http-proxy-middleware');
|
||||||
|
|
||||||
app.use(proxy((pathname, req) => {
|
|
||||||
return pathname === '/' && req.method === 'HEAD';
|
|
||||||
}, {
|
|
||||||
target: 'http://127.0.0.1:8180',
|
|
||||||
changeOrigin: true,
|
|
||||||
autoRewrite: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.use('/api', proxy({
|
app.use('/api', proxy({
|
||||||
target: 'http://127.0.0.1:8545',
|
target: 'http://127.0.0.1:8180',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
autoRewrite: true
|
autoRewrite: true
|
||||||
}));
|
}));
|
||||||
|
@ -124,6 +124,8 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.ui).port.clone(),
|
or |c: &Config| otry!(c.ui).port.clone(),
|
||||||
flag_ui_interface: String = "local",
|
flag_ui_interface: String = "local",
|
||||||
or |c: &Config| otry!(c.ui).interface.clone(),
|
or |c: &Config| otry!(c.ui).interface.clone(),
|
||||||
|
flag_ui_hosts: String = "none",
|
||||||
|
or |c: &Config| otry!(c.ui).hosts.as_ref().map(|vec| vec.join(",")),
|
||||||
flag_ui_path: String = "$BASE/signer",
|
flag_ui_path: String = "$BASE/signer",
|
||||||
or |c: &Config| otry!(c.ui).path.clone(),
|
or |c: &Config| otry!(c.ui).path.clone(),
|
||||||
// NOTE [todr] For security reasons don't put this to config files
|
// NOTE [todr] For security reasons don't put this to config files
|
||||||
@ -188,7 +190,7 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.websockets).interface.clone(),
|
or |c: &Config| otry!(c.websockets).interface.clone(),
|
||||||
flag_ws_apis: String = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore",
|
flag_ws_apis: String = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore",
|
||||||
or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")),
|
or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")),
|
||||||
flag_ws_origins: String = "none",
|
flag_ws_origins: String = "chrome-extension://*",
|
||||||
or |c: &Config| otry!(c.websockets).origins.as_ref().map(|vec| vec.join(",")),
|
or |c: &Config| otry!(c.websockets).origins.as_ref().map(|vec| vec.join(",")),
|
||||||
flag_ws_hosts: String = "none",
|
flag_ws_hosts: String = "none",
|
||||||
or |c: &Config| otry!(c.websockets).hosts.as_ref().map(|vec| vec.join(",")),
|
or |c: &Config| otry!(c.websockets).hosts.as_ref().map(|vec| vec.join(",")),
|
||||||
@ -430,6 +432,7 @@ struct Ui {
|
|||||||
disable: Option<bool>,
|
disable: Option<bool>,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
interface: Option<String>,
|
interface: Option<String>,
|
||||||
|
hosts: Option<Vec<String>>,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,6 +712,7 @@ mod tests {
|
|||||||
flag_no_ui: false,
|
flag_no_ui: false,
|
||||||
flag_ui_port: 8180u16,
|
flag_ui_port: 8180u16,
|
||||||
flag_ui_interface: "127.0.0.1".into(),
|
flag_ui_interface: "127.0.0.1".into(),
|
||||||
|
flag_ui_hosts: "none".into(),
|
||||||
flag_ui_path: "$HOME/.parity/signer".into(),
|
flag_ui_path: "$HOME/.parity/signer".into(),
|
||||||
flag_ui_no_validation: false,
|
flag_ui_no_validation: false,
|
||||||
|
|
||||||
@ -929,6 +933,7 @@ mod tests {
|
|||||||
disable: Some(true),
|
disable: Some(true),
|
||||||
port: None,
|
port: None,
|
||||||
interface: None,
|
interface: None,
|
||||||
|
hosts: None,
|
||||||
path: None,
|
path: None,
|
||||||
}),
|
}),
|
||||||
network: Some(Network {
|
network: Some(Network {
|
||||||
|
@ -110,6 +110,11 @@ UI Options:
|
|||||||
--ui-interface IP Specify the hostname portion of the Trusted UI
|
--ui-interface IP Specify the hostname portion of the Trusted UI
|
||||||
server, IP should be an interface's IP address,
|
server, IP should be an interface's IP address,
|
||||||
or local (default: {flag_ui_interface}).
|
or local (default: {flag_ui_interface}).
|
||||||
|
--ui-hosts HOSTS List of allowed Host header values. This option will
|
||||||
|
validate the Host header sent by the browser, it
|
||||||
|
is additional security against some attack
|
||||||
|
vectors. Special options: "all", "none",
|
||||||
|
(default: {flag_ui_hosts}).
|
||||||
--ui-path PATH Specify directory where Trusted UIs tokens should
|
--ui-path PATH Specify directory where Trusted UIs tokens should
|
||||||
be stored. (default: {flag_ui_path})
|
be stored. (default: {flag_ui_path})
|
||||||
--ui-no-validation Disable Origin and Host headers validation for
|
--ui-no-validation Disable Origin and Host headers validation for
|
||||||
|
@ -30,7 +30,7 @@ use ethcore::client::{VMType};
|
|||||||
use ethcore::miner::{MinerOptions, Banning, StratumOptions};
|
use ethcore::miner::{MinerOptions, Banning, StratumOptions};
|
||||||
use ethcore::verification::queue::VerifierSettings;
|
use ethcore::verification::queue::VerifierSettings;
|
||||||
|
|
||||||
use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration};
|
use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration, UiConfiguration};
|
||||||
use rpc_apis::ApiSet;
|
use rpc_apis::ApiSet;
|
||||||
use parity_rpc::NetworkSettings;
|
use parity_rpc::NetworkSettings;
|
||||||
use cache::CacheConfig;
|
use cache::CacheConfig;
|
||||||
@ -41,7 +41,6 @@ use ethcore_logger::Config as LogConfig;
|
|||||||
use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path};
|
use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path};
|
||||||
use dapps::Configuration as DappsConfiguration;
|
use dapps::Configuration as DappsConfiguration;
|
||||||
use ipfs::Configuration as IpfsConfiguration;
|
use ipfs::Configuration as IpfsConfiguration;
|
||||||
use signer::{Configuration as SignerConfiguration};
|
|
||||||
use secretstore::Configuration as SecretStoreConfiguration;
|
use secretstore::Configuration as SecretStoreConfiguration;
|
||||||
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
|
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
|
||||||
use run::RunCmd;
|
use run::RunCmd;
|
||||||
@ -50,8 +49,6 @@ use presale::ImportWallet;
|
|||||||
use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts};
|
use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts};
|
||||||
use snapshot::{self, SnapshotCommand};
|
use snapshot::{self, SnapshotCommand};
|
||||||
|
|
||||||
const AUTHCODE_FILENAME: &'static str = "authcodes";
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
Run(RunCmd),
|
Run(RunCmd),
|
||||||
@ -59,7 +56,7 @@ pub enum Cmd {
|
|||||||
Account(AccountCmd),
|
Account(AccountCmd),
|
||||||
ImportPresaleWallet(ImportWallet),
|
ImportPresaleWallet(ImportWallet),
|
||||||
Blockchain(BlockchainCmd),
|
Blockchain(BlockchainCmd),
|
||||||
SignerToken(SignerConfiguration),
|
SignerToken(WsConfiguration, UiConfiguration),
|
||||||
SignerSign {
|
SignerSign {
|
||||||
id: Option<usize>,
|
id: Option<usize>,
|
||||||
pwfile: Option<PathBuf>,
|
pwfile: Option<PathBuf>,
|
||||||
@ -118,6 +115,7 @@ impl Configuration {
|
|||||||
let http_conf = self.http_config()?;
|
let http_conf = self.http_config()?;
|
||||||
let ipc_conf = self.ipc_config()?;
|
let ipc_conf = self.ipc_config()?;
|
||||||
let net_conf = self.net_config()?;
|
let net_conf = self.net_config()?;
|
||||||
|
let ui_conf = self.ui_config();
|
||||||
let network_id = self.network_id();
|
let network_id = self.network_id();
|
||||||
let cache_config = self.cache_config();
|
let cache_config = self.cache_config();
|
||||||
let tracing = self.args.flag_tracing.parse()?;
|
let tracing = self.args.flag_tracing.parse()?;
|
||||||
@ -134,10 +132,8 @@ impl Configuration {
|
|||||||
let public_node = self.args.flag_public_node;
|
let public_node = self.args.flag_public_node;
|
||||||
let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive);
|
let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive);
|
||||||
let geth_compatibility = self.args.flag_geth;
|
let geth_compatibility = self.args.flag_geth;
|
||||||
let ui_address = self.ui_port().map(|port| (self.ui_interface(), port));
|
|
||||||
let mut dapps_conf = self.dapps_config();
|
let mut dapps_conf = self.dapps_config();
|
||||||
let ipfs_conf = self.ipfs_config();
|
let ipfs_conf = self.ipfs_config();
|
||||||
let signer_conf = self.signer_config();
|
|
||||||
let secretstore_conf = self.secretstore_config()?;
|
let secretstore_conf = self.secretstore_config()?;
|
||||||
let format = self.format()?;
|
let format = self.format()?;
|
||||||
|
|
||||||
@ -149,11 +145,10 @@ impl Configuration {
|
|||||||
let cmd = if self.args.flag_version {
|
let cmd = if self.args.flag_version {
|
||||||
Cmd::Version
|
Cmd::Version
|
||||||
} else if self.args.cmd_signer {
|
} else if self.args.cmd_signer {
|
||||||
let mut authfile = PathBuf::from(signer_conf.signer_path.clone());
|
let authfile = ::signer::codes_path(&ws_conf.signer_path);
|
||||||
authfile.push(AUTHCODE_FILENAME);
|
|
||||||
|
|
||||||
if self.args.cmd_new_token {
|
if self.args.cmd_new_token {
|
||||||
Cmd::SignerToken(signer_conf)
|
Cmd::SignerToken(ws_conf, ui_conf)
|
||||||
} else if self.args.cmd_sign {
|
} else if self.args.cmd_sign {
|
||||||
let pwfile = self.args.flag_password.get(0).map(|pwfile| {
|
let pwfile = self.args.flag_password.get(0).map(|pwfile| {
|
||||||
PathBuf::from(pwfile)
|
PathBuf::from(pwfile)
|
||||||
@ -161,18 +156,18 @@ impl Configuration {
|
|||||||
Cmd::SignerSign {
|
Cmd::SignerSign {
|
||||||
id: self.args.arg_id,
|
id: self.args.arg_id,
|
||||||
pwfile: pwfile,
|
pwfile: pwfile,
|
||||||
port: signer_conf.port,
|
port: ws_conf.port,
|
||||||
authfile: authfile,
|
authfile: authfile,
|
||||||
}
|
}
|
||||||
} else if self.args.cmd_reject {
|
} else if self.args.cmd_reject {
|
||||||
Cmd::SignerReject {
|
Cmd::SignerReject {
|
||||||
id: self.args.arg_id,
|
id: self.args.arg_id,
|
||||||
port: signer_conf.port,
|
port: ws_conf.port,
|
||||||
authfile: authfile,
|
authfile: authfile,
|
||||||
}
|
}
|
||||||
} else if self.args.cmd_list {
|
} else if self.args.cmd_list {
|
||||||
Cmd::SignerList {
|
Cmd::SignerList {
|
||||||
port: signer_conf.port,
|
port: ws_conf.port,
|
||||||
authfile: authfile,
|
authfile: authfile,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -372,11 +367,10 @@ impl Configuration {
|
|||||||
warp_sync: warp_sync,
|
warp_sync: warp_sync,
|
||||||
public_node: public_node,
|
public_node: public_node,
|
||||||
geth_compatibility: geth_compatibility,
|
geth_compatibility: geth_compatibility,
|
||||||
ui_address: ui_address,
|
|
||||||
net_settings: self.network_settings()?,
|
net_settings: self.network_settings()?,
|
||||||
dapps_conf: dapps_conf,
|
dapps_conf: dapps_conf,
|
||||||
ipfs_conf: ipfs_conf,
|
ipfs_conf: ipfs_conf,
|
||||||
signer_conf: signer_conf,
|
ui_conf: ui_conf,
|
||||||
secretstore_conf: secretstore_conf,
|
secretstore_conf: secretstore_conf,
|
||||||
dapp: self.dapp_to_open()?,
|
dapp: self.dapp_to_open()?,
|
||||||
ui: self.args.cmd_ui,
|
ui: self.args.cmd_ui,
|
||||||
@ -553,13 +547,12 @@ impl Configuration {
|
|||||||
Ok(options)
|
Ok(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signer_config(&self) -> SignerConfiguration {
|
fn ui_config(&self) -> UiConfiguration {
|
||||||
SignerConfiguration {
|
UiConfiguration {
|
||||||
enabled: self.ui_enabled(),
|
enabled: self.ui_enabled(),
|
||||||
port: self.args.flag_ports_shift + self.args.flag_ui_port,
|
|
||||||
interface: self.ui_interface(),
|
interface: self.ui_interface(),
|
||||||
signer_path: self.directories().signer,
|
port: self.args.flag_ports_shift + self.args.flag_ui_port,
|
||||||
skip_origin_validation: self.args.flag_unsafe_expose || self.args.flag_ui_no_validation,
|
hosts: self.ui_hosts(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,6 +761,14 @@ impl Configuration {
|
|||||||
Some(hosts)
|
Some(hosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ui_hosts(&self) -> Option<Vec<String>> {
|
||||||
|
if self.args.flag_ui_no_validation {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hosts(&self.args.flag_ui_hosts, &self.ui_interface())
|
||||||
|
}
|
||||||
|
|
||||||
fn rpc_hosts(&self) -> Option<Vec<String>> {
|
fn rpc_hosts(&self) -> Option<Vec<String>> {
|
||||||
self.hosts(&self.args.flag_jsonrpc_hosts, &self.rpc_interface())
|
self.hosts(&self.args.flag_jsonrpc_hosts, &self.rpc_interface())
|
||||||
}
|
}
|
||||||
@ -825,13 +826,17 @@ impl Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ws_config(&self) -> Result<WsConfiguration, String> {
|
fn ws_config(&self) -> Result<WsConfiguration, String> {
|
||||||
|
let ui = self.ui_config();
|
||||||
|
|
||||||
let conf = WsConfiguration {
|
let conf = WsConfiguration {
|
||||||
enabled: self.ws_enabled(),
|
enabled: self.ws_enabled(),
|
||||||
interface: self.ws_interface(),
|
interface: self.ws_interface(),
|
||||||
port: self.args.flag_ports_shift + self.args.flag_ws_port,
|
port: self.args.flag_ports_shift + self.args.flag_ws_port,
|
||||||
apis: self.args.flag_ws_apis.parse()?,
|
apis: self.args.flag_ws_apis.parse()?,
|
||||||
hosts: self.ws_hosts(),
|
hosts: self.ws_hosts(),
|
||||||
origins: self.ws_origins()
|
origins: self.ws_origins(),
|
||||||
|
signer_path: self.directories().signer.into(),
|
||||||
|
ui_address: ui.address(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(conf)
|
Ok(conf)
|
||||||
@ -928,18 +933,6 @@ impl Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_port(&self) -> Option<u16> {
|
|
||||||
if !self.ui_enabled() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.args.flag_ui_port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui_interface(&self) -> String {
|
|
||||||
self.interface(&self.args.flag_ui_interface)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interface(&self, interface: &str) -> String {
|
fn interface(&self, interface: &str) -> String {
|
||||||
if self.args.flag_unsafe_expose {
|
if self.args.flag_unsafe_expose {
|
||||||
return "0.0.0.0".into();
|
return "0.0.0.0".into();
|
||||||
@ -952,6 +945,11 @@ impl Configuration {
|
|||||||
}.into()
|
}.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn ui_interface(&self) -> String {
|
||||||
|
self.interface(&self.args.flag_ui_interface)
|
||||||
|
}
|
||||||
|
|
||||||
fn rpc_interface(&self) -> String {
|
fn rpc_interface(&self) -> String {
|
||||||
let rpc_interface = self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone());
|
let rpc_interface = self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone());
|
||||||
self.interface(&rpc_interface)
|
self.interface(&rpc_interface)
|
||||||
@ -1050,24 +1048,27 @@ impl Configuration {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use cli::Args;
|
|
||||||
use parity_rpc::NetworkSettings;
|
|
||||||
use ethcore::client::{VMType, BlockId};
|
|
||||||
use ethcore::miner::{MinerOptions, PrioritizationStrategy};
|
|
||||||
use helpers::{default_network_config};
|
|
||||||
use run::RunCmd;
|
|
||||||
use dir::{Directories, default_hypervisor_path};
|
|
||||||
use signer::{Configuration as SignerConfiguration};
|
|
||||||
use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState};
|
|
||||||
use presale::ImportWallet;
|
|
||||||
use params::SpecType;
|
|
||||||
use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts};
|
|
||||||
use devtools::{RandomTempPath};
|
|
||||||
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::fs::{File, create_dir};
|
use std::fs::{File, create_dir};
|
||||||
|
|
||||||
|
use devtools::{RandomTempPath};
|
||||||
|
use ethcore::client::{VMType, BlockId};
|
||||||
|
use ethcore::miner::{MinerOptions, PrioritizationStrategy};
|
||||||
|
use parity_rpc::NetworkSettings;
|
||||||
|
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
|
||||||
|
|
||||||
|
use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts};
|
||||||
|
use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState};
|
||||||
|
use cli::Args;
|
||||||
|
use dir::{Directories, default_hypervisor_path};
|
||||||
|
use helpers::{default_network_config};
|
||||||
|
use params::SpecType;
|
||||||
|
use presale::ImportWallet;
|
||||||
|
use rpc::{WsConfiguration, UiConfiguration};
|
||||||
|
use run::RunCmd;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct TestPasswordReader(&'static str);
|
struct TestPasswordReader(&'static str);
|
||||||
|
|
||||||
@ -1233,12 +1234,20 @@ mod tests {
|
|||||||
let args = vec!["parity", "signer", "new-token"];
|
let args = vec!["parity", "signer", "new-token"];
|
||||||
let conf = parse(&args);
|
let conf = parse(&args);
|
||||||
let expected = Directories::default().signer;
|
let expected = Directories::default().signer;
|
||||||
assert_eq!(conf.into_command().unwrap().cmd, Cmd::SignerToken(SignerConfiguration {
|
assert_eq!(conf.into_command().unwrap().cmd, Cmd::SignerToken(WsConfiguration {
|
||||||
|
enabled: true,
|
||||||
|
interface: "127.0.0.1".into(),
|
||||||
|
port: 8546,
|
||||||
|
apis: ApiSet::UnsafeContext,
|
||||||
|
origins: Some(vec!["chrome-extension://*".into()]),
|
||||||
|
hosts: Some(vec![]),
|
||||||
|
signer_path: expected.into(),
|
||||||
|
ui_address: Some(("127.0.0.1".to_owned(), 8180)),
|
||||||
|
}, UiConfiguration {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
signer_path: expected,
|
|
||||||
interface: "127.0.0.1".into(),
|
interface: "127.0.0.1".into(),
|
||||||
port: 8180,
|
port: 8180,
|
||||||
skip_origin_validation: false,
|
hosts: Some(vec![]),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1273,11 +1282,10 @@ mod tests {
|
|||||||
wal: true,
|
wal: true,
|
||||||
vm_type: Default::default(),
|
vm_type: Default::default(),
|
||||||
geth_compatibility: false,
|
geth_compatibility: false,
|
||||||
ui_address: Some(("127.0.0.1".into(), 8180)),
|
|
||||||
net_settings: Default::default(),
|
net_settings: Default::default(),
|
||||||
dapps_conf: Default::default(),
|
dapps_conf: Default::default(),
|
||||||
ipfs_conf: Default::default(),
|
ipfs_conf: Default::default(),
|
||||||
signer_conf: Default::default(),
|
ui_conf: Default::default(),
|
||||||
secretstore_conf: Default::default(),
|
secretstore_conf: Default::default(),
|
||||||
ui: false,
|
ui: false,
|
||||||
dapp: None,
|
dapp: None,
|
||||||
@ -1457,7 +1465,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_parse_signer_configration() {
|
fn should_parse_ui_configuration() {
|
||||||
// given
|
// given
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -1467,33 +1475,33 @@ mod tests {
|
|||||||
let conf3 = parse(&["parity", "--ui-path", "signer", "--ui-interface", "test"]);
|
let conf3 = parse(&["parity", "--ui-path", "signer", "--ui-interface", "test"]);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(conf0.signer_config(), SignerConfiguration {
|
assert_eq!(conf0.directories().signer, "signer".to_owned());
|
||||||
|
assert_eq!(conf0.ui_config(), UiConfiguration {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: 8180,
|
|
||||||
interface: "127.0.0.1".into(),
|
interface: "127.0.0.1".into(),
|
||||||
signer_path: "signer".into(),
|
|
||||||
skip_origin_validation: false,
|
|
||||||
});
|
|
||||||
assert_eq!(conf1.signer_config(), SignerConfiguration {
|
|
||||||
enabled: true,
|
|
||||||
port: 8180,
|
port: 8180,
|
||||||
interface: "127.0.0.1".into(),
|
hosts: Some(vec![]),
|
||||||
signer_path: "signer".into(),
|
|
||||||
skip_origin_validation: true,
|
|
||||||
});
|
});
|
||||||
assert_eq!(conf2.signer_config(), SignerConfiguration {
|
assert_eq!(conf1.directories().signer, "signer".to_owned());
|
||||||
|
assert_eq!(conf1.ui_config(), UiConfiguration {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
interface: "127.0.0.1".into(),
|
||||||
|
port: 8180,
|
||||||
|
hosts: None,
|
||||||
|
});
|
||||||
|
assert_eq!(conf2.directories().signer, "signer".to_owned());
|
||||||
|
assert_eq!(conf2.ui_config(), UiConfiguration {
|
||||||
|
enabled: true,
|
||||||
|
interface: "127.0.0.1".into(),
|
||||||
port: 3123,
|
port: 3123,
|
||||||
interface: "127.0.0.1".into(),
|
hosts: Some(vec![]),
|
||||||
signer_path: "signer".into(),
|
|
||||||
skip_origin_validation: false,
|
|
||||||
});
|
});
|
||||||
assert_eq!(conf3.signer_config(), SignerConfiguration {
|
assert_eq!(conf3.directories().signer, "signer".to_owned());
|
||||||
|
assert_eq!(conf3.ui_config(), UiConfiguration {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: 8180,
|
|
||||||
interface: "test".into(),
|
interface: "test".into(),
|
||||||
signer_path: "signer".into(),
|
port: 8180,
|
||||||
skip_origin_validation: false,
|
hosts: Some(vec![]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1551,7 +1559,7 @@ mod tests {
|
|||||||
assert_eq!(conf0.network_settings().unwrap().rpc_port, 8546);
|
assert_eq!(conf0.network_settings().unwrap().rpc_port, 8546);
|
||||||
assert_eq!(conf0.http_config().unwrap().port, 8546);
|
assert_eq!(conf0.http_config().unwrap().port, 8546);
|
||||||
assert_eq!(conf0.ws_config().unwrap().port, 8547);
|
assert_eq!(conf0.ws_config().unwrap().port, 8547);
|
||||||
assert_eq!(conf0.signer_config().port, 8181);
|
assert_eq!(conf0.ui_config().port, 8181);
|
||||||
assert_eq!(conf0.secretstore_config().unwrap().port, 8084);
|
assert_eq!(conf0.secretstore_config().unwrap().port, 8084);
|
||||||
assert_eq!(conf0.secretstore_config().unwrap().http_port, 8083);
|
assert_eq!(conf0.secretstore_config().unwrap().http_port, 8083);
|
||||||
assert_eq!(conf0.ipfs_config().port, 5002);
|
assert_eq!(conf0.ipfs_config().port, 5002);
|
||||||
@ -1563,7 +1571,7 @@ mod tests {
|
|||||||
assert_eq!(conf1.network_settings().unwrap().rpc_port, 8545);
|
assert_eq!(conf1.network_settings().unwrap().rpc_port, 8545);
|
||||||
assert_eq!(conf1.http_config().unwrap().port, 8545);
|
assert_eq!(conf1.http_config().unwrap().port, 8545);
|
||||||
assert_eq!(conf1.ws_config().unwrap().port, 8547);
|
assert_eq!(conf1.ws_config().unwrap().port, 8547);
|
||||||
assert_eq!(conf1.signer_config().port, 8181);
|
assert_eq!(conf1.ui_config().port, 8181);
|
||||||
assert_eq!(conf1.secretstore_config().unwrap().port, 8084);
|
assert_eq!(conf1.secretstore_config().unwrap().port, 8084);
|
||||||
assert_eq!(conf1.secretstore_config().unwrap().http_port, 8083);
|
assert_eq!(conf1.secretstore_config().unwrap().http_port, 8083);
|
||||||
assert_eq!(conf1.ipfs_config().port, 5002);
|
assert_eq!(conf1.ipfs_config().port, 5002);
|
||||||
@ -1582,8 +1590,8 @@ mod tests {
|
|||||||
assert_eq!(conf0.http_config().unwrap().hosts, None);
|
assert_eq!(conf0.http_config().unwrap().hosts, None);
|
||||||
assert_eq!(&conf0.ws_config().unwrap().interface, "0.0.0.0");
|
assert_eq!(&conf0.ws_config().unwrap().interface, "0.0.0.0");
|
||||||
assert_eq!(conf0.ws_config().unwrap().hosts, None);
|
assert_eq!(conf0.ws_config().unwrap().hosts, None);
|
||||||
assert_eq!(&conf0.signer_config().interface, "0.0.0.0");
|
assert_eq!(&conf0.ui_config().interface, "0.0.0.0");
|
||||||
assert_eq!(conf0.signer_config().skip_origin_validation, true);
|
assert_eq!(conf0.ui_config().hosts, None);
|
||||||
assert_eq!(&conf0.secretstore_config().unwrap().interface, "0.0.0.0");
|
assert_eq!(&conf0.secretstore_config().unwrap().interface, "0.0.0.0");
|
||||||
assert_eq!(&conf0.secretstore_config().unwrap().http_interface, "0.0.0.0");
|
assert_eq!(&conf0.secretstore_config().unwrap().http_interface, "0.0.0.0");
|
||||||
assert_eq!(&conf0.ipfs_config().interface, "0.0.0.0");
|
assert_eq!(&conf0.ipfs_config().interface, "0.0.0.0");
|
||||||
|
@ -27,6 +27,7 @@ use hash_fetch::urlhint::ContractClient;
|
|||||||
use helpers::replace_home;
|
use helpers::replace_home;
|
||||||
use light::client::Client as LightClient;
|
use light::client::Client as LightClient;
|
||||||
use light::on_demand::{self, OnDemand};
|
use light::on_demand::{self, OnDemand};
|
||||||
|
use rpc;
|
||||||
use rpc_apis::SignerService;
|
use rpc_apis::SignerService;
|
||||||
use parity_reactor;
|
use parity_reactor;
|
||||||
use util::{Bytes, Address};
|
use util::{Bytes, Address};
|
||||||
@ -49,6 +50,15 @@ impl Default for Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Configuration {
|
||||||
|
pub fn address(&self, address: Option<(String, u16)>) -> Option<(String, u16)> {
|
||||||
|
match self.enabled {
|
||||||
|
true => address,
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Registrar implementation of the full client.
|
/// Registrar implementation of the full client.
|
||||||
pub struct FullRegistrar {
|
pub struct FullRegistrar {
|
||||||
/// Handle to the full client.
|
/// Handle to the full client.
|
||||||
@ -125,35 +135,49 @@ impl ContractClient for LightRegistrar {
|
|||||||
|
|
||||||
// TODO: light client implementation forwarding to OnDemand and waiting for future
|
// TODO: light client implementation forwarding to OnDemand and waiting for future
|
||||||
// to resolve.
|
// to resolve.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Dependencies {
|
pub struct Dependencies {
|
||||||
pub sync_status: Arc<SyncStatus>,
|
pub sync_status: Arc<SyncStatus>,
|
||||||
pub contract_client: Arc<ContractClient>,
|
pub contract_client: Arc<ContractClient>,
|
||||||
pub remote: parity_reactor::TokioRemote,
|
pub remote: parity_reactor::TokioRemote,
|
||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub signer: Arc<SignerService>,
|
pub signer: Arc<SignerService>,
|
||||||
|
pub ui_address: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(configuration: Configuration, deps: Dependencies)
|
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Middleware>, String> {
|
||||||
-> Result<Option<Middleware>, String>
|
|
||||||
{
|
|
||||||
if !configuration.enabled {
|
if !configuration.enabled {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
dapps_middleware(
|
server::dapps_middleware(
|
||||||
deps,
|
deps,
|
||||||
configuration.dapps_path,
|
configuration.dapps_path,
|
||||||
configuration.extra_dapps,
|
configuration.extra_dapps,
|
||||||
|
rpc::DAPPS_DOMAIN.into(),
|
||||||
).map(Some)
|
).map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::server::{SyncStatus, Middleware, dapps_middleware};
|
pub fn new_ui(enabled: bool, deps: Dependencies) -> Result<Option<Middleware>, String> {
|
||||||
|
if !enabled {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
server::ui_middleware(
|
||||||
|
deps,
|
||||||
|
rpc::DAPPS_DOMAIN.into(),
|
||||||
|
).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::server::{SyncStatus, Middleware, service};
|
||||||
|
|
||||||
#[cfg(not(feature = "dapps"))]
|
#[cfg(not(feature = "dapps"))]
|
||||||
mod server {
|
mod server {
|
||||||
use super::Dependencies;
|
use super::Dependencies;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use parity_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction};
|
use parity_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction};
|
||||||
|
use rpc_apis;
|
||||||
|
|
||||||
pub type SyncStatus = Fn() -> bool;
|
pub type SyncStatus = Fn() -> bool;
|
||||||
|
|
||||||
@ -170,9 +194,21 @@ mod server {
|
|||||||
_deps: Dependencies,
|
_deps: Dependencies,
|
||||||
_dapps_path: PathBuf,
|
_dapps_path: PathBuf,
|
||||||
_extra_dapps: Vec<PathBuf>,
|
_extra_dapps: Vec<PathBuf>,
|
||||||
|
_dapps_domain: String,
|
||||||
) -> Result<Middleware, String> {
|
) -> Result<Middleware, String> {
|
||||||
Err("Your Parity version has been compiled without WebApps support.".into())
|
Err("Your Parity version has been compiled without WebApps support.".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ui_middleware(
|
||||||
|
_deps: Dependencies,
|
||||||
|
_dapps_domain: String,
|
||||||
|
) -> Result<Middleware, String> {
|
||||||
|
Err("Your Parity version has been compiled without UI support.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn service(_: &Option<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dapps")]
|
#[cfg(feature = "dapps")]
|
||||||
@ -180,6 +216,7 @@ mod server {
|
|||||||
use super::Dependencies;
|
use super::Dependencies;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use rpc_apis;
|
||||||
|
|
||||||
use parity_dapps;
|
use parity_dapps;
|
||||||
use parity_reactor;
|
use parity_reactor;
|
||||||
@ -191,20 +228,62 @@ mod server {
|
|||||||
deps: Dependencies,
|
deps: Dependencies,
|
||||||
dapps_path: PathBuf,
|
dapps_path: PathBuf,
|
||||||
extra_dapps: Vec<PathBuf>,
|
extra_dapps: Vec<PathBuf>,
|
||||||
|
dapps_domain: String,
|
||||||
) -> Result<Middleware, String> {
|
) -> Result<Middleware, String> {
|
||||||
let signer = deps.signer.clone();
|
let signer = deps.signer;
|
||||||
let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
|
let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
|
||||||
let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token));
|
let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token));
|
||||||
|
|
||||||
Ok(parity_dapps::Middleware::new(
|
Ok(parity_dapps::Middleware::dapps(
|
||||||
parity_remote,
|
parity_remote,
|
||||||
deps.signer.address(),
|
deps.ui_address,
|
||||||
dapps_path,
|
dapps_path,
|
||||||
extra_dapps,
|
extra_dapps,
|
||||||
|
dapps_domain,
|
||||||
deps.contract_client,
|
deps.contract_client,
|
||||||
deps.sync_status,
|
deps.sync_status,
|
||||||
web_proxy_tokens,
|
web_proxy_tokens,
|
||||||
deps.fetch.clone(),
|
deps.fetch,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ui_middleware(
|
||||||
|
deps: Dependencies,
|
||||||
|
dapps_domain: String,
|
||||||
|
) -> Result<Middleware, String> {
|
||||||
|
let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
|
||||||
|
Ok(parity_dapps::Middleware::ui(
|
||||||
|
parity_remote,
|
||||||
|
deps.contract_client,
|
||||||
|
deps.sync_status,
|
||||||
|
deps.fetch,
|
||||||
|
dapps_domain,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn service(middleware: &Option<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
|
||||||
|
middleware.as_ref().map(|m| Arc::new(DappsServiceWrapper {
|
||||||
|
endpoints: m.endpoints()
|
||||||
|
}) as Arc<rpc_apis::DappsService>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DappsServiceWrapper {
|
||||||
|
endpoints: parity_dapps::Endpoints,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rpc_apis::DappsService for DappsServiceWrapper {
|
||||||
|
fn list_dapps(&self) -> Vec<rpc_apis::LocalDapp> {
|
||||||
|
self.endpoints.list()
|
||||||
|
.into_iter()
|
||||||
|
.map(|app| rpc_apis::LocalDapp {
|
||||||
|
id: app.id,
|
||||||
|
name: app.name,
|
||||||
|
description: app.description,
|
||||||
|
version: app.version,
|
||||||
|
author: app.author,
|
||||||
|
icon_url: app.icon_url,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ extern crate ethcore_ipc_hypervisor as hypervisor;
|
|||||||
extern crate ethcore_ipc_nano as nanoipc;
|
extern crate ethcore_ipc_nano as nanoipc;
|
||||||
extern crate ethcore_light as light;
|
extern crate ethcore_light as light;
|
||||||
extern crate ethcore_logger;
|
extern crate ethcore_logger;
|
||||||
extern crate ethcore_signer;
|
|
||||||
extern crate ethcore_util as util;
|
extern crate ethcore_util as util;
|
||||||
extern crate ethkey;
|
extern crate ethkey;
|
||||||
extern crate ethsync;
|
extern crate ethsync;
|
||||||
@ -114,9 +113,9 @@ mod presale;
|
|||||||
mod rpc;
|
mod rpc;
|
||||||
mod rpc_apis;
|
mod rpc_apis;
|
||||||
mod run;
|
mod run;
|
||||||
|
mod secretstore;
|
||||||
mod signer;
|
mod signer;
|
||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod secretstore;
|
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod url;
|
mod url;
|
||||||
mod user_defaults;
|
mod user_defaults;
|
||||||
@ -170,7 +169,7 @@ fn execute(command: Execute, can_restart: bool) -> Result<PostExecutionAction, S
|
|||||||
Cmd::Account(account_cmd) => account::execute(account_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::Account(account_cmd) => account::execute(account_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|_| PostExecutionAction::Quit),
|
Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|_| PostExecutionAction::Quit),
|
||||||
Cmd::SignerToken(signer_cmd) => signer::execute(signer_cmd).map(|s| PostExecutionAction::Print(s)),
|
Cmd::SignerToken(ws_conf, ui_conf) => signer::execute(ws_conf, ui_conf).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| PostExecutionAction::Print(s)),
|
Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
Cmd::SignerReject { id, port, authfile } => rpc_cli::signer_reject(id, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
Cmd::SignerReject { id, port, authfile } => rpc_cli::signer_reject(id, port, authfile).map(|s| PostExecutionAction::Print(s)),
|
||||||
|
267
parity/rpc.rs
267
parity/rpc.rs
@ -16,18 +16,24 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use dapps;
|
use dapps;
|
||||||
use parity_rpc::informant::{RpcStats, Middleware};
|
use dir::default_data_path;
|
||||||
use parity_rpc::{self as rpc, HttpServerError, Metadata, Origin, DomainsValidation};
|
use helpers::{parity_ipc_path, replace_home};
|
||||||
use helpers::parity_ipc_path;
|
|
||||||
use jsonrpc_core::MetaIoHandler;
|
use jsonrpc_core::MetaIoHandler;
|
||||||
use parity_reactor::TokioRemote;
|
use parity_reactor::TokioRemote;
|
||||||
|
use parity_rpc::informant::{RpcStats, Middleware};
|
||||||
|
use parity_rpc::{self as rpc, Metadata, DomainsValidation};
|
||||||
use rpc_apis::{self, ApiSet};
|
use rpc_apis::{self, ApiSet};
|
||||||
|
|
||||||
pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware};
|
pub use parity_rpc::{IpcServer, HttpServer, RequestMiddleware};
|
||||||
pub use parity_rpc::ws::Server as WsServer;
|
pub use parity_rpc::ws::Server as WsServer;
|
||||||
|
|
||||||
|
|
||||||
|
pub const DAPPS_DOMAIN: &'static str = "web3.site";
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct HttpConfiguration {
|
pub struct HttpConfiguration {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@ -39,6 +45,15 @@ pub struct HttpConfiguration {
|
|||||||
pub threads: Option<usize>,
|
pub threads: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HttpConfiguration {
|
||||||
|
pub fn address(&self) -> Option<(String, u16)> {
|
||||||
|
match self.enabled {
|
||||||
|
true => Some((self.interface.clone(), self.port)),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for HttpConfiguration {
|
impl Default for HttpConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
HttpConfiguration {
|
HttpConfiguration {
|
||||||
@ -53,6 +68,48 @@ impl Default for HttpConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct UiConfiguration {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub interface: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub hosts: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiConfiguration {
|
||||||
|
pub fn address(&self) -> Option<(String, u16)> {
|
||||||
|
match self.enabled {
|
||||||
|
true => Some((self.interface.clone(), self.port)),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UiConfiguration> for HttpConfiguration {
|
||||||
|
fn from(conf: UiConfiguration) -> Self {
|
||||||
|
HttpConfiguration {
|
||||||
|
enabled: conf.enabled,
|
||||||
|
interface: conf.interface,
|
||||||
|
port: conf.port,
|
||||||
|
apis: rpc_apis::ApiSet::SafeContext,
|
||||||
|
cors: None,
|
||||||
|
hosts: conf.hosts,
|
||||||
|
threads: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiConfiguration {
|
||||||
|
fn default() -> Self {
|
||||||
|
UiConfiguration {
|
||||||
|
enabled: true,
|
||||||
|
port: 8180,
|
||||||
|
interface: "127.0.0.1".into(),
|
||||||
|
hosts: Some(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct IpcConfiguration {
|
pub struct IpcConfiguration {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@ -75,7 +132,7 @@ impl Default for IpcConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct WsConfiguration {
|
pub struct WsConfiguration {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub interface: String,
|
pub interface: String,
|
||||||
@ -83,17 +140,32 @@ pub struct WsConfiguration {
|
|||||||
pub apis: ApiSet,
|
pub apis: ApiSet,
|
||||||
pub origins: Option<Vec<String>>,
|
pub origins: Option<Vec<String>>,
|
||||||
pub hosts: Option<Vec<String>>,
|
pub hosts: Option<Vec<String>>,
|
||||||
|
pub signer_path: PathBuf,
|
||||||
|
pub ui_address: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WsConfiguration {
|
impl Default for WsConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let data_dir = default_data_path();
|
||||||
WsConfiguration {
|
WsConfiguration {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
interface: "127.0.0.1".into(),
|
interface: "127.0.0.1".into(),
|
||||||
port: 8546,
|
port: 8546,
|
||||||
apis: ApiSet::UnsafeContext,
|
apis: ApiSet::UnsafeContext,
|
||||||
origins: Some(Vec::new()),
|
origins: Some(vec!["chrome-extension://*".into()]),
|
||||||
hosts: Some(Vec::new()),
|
hosts: Some(Vec::new()),
|
||||||
|
signer_path: replace_home(&data_dir, "$BASE/signer").into(),
|
||||||
|
ui_address: Some(("127.0.0.1".to_owned(), 8180)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl WsConfiguration {
|
||||||
|
pub fn address(&self) -> Option<(String, u16)> {
|
||||||
|
match self.enabled {
|
||||||
|
true => Some((self.interface.clone(), self.port)),
|
||||||
|
false => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,62 +176,6 @@ pub struct Dependencies<D: rpc_apis::Dependencies> {
|
|||||||
pub stats: Arc<RpcStats>,
|
pub stats: Arc<RpcStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcExtractor;
|
|
||||||
impl rpc::HttpMetaExtractor for RpcExtractor {
|
|
||||||
type Metadata = Metadata;
|
|
||||||
|
|
||||||
fn read_metadata(&self, origin: String, dapps_origin: Option<String>) -> Metadata {
|
|
||||||
let mut metadata = Metadata::default();
|
|
||||||
|
|
||||||
metadata.origin = match (origin.as_str(), dapps_origin) {
|
|
||||||
("null", Some(dapp)) => Origin::Dapps(dapp.into()),
|
|
||||||
_ => Origin::Rpc(origin),
|
|
||||||
};
|
|
||||||
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rpc::IpcMetaExtractor<Metadata> for RpcExtractor {
|
|
||||||
fn extract(&self, _req: &rpc::IpcRequestContext) -> Metadata {
|
|
||||||
let mut metadata = Metadata::default();
|
|
||||||
// TODO [ToDr] Extract proper session id when it's available in context.
|
|
||||||
metadata.origin = Origin::Ipc(1.into());
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WsRpcExtractor;
|
|
||||||
impl rpc::ws::MetaExtractor<Metadata> for WsRpcExtractor {
|
|
||||||
fn extract(&self, req: &rpc::ws::RequestContext) -> Metadata {
|
|
||||||
let mut metadata = Metadata::default();
|
|
||||||
let id = req.session_id as u64;
|
|
||||||
metadata.origin = Origin::Ws(id.into());
|
|
||||||
metadata.session = Some(Arc::new(rpc::PubSubSession::new(req.sender())));
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WsStats {
|
|
||||||
stats: Arc<RpcStats>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rpc::ws::SessionStats for WsStats {
|
|
||||||
fn open_session(&self, _id: rpc::ws::SessionId) {
|
|
||||||
self.stats.open_session()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_session(&self, _id: rpc::ws::SessionId) {
|
|
||||||
self.stats.close_session()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
|
|
||||||
where D: rpc_apis::Dependencies
|
|
||||||
{
|
|
||||||
rpc_apis::setup_rpc(deps.stats.clone(), &*deps.apis, apis)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_ws<D: rpc_apis::Dependencies>(
|
pub fn new_ws<D: rpc_apis::Dependencies>(
|
||||||
conf: WsConfiguration,
|
conf: WsConfiguration,
|
||||||
deps: &Dependencies<D>,
|
deps: &Dependencies<D>,
|
||||||
@ -168,23 +184,41 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = format!("{}:{}", conf.interface, conf.port);
|
let domain = DAPPS_DOMAIN;
|
||||||
|
let ws_address = (conf.interface, conf.port);
|
||||||
|
let url = format!("{}:{}", ws_address.0, ws_address.1);
|
||||||
let addr = url.parse().map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?;
|
let addr = url.parse().map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?;
|
||||||
let handler = setup_apis(conf.apis, deps);
|
|
||||||
let remote = deps.remote.clone();
|
|
||||||
let allowed_origins = into_domains(conf.origins);
|
|
||||||
let allowed_hosts = into_domains(conf.hosts);
|
|
||||||
|
|
||||||
|
|
||||||
|
let full_handler = setup_apis(rpc_apis::ApiSet::SafeContext, deps);
|
||||||
|
let handler = {
|
||||||
|
let mut handler = MetaIoHandler::with_middleware((
|
||||||
|
rpc::WsDispatcher::new(full_handler),
|
||||||
|
Middleware::new(deps.stats.clone(), deps.apis.activity_notifier())
|
||||||
|
));
|
||||||
|
let apis = conf.apis.list_apis().into_iter().collect::<Vec<_>>();
|
||||||
|
deps.apis.extend_with_set(&mut handler, &apis);
|
||||||
|
|
||||||
|
handler
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote = deps.remote.clone();
|
||||||
|
let ui_address = conf.ui_address.clone();
|
||||||
|
let allowed_origins = into_domains(with_domain(conf.origins, domain, &[ui_address]));
|
||||||
|
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(ws_address)]));
|
||||||
|
|
||||||
|
let signer_path = conf.signer_path;
|
||||||
|
let signer_path = conf.ui_address.map(move |_| ::signer::codes_path(&signer_path));
|
||||||
|
let path = signer_path.as_ref().map(|p| p.as_path());
|
||||||
let start_result = rpc::start_ws(
|
let start_result = rpc::start_ws(
|
||||||
&addr,
|
&addr,
|
||||||
handler,
|
handler,
|
||||||
remote.clone(),
|
remote.clone(),
|
||||||
allowed_origins,
|
allowed_origins,
|
||||||
allowed_hosts,
|
allowed_hosts,
|
||||||
WsRpcExtractor,
|
rpc::WsExtractor::new(path.clone()),
|
||||||
WsStats {
|
rpc::WsExtractor::new(path.clone()),
|
||||||
stats: deps.stats.clone(),
|
rpc::WsStats::new(deps.stats.clone()),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
match start_result {
|
match start_result {
|
||||||
@ -197,21 +231,25 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_http<D: rpc_apis::Dependencies>(
|
pub fn new_http<D: rpc_apis::Dependencies>(
|
||||||
|
id: &str,
|
||||||
|
options: &str,
|
||||||
conf: HttpConfiguration,
|
conf: HttpConfiguration,
|
||||||
deps: &Dependencies<D>,
|
deps: &Dependencies<D>,
|
||||||
middleware: Option<dapps::Middleware>
|
middleware: Option<dapps::Middleware>,
|
||||||
) -> Result<Option<HttpServer>, String> {
|
) -> Result<Option<HttpServer>, String> {
|
||||||
if !conf.enabled {
|
if !conf.enabled {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = format!("{}:{}", conf.interface, conf.port);
|
let domain = DAPPS_DOMAIN;
|
||||||
let addr = url.parse().map_err(|_| format!("Invalid HTTP JSON-RPC listen host/port given: {}", url))?;
|
let http_address = (conf.interface, conf.port);
|
||||||
|
let url = format!("{}:{}", http_address.0, http_address.1);
|
||||||
|
let addr = url.parse().map_err(|_| format!("Invalid {} listen host/port given: {}", id, url))?;
|
||||||
let handler = setup_apis(conf.apis, deps);
|
let handler = setup_apis(conf.apis, deps);
|
||||||
let remote = deps.remote.clone();
|
let remote = deps.remote.clone();
|
||||||
|
|
||||||
let cors_domains = into_domains(conf.cors);
|
let cors_domains = into_domains(conf.cors);
|
||||||
let allowed_hosts = into_domains(conf.hosts);
|
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(http_address)]));
|
||||||
|
|
||||||
let start_result = rpc::start_http(
|
let start_result = rpc::start_http(
|
||||||
&addr,
|
&addr,
|
||||||
@ -219,7 +257,7 @@ pub fn new_http<D: rpc_apis::Dependencies>(
|
|||||||
allowed_hosts,
|
allowed_hosts,
|
||||||
handler,
|
handler,
|
||||||
remote,
|
remote,
|
||||||
RpcExtractor,
|
rpc::RpcExtractor,
|
||||||
match (conf.threads, middleware) {
|
match (conf.threads, middleware) {
|
||||||
(Some(threads), None) => rpc::HttpSettings::Threads(threads),
|
(Some(threads), None) => rpc::HttpSettings::Threads(threads),
|
||||||
(None, middleware) => rpc::HttpSettings::Dapps(middleware),
|
(None, middleware) => rpc::HttpSettings::Dapps(middleware),
|
||||||
@ -231,17 +269,13 @@ pub fn new_http<D: rpc_apis::Dependencies>(
|
|||||||
|
|
||||||
match start_result {
|
match start_result {
|
||||||
Ok(server) => Ok(Some(server)),
|
Ok(server) => Ok(Some(server)),
|
||||||
Err(HttpServerError::Io(ref err)) if err.kind() == io::ErrorKind::AddrInUse => Err(
|
Err(rpc::HttpServerError::Io(ref err)) if err.kind() == io::ErrorKind::AddrInUse => Err(
|
||||||
format!("HTTP address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --jsonrpc-port and --jsonrpc-interface options.", url)
|
format!("{} address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --{}-port and --{}-interface options.", id, url, options, options)
|
||||||
),
|
),
|
||||||
Err(e) => Err(format!("HTTP error: {:?}", e)),
|
Err(e) => Err(format!("{} error: {:?}", id, e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_domains<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidation<T> {
|
|
||||||
items.map(|vals| vals.into_iter().map(T::from).collect()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_ipc<D: rpc_apis::Dependencies>(
|
pub fn new_ipc<D: rpc_apis::Dependencies>(
|
||||||
conf: IpcConfiguration,
|
conf: IpcConfiguration,
|
||||||
dependencies: &Dependencies<D>
|
dependencies: &Dependencies<D>
|
||||||
@ -252,48 +286,39 @@ pub fn new_ipc<D: rpc_apis::Dependencies>(
|
|||||||
|
|
||||||
let handler = setup_apis(conf.apis, dependencies);
|
let handler = setup_apis(conf.apis, dependencies);
|
||||||
let remote = dependencies.remote.clone();
|
let remote = dependencies.remote.clone();
|
||||||
let ipc = rpc::start_ipc(
|
match rpc::start_ipc(&conf.socket_addr, handler, remote, rpc::RpcExtractor) {
|
||||||
&conf.socket_addr,
|
|
||||||
handler,
|
|
||||||
remote,
|
|
||||||
RpcExtractor,
|
|
||||||
);
|
|
||||||
|
|
||||||
match ipc {
|
|
||||||
Ok(server) => Ok(Some(server)),
|
Ok(server) => Ok(Some(server)),
|
||||||
Err(io_error) => Err(format!("IPC error: {}", io_error)),
|
Err(io_error) => Err(format!("IPC error: {}", io_error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
fn into_domains<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidation<T> {
|
||||||
mod tests {
|
items.map(|vals| vals.into_iter().map(T::from).collect()).into()
|
||||||
use super::RpcExtractor;
|
}
|
||||||
use parity_rpc::{HttpMetaExtractor, Origin};
|
|
||||||
|
fn with_domain(items: Option<Vec<String>>, domain: &str, addresses: &[Option<(String, u16)>]) -> Option<Vec<String>> {
|
||||||
#[test]
|
items.map(move |items| {
|
||||||
fn should_extract_rpc_origin() {
|
let mut items = items.into_iter().collect::<HashSet<_>>();
|
||||||
// given
|
for address in addresses {
|
||||||
let extractor = RpcExtractor;
|
if let Some((host, port)) = address.clone() {
|
||||||
|
items.insert(format!("{}:{}", host, port));
|
||||||
// when
|
items.insert(format!("{}:{}", host.replace("127.0.0.1", "localhost"), port));
|
||||||
let meta = extractor.read_metadata("http://parity.io".into(), None);
|
items.insert(format!("http://*.{}:{}", domain, port));
|
||||||
let meta1 = extractor.read_metadata("http://parity.io".into(), Some("ignored".into()));
|
items.insert(format!("http://*.{}", domain)); //proxypac
|
||||||
|
}
|
||||||
// then
|
}
|
||||||
assert_eq!(meta.origin, Origin::Rpc("http://parity.io".into()));
|
items.into_iter().collect()
|
||||||
assert_eq!(meta1.origin, Origin::Rpc("http://parity.io".into()));
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
|
||||||
fn should_dapps_origin() {
|
where D: rpc_apis::Dependencies
|
||||||
// given
|
{
|
||||||
let extractor = RpcExtractor;
|
let mut handler = MetaIoHandler::with_middleware(
|
||||||
let dapp = "https://wallet.ethereum.org".to_owned();
|
Middleware::new(deps.stats.clone(), deps.apis.activity_notifier())
|
||||||
|
);
|
||||||
// when
|
let apis = apis.list_apis().into_iter().collect::<Vec<_>>();
|
||||||
let meta = extractor.read_metadata("null".into(), Some(dapp.clone()));
|
deps.apis.extend_with_set(&mut handler, &apis);
|
||||||
|
|
||||||
// then
|
handler
|
||||||
assert_eq!(meta.origin, Origin::Dapps(dapp.into()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,15 @@ use std::collections::HashSet;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
pub use parity_rpc::SignerService;
|
pub use parity_rpc::signer::SignerService;
|
||||||
|
pub use parity_rpc::dapps::{DappsService, LocalDapp};
|
||||||
|
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::AccountProvider;
|
||||||
use ethcore::client::Client;
|
use ethcore::client::Client;
|
||||||
use ethcore::miner::{Miner, ExternalMiner};
|
use ethcore::miner::{Miner, ExternalMiner};
|
||||||
use ethcore::snapshot::SnapshotService;
|
use ethcore::snapshot::SnapshotService;
|
||||||
use parity_rpc::{Metadata, NetworkSettings};
|
use parity_rpc::{Metadata, NetworkSettings};
|
||||||
use parity_rpc::informant::{ActivityNotifier, Middleware, RpcStats, ClientNotifier};
|
use parity_rpc::informant::{ActivityNotifier, ClientNotifier};
|
||||||
use parity_rpc::dispatch::{FullDispatcher, LightDispatcher};
|
use parity_rpc::dispatch::{FullDispatcher, LightDispatcher};
|
||||||
use ethsync::{ManageNetwork, SyncProvider, LightSync};
|
use ethsync::{ManageNetwork, SyncProvider, LightSync};
|
||||||
use hash_fetch::fetch::Client as FetchClient;
|
use hash_fetch::fetch::Client as FetchClient;
|
||||||
@ -183,7 +184,11 @@ pub trait Dependencies {
|
|||||||
fn activity_notifier(&self) -> Self::Notifier;
|
fn activity_notifier(&self) -> Self::Notifier;
|
||||||
|
|
||||||
/// Extend the given I/O handler with endpoints for each API.
|
/// Extend the given I/O handler with endpoints for each API.
|
||||||
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware<Self::Notifier>>, apis: &[Api]);
|
fn extend_with_set<S>(
|
||||||
|
&self,
|
||||||
|
handler: &mut MetaIoHandler<Metadata, S>,
|
||||||
|
apis: &[Api],
|
||||||
|
) where S: core::Middleware<Metadata>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RPC dependencies for a full node.
|
/// RPC dependencies for a full node.
|
||||||
@ -201,19 +206,20 @@ pub struct FullDependencies {
|
|||||||
pub net_service: Arc<ManageNetwork>,
|
pub net_service: Arc<ManageNetwork>,
|
||||||
pub updater: Arc<Updater>,
|
pub updater: Arc<Updater>,
|
||||||
pub geth_compatibility: bool,
|
pub geth_compatibility: bool,
|
||||||
pub dapps_interface: Option<String>,
|
pub dapps_service: Option<Arc<DappsService>>,
|
||||||
pub dapps_port: Option<u16>,
|
pub dapps_address: Option<(String, u16)>,
|
||||||
|
pub ws_address: Option<(String, u16)>,
|
||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub remote: parity_reactor::Remote,
|
pub remote: parity_reactor::Remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FullDependencies {
|
impl FullDependencies {
|
||||||
fn extend_api<T: core::Middleware<Metadata>>(
|
fn extend_api<S>(
|
||||||
&self,
|
&self,
|
||||||
handler: &mut MetaIoHandler<Metadata, T>,
|
handler: &mut MetaIoHandler<Metadata, S>,
|
||||||
apis: &[Api],
|
apis: &[Api],
|
||||||
for_generic_pubsub: bool,
|
for_generic_pubsub: bool,
|
||||||
) {
|
) where S: core::Middleware<Metadata> {
|
||||||
use parity_rpc::v1::*;
|
use parity_rpc::v1::*;
|
||||||
|
|
||||||
macro_rules! add_signing_methods {
|
macro_rules! add_signing_methods {
|
||||||
@ -288,8 +294,8 @@ impl FullDependencies {
|
|||||||
self.logger.clone(),
|
self.logger.clone(),
|
||||||
self.settings.clone(),
|
self.settings.clone(),
|
||||||
signer,
|
signer,
|
||||||
self.dapps_interface.clone(),
|
self.dapps_address.clone(),
|
||||||
self.dapps_port,
|
self.ws_address.clone(),
|
||||||
).to_delegate());
|
).to_delegate());
|
||||||
|
|
||||||
if !for_generic_pubsub {
|
if !for_generic_pubsub {
|
||||||
@ -312,6 +318,7 @@ impl FullDependencies {
|
|||||||
&self.miner,
|
&self.miner,
|
||||||
&self.updater,
|
&self.updater,
|
||||||
&self.net_service,
|
&self.net_service,
|
||||||
|
self.dapps_service.clone(),
|
||||||
self.fetch.clone(),
|
self.fetch.clone(),
|
||||||
).to_delegate())
|
).to_delegate())
|
||||||
},
|
},
|
||||||
@ -339,7 +346,11 @@ impl Dependencies for FullDependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware<Self::Notifier>>, apis: &[Api]) {
|
fn extend_with_set<S>(
|
||||||
|
&self,
|
||||||
|
handler: &mut MetaIoHandler<Metadata, S>,
|
||||||
|
apis: &[Api],
|
||||||
|
) where S: core::Middleware<Metadata> {
|
||||||
self.extend_api(handler, apis, false)
|
self.extend_api(handler, apis, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,8 +374,9 @@ pub struct LightDependencies {
|
|||||||
pub on_demand: Arc<::light::on_demand::OnDemand>,
|
pub on_demand: Arc<::light::on_demand::OnDemand>,
|
||||||
pub cache: Arc<Mutex<LightDataCache>>,
|
pub cache: Arc<Mutex<LightDataCache>>,
|
||||||
pub transaction_queue: Arc<RwLock<LightTransactionQueue>>,
|
pub transaction_queue: Arc<RwLock<LightTransactionQueue>>,
|
||||||
pub dapps_interface: Option<String>,
|
pub dapps_service: Option<Arc<DappsService>>,
|
||||||
pub dapps_port: Option<u16>,
|
pub dapps_address: Option<(String, u16)>,
|
||||||
|
pub ws_address: Option<(String, u16)>,
|
||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub geth_compatibility: bool,
|
pub geth_compatibility: bool,
|
||||||
pub remote: parity_reactor::Remote,
|
pub remote: parity_reactor::Remote,
|
||||||
@ -457,8 +469,8 @@ impl LightDependencies {
|
|||||||
self.logger.clone(),
|
self.logger.clone(),
|
||||||
self.settings.clone(),
|
self.settings.clone(),
|
||||||
signer,
|
signer,
|
||||||
self.dapps_interface.clone(),
|
self.dapps_address.clone(),
|
||||||
self.dapps_port,
|
self.ws_address.clone(),
|
||||||
).to_delegate());
|
).to_delegate());
|
||||||
|
|
||||||
if !for_generic_pubsub {
|
if !for_generic_pubsub {
|
||||||
@ -479,6 +491,7 @@ impl LightDependencies {
|
|||||||
Api::ParitySet => {
|
Api::ParitySet => {
|
||||||
handler.extend_with(light::ParitySetClient::new(
|
handler.extend_with(light::ParitySetClient::new(
|
||||||
self.sync.clone(),
|
self.sync.clone(),
|
||||||
|
self.dapps_service.clone(),
|
||||||
self.fetch.clone(),
|
self.fetch.clone(),
|
||||||
).to_delegate())
|
).to_delegate())
|
||||||
},
|
},
|
||||||
@ -502,7 +515,12 @@ impl Dependencies for LightDependencies {
|
|||||||
type Notifier = LightClientNotifier;
|
type Notifier = LightClientNotifier;
|
||||||
|
|
||||||
fn activity_notifier(&self) -> Self::Notifier { LightClientNotifier }
|
fn activity_notifier(&self) -> Self::Notifier { LightClientNotifier }
|
||||||
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware<Self::Notifier>>, apis: &[Api]) {
|
|
||||||
|
fn extend_with_set<S>(
|
||||||
|
&self,
|
||||||
|
handler: &mut MetaIoHandler<Metadata, S>,
|
||||||
|
apis: &[Api],
|
||||||
|
) where S: core::Middleware<Metadata> {
|
||||||
self.extend_api(handler, apis, false)
|
self.extend_api(handler, apis, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,15 +570,6 @@ impl ApiSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_rpc<D: Dependencies>(stats: Arc<RpcStats>, deps: &D, apis: ApiSet) -> MetaIoHandler<Metadata, Middleware<D::Notifier>> {
|
|
||||||
let mut handler = MetaIoHandler::with_middleware(Middleware::new(stats, deps.activity_notifier()));
|
|
||||||
// it's turned into vector, cause ont of the cases requires &[]
|
|
||||||
let apis = apis.list_apis().into_iter().collect::<Vec<_>>();
|
|
||||||
deps.extend_with_set(&mut handler, &apis[..]);
|
|
||||||
|
|
||||||
handler
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{Api, ApiSet};
|
use super::{Api, ApiSet};
|
||||||
|
173
parity/run.rs
173
parity/run.rs
@ -49,11 +49,11 @@ use cache::CacheConfig;
|
|||||||
use user_defaults::UserDefaults;
|
use user_defaults::UserDefaults;
|
||||||
use dapps;
|
use dapps;
|
||||||
use ipfs;
|
use ipfs;
|
||||||
use signer;
|
|
||||||
use secretstore;
|
|
||||||
use modules;
|
use modules;
|
||||||
use rpc_apis;
|
|
||||||
use rpc;
|
use rpc;
|
||||||
|
use rpc_apis;
|
||||||
|
use secretstore;
|
||||||
|
use signer;
|
||||||
use url;
|
use url;
|
||||||
|
|
||||||
// how often to take periodic snapshots.
|
// how often to take periodic snapshots.
|
||||||
@ -99,11 +99,10 @@ pub struct RunCmd {
|
|||||||
pub wal: bool,
|
pub wal: bool,
|
||||||
pub vm_type: VMType,
|
pub vm_type: VMType,
|
||||||
pub geth_compatibility: bool,
|
pub geth_compatibility: bool,
|
||||||
pub ui_address: Option<(String, u16)>,
|
|
||||||
pub net_settings: NetworkSettings,
|
pub net_settings: NetworkSettings,
|
||||||
pub dapps_conf: dapps::Configuration,
|
pub dapps_conf: dapps::Configuration,
|
||||||
pub ipfs_conf: ipfs::Configuration,
|
pub ipfs_conf: ipfs::Configuration,
|
||||||
pub signer_conf: signer::Configuration,
|
pub ui_conf: rpc::UiConfiguration,
|
||||||
pub secretstore_conf: secretstore::Configuration,
|
pub secretstore_conf: secretstore::Configuration,
|
||||||
pub dapp: Option<String>,
|
pub dapp: Option<String>,
|
||||||
pub ui: bool,
|
pub ui: bool,
|
||||||
@ -119,12 +118,12 @@ pub struct RunCmd {
|
|||||||
pub no_persistent_txqueue: bool,
|
pub no_persistent_txqueue: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_ui(signer_conf: &signer::Configuration) -> Result<(), String> {
|
pub fn open_ui(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> Result<(), String> {
|
||||||
if !signer_conf.enabled {
|
if !ui_conf.enabled {
|
||||||
return Err("Cannot use UI command with UI turned off.".into())
|
return Err("Cannot use UI command with UI turned off.".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = signer::generate_token_and_url(signer_conf)?;
|
let token = signer::generate_token_and_url(ws_conf, ui_conf)?;
|
||||||
// Open a browser
|
// Open a browser
|
||||||
url::open(&token.url);
|
url::open(&token.url);
|
||||||
// Print a message
|
// Print a message
|
||||||
@ -195,7 +194,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, compaction.clone())?;
|
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, compaction.clone())?;
|
||||||
|
|
||||||
// create dirs used by parity
|
// create dirs used by parity
|
||||||
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?;
|
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?;
|
||||||
|
|
||||||
info!("Starting {}", Colour::White.bold().paint(version()));
|
info!("Starting {}", Colour::White.bold().paint(version()));
|
||||||
info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client"));
|
info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client"));
|
||||||
@ -267,31 +266,47 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
// prepare account provider
|
// prepare account provider
|
||||||
let account_provider = Arc::new(prepare_account_provider(&cmd.spec, &cmd.dirs, &spec.data_dir, cmd.acc_conf, &passwords)?);
|
let account_provider = Arc::new(prepare_account_provider(&cmd.spec, &cmd.dirs, &spec.data_dir, cmd.acc_conf, &passwords)?);
|
||||||
let rpc_stats = Arc::new(informant::RpcStats::default());
|
let rpc_stats = Arc::new(informant::RpcStats::default());
|
||||||
let signer_path = cmd.signer_conf.signer_path.clone();
|
|
||||||
|
// the dapps server
|
||||||
|
let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf));
|
||||||
|
let dapps_deps = {
|
||||||
|
let contract_client = Arc::new(::dapps::LightRegistrar {
|
||||||
|
client: service.client().clone(),
|
||||||
|
sync: light_sync.clone(),
|
||||||
|
on_demand: on_demand.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let sync = light_sync.clone();
|
||||||
|
dapps::Dependencies {
|
||||||
|
sync_status: Arc::new(move || sync.is_major_importing()),
|
||||||
|
contract_client: contract_client,
|
||||||
|
remote: event_loop.raw_remote(),
|
||||||
|
fetch: fetch.clone(),
|
||||||
|
signer: signer_service.clone(),
|
||||||
|
ui_address: cmd.ui_conf.address(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?;
|
||||||
|
let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?;
|
||||||
|
|
||||||
// start RPCs
|
// start RPCs
|
||||||
|
let dapps_service = dapps::service(&dapps_middleware);
|
||||||
let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies {
|
let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies {
|
||||||
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
|
signer_service: signer_service,
|
||||||
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
|
|
||||||
}, cmd.ui_address)),
|
|
||||||
client: service.client().clone(),
|
client: service.client().clone(),
|
||||||
sync: light_sync.clone(),
|
sync: light_sync.clone(),
|
||||||
net: light_sync.clone(),
|
net: light_sync.clone(),
|
||||||
secret_store: account_provider,
|
secret_store: account_provider,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
settings: Arc::new(cmd.net_settings),
|
settings: Arc::new(cmd.net_settings),
|
||||||
on_demand: on_demand.clone(),
|
on_demand: on_demand,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
transaction_queue: txq,
|
transaction_queue: txq,
|
||||||
dapps_interface: match cmd.dapps_conf.enabled {
|
dapps_service: dapps_service,
|
||||||
true => Some(cmd.http_conf.interface.clone()),
|
dapps_address: cmd.dapps_conf.address(cmd.http_conf.address()),
|
||||||
false => None,
|
ws_address: cmd.ws_conf.address(),
|
||||||
},
|
fetch: fetch,
|
||||||
dapps_port: match cmd.dapps_conf.enabled {
|
|
||||||
true => Some(cmd.http_conf.port),
|
|
||||||
false => None,
|
|
||||||
},
|
|
||||||
fetch: fetch.clone(),
|
|
||||||
geth_compatibility: cmd.geth_compatibility,
|
geth_compatibility: cmd.geth_compatibility,
|
||||||
remote: event_loop.remote(),
|
remote: event_loop.remote(),
|
||||||
});
|
});
|
||||||
@ -302,39 +317,11 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
stats: rpc_stats.clone(),
|
stats: rpc_stats.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// the dapps server
|
|
||||||
let dapps_deps = {
|
|
||||||
let contract_client = Arc::new(::dapps::LightRegistrar {
|
|
||||||
client: service.client().clone(),
|
|
||||||
sync: light_sync.clone(),
|
|
||||||
on_demand: on_demand,
|
|
||||||
});
|
|
||||||
|
|
||||||
let sync = light_sync.clone();
|
|
||||||
dapps::Dependencies {
|
|
||||||
sync_status: Arc::new(move || sync.is_major_importing()),
|
|
||||||
contract_client: contract_client,
|
|
||||||
remote: event_loop.raw_remote(),
|
|
||||||
fetch: fetch,
|
|
||||||
signer: deps_for_rpc_apis.signer_service.clone(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?;
|
|
||||||
|
|
||||||
// start rpc servers
|
// start rpc servers
|
||||||
let _ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?;
|
let _ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?;
|
||||||
let _http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
|
let _http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
|
||||||
let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
|
let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
|
||||||
|
let _ui_server = rpc::new_http("Parity Wallet (UI)", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?;
|
||||||
// the signer server
|
|
||||||
let signer_deps = signer::Dependencies {
|
|
||||||
apis: deps_for_rpc_apis.clone(),
|
|
||||||
remote: event_loop.raw_remote(),
|
|
||||||
rpc_stats: rpc_stats.clone(),
|
|
||||||
};
|
|
||||||
let signing_queue = deps_for_rpc_apis.signer_service.queue();
|
|
||||||
let _signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
|
|
||||||
|
|
||||||
// minimal informant thread. Just prints block number every 5 seconds.
|
// minimal informant thread. Just prints block number every 5 seconds.
|
||||||
// TODO: integrate with informant.rs
|
// TODO: integrate with informant.rs
|
||||||
@ -351,9 +338,9 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
|
|||||||
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> {
|
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> {
|
||||||
if cmd.ui && cmd.dapps_conf.enabled {
|
if cmd.ui && cmd.dapps_conf.enabled {
|
||||||
// Check if Parity is already running
|
// Check if Parity is already running
|
||||||
let addr = format!("{}:{}", cmd.signer_conf.interface, cmd.signer_conf.port);
|
let addr = format!("{}:{}", cmd.ui_conf.interface, cmd.ui_conf.port);
|
||||||
if !TcpListener::bind(&addr as &str).is_ok() {
|
if !TcpListener::bind(&addr as &str).is_ok() {
|
||||||
return open_ui(&cmd.signer_conf).map(|_| (false, None));
|
return open_ui(&cmd.ws_conf, &cmd.ui_conf).map(|_| (false, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +395,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?;
|
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?;
|
||||||
|
|
||||||
// create dirs used by parity
|
// create dirs used by parity
|
||||||
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?;
|
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?;
|
||||||
|
|
||||||
// run in daemon mode
|
// run in daemon mode
|
||||||
if let Some(pid_file) = cmd.daemon {
|
if let Some(pid_file) = cmd.daemon {
|
||||||
@ -620,16 +607,33 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
|
|
||||||
// set up dependencies for rpc servers
|
// set up dependencies for rpc servers
|
||||||
let rpc_stats = Arc::new(informant::RpcStats::default());
|
let rpc_stats = Arc::new(informant::RpcStats::default());
|
||||||
let signer_path = cmd.signer_conf.signer_path.clone();
|
|
||||||
let secret_store = match cmd.public_node {
|
let secret_store = match cmd.public_node {
|
||||||
true => None,
|
true => None,
|
||||||
false => Some(account_provider.clone())
|
false => Some(account_provider.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf));
|
||||||
|
|
||||||
|
// the dapps server
|
||||||
|
let dapps_deps = {
|
||||||
|
let (sync, client) = (sync_provider.clone(), client.clone());
|
||||||
|
let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() });
|
||||||
|
|
||||||
|
dapps::Dependencies {
|
||||||
|
sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())),
|
||||||
|
contract_client: contract_client,
|
||||||
|
remote: event_loop.raw_remote(),
|
||||||
|
fetch: fetch.clone(),
|
||||||
|
signer: signer_service.clone(),
|
||||||
|
ui_address: cmd.ui_conf.address(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?;
|
||||||
|
let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?;
|
||||||
|
|
||||||
|
let dapps_service = dapps::service(&dapps_middleware);
|
||||||
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
|
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
|
||||||
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
|
signer_service: signer_service,
|
||||||
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
|
|
||||||
}, cmd.ui_address)),
|
|
||||||
snapshot: snapshot_service.clone(),
|
snapshot: snapshot_service.clone(),
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
sync: sync_provider.clone(),
|
sync: sync_provider.clone(),
|
||||||
@ -642,14 +646,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
net_service: manage_network.clone(),
|
net_service: manage_network.clone(),
|
||||||
updater: updater.clone(),
|
updater: updater.clone(),
|
||||||
geth_compatibility: cmd.geth_compatibility,
|
geth_compatibility: cmd.geth_compatibility,
|
||||||
dapps_interface: match cmd.dapps_conf.enabled {
|
dapps_service: dapps_service,
|
||||||
true => Some(cmd.http_conf.interface.clone()),
|
dapps_address: cmd.dapps_conf.address(cmd.http_conf.address()),
|
||||||
false => None,
|
ws_address: cmd.ws_conf.address(),
|
||||||
},
|
|
||||||
dapps_port: match cmd.dapps_conf.enabled {
|
|
||||||
true => Some(cmd.http_conf.port),
|
|
||||||
false => None,
|
|
||||||
},
|
|
||||||
fetch: fetch.clone(),
|
fetch: fetch.clone(),
|
||||||
remote: event_loop.remote(),
|
remote: event_loop.remote(),
|
||||||
});
|
});
|
||||||
@ -660,34 +659,12 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
stats: rpc_stats.clone(),
|
stats: rpc_stats.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// the dapps server
|
|
||||||
let dapps_deps = {
|
|
||||||
let (sync, client) = (sync_provider.clone(), client.clone());
|
|
||||||
let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() });
|
|
||||||
|
|
||||||
dapps::Dependencies {
|
|
||||||
sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())),
|
|
||||||
contract_client: contract_client,
|
|
||||||
remote: event_loop.raw_remote(),
|
|
||||||
fetch: fetch.clone(),
|
|
||||||
signer: deps_for_rpc_apis.signer_service.clone(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?;
|
|
||||||
|
|
||||||
// start rpc servers
|
// start rpc servers
|
||||||
let ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?;
|
let ws_server = rpc::new_ws(cmd.ws_conf.clone(), &dependencies)?;
|
||||||
let http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
|
|
||||||
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
|
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
|
||||||
|
let http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
|
||||||
// the signer server
|
// the ui server
|
||||||
let signer_deps = signer::Dependencies {
|
let ui_server = rpc::new_http("UI WALLET", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?;
|
||||||
apis: deps_for_rpc_apis.clone(),
|
|
||||||
remote: event_loop.raw_remote(),
|
|
||||||
rpc_stats: rpc_stats.clone(),
|
|
||||||
};
|
|
||||||
let signing_queue = deps_for_rpc_apis.signer_service.queue();
|
|
||||||
let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
|
|
||||||
|
|
||||||
// secret store key server
|
// secret store key server
|
||||||
let secretstore_deps = secretstore::Dependencies {
|
let secretstore_deps = secretstore::Dependencies {
|
||||||
@ -746,7 +723,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
|
|
||||||
// start ui
|
// start ui
|
||||||
if cmd.ui {
|
if cmd.ui {
|
||||||
open_ui(&cmd.signer_conf)?;
|
open_ui(&cmd.ws_conf, &cmd.ui_conf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dapp) = cmd.dapp {
|
if let Some(dapp) = cmd.dapp {
|
||||||
@ -756,11 +733,11 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
// Handle exit
|
// Handle exit
|
||||||
let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart);
|
let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart);
|
||||||
|
|
||||||
// drop this stuff as soon as exit detected.
|
|
||||||
drop((ws_server, http_server, ipc_server, signer_server, secretstore_key_server, ipfs_server, event_loop));
|
|
||||||
|
|
||||||
info!("Finishing work, please wait...");
|
info!("Finishing work, please wait...");
|
||||||
|
|
||||||
|
// drop this stuff as soon as exit detected.
|
||||||
|
drop((ws_server, http_server, ipc_server, ui_server, secretstore_key_server, ipfs_server, event_loop));
|
||||||
|
|
||||||
// to make sure timer does not spawn requests while shutdown is in progress
|
// to make sure timer does not spawn requests while shutdown is in progress
|
||||||
informant.shutdown();
|
informant.shutdown();
|
||||||
// just Arc is dropping here, to allow other reference release in its default time
|
// just Arc is dropping here, to allow other reference release in its default time
|
||||||
|
125
parity/signer.rs
125
parity/signer.rs
@ -15,51 +15,16 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use ethcore_signer::Server as SignerServer;
|
|
||||||
|
|
||||||
use ansi_term::Colour;
|
use ansi_term::Colour;
|
||||||
use dir::default_data_path;
|
use rpc;
|
||||||
use parity_rpc::informant::RpcStats;
|
|
||||||
use parity_rpc::{self, ConfirmationsQueue};
|
|
||||||
use ethcore_signer as signer;
|
|
||||||
use helpers::replace_home;
|
|
||||||
use parity_reactor::TokioRemote;
|
|
||||||
use rpc_apis;
|
use rpc_apis;
|
||||||
|
use parity_rpc;
|
||||||
use path::restrict_permissions_owner;
|
use path::restrict_permissions_owner;
|
||||||
use util::H256;
|
|
||||||
|
|
||||||
const CODES_FILENAME: &'static str = "authcodes";
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
pub const CODES_FILENAME: &'static str = "authcodes";
|
||||||
pub struct Configuration {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub port: u16,
|
|
||||||
pub interface: String,
|
|
||||||
pub signer_path: String,
|
|
||||||
pub skip_origin_validation: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Configuration {
|
|
||||||
fn default() -> Self {
|
|
||||||
let data_dir = default_data_path();
|
|
||||||
Configuration {
|
|
||||||
enabled: true,
|
|
||||||
port: 8180,
|
|
||||||
interface: "127.0.0.1".into(),
|
|
||||||
signer_path: replace_home(&data_dir, "$BASE/signer"),
|
|
||||||
skip_origin_validation: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Dependencies<D: rpc_apis::Dependencies> {
|
|
||||||
pub apis: Arc<D>,
|
|
||||||
pub remote: TokioRemote,
|
|
||||||
pub rpc_stats: Arc<RpcStats>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NewToken {
|
pub struct NewToken {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
@ -67,42 +32,29 @@ pub struct NewToken {
|
|||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
pub fn new_service(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> rpc_apis::SignerService {
|
||||||
pub struct StandardExtractor;
|
let signer_path = ws_conf.signer_path.clone();
|
||||||
impl signer::MetaExtractor<parity_rpc::Metadata> for StandardExtractor {
|
let signer_enabled = ui_conf.enabled;
|
||||||
fn extract_metadata(&self, session: &H256) -> parity_rpc::Metadata {
|
|
||||||
let mut metadata = parity_rpc::Metadata::default();
|
rpc_apis::SignerService::new(move || {
|
||||||
metadata.origin = parity_rpc::Origin::Signer((*session).into());
|
generate_new_token(&signer_path).map_err(|e| format!("{:?}", e))
|
||||||
metadata
|
}, signer_enabled)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start<D: rpc_apis::Dependencies>(
|
pub fn codes_path(path: &Path) -> PathBuf {
|
||||||
conf: Configuration,
|
let mut p = path.to_owned();
|
||||||
queue: Arc<ConfirmationsQueue>,
|
|
||||||
deps: Dependencies<D>,
|
|
||||||
) -> Result<Option<SignerServer>, String> {
|
|
||||||
if !conf.enabled {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some(do_start(conf, queue, deps)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn codes_path(path: String) -> PathBuf {
|
|
||||||
let mut p = PathBuf::from(path);
|
|
||||||
p.push(CODES_FILENAME);
|
p.push(CODES_FILENAME);
|
||||||
let _ = restrict_permissions_owner(&p, true, false);
|
let _ = restrict_permissions_owner(&p, true, false);
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(cmd: Configuration) -> Result<String, String> {
|
pub fn execute(ws_conf: rpc::WsConfiguration, ui_conf: rpc::UiConfiguration) -> Result<String, String> {
|
||||||
Ok(generate_token_and_url(&cmd)?.message)
|
Ok(generate_token_and_url(&ws_conf, &ui_conf)?.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_token_and_url(conf: &Configuration) -> Result<NewToken, String> {
|
pub fn generate_token_and_url(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration) -> Result<NewToken, String> {
|
||||||
let code = generate_new_token(conf.signer_path.clone()).map_err(|err| format!("Error generating token: {}", err))?;
|
let code = generate_new_token(&ws_conf.signer_path).map_err(|err| format!("Error generating token: {:?}", err))?;
|
||||||
let auth_url = format!("http://{}:{}/#/auth?token={}", conf.interface, conf.port, code);
|
let auth_url = format!("http://{}:{}/#/auth?token={}", ui_conf.interface, ui_conf.port, code);
|
||||||
// And print in to the console
|
// And print in to the console
|
||||||
Ok(NewToken {
|
Ok(NewToken {
|
||||||
token: code.clone(),
|
token: code.clone(),
|
||||||
@ -119,49 +71,12 @@ Or use the generated token:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_new_token(path: String) -> io::Result<String> {
|
fn generate_new_token(path: &Path) -> io::Result<String> {
|
||||||
let path = codes_path(path);
|
let path = codes_path(path);
|
||||||
let mut codes = signer::AuthCodes::from_file(&path)?;
|
let mut codes = parity_rpc::AuthCodes::from_file(&path)?;
|
||||||
codes.clear_garbage();
|
codes.clear_garbage();
|
||||||
let code = codes.generate_new()?;
|
let code = codes.generate_new()?;
|
||||||
codes.to_file(&path)?;
|
codes.to_file(&path)?;
|
||||||
trace!("New key code created: {}", Colour::White.bold().paint(&code[..]));
|
trace!("New key code created: {}", Colour::White.bold().paint(&code[..]));
|
||||||
Ok(code)
|
Ok(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_start<D: rpc_apis::Dependencies>(
|
|
||||||
conf: Configuration,
|
|
||||||
queue: Arc<ConfirmationsQueue>,
|
|
||||||
deps: Dependencies<D>
|
|
||||||
) -> Result<SignerServer, String> {
|
|
||||||
let addr = format!("{}:{}", conf.interface, conf.port)
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| format!("Invalid port specified: {}", conf.port))?;
|
|
||||||
|
|
||||||
let start_result = {
|
|
||||||
let server = signer::ServerBuilder::new(
|
|
||||||
queue,
|
|
||||||
codes_path(conf.signer_path),
|
|
||||||
);
|
|
||||||
if conf.skip_origin_validation {
|
|
||||||
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Trusted Signer with no origin validation."));
|
|
||||||
info!("If you do not intend this, exit now.");
|
|
||||||
}
|
|
||||||
let server = server.skip_origin_validation(conf.skip_origin_validation);
|
|
||||||
let server = server.stats(deps.rpc_stats.clone());
|
|
||||||
let handler = rpc_apis::setup_rpc(deps.rpc_stats, &*deps.apis, rpc_apis::ApiSet::SafeContext);
|
|
||||||
let remote = deps.remote.clone();
|
|
||||||
server.start_with_extractor(addr, handler, remote, StandardExtractor)
|
|
||||||
};
|
|
||||||
|
|
||||||
match start_result {
|
|
||||||
Err(signer::ServerError::IoError(err)) => match err.kind() {
|
|
||||||
io::ErrorKind::AddrInUse => Err(format!("Trusted UI address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --ui-port and --ui-interface options.", addr)),
|
|
||||||
_ => Err(format!("Trusted Signer io error: {}", err)),
|
|
||||||
},
|
|
||||||
Err(e) => Err(format!("Trusted Signer Error: {:?}", e)),
|
|
||||||
Ok(server) => Ok(server),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,9 +8,13 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
cid = "0.2"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
|
multihash = "0.5"
|
||||||
order-stat = "0.1"
|
order-stat = "0.1"
|
||||||
|
rand = "0.3"
|
||||||
|
rust-crypto = "0.2"
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
semver = "0.6"
|
semver = "0.6"
|
||||||
serde = "0.9"
|
serde = "0.9"
|
||||||
@ -19,10 +23,6 @@ serde_json = "0.9"
|
|||||||
time = "0.1"
|
time = "0.1"
|
||||||
tokio-timer = "0.1"
|
tokio-timer = "0.1"
|
||||||
transient-hashmap = "0.4"
|
transient-hashmap = "0.4"
|
||||||
cid = "0.2.1"
|
|
||||||
multihash = "0.5"
|
|
||||||
rust-crypto = "0.2.36"
|
|
||||||
rand = "0.3"
|
|
||||||
|
|
||||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||||
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
description = "Rpc test client."
|
|
||||||
name = "rpctest"
|
|
||||||
version = "1.7.0"
|
|
||||||
license = "GPL-3.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
|
||||||
docopt = "0.7"
|
|
||||||
ethcore = { path = "../../ethcore" }
|
|
||||||
ethcore-devtools = { path = "../../devtools" }
|
|
||||||
ethcore-util = { path = "../../util" }
|
|
||||||
ethjson = { path = "../../json" }
|
|
||||||
parity-rpc = { path = ".." }
|
|
||||||
rustc-serialize = "0.3"
|
|
||||||
serde_json = "0.8"
|
|
@ -1,148 +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/>.
|
|
||||||
|
|
||||||
extern crate ctrlc;
|
|
||||||
extern crate docopt;
|
|
||||||
extern crate ethcore;
|
|
||||||
extern crate ethcore_devtools as devtools;
|
|
||||||
extern crate ethcore_util as util;
|
|
||||||
extern crate ethjson;
|
|
||||||
extern crate parity_rpc as rpc;
|
|
||||||
extern crate rustc_serialize;
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::{Arc, Mutex, Condvar};
|
|
||||||
use std::process;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
|
||||||
use docopt::Docopt;
|
|
||||||
use ctrlc::CtrlC;
|
|
||||||
use ethcore::spec::Genesis;
|
|
||||||
use ethcore::pod_state::PodState;
|
|
||||||
use ethcore::ethereum;
|
|
||||||
use ethcore::client::{BlockChainClient, Client, ClientConfig};
|
|
||||||
use devtools::RandomTempPath;
|
|
||||||
use util::IoChannel;
|
|
||||||
use rpc::v1::tests::helpers::{TestSyncProvider, Config as SyncConfig, TestMinerService, TestAccountProvider, TestAccount};
|
|
||||||
use rpc::v1::{Eth, EthClient, EthFilter, EthFilterClient};
|
|
||||||
use util::panics::MayPanic;
|
|
||||||
use util::hash::Address;
|
|
||||||
|
|
||||||
const USAGE: &'static str = r#"
|
|
||||||
Parity rpctest client.
|
|
||||||
By Wood/Paronyan/Kotewicz/Drwięga/Volf.
|
|
||||||
Copyright 2015, 2016, 2017 Parity Technologies (UK) Ltd
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
rpctest --json <test-file> --name <test-name> [options]
|
|
||||||
rpctest --help
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--jsonrpc-addr HOST Specify the hostname portion of the JSONRPC API
|
|
||||||
server [default: 127.0.0.1].
|
|
||||||
--jsonrpc-port PORT Specify the port portion of the JSONRPC API server
|
|
||||||
[default: 8545].
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Debug, RustcDecodable)]
|
|
||||||
struct Args {
|
|
||||||
arg_test_file: String,
|
|
||||||
arg_test_name: String,
|
|
||||||
flag_jsonrpc_addr: String,
|
|
||||||
flag_jsonrpc_port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Configuration {
|
|
||||||
args: Args,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Configuration {
|
|
||||||
fn parse() -> Self {
|
|
||||||
Configuration {
|
|
||||||
args: Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(&self) {
|
|
||||||
println!("file path: {:?}", self.args.arg_test_file);
|
|
||||||
println!("test name: {:?}", self.args.arg_test_name);
|
|
||||||
|
|
||||||
let path = Path::new(&self.args.arg_test_file);
|
|
||||||
let file = File::open(path).unwrap_or_else(|_| {
|
|
||||||
println!("Cannot open file.");
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
let tests: ethjson::blockchain::Test = serde_json::from_reader(file).unwrap_or_else(|err| {
|
|
||||||
println!("Invalid json file.");
|
|
||||||
println!("{:?}", err);
|
|
||||||
process::exit(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
let blockchain = tests.get(&self.args.arg_test_name).unwrap_or_else(|| {
|
|
||||||
println!("Invalid test name.");
|
|
||||||
process::exit(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
let genesis = Genesis::from(blockchain.genesis());
|
|
||||||
let state = PodState::from(blockchain.pre_state.clone());
|
|
||||||
let mut spec = ethereum::new_frontier_test();
|
|
||||||
spec.set_genesis_state(state);
|
|
||||||
spec.overwrite_genesis_params(genesis);
|
|
||||||
assert!(spec.is_state_root_valid());
|
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
|
||||||
{
|
|
||||||
let client: Arc<Client> = Client::new(ClientConfig::default(), spec, temp.as_path(), IoChannel::disconnected()).unwrap();
|
|
||||||
for b in &blockchain.blocks_rlp() {
|
|
||||||
let _ = client.import_block(b.clone());
|
|
||||||
client.flush_queue();
|
|
||||||
client.import_verified_blocks();
|
|
||||||
}
|
|
||||||
let sync = Arc::new(TestSyncProvider::new(SyncConfig {
|
|
||||||
protocol_version: 65,
|
|
||||||
num_peers: 120
|
|
||||||
}));
|
|
||||||
|
|
||||||
let miner = Arc::new(TestMinerService::default());
|
|
||||||
let mut accs = HashMap::new();
|
|
||||||
accs.insert(Address::from(1), TestAccount::new("test"));
|
|
||||||
let accounts = Arc::new(TestAccountProvider::new(accs));
|
|
||||||
let server = rpc::RpcServer::new();
|
|
||||||
server.add_delegate(EthClient::new(&client, &sync, &accounts, &miner, true).to_delegate());
|
|
||||||
server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate());
|
|
||||||
|
|
||||||
let url = format!("{}:{}", self.args.flag_jsonrpc_addr, self.args.flag_jsonrpc_port);
|
|
||||||
let panic_handler = server.start_http(url.as_ref(), "*", 1);
|
|
||||||
let exit = Arc::new(Condvar::new());
|
|
||||||
|
|
||||||
let e = exit.clone();
|
|
||||||
CtrlC::set_handler(move || { e.notify_all(); });
|
|
||||||
|
|
||||||
let e = exit.clone();
|
|
||||||
panic_handler.on_panic(move |_reason| { e.notify_all(); });
|
|
||||||
|
|
||||||
let mutex = Mutex::new(());
|
|
||||||
let _ = exit.wait(mutex.lock()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Configuration::parse().execute();
|
|
||||||
}
|
|
@ -14,11 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
use rand::os::OsRng;
|
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, time, mem};
|
use std::{fs, time, mem};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use rand::os::OsRng;
|
||||||
use util::{H256, Hashable, Itertools};
|
use util::{H256, Hashable, Itertools};
|
||||||
|
|
||||||
/// Providing current time in seconds
|
/// Providing current time in seconds
|
||||||
@ -347,5 +348,3 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,11 +14,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Transport-specific metadata extractors.
|
||||||
|
|
||||||
use jsonrpc_core;
|
use jsonrpc_core;
|
||||||
use http;
|
use http;
|
||||||
use hyper;
|
use hyper;
|
||||||
use minihttp;
|
use minihttp;
|
||||||
use HttpMetaExtractor;
|
|
||||||
|
/// HTTP RPC server impl-independent metadata extractor
|
||||||
|
pub trait HttpMetaExtractor: Send + Sync + 'static {
|
||||||
|
/// Type of Metadata
|
||||||
|
type Metadata: jsonrpc_core::Metadata;
|
||||||
|
/// Extracts metadata from given params.
|
||||||
|
fn read_metadata(&self, origin: Option<String>, user_agent: Option<String>, dapps_origin: Option<String>) -> Self::Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct HyperMetaExtractor<T> {
|
pub struct HyperMetaExtractor<T> {
|
||||||
extractor: T,
|
extractor: T,
|
||||||
@ -37,13 +46,14 @@ impl<M, T> http::MetaExtractor<M> for HyperMetaExtractor<T> where
|
|||||||
M: jsonrpc_core::Metadata,
|
M: jsonrpc_core::Metadata,
|
||||||
{
|
{
|
||||||
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> M {
|
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> M {
|
||||||
let origin = req.headers().get::<hyper::header::Origin>()
|
let as_string = |header: Option<&http::request_response::header::Raw>| header
|
||||||
.map(|origin| format!("{}://{}", origin.scheme, origin.host))
|
|
||||||
.unwrap_or_else(|| "unknown".into());
|
|
||||||
let dapps_origin = req.headers().get_raw("x-parity-origin")
|
|
||||||
.and_then(|raw| raw.one())
|
.and_then(|raw| raw.one())
|
||||||
.map(|raw| String::from_utf8_lossy(raw).into_owned());
|
.map(|raw| String::from_utf8_lossy(raw).into_owned());
|
||||||
self.extractor.read_metadata(origin, dapps_origin)
|
|
||||||
|
let origin = as_string(req.headers().get_raw("origin"));
|
||||||
|
let user_agent = as_string(req.headers().get_raw("user-agent"));
|
||||||
|
let dapps_origin = as_string(req.headers().get_raw("x-parity-origin"));
|
||||||
|
self.extractor.read_metadata(origin, user_agent, dapps_origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +74,10 @@ impl<M, T> minihttp::MetaExtractor<M> for MiniMetaExtractor<T> where
|
|||||||
M: jsonrpc_core::Metadata,
|
M: jsonrpc_core::Metadata,
|
||||||
{
|
{
|
||||||
fn read_metadata(&self, req: &minihttp::Req) -> M {
|
fn read_metadata(&self, req: &minihttp::Req) -> M {
|
||||||
let origin = req.header("origin")
|
let origin = req.header("origin").map(|h| h.to_owned());
|
||||||
.unwrap_or_else(|| "unknown")
|
let user_agent = req.header("user-agent").map(|h| h.to_owned());
|
||||||
.to_owned();
|
|
||||||
let dapps_origin = req.header("x-parity-origin").map(|h| h.to_owned());
|
let dapps_origin = req.header("x-parity-origin").map(|h| h.to_owned());
|
||||||
|
|
||||||
self.extractor.read_metadata(origin, dapps_origin)
|
self.extractor.read_metadata(origin, user_agent, dapps_origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,13 +14,18 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Ethcore rpc.
|
//! Parity RPC.
|
||||||
#![warn(missing_docs)]
|
|
||||||
#![cfg_attr(feature="nightly", feature(plugin))]
|
|
||||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![cfg_attr(feature="dev", feature(plugin))]
|
||||||
|
#![cfg_attr(feature="dev", plugin(clippy))]
|
||||||
|
|
||||||
|
extern crate cid;
|
||||||
|
extern crate crypto as rust_crypto;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
extern crate multihash;
|
||||||
extern crate order_stat;
|
extern crate order_stat;
|
||||||
|
extern crate rand;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate semver;
|
extern crate semver;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
@ -28,10 +33,6 @@ extern crate serde_json;
|
|||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate tokio_timer;
|
extern crate tokio_timer;
|
||||||
extern crate transient_hashmap;
|
extern crate transient_hashmap;
|
||||||
extern crate cid;
|
|
||||||
extern crate multihash;
|
|
||||||
extern crate crypto as rust_crypto;
|
|
||||||
extern crate rand;
|
|
||||||
|
|
||||||
extern crate jsonrpc_core;
|
extern crate jsonrpc_core;
|
||||||
extern crate jsonrpc_http_server as http;
|
extern crate jsonrpc_http_server as http;
|
||||||
@ -41,6 +42,7 @@ extern crate jsonrpc_pubsub;
|
|||||||
|
|
||||||
extern crate ethash;
|
extern crate ethash;
|
||||||
extern crate ethcore;
|
extern crate ethcore;
|
||||||
|
extern crate ethcore_devtools as devtools;
|
||||||
extern crate ethcore_io as io;
|
extern crate ethcore_io as io;
|
||||||
extern crate ethcore_ipc;
|
extern crate ethcore_ipc;
|
||||||
extern crate ethcore_light as light;
|
extern crate ethcore_light as light;
|
||||||
@ -66,8 +68,6 @@ extern crate serde_derive;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate ethjson;
|
extern crate ethjson;
|
||||||
#[cfg(test)]
|
|
||||||
extern crate ethcore_devtools as devtools;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -75,9 +75,12 @@ extern crate pretty_assertions;
|
|||||||
|
|
||||||
pub extern crate jsonrpc_ws_server as ws;
|
pub extern crate jsonrpc_ws_server as ws;
|
||||||
|
|
||||||
mod metadata;
|
mod authcodes;
|
||||||
|
mod http_common;
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
|
|
||||||
|
pub mod tests;
|
||||||
|
|
||||||
pub use jsonrpc_pubsub::Session as PubSubSession;
|
pub use jsonrpc_pubsub::Session as PubSubSession;
|
||||||
pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext};
|
pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext};
|
||||||
pub use http::{
|
pub use http::{
|
||||||
@ -86,8 +89,11 @@ pub use http::{
|
|||||||
AccessControlAllowOrigin, Host, DomainsValidation
|
AccessControlAllowOrigin, Host, DomainsValidation
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch};
|
pub use v1::{NetworkSettings, Metadata, Origin, informant, dispatch, signer, dapps};
|
||||||
pub use v1::block_import::is_major_importing;
|
pub use v1::block_import::is_major_importing;
|
||||||
|
pub use v1::extractors::{RpcExtractor, WsExtractor, WsStats, WsDispatcher};
|
||||||
|
pub use authcodes::{AuthCodes, TimeProvider};
|
||||||
|
pub use http_common::HttpMetaExtractor;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use http::tokio_core;
|
use http::tokio_core;
|
||||||
@ -100,6 +106,16 @@ pub enum HttpServer {
|
|||||||
Hyper(http::Server),
|
Hyper(http::Server),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HttpServer {
|
||||||
|
/// Returns current listening address.
|
||||||
|
pub fn address(&self) -> &SocketAddr {
|
||||||
|
match *self {
|
||||||
|
HttpServer::Mini(ref s) => s.address(),
|
||||||
|
HttpServer::Hyper(ref s) => &s.addrs()[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// RPC HTTP Server error
|
/// RPC HTTP Server error
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HttpServerError {
|
pub enum HttpServerError {
|
||||||
@ -128,14 +144,6 @@ impl From<minihttp::Error> for HttpServerError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP RPC server impl-independent metadata extractor
|
|
||||||
pub trait HttpMetaExtractor: Send + Sync + 'static {
|
|
||||||
/// Type of Metadata
|
|
||||||
type Metadata: jsonrpc_core::Metadata;
|
|
||||||
/// Extracts metadata from given params.
|
|
||||||
fn read_metadata(&self, origin: String, dapps_origin: Option<String>) -> Self::Metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// HTTP server implementation-specific settings.
|
/// HTTP server implementation-specific settings.
|
||||||
pub enum HttpSettings<R: RequestMiddleware> {
|
pub enum HttpSettings<R: RequestMiddleware> {
|
||||||
/// Enable fast minihttp server with given number of threads.
|
/// Enable fast minihttp server with given number of threads.
|
||||||
@ -164,7 +172,7 @@ pub fn start_http<M, S, H, T, R>(
|
|||||||
HttpSettings::Dapps(middleware) => {
|
HttpSettings::Dapps(middleware) => {
|
||||||
let mut builder = http::ServerBuilder::new(handler)
|
let mut builder = http::ServerBuilder::new(handler)
|
||||||
.event_loop_remote(remote)
|
.event_loop_remote(remote)
|
||||||
.meta_extractor(metadata::HyperMetaExtractor::new(extractor))
|
.meta_extractor(http_common::HyperMetaExtractor::new(extractor))
|
||||||
.cors(cors_domains.into())
|
.cors(cors_domains.into())
|
||||||
.allowed_hosts(allowed_hosts.into());
|
.allowed_hosts(allowed_hosts.into());
|
||||||
|
|
||||||
@ -177,7 +185,7 @@ pub fn start_http<M, S, H, T, R>(
|
|||||||
HttpSettings::Threads(threads) => {
|
HttpSettings::Threads(threads) => {
|
||||||
minihttp::ServerBuilder::new(handler)
|
minihttp::ServerBuilder::new(handler)
|
||||||
.threads(threads)
|
.threads(threads)
|
||||||
.meta_extractor(metadata::MiniMetaExtractor::new(extractor))
|
.meta_extractor(http_common::MiniMetaExtractor::new(extractor))
|
||||||
.cors(cors_domains.into())
|
.cors(cors_domains.into())
|
||||||
.allowed_hosts(allowed_hosts.into())
|
.allowed_hosts(allowed_hosts.into())
|
||||||
.start_http(addr)
|
.start_http(addr)
|
||||||
@ -205,13 +213,14 @@ pub fn start_ipc<M, S, H, T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start WS server and return `Server` handle.
|
/// Start WS server and return `Server` handle.
|
||||||
pub fn start_ws<M, S, H, T, U>(
|
pub fn start_ws<M, S, H, T, U, V>(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
handler: H,
|
handler: H,
|
||||||
remote: tokio_core::reactor::Remote,
|
remote: tokio_core::reactor::Remote,
|
||||||
allowed_origins: ws::DomainsValidation<ws::Origin>,
|
allowed_origins: ws::DomainsValidation<ws::Origin>,
|
||||||
allowed_hosts: ws::DomainsValidation<ws::Host>,
|
allowed_hosts: ws::DomainsValidation<ws::Host>,
|
||||||
extractor: T,
|
extractor: T,
|
||||||
|
middleware: V,
|
||||||
stats: U,
|
stats: U,
|
||||||
) -> Result<ws::Server, ws::Error> where
|
) -> Result<ws::Server, ws::Error> where
|
||||||
M: jsonrpc_core::Metadata,
|
M: jsonrpc_core::Metadata,
|
||||||
@ -219,9 +228,11 @@ pub fn start_ws<M, S, H, T, U>(
|
|||||||
H: Into<jsonrpc_core::MetaIoHandler<M, S>>,
|
H: Into<jsonrpc_core::MetaIoHandler<M, S>>,
|
||||||
T: ws::MetaExtractor<M>,
|
T: ws::MetaExtractor<M>,
|
||||||
U: ws::SessionStats,
|
U: ws::SessionStats,
|
||||||
|
V: ws::RequestMiddleware,
|
||||||
{
|
{
|
||||||
ws::ServerBuilder::new(handler)
|
ws::ServerBuilder::new(handler)
|
||||||
.event_loop_remote(remote)
|
.event_loop_remote(remote)
|
||||||
|
.request_middleware(middleware)
|
||||||
.allowed_origins(allowed_origins)
|
.allowed_origins(allowed_origins)
|
||||||
.allowed_hosts(allowed_hosts)
|
.allowed_hosts(allowed_hosts)
|
||||||
.session_meta_extractor(extractor)
|
.session_meta_extractor(extractor)
|
||||||
|
84
rpc/src/tests/helpers.rs
Normal file
84
rpc/src/tests/helpers.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use devtools::RandomTempPath;
|
||||||
|
use parity_reactor::{EventLoop, TokioRemote};
|
||||||
|
|
||||||
|
use authcodes::AuthCodes;
|
||||||
|
|
||||||
|
/// Server with event loop
|
||||||
|
pub struct Server<T> {
|
||||||
|
/// Server
|
||||||
|
pub server: T,
|
||||||
|
/// RPC Event Loop
|
||||||
|
pub event_loop: EventLoop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Server<T> {
|
||||||
|
pub fn new<F>(f: F) -> Server<T> where
|
||||||
|
F: FnOnce(TokioRemote) -> T,
|
||||||
|
{
|
||||||
|
let event_loop = EventLoop::spawn();
|
||||||
|
let remote = event_loop.raw_remote();
|
||||||
|
|
||||||
|
Server {
|
||||||
|
server: f(remote),
|
||||||
|
event_loop: event_loop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Server<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct representing authcodes
|
||||||
|
pub struct GuardedAuthCodes {
|
||||||
|
authcodes: AuthCodes,
|
||||||
|
/// The path to the mock authcodes
|
||||||
|
pub path: RandomTempPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuardedAuthCodes {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut path = RandomTempPath::new();
|
||||||
|
path.panic_on_drop_failure = false;
|
||||||
|
|
||||||
|
GuardedAuthCodes {
|
||||||
|
authcodes: AuthCodes::from_file(&path).unwrap(),
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for GuardedAuthCodes {
|
||||||
|
type Target = AuthCodes;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.authcodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for GuardedAuthCodes {
|
||||||
|
fn deref_mut(&mut self) -> &mut AuthCodes {
|
||||||
|
&mut self.authcodes
|
||||||
|
}
|
||||||
|
}
|
@ -14,12 +14,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
extern crate rustc_version;
|
//! RPC integration tests.
|
||||||
|
|
||||||
use rustc_version::{version_meta, Channel};
|
mod helpers;
|
||||||
|
#[cfg(test)] mod rpc;
|
||||||
fn main() {
|
pub mod ws;
|
||||||
if let Channel::Nightly = version_meta().channel {
|
|
||||||
println!("cargo:rustc-cfg=nightly");
|
|
||||||
}
|
|
||||||
}
|
|
172
rpc/src/tests/rpc.rs
Normal file
172
rpc/src/tests/rpc.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// 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 devtools::http_client;
|
||||||
|
use jsonrpc_core::MetaIoHandler;
|
||||||
|
use http::{self, hyper};
|
||||||
|
|
||||||
|
use {HttpSettings, HttpServer};
|
||||||
|
use tests::helpers::Server;
|
||||||
|
use v1::{extractors, Metadata};
|
||||||
|
|
||||||
|
fn serve(handler: Option<MetaIoHandler<Metadata>>) -> Server<HttpServer> {
|
||||||
|
let address = "127.0.0.1:0".parse().unwrap();
|
||||||
|
let handler = handler.unwrap_or_default();
|
||||||
|
|
||||||
|
Server::new(|remote| ::start_http(
|
||||||
|
&address,
|
||||||
|
http::DomainsValidation::Disabled,
|
||||||
|
http::DomainsValidation::Disabled,
|
||||||
|
handler,
|
||||||
|
remote,
|
||||||
|
extractors::RpcExtractor,
|
||||||
|
HttpSettings::Dapps(Some(|_req: &hyper::server::Request<hyper::net::HttpStream>, _control: &hyper::Control| {
|
||||||
|
http::RequestMiddlewareAction::Proceed {
|
||||||
|
should_continue_on_invalid_cors: false
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a single request to running server
|
||||||
|
fn request(server: Server<HttpServer>, request: &str) -> http_client::Response {
|
||||||
|
http_client::request(server.server.address(), request)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod testsing {
|
||||||
|
use jsonrpc_core::{MetaIoHandler, Value};
|
||||||
|
use jsonrpc_core::futures::{Future, future};
|
||||||
|
use v1::Metadata;
|
||||||
|
use super::{request, Server};
|
||||||
|
|
||||||
|
fn serve() -> (Server<::HttpServer>, ::std::net::SocketAddr) {
|
||||||
|
let mut io = MetaIoHandler::default();
|
||||||
|
io.add_method_with_meta("hello", |_, meta: Metadata| {
|
||||||
|
future::ok(Value::String(format!("{}", meta.origin))).boxed()
|
||||||
|
});
|
||||||
|
let server = super::serve(Some(io));
|
||||||
|
let address = server.server.address().to_owned();
|
||||||
|
|
||||||
|
(server, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_extract_rpc_origin() {
|
||||||
|
// given
|
||||||
|
let (server, address) = serve();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#;
|
||||||
|
let expected = "34\n{\"jsonrpc\":\"2.0\",\"result\":\"unknown via RPC\",\"id\":1}\n\n0\n\n";
|
||||||
|
let res = request(server,
|
||||||
|
&format!("\
|
||||||
|
POST / HTTP/1.1\r\n\
|
||||||
|
Host: {}\r\n\
|
||||||
|
Content-Type: application/json\r\n\
|
||||||
|
Content-Length: {}\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
", address, req.len(), req)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
res.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_eq!(res.body, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_extract_rpc_origin_with_service() {
|
||||||
|
// given
|
||||||
|
let (server, address) = serve();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#;
|
||||||
|
let expected = "38\n{\"jsonrpc\":\"2.0\",\"result\":\"curl/7.16.3 via RPC\",\"id\":1}\n\n0\n\n";
|
||||||
|
let res = request(server,
|
||||||
|
&format!("\
|
||||||
|
POST / HTTP/1.1\r\n\
|
||||||
|
Host: {}\r\n\
|
||||||
|
Content-Type: application/json\r\n\
|
||||||
|
Content-Length: {}\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
User-Agent: curl/7.16.3\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
", address, req.len(), req)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
res.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_eq!(res.body, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_extract_dapp_origin() {
|
||||||
|
// given
|
||||||
|
let (server, address) = serve();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#;
|
||||||
|
let expected = "3A\n{\"jsonrpc\":\"2.0\",\"result\":\"Dapp http://parity.io\",\"id\":1}\n\n0\n\n";
|
||||||
|
let res = request(server,
|
||||||
|
&format!("\
|
||||||
|
POST / HTTP/1.1\r\n\
|
||||||
|
Host: {}\r\n\
|
||||||
|
Content-Type: application/json\r\n\
|
||||||
|
Content-Length: {}\r\n\
|
||||||
|
Origin: http://parity.io\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
User-Agent: curl/7.16.3\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
", address, req.len(), req)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
res.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_eq!(res.body, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_extract_dapp_origin_from_extension() {
|
||||||
|
// given
|
||||||
|
let (server, address) = serve();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let req = r#"{"method":"hello","params":[],"jsonrpc":"2.0","id":1}"#;
|
||||||
|
let expected = "44\n{\"jsonrpc\":\"2.0\",\"result\":\"Dapp http://wallet.ethereum.org\",\"id\":1}\n\n0\n\n";
|
||||||
|
let res = request(server,
|
||||||
|
&format!("\
|
||||||
|
POST / HTTP/1.1\r\n\
|
||||||
|
Host: {}\r\n\
|
||||||
|
Content-Type: application/json\r\n\
|
||||||
|
Content-Length: {}\r\n\
|
||||||
|
Origin: null\r\n\
|
||||||
|
X-Parity-Origin: http://wallet.ethereum.org\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
User-Agent: curl/7.16.3\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
", address, req.len(), req)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
res.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_eq!(res.body, expected);
|
||||||
|
}
|
||||||
|
}
|
@ -14,79 +14,42 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
//! WebSockets server tests.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use devtools::http_client;
|
use devtools::http_client;
|
||||||
use devtools::RandomTempPath;
|
use jsonrpc_core::MetaIoHandler;
|
||||||
|
|
||||||
use rpc::ConfirmationsQueue;
|
|
||||||
use jsonrpc_core::IoHandler;
|
|
||||||
use jsonrpc_server_utils::reactor::RpcEventLoop;
|
|
||||||
use rand;
|
use rand;
|
||||||
|
use ws;
|
||||||
|
|
||||||
use ServerBuilder;
|
use v1::{extractors, informant};
|
||||||
use Server;
|
use tests::helpers::{GuardedAuthCodes, Server};
|
||||||
use AuthCodes;
|
|
||||||
|
|
||||||
/// Struct representing authcodes
|
|
||||||
pub struct GuardedAuthCodes {
|
|
||||||
authcodes: AuthCodes,
|
|
||||||
/// The path to the mock authcodes
|
|
||||||
pub path: RandomTempPath,
|
|
||||||
}
|
|
||||||
impl Deref for GuardedAuthCodes {
|
|
||||||
type Target = AuthCodes;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.authcodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DerefMut for GuardedAuthCodes {
|
|
||||||
fn deref_mut(&mut self) -> &mut AuthCodes {
|
|
||||||
&mut self.authcodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server with event loop
|
|
||||||
pub struct ServerLoop {
|
|
||||||
/// Signer Server
|
|
||||||
pub server: Server,
|
|
||||||
/// RPC Event Loop
|
|
||||||
pub event_loop: RpcEventLoop,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for ServerLoop {
|
|
||||||
type Target = Server;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setup a mock signer for tests
|
/// Setup a mock signer for tests
|
||||||
pub fn serve() -> (ServerLoop, usize, GuardedAuthCodes) {
|
pub fn serve() -> (Server<ws::Server>, usize, GuardedAuthCodes) {
|
||||||
let mut path = RandomTempPath::new();
|
|
||||||
path.panic_on_drop_failure = false;
|
|
||||||
let queue = Arc::new(ConfirmationsQueue::default());
|
|
||||||
let builder = ServerBuilder::new(queue, path.to_path_buf());
|
|
||||||
let port = 35000 + rand::random::<usize>() % 10000;
|
let port = 35000 + rand::random::<usize>() % 10000;
|
||||||
let event_loop = RpcEventLoop::spawn().unwrap();
|
let address = format!("127.0.0.1:{}", port).parse().unwrap();
|
||||||
let io = IoHandler::default();
|
let io = MetaIoHandler::default();
|
||||||
let remote = event_loop.remote();
|
let authcodes = GuardedAuthCodes::new();
|
||||||
let server = builder.start(format!("127.0.0.1:{}", port).parse().unwrap(), io, remote).unwrap();
|
let stats = Arc::new(informant::RpcStats::default());
|
||||||
let res = ServerLoop {
|
|
||||||
server: server,
|
|
||||||
event_loop: event_loop,
|
|
||||||
};
|
|
||||||
|
|
||||||
(res, port, GuardedAuthCodes {
|
let res = Server::new(|remote| ::start_ws(
|
||||||
authcodes: AuthCodes::from_file(&path).unwrap(),
|
&address,
|
||||||
path: path,
|
io,
|
||||||
})
|
remote,
|
||||||
|
ws::DomainsValidation::Disabled,
|
||||||
|
ws::DomainsValidation::Disabled,
|
||||||
|
extractors::WsExtractor::new(Some(&authcodes.path)),
|
||||||
|
extractors::WsExtractor::new(Some(&authcodes.path)),
|
||||||
|
extractors::WsStats::new(stats),
|
||||||
|
).unwrap());
|
||||||
|
|
||||||
|
(res, port, authcodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test a single request to running server
|
/// Test a single request to running server
|
||||||
pub fn request(server: ServerLoop, request: &str) -> http_client::Response {
|
pub fn request(server: Server<ws::Server>, request: &str) -> http_client::Response {
|
||||||
http_client::request(server.server.addr(), request)
|
http_client::request(server.server.addr(), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,49 +60,6 @@ mod testing {
|
|||||||
use devtools::http_client;
|
use devtools::http_client;
|
||||||
use super::{serve, request};
|
use super::{serve, request};
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_reject_invalid_host() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET / HTTP/1.1\r\n\
|
|
||||||
Host: test:8180\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
|
||||||
assert!(response.body.contains("URL Blocked"));
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_allow_home_parity_host() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET http://parity.web3.site/ HTTP/1.1\r\n\
|
|
||||||
Host: parity.web3.site\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_not_redirect_to_parity_host() {
|
fn should_not_redirect_to_parity_host() {
|
||||||
// given
|
// given
|
||||||
@ -157,48 +77,7 @@ mod testing {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
assert_eq!(response.status, "HTTP/1.1 200 Ok".to_owned());
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_serve_styles_even_on_disallowed_domain() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET /styles.css HTTP/1.1\r\n\
|
|
||||||
Host: test:8180\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_200_ok_for_connect_requests() {
|
|
||||||
// given
|
|
||||||
let server = serve().0;
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
CONNECT parity.web3.site:8080 HTTP/1.1\r\n\
|
|
||||||
Host: parity.web3.site\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
{}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -221,7 +100,7 @@ mod testing {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
|
||||||
http_client::assert_security_headers_present(&response.headers, None);
|
http_client::assert_security_headers_present(&response.headers, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +179,7 @@ mod testing {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned());
|
||||||
assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned());
|
assert_eq!(response2.status, "HTTP/1.1 403 Forbidden".to_owned());
|
||||||
http_client::assert_security_headers_present(&response2.headers, None);
|
http_client::assert_security_headers_present(&response2.headers, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
263
rpc/src/v1/extractors.rs
Normal file
263
rpc/src/v1/extractors.rs
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//! Parity-specific metadata extractors.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use authcodes;
|
||||||
|
use http_common::HttpMetaExtractor;
|
||||||
|
use ipc;
|
||||||
|
use jsonrpc_core as core;
|
||||||
|
use jsonrpc_pubsub::Session;
|
||||||
|
use ws;
|
||||||
|
use util::H256;
|
||||||
|
|
||||||
|
use v1::{Metadata, Origin};
|
||||||
|
use v1::informant::RpcStats;
|
||||||
|
|
||||||
|
/// Common HTTP & IPC metadata extractor.
|
||||||
|
pub struct RpcExtractor;
|
||||||
|
|
||||||
|
impl HttpMetaExtractor for RpcExtractor {
|
||||||
|
type Metadata = Metadata;
|
||||||
|
|
||||||
|
fn read_metadata(&self, origin: Option<String>, user_agent: Option<String>, dapps_origin: Option<String>) -> Metadata {
|
||||||
|
let mut metadata = Metadata::default();
|
||||||
|
|
||||||
|
metadata.origin = match (origin.as_ref().map(|s| s.as_str()), user_agent, dapps_origin) {
|
||||||
|
(Some("null"), _, Some(dapp)) => Origin::Dapps(dapp.into()),
|
||||||
|
(Some(dapp), _, _) => Origin::Dapps(dapp.to_owned().into()),
|
||||||
|
(None, Some(service), _) => Origin::Rpc(service.into()),
|
||||||
|
(None, _, _) => Origin::Rpc("unknown".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ipc::MetaExtractor<Metadata> for RpcExtractor {
|
||||||
|
fn extract(&self, _req: &ipc::RequestContext) -> Metadata {
|
||||||
|
let mut metadata = Metadata::default();
|
||||||
|
// TODO [ToDr] Extract proper session id when it's available in context.
|
||||||
|
metadata.origin = Origin::Ipc(1.into());
|
||||||
|
metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WebSockets server metadata extractor and request middleware.
|
||||||
|
pub struct WsExtractor {
|
||||||
|
authcodes_path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsExtractor {
|
||||||
|
/// Creates new `WsExtractor` with given authcodes path.
|
||||||
|
pub fn new(path: Option<&Path>) -> Self {
|
||||||
|
WsExtractor {
|
||||||
|
authcodes_path: path.map(|p| p.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ws::MetaExtractor<Metadata> for WsExtractor {
|
||||||
|
fn extract(&self, req: &ws::RequestContext) -> Metadata {
|
||||||
|
let mut metadata = Metadata::default();
|
||||||
|
let id = req.session_id as u64;
|
||||||
|
// TODO [ToDr] Extract dapp from Origin
|
||||||
|
let dapp = "".into();
|
||||||
|
metadata.origin = match self.authcodes_path {
|
||||||
|
Some(ref path) => {
|
||||||
|
let authorization = req.protocols.get(0).and_then(|p| auth_token_hash(&path, p));
|
||||||
|
match authorization {
|
||||||
|
Some(id) => Origin::Signer { session: id.into(), dapp: dapp },
|
||||||
|
None => Origin::Ws { session: id.into(), dapp: dapp },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Origin::Ws { session: id.into(), dapp: dapp },
|
||||||
|
};
|
||||||
|
metadata.session = Some(Arc::new(Session::new(req.sender())));
|
||||||
|
metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ws::RequestMiddleware for WsExtractor {
|
||||||
|
fn process(&self, req: &ws::ws::Request) -> ws::MiddlewareAction {
|
||||||
|
use self::ws::ws::Response;
|
||||||
|
|
||||||
|
// Reply with 200 Ok to HEAD requests.
|
||||||
|
if req.method() == "HEAD" {
|
||||||
|
let mut response = Response::new(200, "Ok");
|
||||||
|
add_security_headers(&mut response);
|
||||||
|
return Some(response).into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display WS info.
|
||||||
|
if req.header("sec-websocket-key").is_none() {
|
||||||
|
let mut response = Response::new(200, "Ok");
|
||||||
|
response.set_body("WebSocket interface is active. Open WS connection to access RPC.");
|
||||||
|
add_security_headers(&mut response);
|
||||||
|
return Some(response).into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If protocol is provided it needs to be valid.
|
||||||
|
let protocols = req.protocols().ok().unwrap_or_else(Vec::new);
|
||||||
|
if let Some(ref path) = self.authcodes_path {
|
||||||
|
if protocols.len() == 1 {
|
||||||
|
let authorization = auth_token_hash(&path, protocols[0]);
|
||||||
|
if authorization.is_none() {
|
||||||
|
warn!(
|
||||||
|
"Blocked connection from {} using invalid token.",
|
||||||
|
req.header("origin").and_then(|e| ::std::str::from_utf8(e).ok()).unwrap_or("Unknown Origin")
|
||||||
|
);
|
||||||
|
let mut response = Response::new(403, "Forbidden");
|
||||||
|
add_security_headers(&mut response);
|
||||||
|
return Some(response).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just proceed.
|
||||||
|
ws::MiddlewareAction::Proceed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_security_headers(res: &mut ws::ws::Response) {
|
||||||
|
let mut headers = res.headers_mut();
|
||||||
|
headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec()));
|
||||||
|
headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec()));
|
||||||
|
headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn auth_token_hash(codes_path: &Path, protocol: &str) -> Option<H256> {
|
||||||
|
let mut split = protocol.split('_');
|
||||||
|
let auth = split.next().and_then(|v| v.parse().ok());
|
||||||
|
let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok());
|
||||||
|
|
||||||
|
if let (Some(auth), Some(time)) = (auth, time) {
|
||||||
|
// Check if the code is valid
|
||||||
|
return authcodes::AuthCodes::from_file(codes_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|mut codes| {
|
||||||
|
// remove old tokens
|
||||||
|
codes.clear_garbage();
|
||||||
|
|
||||||
|
let res = codes.is_valid(&auth, time);
|
||||||
|
// make sure to save back authcodes - it might have been modified
|
||||||
|
if codes.to_file(codes_path).is_err() {
|
||||||
|
warn!(target: "signer", "Couldn't save authorization codes to file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if res {
|
||||||
|
Some(auth)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WebSockets RPC usage statistics.
|
||||||
|
pub struct WsStats {
|
||||||
|
stats: Arc<RpcStats>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsStats {
|
||||||
|
/// Creates new WS usage tracker.
|
||||||
|
pub fn new(stats: Arc<RpcStats>) -> Self {
|
||||||
|
WsStats {
|
||||||
|
stats: stats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ws::SessionStats for WsStats {
|
||||||
|
fn open_session(&self, _id: ws::SessionId) {
|
||||||
|
self.stats.open_session()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_session(&self, _id: ws::SessionId) {
|
||||||
|
self.stats.close_session()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WebSockets middleware dispatching requests to different handles dependning on metadata.
|
||||||
|
pub struct WsDispatcher<M: core::Middleware<Metadata>> {
|
||||||
|
full_handler: core::MetaIoHandler<Metadata, M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: core::Middleware<Metadata>> WsDispatcher<M> {
|
||||||
|
/// Create new `WsDispatcher` with given full handler.
|
||||||
|
pub fn new(full_handler: core::MetaIoHandler<Metadata, M>) -> Self {
|
||||||
|
WsDispatcher {
|
||||||
|
full_handler: full_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: core::Middleware<Metadata>> core::Middleware<Metadata> for WsDispatcher<M> {
|
||||||
|
fn on_request<F>(&self, request: core::Request, meta: Metadata, process: F) -> core::FutureResponse where
|
||||||
|
F: FnOnce(core::Request, Metadata) -> core::FutureResponse,
|
||||||
|
{
|
||||||
|
let use_full = match &meta.origin {
|
||||||
|
&Origin::Signer { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if use_full {
|
||||||
|
self.full_handler.handle_rpc_request(request, meta)
|
||||||
|
} else {
|
||||||
|
process(request, meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::RpcExtractor;
|
||||||
|
use {HttpMetaExtractor, Origin};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_extract_rpc_origin() {
|
||||||
|
// given
|
||||||
|
let extractor = RpcExtractor;
|
||||||
|
|
||||||
|
// when
|
||||||
|
let meta1 = extractor.read_metadata(None, None, None);
|
||||||
|
let meta2 = extractor.read_metadata(None, Some("http://parity.io".to_owned()), None);
|
||||||
|
let meta3 = extractor.read_metadata(None, Some("http://parity.io".to_owned()), Some("ignored".into()));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(meta1.origin, Origin::Rpc("unknown".into()));
|
||||||
|
assert_eq!(meta2.origin, Origin::Rpc("http://parity.io".into()));
|
||||||
|
assert_eq!(meta3.origin, Origin::Rpc("http://parity.io".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_dapps_origin() {
|
||||||
|
// given
|
||||||
|
let extractor = RpcExtractor;
|
||||||
|
let dapp = "https://wallet.ethereum.org".to_owned();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let meta = extractor.read_metadata(Some("null".into()), None, Some(dapp.clone()));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(meta.origin, Origin::Dapps(dapp.into()));
|
||||||
|
}
|
||||||
|
}
|
33
rpc/src/v1/helpers/dapps.rs
Normal file
33
rpc/src/v1/helpers/dapps.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//! Dapps Service
|
||||||
|
|
||||||
|
use v1::types::LocalDapp;
|
||||||
|
|
||||||
|
/// Dapps Server service.
|
||||||
|
pub trait DappsService: Send + Sync + 'static {
|
||||||
|
/// List available local dapps.
|
||||||
|
fn list_dapps(&self) -> Vec<LocalDapp>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> DappsService for F where
|
||||||
|
F: Fn() -> Vec<LocalDapp> + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
fn list_dapps(&self) -> Vec<LocalDapp> {
|
||||||
|
(*self)()
|
||||||
|
}
|
||||||
|
}
|
@ -209,6 +209,14 @@ pub fn dapps_disabled() -> Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ws_disabled() -> Error {
|
||||||
|
Error {
|
||||||
|
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
|
||||||
|
message: "WebSockets Server is disabled. This API is not available.".into(),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn network_disabled() -> Error {
|
pub fn network_disabled() -> Error {
|
||||||
Error {
|
Error {
|
||||||
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
|
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
|
||||||
|
@ -19,10 +19,10 @@ pub mod errors;
|
|||||||
|
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub mod block_import;
|
pub mod block_import;
|
||||||
|
pub mod dapps;
|
||||||
pub mod dispatch;
|
pub mod dispatch;
|
||||||
pub mod fake_sign;
|
pub mod fake_sign;
|
||||||
pub mod light_fetch;
|
pub mod light_fetch;
|
||||||
pub mod informant;
|
|
||||||
pub mod oneshot;
|
pub mod oneshot;
|
||||||
pub mod ipfs;
|
pub mod ipfs;
|
||||||
pub mod secretstore;
|
pub mod secretstore;
|
||||||
@ -50,3 +50,7 @@ pub use self::signing_queue::{
|
|||||||
pub use self::signer::SignerService;
|
pub use self::signer::SignerService;
|
||||||
pub use self::subscribers::Subscribers;
|
pub use self::subscribers::Subscribers;
|
||||||
pub use self::subscription_manager::GenericPollManager;
|
pub use self::subscription_manager::GenericPollManager;
|
||||||
|
|
||||||
|
pub fn to_url(address: &Option<(String, u16)>) -> Option<String> {
|
||||||
|
address.as_ref().map(|&(ref iface, ref port)| format!("{}:{}", iface, port))
|
||||||
|
}
|
||||||
|
@ -27,21 +27,21 @@ const TOKEN_LIFETIME_SECS: u32 = 3600;
|
|||||||
|
|
||||||
/// Manages communication with Signer crate
|
/// Manages communication with Signer crate
|
||||||
pub struct SignerService {
|
pub struct SignerService {
|
||||||
|
is_enabled: bool,
|
||||||
queue: Arc<ConfirmationsQueue>,
|
queue: Arc<ConfirmationsQueue>,
|
||||||
web_proxy_tokens: Mutex<TransientHashMap<String, ()>>,
|
web_proxy_tokens: Mutex<TransientHashMap<String, ()>>,
|
||||||
generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>,
|
generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>,
|
||||||
address: Option<(String, u16)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignerService {
|
impl SignerService {
|
||||||
/// Creates new Signer Service given function to generate new tokens.
|
/// Creates new Signer Service given function to generate new tokens.
|
||||||
pub fn new<F>(new_token: F, address: Option<(String, u16)>) -> Self
|
pub fn new<F>(new_token: F, is_enabled: bool) -> Self
|
||||||
where F: Fn() -> Result<String, String> + Send + Sync + 'static {
|
where F: Fn() -> Result<String, String> + Send + Sync + 'static {
|
||||||
SignerService {
|
SignerService {
|
||||||
queue: Arc::new(ConfirmationsQueue::default()),
|
queue: Arc::new(ConfirmationsQueue::default()),
|
||||||
web_proxy_tokens: Mutex::new(TransientHashMap::new(TOKEN_LIFETIME_SECS)),
|
web_proxy_tokens: Mutex::new(TransientHashMap::new(TOKEN_LIFETIME_SECS)),
|
||||||
generate_new_token: Box::new(new_token),
|
generate_new_token: Box::new(new_token),
|
||||||
address: address,
|
is_enabled: is_enabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,20 +69,15 @@ impl SignerService {
|
|||||||
self.queue.clone()
|
self.queue.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns signer address (if signer enabled) or `None` otherwise
|
|
||||||
pub fn address(&self) -> Option<(String, u16)> {
|
|
||||||
self.address.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if Signer is enabled.
|
/// Returns true if Signer is enabled.
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.address.is_some()
|
self.is_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
/// Creates new Signer Service for tests.
|
/// Creates new Signer Service for tests.
|
||||||
pub fn new_test(address: Option<(String, u16)>) -> Self {
|
pub fn new_test(is_enabled: bool) -> Self {
|
||||||
SignerService::new(|| Ok("new_token".into()), address)
|
SignerService::new(|| Ok("new_token".into()), is_enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ use light::client::LightChainClient;
|
|||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use jsonrpc_macros::Trailing;
|
use jsonrpc_macros::Trailing;
|
||||||
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
||||||
use v1::helpers::dispatch::LightDispatcher;
|
use v1::helpers::dispatch::LightDispatcher;
|
||||||
use v1::helpers::light_fetch::LightFetch;
|
use v1::helpers::light_fetch::LightFetch;
|
||||||
use v1::metadata::Metadata;
|
use v1::metadata::Metadata;
|
||||||
@ -54,8 +54,8 @@ pub struct ParityClient {
|
|||||||
logger: Arc<RotatingLogger>,
|
logger: Arc<RotatingLogger>,
|
||||||
settings: Arc<NetworkSettings>,
|
settings: Arc<NetworkSettings>,
|
||||||
signer: Option<Arc<SignerService>>,
|
signer: Option<Arc<SignerService>>,
|
||||||
dapps_interface: Option<String>,
|
dapps_address: Option<(String, u16)>,
|
||||||
dapps_port: Option<u16>,
|
ws_address: Option<(String, u16)>,
|
||||||
eip86_transition: u64,
|
eip86_transition: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +68,8 @@ impl ParityClient {
|
|||||||
logger: Arc<RotatingLogger>,
|
logger: Arc<RotatingLogger>,
|
||||||
settings: Arc<NetworkSettings>,
|
settings: Arc<NetworkSettings>,
|
||||||
signer: Option<Arc<SignerService>>,
|
signer: Option<Arc<SignerService>>,
|
||||||
dapps_interface: Option<String>,
|
dapps_address: Option<(String, u16)>,
|
||||||
dapps_port: Option<u16>,
|
ws_address: Option<(String, u16)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ParityClient {
|
ParityClient {
|
||||||
light_dispatch: light_dispatch,
|
light_dispatch: light_dispatch,
|
||||||
@ -77,8 +77,8 @@ impl ParityClient {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
dapps_interface: dapps_interface,
|
dapps_address: dapps_address,
|
||||||
dapps_port: dapps_port,
|
ws_address: ws_address,
|
||||||
eip86_transition: client.eip86_transition(),
|
eip86_transition: client.eip86_transition(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,22 +294,14 @@ impl Parity for ParityClient {
|
|||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signer_port(&self) -> Result<u16, Error> {
|
fn dapps_url(&self) -> Result<String, Error> {
|
||||||
self.signer
|
helpers::to_url(&self.dapps_address)
|
||||||
.clone()
|
|
||||||
.and_then(|signer| signer.address())
|
|
||||||
.map(|address| address.1)
|
|
||||||
.ok_or_else(|| errors::signer_disabled())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dapps_port(&self) -> Result<u16, Error> {
|
|
||||||
self.dapps_port
|
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::dapps_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dapps_interface(&self) -> Result<String, Error> {
|
fn ws_url(&self) -> Result<String, Error> {
|
||||||
self.dapps_interface.clone()
|
helpers::to_url(&self.ws_address)
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::ws_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
||||||
|
@ -26,21 +26,24 @@ use futures::{BoxFuture, Future};
|
|||||||
use util::sha3;
|
use util::sha3;
|
||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
|
use v1::helpers::dapps::DappsService;
|
||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
use v1::traits::ParitySet;
|
use v1::traits::ParitySet;
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp};
|
||||||
|
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
pub struct ParitySetClient<F> {
|
pub struct ParitySetClient<F> {
|
||||||
net: Arc<ManageNetwork>,
|
net: Arc<ManageNetwork>,
|
||||||
|
dapps: Option<Arc<DappsService>>,
|
||||||
fetch: F,
|
fetch: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: Fetch> ParitySetClient<F> {
|
impl<F: Fetch> ParitySetClient<F> {
|
||||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||||
pub fn new(net: Arc<ManageNetwork>, fetch: F) -> Self {
|
pub fn new(net: Arc<ManageNetwork>, dapps: Option<Arc<DappsService>>, fetch: F) -> Self {
|
||||||
ParitySetClient {
|
ParitySetClient {
|
||||||
net: net,
|
net: net,
|
||||||
|
dapps: dapps,
|
||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,6 +135,10 @@ impl<F: Fetch> ParitySet for ParitySetClient<F> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error> {
|
||||||
|
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
|
||||||
|
}
|
||||||
|
|
||||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
||||||
Err(errors::light_unimplemented(None))
|
Err(errors::light_unimplemented(None))
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ use crypto::DEFAULT_MAC;
|
|||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use jsonrpc_macros::Trailing;
|
use jsonrpc_macros::Trailing;
|
||||||
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings};
|
||||||
use v1::helpers::accounts::unwrap_provider;
|
use v1::helpers::accounts::unwrap_provider;
|
||||||
use v1::metadata::Metadata;
|
use v1::metadata::Metadata;
|
||||||
use v1::traits::Parity;
|
use v1::traits::Parity;
|
||||||
@ -67,8 +67,8 @@ pub struct ParityClient<C, M, S: ?Sized, U> where
|
|||||||
logger: Arc<RotatingLogger>,
|
logger: Arc<RotatingLogger>,
|
||||||
settings: Arc<NetworkSettings>,
|
settings: Arc<NetworkSettings>,
|
||||||
signer: Option<Arc<SignerService>>,
|
signer: Option<Arc<SignerService>>,
|
||||||
dapps_interface: Option<String>,
|
dapps_address: Option<(String, u16)>,
|
||||||
dapps_port: Option<u16>,
|
ws_address: Option<(String, u16)>,
|
||||||
eip86_transition: u64,
|
eip86_transition: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
|||||||
logger: Arc<RotatingLogger>,
|
logger: Arc<RotatingLogger>,
|
||||||
settings: Arc<NetworkSettings>,
|
settings: Arc<NetworkSettings>,
|
||||||
signer: Option<Arc<SignerService>>,
|
signer: Option<Arc<SignerService>>,
|
||||||
dapps_interface: Option<String>,
|
dapps_address: Option<(String, u16)>,
|
||||||
dapps_port: Option<u16>,
|
ws_address: Option<(String, u16)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ParityClient {
|
ParityClient {
|
||||||
client: Arc::downgrade(client),
|
client: Arc::downgrade(client),
|
||||||
@ -102,8 +102,8 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
dapps_interface: dapps_interface,
|
dapps_address: dapps_address,
|
||||||
dapps_port: dapps_port,
|
ws_address: ws_address,
|
||||||
eip86_transition: client.eip86_transition(),
|
eip86_transition: client.eip86_transition(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,22 +317,14 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signer_port(&self) -> Result<u16, Error> {
|
fn dapps_url(&self) -> Result<String, Error> {
|
||||||
self.signer
|
helpers::to_url(&self.dapps_address)
|
||||||
.clone()
|
|
||||||
.and_then(|signer| signer.address())
|
|
||||||
.map(|address| address.1)
|
|
||||||
.ok_or_else(|| errors::signer_disabled())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dapps_port(&self) -> Result<u16, Error> {
|
|
||||||
self.dapps_port
|
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::dapps_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dapps_interface(&self) -> Result<String, Error> {
|
fn ws_url(&self) -> Result<String, Error> {
|
||||||
self.dapps_interface.clone()
|
helpers::to_url(&self.ws_address)
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::ws_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
fn next_nonce(&self, address: H160) -> BoxFuture<U256, Error> {
|
||||||
|
@ -28,9 +28,10 @@ use util::sha3;
|
|||||||
use updater::{Service as UpdateService};
|
use updater::{Service as UpdateService};
|
||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
|
use v1::helpers::dapps::DappsService;
|
||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
use v1::traits::ParitySet;
|
use v1::traits::ParitySet;
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp};
|
||||||
|
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
||||||
@ -38,6 +39,7 @@ pub struct ParitySetClient<C, M, U, F = fetch::Client> {
|
|||||||
miner: Weak<M>,
|
miner: Weak<M>,
|
||||||
updater: Weak<U>,
|
updater: Weak<U>,
|
||||||
net: Weak<ManageNetwork>,
|
net: Weak<ManageNetwork>,
|
||||||
|
dapps: Option<Arc<DappsService>>,
|
||||||
fetch: F,
|
fetch: F,
|
||||||
eip86_transition: u64,
|
eip86_transition: u64,
|
||||||
}
|
}
|
||||||
@ -46,12 +48,20 @@ impl<C, M, U, F> ParitySetClient<C, M, U, F>
|
|||||||
where C: MiningBlockChainClient + 'static,
|
where C: MiningBlockChainClient + 'static,
|
||||||
{
|
{
|
||||||
/// Creates new `ParitySetClient` with given `Fetch`.
|
/// Creates new `ParitySetClient` with given `Fetch`.
|
||||||
pub fn new(client: &Arc<C>, miner: &Arc<M>, updater: &Arc<U>, net: &Arc<ManageNetwork>, fetch: F) -> Self {
|
pub fn new(
|
||||||
|
client: &Arc<C>,
|
||||||
|
miner: &Arc<M>,
|
||||||
|
updater: &Arc<U>,
|
||||||
|
net: &Arc<ManageNetwork>,
|
||||||
|
dapps: Option<Arc<DappsService>>,
|
||||||
|
fetch: F,
|
||||||
|
) -> Self {
|
||||||
ParitySetClient {
|
ParitySetClient {
|
||||||
client: Arc::downgrade(client),
|
client: Arc::downgrade(client),
|
||||||
miner: Arc::downgrade(miner),
|
miner: Arc::downgrade(miner),
|
||||||
updater: Arc::downgrade(updater),
|
updater: Arc::downgrade(updater),
|
||||||
net: Arc::downgrade(net),
|
net: Arc::downgrade(net),
|
||||||
|
dapps: dapps,
|
||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
eip86_transition: client.eip86_transition(),
|
eip86_transition: client.eip86_transition(),
|
||||||
}
|
}
|
||||||
@ -166,6 +176,10 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error> {
|
||||||
|
self.dapps.as_ref().map(|dapps| dapps.list_dapps()).ok_or_else(errors::dapps_disabled)
|
||||||
|
}
|
||||||
|
|
||||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.upgrade_ready().map(Into::into))
|
Ok(updater.upgrade_ready().map(Into::into))
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Parity RPC requests Metadata.
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use jsonrpc_core;
|
use jsonrpc_core;
|
||||||
@ -35,7 +36,9 @@ impl Metadata {
|
|||||||
pub fn dapp_id(&self) -> DappId {
|
pub fn dapp_id(&self) -> DappId {
|
||||||
// TODO [ToDr] Extract dapp info from Ws connections.
|
// TODO [ToDr] Extract dapp info from Ws connections.
|
||||||
match self.origin {
|
match self.origin {
|
||||||
Origin::Dapps(ref dapp_id) => dapp_id.clone(),
|
Origin::Dapps(ref dapp) => dapp.clone(),
|
||||||
|
Origin::Ws { ref dapp, .. } => dapp.clone(),
|
||||||
|
Origin::Signer { ref dapp, .. } => dapp.clone(),
|
||||||
_ => DappId::default(),
|
_ => DappId::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,14 +52,30 @@ macro_rules! try_bf {
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod impls;
|
mod impls;
|
||||||
mod metadata;
|
mod types;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub mod extractors;
|
||||||
|
pub mod informant;
|
||||||
|
pub mod metadata;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod tests;
|
|
||||||
pub mod types;
|
|
||||||
|
|
||||||
pub use self::traits::{Web3, Eth, EthFilter, EthPubSub, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore};
|
pub use self::traits::{Web3, Eth, EthFilter, EthPubSub, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore};
|
||||||
pub use self::impls::*;
|
pub use self::impls::*;
|
||||||
pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch};
|
pub use self::helpers::{NetworkSettings, block_import, dispatch};
|
||||||
pub use self::metadata::Metadata;
|
pub use self::metadata::Metadata;
|
||||||
pub use self::types::Origin;
|
pub use self::types::Origin;
|
||||||
|
pub use self::extractors::{RpcExtractor, WsExtractor, WsStats, WsDispatcher};
|
||||||
|
|
||||||
|
/// Signer utilities
|
||||||
|
pub mod signer {
|
||||||
|
pub use super::helpers::{SigningQueue, SignerService, ConfirmationsQueue};
|
||||||
|
pub use super::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dapps integration utilities
|
||||||
|
pub mod dapps {
|
||||||
|
pub use super::helpers::dapps::DappsService;
|
||||||
|
pub use super::types::LocalDapp;
|
||||||
|
}
|
||||||
|
37
rpc/src/v1/tests/helpers/dapps.rs
Normal file
37
rpc/src/v1/tests/helpers/dapps.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//! Test implementation of dapps service.
|
||||||
|
|
||||||
|
use v1::types::LocalDapp;
|
||||||
|
use v1::helpers::dapps::DappsService;
|
||||||
|
|
||||||
|
/// Test implementation of dapps service. Will always return the same list of dapps.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct TestDappsService;
|
||||||
|
|
||||||
|
impl DappsService for TestDappsService {
|
||||||
|
fn list_dapps(&self) -> Vec<LocalDapp> {
|
||||||
|
vec![LocalDapp {
|
||||||
|
id: "skeleton".into(),
|
||||||
|
name: "Skeleton".into(),
|
||||||
|
description: "A skeleton dapp".into(),
|
||||||
|
version: "0.1".into(),
|
||||||
|
author: "Parity Technologies Ltd".into(),
|
||||||
|
icon_url: "title.png".into(),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -16,14 +16,16 @@
|
|||||||
|
|
||||||
//! Test rpc services.
|
//! Test rpc services.
|
||||||
|
|
||||||
mod sync_provider;
|
mod dapps;
|
||||||
mod miner_service;
|
|
||||||
mod fetch;
|
mod fetch;
|
||||||
|
mod miner_service;
|
||||||
mod snapshot_service;
|
mod snapshot_service;
|
||||||
|
mod sync_provider;
|
||||||
mod update_service;
|
mod update_service;
|
||||||
|
|
||||||
pub use self::sync_provider::{Config, TestSyncProvider};
|
pub use self::dapps::TestDappsService;
|
||||||
pub use self::miner_service::TestMinerService;
|
|
||||||
pub use self::fetch::TestFetch;
|
pub use self::fetch::TestFetch;
|
||||||
|
pub use self::miner_service::TestMinerService;
|
||||||
pub use self::snapshot_service::TestSnapshotService;
|
pub use self::snapshot_service::TestSnapshotService;
|
||||||
|
pub use self::sync_provider::{Config, TestSyncProvider};
|
||||||
pub use self::update_service::TestUpdater;
|
pub use self::update_service::TestUpdater;
|
@ -41,8 +41,8 @@ pub struct Dependencies {
|
|||||||
pub settings: Arc<NetworkSettings>,
|
pub settings: Arc<NetworkSettings>,
|
||||||
pub network: Arc<ManageNetwork>,
|
pub network: Arc<ManageNetwork>,
|
||||||
pub accounts: Arc<AccountProvider>,
|
pub accounts: Arc<AccountProvider>,
|
||||||
pub dapps_interface: Option<String>,
|
pub dapps_address: Option<(String, u16)>,
|
||||||
pub dapps_port: Option<u16>,
|
pub ws_address: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dependencies {
|
impl Dependencies {
|
||||||
@ -66,8 +66,8 @@ impl Dependencies {
|
|||||||
}),
|
}),
|
||||||
network: Arc::new(TestManageNetwork),
|
network: Arc::new(TestManageNetwork),
|
||||||
accounts: Arc::new(AccountProvider::transient_provider()),
|
accounts: Arc::new(AccountProvider::transient_provider()),
|
||||||
dapps_interface: Some("127.0.0.1".into()),
|
dapps_address: Some(("127.0.0.1".into(), 18080)),
|
||||||
dapps_port: Some(18080),
|
ws_address: Some(("127.0.0.1".into(), 18546)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,8 +84,8 @@ impl Dependencies {
|
|||||||
self.logger.clone(),
|
self.logger.clone(),
|
||||||
self.settings.clone(),
|
self.settings.clone(),
|
||||||
signer,
|
signer,
|
||||||
self.dapps_interface.clone(),
|
self.dapps_address.clone(),
|
||||||
self.dapps_port,
|
self.ws_address.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +345,7 @@ fn rpc_parity_node_name() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rpc_parity_unsigned_transactions_count() {
|
fn rpc_parity_unsigned_transactions_count() {
|
||||||
let deps = Dependencies::new();
|
let deps = Dependencies::new();
|
||||||
let io = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180))));
|
let io = deps.with_signer(SignerService::new_test(true));
|
||||||
|
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#;
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#;
|
||||||
let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#;
|
let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#;
|
||||||
@ -386,16 +386,17 @@ fn rpc_parity_encrypt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_parity_signer_port() {
|
fn rpc_parity_ws_address() {
|
||||||
// given
|
// given
|
||||||
let deps = Dependencies::new();
|
let mut deps = Dependencies::new();
|
||||||
let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180))));
|
let io1 = deps.default_client();
|
||||||
|
deps.ws_address = None;
|
||||||
let io2 = deps.default_client();
|
let io2 = deps.default_client();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_signerPort", "params": [], "id": 1}"#;
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_wsUrl", "params": [], "id": 1}"#;
|
||||||
let response1 = r#"{"jsonrpc":"2.0","result":18180,"id":1}"#;
|
let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1:18546","id":1}"#;
|
||||||
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Trusted Signer is disabled. This API is not available."},"id":1}"#;
|
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"WebSockets Server is disabled. This API is not available."},"id":1}"#;
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned()));
|
assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned()));
|
||||||
@ -403,34 +404,16 @@ fn rpc_parity_signer_port() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_parity_dapps_port() {
|
fn rpc_parity_dapps_address() {
|
||||||
// given
|
// given
|
||||||
let mut deps = Dependencies::new();
|
let mut deps = Dependencies::new();
|
||||||
let io1 = deps.default_client();
|
let io1 = deps.default_client();
|
||||||
deps.dapps_port = None;
|
deps.dapps_address = None;
|
||||||
let io2 = deps.default_client();
|
let io2 = deps.default_client();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsPort", "params": [], "id": 1}"#;
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsUrl", "params": [], "id": 1}"#;
|
||||||
let response1 = r#"{"jsonrpc":"2.0","result":18080,"id":1}"#;
|
let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1:18080","id":1}"#;
|
||||||
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Dapps Server is disabled. This API is not available."},"id":1}"#;
|
|
||||||
|
|
||||||
// then
|
|
||||||
assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned()));
|
|
||||||
assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rpc_parity_dapps_interface() {
|
|
||||||
// given
|
|
||||||
let mut deps = Dependencies::new();
|
|
||||||
let io1 = deps.default_client();
|
|
||||||
deps.dapps_interface = None;
|
|
||||||
let io2 = deps.default_client();
|
|
||||||
|
|
||||||
// when
|
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsInterface", "params": [], "id": 1}"#;
|
|
||||||
let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1","id":1}"#;
|
|
||||||
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Dapps Server is disabled. This API is not available."},"id":1}"#;
|
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"Dapps Server is disabled. This API is not available."},"id":1}"#;
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
@ -25,7 +25,7 @@ use ethsync::ManageNetwork;
|
|||||||
|
|
||||||
use jsonrpc_core::IoHandler;
|
use jsonrpc_core::IoHandler;
|
||||||
use v1::{ParitySet, ParitySetClient};
|
use v1::{ParitySet, ParitySetClient};
|
||||||
use v1::tests::helpers::{TestMinerService, TestFetch, TestUpdater};
|
use v1::tests::helpers::{TestMinerService, TestFetch, TestUpdater, TestDappsService};
|
||||||
use super::manage_network::TestManageNetwork;
|
use super::manage_network::TestManageNetwork;
|
||||||
|
|
||||||
fn miner_service() -> Arc<TestMinerService> {
|
fn miner_service() -> Arc<TestMinerService> {
|
||||||
@ -46,8 +46,14 @@ fn updater_service() -> Arc<TestUpdater> {
|
|||||||
|
|
||||||
pub type TestParitySetClient = ParitySetClient<TestBlockChainClient, TestMinerService, TestUpdater, TestFetch>;
|
pub type TestParitySetClient = ParitySetClient<TestBlockChainClient, TestMinerService, TestUpdater, TestFetch>;
|
||||||
|
|
||||||
fn parity_set_client(client: &Arc<TestBlockChainClient>, miner: &Arc<TestMinerService>, updater: &Arc<TestUpdater>, net: &Arc<TestManageNetwork>) -> TestParitySetClient {
|
fn parity_set_client(
|
||||||
ParitySetClient::new(client, miner, updater, &(net.clone() as Arc<ManageNetwork>), TestFetch::default())
|
client: &Arc<TestBlockChainClient>,
|
||||||
|
miner: &Arc<TestMinerService>,
|
||||||
|
updater: &Arc<TestUpdater>,
|
||||||
|
net: &Arc<TestManageNetwork>,
|
||||||
|
) -> TestParitySetClient {
|
||||||
|
let dapps_service = Arc::new(TestDappsService);
|
||||||
|
ParitySetClient::new(client, miner, updater, &(net.clone() as Arc<ManageNetwork>), Some(dapps_service), TestFetch::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -232,3 +238,18 @@ fn rpc_parity_remove_transaction() {
|
|||||||
miner.pending_transactions.lock().insert(hash, signed);
|
miner.pending_transactions.lock().insert(hash, signed);
|
||||||
assert_eq!(io.handle_request_sync(&request), Some(response.to_owned()));
|
assert_eq!(io.handle_request_sync(&request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_parity_set_dapps_list() {
|
||||||
|
let miner = miner_service();
|
||||||
|
let client = client_service();
|
||||||
|
let network = network_service();
|
||||||
|
let updater = updater_service();
|
||||||
|
let mut io = IoHandler::new();
|
||||||
|
io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate());
|
||||||
|
|
||||||
|
let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsList", "params":[], "id": 1}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":[{"author":"Parity Technologies Ltd","description":"A skeleton dapp","iconUrl":"title.png","id":"skeleton","name":"Skeleton","version":"0.1"}],"id":1}"#;
|
||||||
|
|
||||||
|
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
|
}
|
||||||
|
@ -58,7 +58,7 @@ fn miner_service() -> Arc<TestMinerService> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signer_tester() -> SignerTester {
|
fn signer_tester() -> SignerTester {
|
||||||
let signer = Arc::new(SignerService::new_test(None));
|
let signer = Arc::new(SignerService::new_test(false));
|
||||||
let accounts = accounts_provider();
|
let accounts = accounts_provider();
|
||||||
let opt_accounts = Some(accounts.clone());
|
let opt_accounts = Some(accounts.clone());
|
||||||
let client = blockchain_client();
|
let client = blockchain_client();
|
||||||
|
@ -47,7 +47,7 @@ struct SigningTester {
|
|||||||
|
|
||||||
impl Default for SigningTester {
|
impl Default for SigningTester {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let signer = Arc::new(SignerService::new_test(None));
|
let signer = Arc::new(SignerService::new_test(false));
|
||||||
let client = Arc::new(TestBlockChainClient::default());
|
let client = Arc::new(TestBlockChainClient::default());
|
||||||
let miner = Arc::new(TestMinerService::default());
|
let miner = Arc::new(TestMinerService::default());
|
||||||
let accounts = Arc::new(AccountProvider::transient_provider());
|
let accounts = Arc::new(AccountProvider::transient_provider());
|
||||||
|
@ -151,17 +151,13 @@ build_rpc_trait! {
|
|||||||
#[rpc(name = "parity_localTransactions")]
|
#[rpc(name = "parity_localTransactions")]
|
||||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error>;
|
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error>;
|
||||||
|
|
||||||
/// Returns current Trusted Signer port or an error if signer is disabled.
|
/// Returns current Dapps Server interface and port or an error if dapps server is disabled.
|
||||||
#[rpc(name = "parity_signerPort")]
|
#[rpc(name = "parity_dappsUrl")]
|
||||||
fn signer_port(&self) -> Result<u16, Error>;
|
fn dapps_url(&self) -> Result<String, Error>;
|
||||||
|
|
||||||
/// Returns current Dapps Server port or an error if dapps server is disabled.
|
/// Returns current WS Server interface and port or an error if ws server is disabled.
|
||||||
#[rpc(name = "parity_dappsPort")]
|
#[rpc(name = "parity_wsUrl")]
|
||||||
fn dapps_port(&self) -> Result<u16, Error>;
|
fn ws_url(&self) -> Result<String, Error>;
|
||||||
|
|
||||||
/// Returns current Dapps Server interface address or an error if dapps server is disabled.
|
|
||||||
#[rpc(name = "parity_dappsInterface")]
|
|
||||||
fn dapps_interface(&self) -> Result<String, Error>;
|
|
||||||
|
|
||||||
/// Returns next nonce for particular sender. Should include all transactions in the queue.
|
/// Returns next nonce for particular sender. Should include all transactions in the queue.
|
||||||
#[rpc(async, name = "parity_nextNonce")]
|
#[rpc(async, name = "parity_nextNonce")]
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use futures::BoxFuture;
|
use futures::BoxFuture;
|
||||||
|
|
||||||
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction};
|
use v1::types::{Bytes, H160, H256, U256, ReleaseInfo, Transaction, LocalDapp};
|
||||||
|
|
||||||
build_rpc_trait! {
|
build_rpc_trait! {
|
||||||
/// Parity-specific rpc interface for operations altering the settings.
|
/// Parity-specific rpc interface for operations altering the settings.
|
||||||
@ -96,6 +96,10 @@ build_rpc_trait! {
|
|||||||
#[rpc(async, name = "parity_hashContent")]
|
#[rpc(async, name = "parity_hashContent")]
|
||||||
fn hash_content(&self, String) -> BoxFuture<H256, Error>;
|
fn hash_content(&self, String) -> BoxFuture<H256, Error>;
|
||||||
|
|
||||||
|
/// Returns a list of local dapps
|
||||||
|
#[rpc(name = "parity_dappsList")]
|
||||||
|
fn dapps_list(&self) -> Result<Vec<LocalDapp>, Error>;
|
||||||
|
|
||||||
/// Is there a release ready for install?
|
/// Is there a release ready for install?
|
||||||
#[rpc(name = "parity_upgradeReady")]
|
#[rpc(name = "parity_upgradeReady")]
|
||||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error>;
|
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error>;
|
||||||
|
@ -283,12 +283,15 @@ mod tests {
|
|||||||
nonce: Some(1.into()),
|
nonce: Some(1.into()),
|
||||||
condition: None,
|
condition: None,
|
||||||
}),
|
}),
|
||||||
origin: Origin::Signer(5.into()),
|
origin: Origin::Signer {
|
||||||
|
dapp: "http://parity.io".into(),
|
||||||
|
session: 5.into(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
let res = serde_json::to_string(&ConfirmationRequest::from(request));
|
||||||
let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":"0x0000000000000000000000000000000000000000000000000000000000000005"}}"#;
|
let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":{"dapp":"http://parity.io","session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#;
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(res.unwrap(), expected.to_owned());
|
assert_eq!(res.unwrap(), expected.to_owned());
|
||||||
|
57
rpc/src/v1/types/dapps.rs
Normal file
57
rpc/src/v1/types/dapps.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
/// Local Dapp
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct LocalDapp {
|
||||||
|
/// ID of local dapp
|
||||||
|
pub id: String,
|
||||||
|
/// Dapp name
|
||||||
|
pub name: String,
|
||||||
|
/// Dapp description
|
||||||
|
pub description: String,
|
||||||
|
/// Dapp version string
|
||||||
|
pub version: String,
|
||||||
|
/// Dapp author
|
||||||
|
pub author: String,
|
||||||
|
/// Dapp icon
|
||||||
|
#[serde(rename="iconUrl")]
|
||||||
|
pub icon_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json;
|
||||||
|
use super::LocalDapp;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dapp_serialization() {
|
||||||
|
let s = r#"{"id":"skeleton","name":"Skeleton","description":"A skeleton dapp","version":"0.1","author":"Parity Technologies Ltd","iconUrl":"title.png"}"#;
|
||||||
|
|
||||||
|
let dapp = LocalDapp {
|
||||||
|
id: "skeleton".into(),
|
||||||
|
name: "Skeleton".into(),
|
||||||
|
description: "A skeleton dapp".into(),
|
||||||
|
version: "0.1".into(),
|
||||||
|
author: "Parity Technologies Ltd".into(),
|
||||||
|
icon_url: "title.png".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&dapp).unwrap();
|
||||||
|
assert_eq!(serialized, s);
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ mod bytes;
|
|||||||
mod call_request;
|
mod call_request;
|
||||||
mod confirmations;
|
mod confirmations;
|
||||||
mod consensus_status;
|
mod consensus_status;
|
||||||
|
mod dapps;
|
||||||
mod derivation;
|
mod derivation;
|
||||||
mod filter;
|
mod filter;
|
||||||
mod hash;
|
mod hash;
|
||||||
@ -55,6 +56,7 @@ pub use self::confirmations::{
|
|||||||
TransactionModification, SignRequest, DecryptRequest, Either
|
TransactionModification, SignRequest, DecryptRequest, Either
|
||||||
};
|
};
|
||||||
pub use self::consensus_status::*;
|
pub use self::consensus_status::*;
|
||||||
|
pub use self::dapps::LocalDapp;
|
||||||
pub use self::derivation::{DeriveHash, DeriveHierarchical, Derive};
|
pub use self::derivation::{DeriveHash, DeriveHierarchical, Derive};
|
||||||
pub use self::filter::{Filter, FilterChanges};
|
pub use self::filter::{Filter, FilterChanges};
|
||||||
pub use self::hash::{H64, H160, H256, H512, H520, H2048};
|
pub use self::hash::{H64, H160, H256, H512, H520, H2048};
|
||||||
|
@ -33,12 +33,22 @@ pub enum Origin {
|
|||||||
/// IPC server (includes session hash)
|
/// IPC server (includes session hash)
|
||||||
#[serde(rename="ipc")]
|
#[serde(rename="ipc")]
|
||||||
Ipc(H256),
|
Ipc(H256),
|
||||||
/// WS server (includes session hash)
|
/// WS server
|
||||||
#[serde(rename="ws")]
|
#[serde(rename="ws")]
|
||||||
Ws(H256),
|
Ws {
|
||||||
/// Signer (includes session hash)
|
/// Dapp id
|
||||||
|
dapp: DappId,
|
||||||
|
/// Session id
|
||||||
|
session: H256,
|
||||||
|
},
|
||||||
|
/// Signer (authorized WS server)
|
||||||
#[serde(rename="signer")]
|
#[serde(rename="signer")]
|
||||||
Signer(H256),
|
Signer {
|
||||||
|
/// Dapp id
|
||||||
|
dapp: DappId,
|
||||||
|
/// Session id
|
||||||
|
session: H256
|
||||||
|
},
|
||||||
/// Unknown
|
/// Unknown
|
||||||
#[serde(rename="unknown")]
|
#[serde(rename="unknown")]
|
||||||
Unknown,
|
Unknown,
|
||||||
@ -53,11 +63,11 @@ impl Default for Origin {
|
|||||||
impl fmt::Display for Origin {
|
impl fmt::Display for Origin {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Origin::Rpc(ref origin) => write!(f, "RPC (service: {})", origin),
|
Origin::Rpc(ref origin) => write!(f, "{} via RPC", origin),
|
||||||
Origin::Dapps(ref origin) => write!(f, "Dapp {}", origin),
|
Origin::Dapps(ref origin) => write!(f, "Dapp {}", origin),
|
||||||
Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session),
|
Origin::Ipc(ref session) => write!(f, "IPC (session: {})", session),
|
||||||
Origin::Ws(ref session) => write!(f, "WebSocket (session: {})", session),
|
Origin::Ws { ref session, ref dapp } => write!(f, "{} via WebSocket (session: {})", dapp, session),
|
||||||
Origin::Signer(ref session) => write!(f, "UI (session: {})", session),
|
Origin::Signer { ref session, ref dapp } => write!(f, "{} via UI (session: {})", dapp, session),
|
||||||
Origin::Unknown => write!(f, "unknown origin"),
|
Origin::Unknown => write!(f, "unknown origin"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,9 +124,15 @@ mod tests {
|
|||||||
let o1 = Origin::Rpc("test service".into());
|
let o1 = Origin::Rpc("test service".into());
|
||||||
let o2 = Origin::Dapps("http://parity.io".into());
|
let o2 = Origin::Dapps("http://parity.io".into());
|
||||||
let o3 = Origin::Ipc(5.into());
|
let o3 = Origin::Ipc(5.into());
|
||||||
let o4 = Origin::Signer(10.into());
|
let o4 = Origin::Signer {
|
||||||
|
dapp: "http://parity.io".into(),
|
||||||
|
session: 10.into(),
|
||||||
|
};
|
||||||
let o5 = Origin::Unknown;
|
let o5 = Origin::Unknown;
|
||||||
let o6 = Origin::Ws(5.into());
|
let o6 = Origin::Ws {
|
||||||
|
dapp: "http://parity.io".into(),
|
||||||
|
session: 5.into(),
|
||||||
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let res1 = serde_json::to_string(&o1).unwrap();
|
let res1 = serde_json::to_string(&o1).unwrap();
|
||||||
@ -130,9 +146,9 @@ mod tests {
|
|||||||
assert_eq!(res1, r#"{"rpc":"test service"}"#);
|
assert_eq!(res1, r#"{"rpc":"test service"}"#);
|
||||||
assert_eq!(res2, r#"{"dapp":"http://parity.io"}"#);
|
assert_eq!(res2, r#"{"dapp":"http://parity.io"}"#);
|
||||||
assert_eq!(res3, r#"{"ipc":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#);
|
assert_eq!(res3, r#"{"ipc":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#);
|
||||||
assert_eq!(res4, r#"{"signer":"0x000000000000000000000000000000000000000000000000000000000000000a"}"#);
|
assert_eq!(res4, r#"{"signer":{"dapp":"http://parity.io","session":"0x000000000000000000000000000000000000000000000000000000000000000a"}}"#);
|
||||||
assert_eq!(res5, r#""unknown""#);
|
assert_eq!(res5, r#""unknown""#);
|
||||||
assert_eq!(res6, r#"{"ws":"0x0000000000000000000000000000000000000000000000000000000000000005"}"#);
|
assert_eq!(res6, r#"{"ws":{"dapp":"http://parity.io","session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -7,7 +7,7 @@ extern crate ethcore_bigint as bigint;
|
|||||||
extern crate parity_rpc as rpc;
|
extern crate parity_rpc as rpc;
|
||||||
extern crate parity_rpc_client as client;
|
extern crate parity_rpc_client as client;
|
||||||
|
|
||||||
use rpc::v1::types::{U256, ConfirmationRequest};
|
use rpc::signer::{U256, ConfirmationRequest};
|
||||||
use client::signer_client::SignerRpc;
|
use client::signer_client::SignerRpc;
|
||||||
use std::io::{Write, BufRead, BufReader, stdout, stdin};
|
use std::io::{Write, BufRead, BufReader, stdout, stdin};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -14,8 +14,8 @@ serde = "0.9"
|
|||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
tempdir = "0.3.5"
|
tempdir = "0.3.5"
|
||||||
url = "1.2.0"
|
url = "1.2.0"
|
||||||
|
matches = "0.1"
|
||||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||||
ws = { git = "https://github.com/paritytech/ws-rs.git", branch = "parity-1.7" }
|
jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||||
parity-rpc = { path = "../rpc" }
|
parity-rpc = { path = "../rpc" }
|
||||||
ethcore-signer = { path = "../signer" }
|
|
||||||
ethcore-util = { path = "../util" }
|
ethcore-util = { path = "../util" }
|
||||||
|
@ -13,7 +13,7 @@ use util::{Hashable, Mutex};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
use ws::{
|
use ws::ws::{
|
||||||
self,
|
self,
|
||||||
Request,
|
Request,
|
||||||
Handler,
|
Handler,
|
||||||
@ -204,6 +204,7 @@ impl Rpc {
|
|||||||
let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?;
|
let rpc = Self::connect(url, authpath).map(|rpc| rpc).wait()?;
|
||||||
rpc
|
rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-blocking, returns a future
|
/// Non-blocking, returns a future
|
||||||
pub fn connect(
|
pub fn connect(
|
||||||
url: &str, authpath: &PathBuf
|
url: &str, authpath: &PathBuf
|
||||||
@ -241,6 +242,7 @@ impl Rpc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-blocking, returns a future of the request response
|
/// Non-blocking, returns a future of the request response
|
||||||
pub fn request<T>(
|
pub fn request<T>(
|
||||||
&mut self, method: &'static str, params: Vec<JsonValue>
|
&mut self, method: &'static str, params: Vec<JsonValue>
|
||||||
|
@ -1,34 +1,36 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod signer_client;
|
pub mod signer_client;
|
||||||
|
|
||||||
extern crate ethcore_signer;
|
|
||||||
extern crate ethcore_util as util;
|
extern crate ethcore_util as util;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate jsonrpc_core;
|
extern crate jsonrpc_core;
|
||||||
|
extern crate jsonrpc_ws_server as ws;
|
||||||
extern crate parity_rpc as rpc;
|
extern crate parity_rpc as rpc;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate ws;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate matches;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[macro_use]
|
|
||||||
extern crate matches;
|
|
||||||
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use client::{Rpc, RpcError};
|
use client::{Rpc, RpcError};
|
||||||
use ethcore_signer;
|
use rpc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_connection_refused() {
|
fn test_connection_refused() {
|
||||||
let (_srv, port, mut authcodes) = ethcore_signer::tests::serve();
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
let _ = authcodes.generate_new();
|
let _ = authcodes.generate_new();
|
||||||
authcodes.to_file(&authcodes.path).unwrap();
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
@ -43,7 +45,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_authcode_fail() {
|
fn test_authcode_fail() {
|
||||||
let (_srv, port, _) = ethcore_signer::tests::serve();
|
let (_srv, port, _) = rpc::tests::ws::serve();
|
||||||
let path = PathBuf::from("nonexist");
|
let path = PathBuf::from("nonexist");
|
||||||
|
|
||||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
||||||
@ -55,7 +57,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_authcode_correct() {
|
fn test_authcode_correct() {
|
||||||
let (_srv, port, mut authcodes) = ethcore_signer::tests::serve();
|
let (_srv, port, mut authcodes) = rpc::tests::ws::serve();
|
||||||
|
|
||||||
let _ = authcodes.generate_new();
|
let _ = authcodes.generate_new();
|
||||||
authcodes.to_file(&authcodes.path).unwrap();
|
authcodes.to_file(&authcodes.path).unwrap();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use client::{Rpc, RpcError};
|
use client::{Rpc, RpcError};
|
||||||
use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
|
use rpc::signer::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json::{Value as JsonValue, to_value};
|
use serde_json::{Value as JsonValue, to_value};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -13,11 +13,11 @@ impl SignerRpc {
|
|||||||
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
||||||
Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? })
|
Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? })
|
||||||
}
|
}
|
||||||
pub fn requests_to_confirm(&mut self) ->
|
|
||||||
BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled>
|
pub fn requests_to_confirm(&mut self) -> BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled> {
|
||||||
{
|
|
||||||
self.rpc.request("signer_requestsToConfirm", vec![])
|
self.rpc.request("signer_requestsToConfirm", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm_request(
|
pub fn confirm_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: U256,
|
id: U256,
|
||||||
@ -25,17 +25,15 @@ impl SignerRpc {
|
|||||||
new_gas_price: Option<U256>,
|
new_gas_price: Option<U256>,
|
||||||
new_condition: Option<Option<TransactionCondition>>,
|
new_condition: Option<Option<TransactionCondition>>,
|
||||||
pwd: &str
|
pwd: &str
|
||||||
) -> BoxFuture<Result<U256, RpcError>, Canceled>
|
) -> BoxFuture<Result<U256, RpcError>, Canceled> {
|
||||||
{
|
|
||||||
self.rpc.request("signer_confirmRequest", vec![
|
self.rpc.request("signer_confirmRequest", vec![
|
||||||
Self::to_value(&format!("{:#x}", id)),
|
Self::to_value(&format!("{:#x}", id)),
|
||||||
Self::to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
|
Self::to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
|
||||||
Self::to_value(&pwd),
|
Self::to_value(&pwd),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
pub fn reject_request(&mut self, id: U256) ->
|
|
||||||
BoxFuture<Result<bool, RpcError>, Canceled>
|
pub fn reject_request(&mut self, id: U256) -> BoxFuture<Result<bool, RpcError>, Canceled> {
|
||||||
{
|
|
||||||
self.rpc.request("signer_rejectRequest", vec![
|
self.rpc.request("signer_rejectRequest", vec![
|
||||||
JsonValue::String(format!("{:#x}", id))
|
JsonValue::String(format!("{:#x}", id))
|
||||||
])
|
])
|
||||||
|
@ -7,7 +7,8 @@ export TARGETS="
|
|||||||
-p ethcore-bigint\
|
-p ethcore-bigint\
|
||||||
-p parity-dapps \
|
-p parity-dapps \
|
||||||
-p parity-rpc \
|
-p parity-rpc \
|
||||||
-p ethcore-signer \
|
-p parity-rpc-client \
|
||||||
|
-p rpc-cli \
|
||||||
-p ethcore-util \
|
-p ethcore-util \
|
||||||
-p ethcore-network \
|
-p ethcore-network \
|
||||||
-p ethcore-io \
|
-p ethcore-io \
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
[package]
|
|
||||||
description = "Ethcore Trusted Signer"
|
|
||||||
homepage = "http://parity.io"
|
|
||||||
license = "GPL-3.0"
|
|
||||||
name = "ethcore-signer"
|
|
||||||
version = "1.7.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
rustc_version = "0.1"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rand = "0.3.14"
|
|
||||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
|
||||||
jsonrpc-server-utils = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
|
||||||
log = "0.3"
|
|
||||||
env_logger = "0.4"
|
|
||||||
ws = { git = "https://github.com/paritytech/ws-rs.git", branch = "parity-1.7" }
|
|
||||||
parity-dapps-glue = { version = "1.7", optional = true }
|
|
||||||
ethcore-util = { path = "../util" }
|
|
||||||
ethcore-io = { path = "../util/io" }
|
|
||||||
parity-rpc = { path = "../rpc" }
|
|
||||||
ethcore-devtools = { path = "../devtools" }
|
|
||||||
parity-ui = { path = "../dapps/ui", version = "1.4", optional = true }
|
|
||||||
|
|
||||||
clippy = { version = "0.0.103", optional = true}
|
|
||||||
|
|
||||||
[features]
|
|
||||||
dev = ["clippy"]
|
|
||||||
ui = ["parity-dapps-glue", "parity-ui", "parity-ui/no-precompiled-js"]
|
|
||||||
ui-precompiled = ["parity-dapps-glue", "parity-ui", "parity-ui/use-precompiled-js"]
|
|
@ -1,73 +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/>.
|
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
#![cfg_attr(all(nightly, feature="dev"), feature(plugin))]
|
|
||||||
#![cfg_attr(all(nightly, feature="dev"), plugin(clippy))]
|
|
||||||
|
|
||||||
//! Signer module
|
|
||||||
//!
|
|
||||||
//! This module manages your private keys and accounts/identities
|
|
||||||
//! that can be used within Dapps.
|
|
||||||
//!
|
|
||||||
//! It exposes API (over `WebSockets`) accessed by Signer UIs.
|
|
||||||
//! Each transaction sent by Dapp is broadcasted to Signer UIs
|
|
||||||
//! and their responsibility is to confirm (or confirm and sign)
|
|
||||||
//! the transaction for you.
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! extern crate jsonrpc_core;
|
|
||||||
//! extern crate jsonrpc_server_utils;
|
|
||||||
//! extern crate ethcore_signer;
|
|
||||||
//! extern crate parity_rpc;
|
|
||||||
//!
|
|
||||||
//! use std::sync::Arc;
|
|
||||||
//! use jsonrpc_core::IoHandler;
|
|
||||||
//! use jsonrpc_server_utils::reactor::RpcEventLoop;
|
|
||||||
//! use ethcore_signer::ServerBuilder;
|
|
||||||
//! use parity_rpc::ConfirmationsQueue;
|
|
||||||
//!
|
|
||||||
//! fn main() {
|
|
||||||
//! let queue = Arc::new(ConfirmationsQueue::default());
|
|
||||||
//! let io = IoHandler::default();
|
|
||||||
//! let event_loop = RpcEventLoop::spawn().unwrap();
|
|
||||||
//! let remote = event_loop.remote();
|
|
||||||
//! let _server = ServerBuilder::new(queue, "/tmp/authcodes".into())
|
|
||||||
//! .start("127.0.0.1:8084".parse().unwrap(), io, remote);
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate rand;
|
|
||||||
|
|
||||||
extern crate ethcore_io as io;
|
|
||||||
extern crate ethcore_util as util;
|
|
||||||
extern crate jsonrpc_core;
|
|
||||||
extern crate jsonrpc_server_utils;
|
|
||||||
extern crate parity_rpc as rpc;
|
|
||||||
extern crate ws;
|
|
||||||
|
|
||||||
extern crate ethcore_devtools as devtools;
|
|
||||||
|
|
||||||
mod authcode_store;
|
|
||||||
mod ws_server;
|
|
||||||
|
|
||||||
/// Exported tests for use in signer RPC client testing
|
|
||||||
pub mod tests;
|
|
||||||
pub use authcode_store::*;
|
|
||||||
pub use ws_server::*;
|
|
@ -1,21 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
{meta}
|
|
||||||
<title>{title}</title>
|
|
||||||
<link rel="stylesheet" href="/styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="parity-navbar"></div>
|
|
||||||
<div class="parity-box">
|
|
||||||
<h1>{title}</h1>
|
|
||||||
<h3>{message}</h3>
|
|
||||||
<p><code>{details}</code></p>
|
|
||||||
</div>
|
|
||||||
<div class="parity-status">
|
|
||||||
<small>{version}</small>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,219 +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/>.
|
|
||||||
|
|
||||||
//! `WebSockets` server.
|
|
||||||
|
|
||||||
use ws;
|
|
||||||
use std::default::Default;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::ops::Drop;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
|
||||||
use std;
|
|
||||||
|
|
||||||
use io::{PanicHandler, OnPanicListener, MayPanic};
|
|
||||||
use jsonrpc_core::{Metadata, Middleware, MetaIoHandler};
|
|
||||||
use jsonrpc_server_utils::tokio_core::reactor::Remote;
|
|
||||||
use rpc::{ConfirmationsQueue};
|
|
||||||
use rpc::informant::RpcStats;
|
|
||||||
|
|
||||||
mod session;
|
|
||||||
|
|
||||||
pub use self::session::MetaExtractor;
|
|
||||||
|
|
||||||
/// Signer startup error
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ServerError {
|
|
||||||
/// Wrapped `std::io::Error`
|
|
||||||
IoError(std::io::Error),
|
|
||||||
/// Other `ws-rs` error
|
|
||||||
WebSocket(ws::Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ws::Error> for ServerError {
|
|
||||||
fn from(err: ws::Error) -> Self {
|
|
||||||
match err.kind {
|
|
||||||
ws::ErrorKind::Io(e) => ServerError::IoError(e),
|
|
||||||
_ => ServerError::WebSocket(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dummy metadata extractor
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NoopExtractor;
|
|
||||||
impl<M: Metadata> session::MetaExtractor<M> for NoopExtractor {}
|
|
||||||
|
|
||||||
/// Builder for `WebSockets` server
|
|
||||||
pub struct ServerBuilder {
|
|
||||||
queue: Arc<ConfirmationsQueue>,
|
|
||||||
authcodes_path: PathBuf,
|
|
||||||
skip_origin_validation: bool,
|
|
||||||
stats: Option<Arc<RpcStats>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServerBuilder {
|
|
||||||
/// Creates new `ServerBuilder`
|
|
||||||
pub fn new(queue: Arc<ConfirmationsQueue>, authcodes_path: PathBuf) -> Self {
|
|
||||||
ServerBuilder {
|
|
||||||
queue: queue,
|
|
||||||
authcodes_path: authcodes_path,
|
|
||||||
skip_origin_validation: false,
|
|
||||||
stats: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If set to `true` server will not verify Origin of incoming requests.
|
|
||||||
/// Not recommended. Use only for development.
|
|
||||||
pub fn skip_origin_validation(mut self, skip: bool) -> Self {
|
|
||||||
self.skip_origin_validation = skip;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure statistic collection
|
|
||||||
pub fn stats(mut self, stats: Arc<RpcStats>) -> Self {
|
|
||||||
self.stats = Some(stats);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new `WebSocket` server in separate thread.
|
|
||||||
/// Returns a `Server` handle which closes the server when droped.
|
|
||||||
pub fn start<M: Metadata, S: Middleware<M>, H: Into<MetaIoHandler<M, S>>>(
|
|
||||||
self,
|
|
||||||
addr: SocketAddr,
|
|
||||||
handler: H,
|
|
||||||
remote: Remote,
|
|
||||||
) -> Result<Server, ServerError> {
|
|
||||||
self.start_with_extractor(addr, handler, remote, NoopExtractor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new `WebSocket` server in separate thread.
|
|
||||||
/// Returns a `Server` handle which closes the server when droped.
|
|
||||||
pub fn start_with_extractor<M: Metadata, S: Middleware<M>, H: Into<MetaIoHandler<M, S>>, T: session::MetaExtractor<M>>(
|
|
||||||
self,
|
|
||||||
addr: SocketAddr,
|
|
||||||
handler: H,
|
|
||||||
remote: Remote,
|
|
||||||
meta_extractor: T,
|
|
||||||
) -> Result<Server, ServerError> {
|
|
||||||
Server::start(
|
|
||||||
addr,
|
|
||||||
handler.into(),
|
|
||||||
remote,
|
|
||||||
self.queue,
|
|
||||||
self.authcodes_path,
|
|
||||||
self.skip_origin_validation,
|
|
||||||
self.stats,
|
|
||||||
meta_extractor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `WebSockets` server implementation.
|
|
||||||
pub struct Server {
|
|
||||||
handle: Option<thread::JoinHandle<()>>,
|
|
||||||
broadcaster: ws::Sender,
|
|
||||||
queue: Arc<ConfirmationsQueue>,
|
|
||||||
panic_handler: Arc<PanicHandler>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Server {
|
|
||||||
/// Returns the address this server is listening on
|
|
||||||
pub fn addr(&self) -> &SocketAddr {
|
|
||||||
&self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new `WebSocket` server in separate thread.
|
|
||||||
/// Returns a `Server` handle which closes the server when droped.
|
|
||||||
fn start<M: Metadata, S: Middleware<M>, T: session::MetaExtractor<M>>(
|
|
||||||
addr: SocketAddr,
|
|
||||||
handler: MetaIoHandler<M, S>,
|
|
||||||
remote: Remote,
|
|
||||||
queue: Arc<ConfirmationsQueue>,
|
|
||||||
authcodes_path: PathBuf,
|
|
||||||
skip_origin_validation: bool,
|
|
||||||
stats: Option<Arc<RpcStats>>,
|
|
||||||
meta_extractor: T,
|
|
||||||
) -> Result<Server, ServerError> {
|
|
||||||
let config = {
|
|
||||||
let mut config = ws::Settings::default();
|
|
||||||
// accept only handshakes beginning with GET
|
|
||||||
config.method_strict = true;
|
|
||||||
// Was shutting down server when suspending on linux:
|
|
||||||
config.shutdown_on_interrupt = false;
|
|
||||||
config
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create WebSocket
|
|
||||||
let origin = format!("{}", addr);
|
|
||||||
let port = addr.port();
|
|
||||||
let ws = ws::Builder::new().with_settings(config).build(
|
|
||||||
session::Factory::new(handler, remote, origin, port, authcodes_path, skip_origin_validation, stats, meta_extractor)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let panic_handler = PanicHandler::new_in_arc();
|
|
||||||
let ph = panic_handler.clone();
|
|
||||||
let broadcaster = ws.broadcaster();
|
|
||||||
|
|
||||||
// Spawn a thread with event loop
|
|
||||||
let handle = thread::spawn(move || {
|
|
||||||
ph.catch_panic(move || {
|
|
||||||
match ws.listen(addr).map_err(ServerError::from) {
|
|
||||||
Err(ServerError::IoError(io)) => die(format!(
|
|
||||||
"Signer: Could not start listening on specified address. Make sure that no other instance is running on Signer's port. Details: {:?}",
|
|
||||||
io
|
|
||||||
)),
|
|
||||||
Err(any_error) => die(format!(
|
|
||||||
"Signer: Unknown error occurred when starting Signer. Details: {:?}",
|
|
||||||
any_error
|
|
||||||
)),
|
|
||||||
Ok(server) => server,
|
|
||||||
}
|
|
||||||
}).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return a handle
|
|
||||||
Ok(Server {
|
|
||||||
handle: Some(handle),
|
|
||||||
broadcaster: broadcaster,
|
|
||||||
queue: queue,
|
|
||||||
panic_handler: panic_handler,
|
|
||||||
addr: addr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MayPanic for Server {
|
|
||||||
fn on_panic<F>(&self, closure: F) where F: OnPanicListener {
|
|
||||||
self.panic_handler.on_panic(closure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Server {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.queue.finish();
|
|
||||||
self.broadcaster.shutdown().unwrap();
|
|
||||||
self.handle.take().unwrap().join().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn die(msg: String) -> ! {
|
|
||||||
println!("ERROR: {}", msg);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
@ -1,333 +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/>.
|
|
||||||
|
|
||||||
//! Session handlers factory.
|
|
||||||
|
|
||||||
use std::path::{PathBuf, Path};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use authcode_store::AuthCodes;
|
|
||||||
use jsonrpc_core::{Metadata, Middleware, MetaIoHandler};
|
|
||||||
use jsonrpc_core::futures::Future;
|
|
||||||
use jsonrpc_server_utils::tokio_core::reactor::Remote;
|
|
||||||
use rpc::informant::RpcStats;
|
|
||||||
use util::{H256, version};
|
|
||||||
use ws;
|
|
||||||
|
|
||||||
#[cfg(feature = "parity-ui")]
|
|
||||||
mod ui {
|
|
||||||
extern crate parity_ui as ui;
|
|
||||||
extern crate parity_dapps_glue as dapps;
|
|
||||||
|
|
||||||
use self::dapps::WebApp;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Handler {
|
|
||||||
ui: ui::App,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler {
|
|
||||||
pub fn handle(&self, req: &str) -> Option<&dapps::File> {
|
|
||||||
let file = match req {
|
|
||||||
"" | "/" => "index.html",
|
|
||||||
path => &path[1..],
|
|
||||||
};
|
|
||||||
self.ui.file(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "parity-ui"))]
|
|
||||||
mod ui {
|
|
||||||
pub struct File {
|
|
||||||
pub content: &'static [u8],
|
|
||||||
pub content_type: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Handler;
|
|
||||||
|
|
||||||
impl Handler {
|
|
||||||
pub fn handle(&self, _req: &str) -> Option<&File> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const HOME_DOMAIN: &'static str = "parity.web3.site";
|
|
||||||
|
|
||||||
fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool {
|
|
||||||
match header.map(|h| String::from_utf8_lossy(h).into_owned()) {
|
|
||||||
Some(ref origin) if origin.starts_with("chrome-extension://") => true,
|
|
||||||
Some(ref origin) if origin.starts_with(self_origin) => true,
|
|
||||||
Some(ref origin) if origin.starts_with(&format!("http://{}", self_origin)) => true,
|
|
||||||
Some(ref origin) if origin.starts_with(HOME_DOMAIN) => true,
|
|
||||||
Some(ref origin) if origin.starts_with(&format!("http://{}", HOME_DOMAIN)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn auth_token_hash(codes_path: &Path, protocols: ws::Result<Vec<&str>>) -> Option<H256> {
|
|
||||||
match protocols {
|
|
||||||
Ok(ref protocols) if protocols.len() == 1 => {
|
|
||||||
let protocol = protocols[0];
|
|
||||||
let mut split = protocol.split('_');
|
|
||||||
let auth = split.next().and_then(|v| H256::from_str(v).ok());
|
|
||||||
let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok());
|
|
||||||
|
|
||||||
if let (Some(auth), Some(time)) = (auth, time) {
|
|
||||||
// Check if the code is valid
|
|
||||||
AuthCodes::from_file(codes_path)
|
|
||||||
.ok()
|
|
||||||
.and_then(|mut codes| {
|
|
||||||
// remove old tokens
|
|
||||||
codes.clear_garbage();
|
|
||||||
|
|
||||||
let res = codes.is_valid(&auth, time);
|
|
||||||
// make sure to save back authcodes - it might have been modified
|
|
||||||
if codes.to_file(codes_path).is_err() {
|
|
||||||
warn!(target: "signer", "Couldn't save authorization codes to file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if res {
|
|
||||||
Some(auth)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response {
|
|
||||||
let content_len = format!("{}", response.len());
|
|
||||||
{
|
|
||||||
let mut headers = response.headers_mut();
|
|
||||||
headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec()));
|
|
||||||
headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec()));
|
|
||||||
headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec()));
|
|
||||||
headers.push(("Server".into(), b"Parity/SignerUI".to_vec()));
|
|
||||||
headers.push(("Content-Length".into(), content_len.as_bytes().to_vec()));
|
|
||||||
headers.push(("Content-Type".into(), mime.as_bytes().to_vec()));
|
|
||||||
headers.push(("Connection".into(), b"close".to_vec()));
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata extractor from session data.
|
|
||||||
pub trait MetaExtractor<M: Metadata>: Send + Clone + 'static {
|
|
||||||
/// Extract metadata for given session
|
|
||||||
fn extract_metadata(&self, _session_id: &H256) -> M {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Session<M: Metadata, S: Middleware<M>, T> {
|
|
||||||
session_id: H256,
|
|
||||||
out: ws::Sender,
|
|
||||||
skip_origin_validation: bool,
|
|
||||||
self_origin: String,
|
|
||||||
self_port: u16,
|
|
||||||
authcodes_path: PathBuf,
|
|
||||||
handler: Arc<MetaIoHandler<M, S>>,
|
|
||||||
remote: Remote,
|
|
||||||
file_handler: Arc<ui::Handler>,
|
|
||||||
stats: Option<Arc<RpcStats>>,
|
|
||||||
meta_extractor: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Metadata, S: Middleware<M>, T> Drop for Session<M, S, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.stats.as_ref().map(|stats| stats.close_session());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Handler for Session<M, S, T> {
|
|
||||||
fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> {
|
|
||||||
trace!(target: "signer", "Handling request: {:?}", req);
|
|
||||||
|
|
||||||
// TODO [ToDr] ws server is not handling proxied requests correctly:
|
|
||||||
// Trim domain name from resource part:
|
|
||||||
let resource = req.resource().trim_left_matches(&format!("http://{}:{}", HOME_DOMAIN, self.self_port));
|
|
||||||
let resource = resource.trim_left_matches(&format!("http://{}", HOME_DOMAIN));
|
|
||||||
|
|
||||||
// Styles file is allowed for error pages to display nicely.
|
|
||||||
let is_styles_file = resource == "/styles.css";
|
|
||||||
|
|
||||||
// Check request origin and host header.
|
|
||||||
if !self.skip_origin_validation {
|
|
||||||
let origin = req.header("origin").or_else(|| req.header("Origin")).map(|x| &x[..]);
|
|
||||||
let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]);
|
|
||||||
|
|
||||||
let is_valid = origin_is_allowed(&self.self_origin, origin) || (origin.is_none() && origin_is_allowed(&self.self_origin, host));
|
|
||||||
let is_valid = is_styles_file || is_valid;
|
|
||||||
|
|
||||||
if !is_valid {
|
|
||||||
warn!(target: "signer", "Blocked connection to Signer API from untrusted origin.");
|
|
||||||
return Ok(error(
|
|
||||||
ErrorType::Forbidden,
|
|
||||||
"URL Blocked",
|
|
||||||
"You are not allowed to access Trusted Signer using this URL.",
|
|
||||||
Some(&format!("Use: http://{}", self.self_origin)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PROXY requests when running behind home.parity
|
|
||||||
if req.method() == "CONNECT" {
|
|
||||||
let mut res = ws::Response::ok("".into());
|
|
||||||
res.headers_mut().push(("Content-Length".into(), b"0".to_vec()));
|
|
||||||
res.headers_mut().push(("Connection".into(), b"keep-alive".to_vec()));
|
|
||||||
return Ok(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect if it's a websocket request
|
|
||||||
// (styles file skips origin validation, so make sure to prevent WS connections on this resource)
|
|
||||||
if req.header("sec-websocket-key").is_some() && !is_styles_file {
|
|
||||||
// Check authorization
|
|
||||||
let auth_token_hash = auth_token_hash(&self.authcodes_path, req.protocols());
|
|
||||||
match auth_token_hash {
|
|
||||||
None => {
|
|
||||||
info!(target: "signer", "Unauthorized connection to Signer API blocked.");
|
|
||||||
return Ok(error(ErrorType::Forbidden, "Not Authorized", "Request to this API was not authorized.", None));
|
|
||||||
},
|
|
||||||
Some(auth) => {
|
|
||||||
self.session_id = auth;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let protocols = req.protocols().expect("Existence checked by authorization.");
|
|
||||||
let protocol = protocols.get(0).expect("Proved by authorization.");
|
|
||||||
return ws::Response::from_request(req).map(|mut res| {
|
|
||||||
// To make WebSockets connection successful we need to send back the protocol header.
|
|
||||||
res.set_protocol(protocol);
|
|
||||||
res
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(target: "signer", "Requesting resource: {:?}", resource);
|
|
||||||
// Otherwise try to serve a page.
|
|
||||||
Ok(self.file_handler.handle(resource)
|
|
||||||
.map_or_else(
|
|
||||||
// return 404 not found
|
|
||||||
|| error(ErrorType::NotFound, "Not found", "Requested file was not found.", None),
|
|
||||||
// or serve the file
|
|
||||||
|f| add_headers(ws::Response::ok_raw(f.content.to_vec()), f.content_type)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
|
|
||||||
let req = msg.as_text()?;
|
|
||||||
let out = self.out.clone();
|
|
||||||
// TODO [ToDr] Move to on_connect
|
|
||||||
let metadata = self.meta_extractor.extract_metadata(&self.session_id);
|
|
||||||
|
|
||||||
let future = self.handler.handle_request(req, metadata).map(move |response| {
|
|
||||||
if let Some(result) = response {
|
|
||||||
let res = out.send(result);
|
|
||||||
if let Err(e) = res {
|
|
||||||
warn!(target: "signer", "Error while sending response: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.remote.spawn(move |_| future);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Factory<M: Metadata, S: Middleware<M>, T> {
|
|
||||||
handler: Arc<MetaIoHandler<M, S>>,
|
|
||||||
remote: Remote,
|
|
||||||
skip_origin_validation: bool,
|
|
||||||
self_origin: String,
|
|
||||||
self_port: u16,
|
|
||||||
authcodes_path: PathBuf,
|
|
||||||
meta_extractor: T,
|
|
||||||
file_handler: Arc<ui::Handler>,
|
|
||||||
stats: Option<Arc<RpcStats>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Metadata, S: Middleware<M>, T> Factory<M, S, T> {
|
|
||||||
pub fn new(
|
|
||||||
handler: MetaIoHandler<M, S>,
|
|
||||||
remote: Remote,
|
|
||||||
self_origin: String,
|
|
||||||
self_port: u16,
|
|
||||||
authcodes_path: PathBuf,
|
|
||||||
skip_origin_validation: bool,
|
|
||||||
stats: Option<Arc<RpcStats>>,
|
|
||||||
meta_extractor: T,
|
|
||||||
) -> Self {
|
|
||||||
Factory {
|
|
||||||
handler: Arc::new(handler),
|
|
||||||
remote: remote,
|
|
||||||
skip_origin_validation: skip_origin_validation,
|
|
||||||
self_origin: self_origin,
|
|
||||||
self_port: self_port,
|
|
||||||
authcodes_path: authcodes_path,
|
|
||||||
meta_extractor: meta_extractor,
|
|
||||||
file_handler: Arc::new(ui::Handler::default()),
|
|
||||||
stats: stats,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Metadata, S: Middleware<M>, T: MetaExtractor<M>> ws::Factory for Factory<M, S, T> {
|
|
||||||
type Handler = Session<M, S, T>;
|
|
||||||
|
|
||||||
fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler {
|
|
||||||
self.stats.as_ref().map(|stats| stats.open_session());
|
|
||||||
|
|
||||||
Session {
|
|
||||||
session_id: 0.into(),
|
|
||||||
out: sender,
|
|
||||||
handler: self.handler.clone(),
|
|
||||||
remote: self.remote.clone(),
|
|
||||||
skip_origin_validation: self.skip_origin_validation,
|
|
||||||
self_origin: self.self_origin.clone(),
|
|
||||||
self_port: self.self_port,
|
|
||||||
authcodes_path: self.authcodes_path.clone(),
|
|
||||||
meta_extractor: self.meta_extractor.clone(),
|
|
||||||
file_handler: self.file_handler.clone(),
|
|
||||||
stats: self.stats.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ErrorType {
|
|
||||||
NotFound,
|
|
||||||
Forbidden,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error(error: ErrorType, title: &str, message: &str, details: Option<&str>) -> ws::Response {
|
|
||||||
let content = format!(
|
|
||||||
include_str!("./error_tpl.html"),
|
|
||||||
title=title,
|
|
||||||
meta="",
|
|
||||||
message=message,
|
|
||||||
details=details.unwrap_or(""),
|
|
||||||
version=version(),
|
|
||||||
);
|
|
||||||
let res = match error {
|
|
||||||
ErrorType::NotFound => ws::Response::not_found(content),
|
|
||||||
ErrorType::Forbidden => ws::Response::forbidden(content),
|
|
||||||
};
|
|
||||||
add_headers(res, "text/html")
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user