diff --git a/Cargo.lock b/Cargo.lock index 8a70f35a2..83e643900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,6 @@ dependencies = [ "docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.7.0", - "ethcore-dapps 1.7.0", "ethcore-devtools 1.7.0", "ethcore-io 1.7.0", "ethcore-ipc 1.7.0", @@ -27,12 +26,12 @@ dependencies = [ "ethsync 1.7.0", "evmbin 0.1.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "isatty 0.1.1 (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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-dapps 1.7.0", "parity-hash-fetch 1.7.0", "parity-ipfs-api 1.7.0", "parity-local-store 0.1.0", @@ -40,6 +39,7 @@ dependencies = [ "parity-rpc-client 1.4.0", "parity-updater 1.7.0", "path 0.1.0", + "pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -309,6 +309,11 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "difference" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "docopt" version = "0.7.0" @@ -446,40 +451,6 @@ dependencies = [ "siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethcore-dapps" -version = "1.7.0" -dependencies = [ - "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "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-rpc 1.7.0", - "ethcore-util 1.7.0", - "fetch 0.1.0", - "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-http-server 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)", - "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 1.6.1 (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-hash-fetch 1.7.0", - "parity-reactor 0.1.0", - "parity-ui 1.7.0", - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 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)", - "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ethcore-devtools" version = "1.7.0" @@ -642,6 +613,7 @@ dependencies = [ "jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", + "jsonrpc-minihttp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", @@ -1088,7 +1060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1100,7 +1072,7 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", @@ -1113,7 +1085,7 @@ dependencies = [ [[package]] name = "jsonrpc-ipc-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "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)", @@ -1125,17 +1097,31 @@ dependencies = [ [[package]] name = "jsonrpc-macros" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-minihttp-server" +version = "7.0.0" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" +dependencies = [ + "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.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)", + "tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jsonrpc-pubsub" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1145,7 +1131,7 @@ dependencies = [ [[package]] name = "jsonrpc-server-utils" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1156,7 +1142,7 @@ dependencies = [ [[package]] name = "jsonrpc-tcp-server" version = "7.0.0" -source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#f4521e8a543145bec7936de0f875d6550e92c7f7" +source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3" dependencies = [ "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)", @@ -1604,6 +1590,38 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-dapps" +version = "1.7.0" +dependencies = [ + "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "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-util 1.7.0", + "fetch 0.1.0", + "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-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", + "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 1.6.1 (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-hash-fetch 1.7.0", + "parity-reactor 0.1.0", + "parity-ui 1.7.0", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 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)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parity-dapps-glue" version = "1.7.0" @@ -1826,6 +1844,14 @@ name = "podio" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "pretty_assertions" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "primal" version = "0.2.3" @@ -2431,6 +2457,21 @@ dependencies = [ "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-minihttp" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/tokio-minihttp#8acbafae3e77e7f7eb516b441ec84695580221dd" +dependencies = [ + "futures 0.1.11 (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.6 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-named-pipes" version = "0.1.0" @@ -2441,6 +2482,22 @@ dependencies = [ "tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-proto" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/tokio-proto#f6ee08cb594fa2fc1b4178eaaca0855d66e68fd3" +dependencies = [ + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-proto" version = "0.1.0" @@ -2706,6 +2763,7 @@ dependencies = [ "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "" "checksum daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "271ec51b7e0bee92f0d04601422c73eb76ececf197026711c97ad25038a010cf" "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" +"checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8" "checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8" "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" @@ -2739,6 +2797,7 @@ dependencies = [ "checksum jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" "checksum jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" "checksum jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" +"checksum jsonrpc-minihttp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" "checksum jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" "checksum jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" "checksum jsonrpc-tcp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "" @@ -2802,6 +2861,7 @@ dependencies = [ "checksum phf_shared 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fee4d039930e4f45123c9b15976cf93a499847b6483dc09c42ea0ec4940f2aa6" "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" "checksum podio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e5422a1ee1bc57cc47ae717b0137314258138f38fd5f3cea083f43a9725383a0" +"checksum pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2412f3332a07c7a2a50168988dcc184f32180a9758ad470390e5f55e089f6b6e" "checksum primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0e31b86efadeaeb1235452171a66689682783149a6249ff334a2c5d8218d00a4" "checksum primal-bit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "464a91febc06166783d4f5ba3577b5ed8dda8e421012df80bfe48a971ed7be8f" "checksum primal-check 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "647c81b67bb9551a7b88d0bcd785ac35b7d0bf4b2f358683d7c2375d04daec51" @@ -2871,7 +2931,9 @@ dependencies = [ "checksum tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3d1be481b55126f02ef88ff86748086473cb537a949fc4a8f4be403a530ae54b" "checksum tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6a278fde45f1be68e44995227d426aaa4841e0980bb0a21b981092f28c3c8473" "checksum tokio-line 0.1.0 (git+https://github.com/tokio-rs/tokio-line)" = "" +"checksum tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)" = "" "checksum tokio-named-pipes 0.1.0 (git+https://github.com/alexcrichton/tokio-named-pipes)" = "" +"checksum tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)" = "" "checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" "checksum tokio-uds 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc7b5fc8e19e220b29566d1750949224a518478eab9cebc8df60583242ca30a" diff --git a/Cargo.toml b/Cargo.toml index 737c21b09..b82490e88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ serde_json = "0.9" app_dirs = "1.1.1" fdlimit = "0.1" ws2_32-sys = "0.2" -hyper = { default-features = false, git = "https://github.com/paritytech/hyper" } ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } ethsync = { path = "sync" } @@ -50,8 +49,9 @@ parity-ipfs-api = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } parity-local-store = { path = "local-store" } -ethcore-dapps = { path = "dapps", optional = true } path = { path = "util/path" } + +parity-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.103", optional = true} ethcore-secretstore = { path = "secret_store", optional = true } @@ -60,6 +60,7 @@ rustc_version = "0.2" [dev-dependencies] ethcore-ipc-tests = { path = "ipc/tests" } +pretty_assertions = "0.1" [target.'cfg(windows)'.dependencies] winapi = "0.2" @@ -71,18 +72,18 @@ daemonize = "0.2" default = ["ui-precompiled"] ui = [ "dapps", - "ethcore-dapps/ui", + "parity-dapps/ui", "ethcore-signer/ui", ] ui-precompiled = [ "dapps", "ethcore-signer/ui-precompiled", - "ethcore-dapps/ui-precompiled", + "parity-dapps/ui-precompiled", ] -dapps = ["ethcore-dapps"] +dapps = ["parity-dapps"] ipc = ["ethcore/ipc", "ethsync/ipc"] jit = ["ethcore/jit"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "parity-dapps/dev", "ethcore-signer/dev"] json-tests = ["ethcore/json-tests"] test-heavy = ["ethcore/test-heavy"] ethkey-cli = ["ethcore/ethkey-cli"] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 07f136d78..429ed01f5 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -1,6 +1,6 @@ [package] description = "Parity Dapps crate" -name = "ethcore-dapps" +name = "parity-dapps" version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] @@ -28,11 +28,8 @@ zip = { version = "0.1", default-features = false } 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" } -# TODO [ToDr] Temporary solution, server should be merged with RPC. -jsonrpc-server-utils = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } ethcore-devtools = { path = "../devtools" } -ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } parity-hash-fetch = { path = "../hash-fetch" } @@ -42,7 +39,7 @@ parity-ui = { path = "./ui" } clippy = { version = "0.0.103", optional = true} [features] -dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] +dev = ["clippy", "ethcore-util/dev"] ui = ["parity-ui/no-precompiled-js"] ui-precompiled = ["parity-ui/use-precompiled-js"] diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index e07bd4535..df3386358 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::Arc; use unicase::UniCase; use hyper::{server, net, Decoder, Encoder, Next, Control}; use hyper::header; @@ -26,48 +25,49 @@ use apps::fetcher::Fetcher; use handlers::extract_url; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; -use jsonrpc_http_server; -use jsonrpc_server_utils::cors; +use jsonrpc_http_server::{self, AccessControlAllowOrigin}; #[derive(Clone)] -pub struct RestApi { - cors_domains: Option>, - endpoints: Arc, - fetcher: Arc, +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>, + apps: Vec, + fetcher: F, } -impl RestApi { - pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { +impl RestApi { + pub fn new(cors_domains: Vec, endpoints: &Endpoints, fetcher: F) -> Box { Box::new(RestApi { cors_domains: Some(cors_domains), - endpoints: endpoints, + apps: Self::list_apps(endpoints), fetcher: fetcher, }) } - fn list_apps(&self) -> Vec { - self.endpoints.iter().filter_map(|(ref k, ref e)| { + fn list_apps(endpoints: &Endpoints) -> Vec { + 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 { fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box { - Box::new(RestApiRouter::new(self.clone(), path, control)) + Box::new(RestApiRouter::new((*self).clone(), path, control)) } } -struct RestApiRouter { - api: RestApi, +struct RestApiRouter { + api: RestApi, cors_header: Option, path: Option, control: Option, handler: Box, } -impl RestApiRouter { - fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { +impl RestApiRouter { + fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { RestApiRouter { path: Some(path), cors_header: None, @@ -114,7 +114,7 @@ impl RestApiRouter { } } -impl server::Handler for RestApiRouter { +impl server::Handler for RestApiRouter { fn on_request(&mut self, request: server::Request) -> Next { self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains).into(); @@ -142,7 +142,7 @@ impl server::Handler for RestApiRouter { if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() } let handler = endpoint.and_then(|v| match v { - "apps" => Some(response::as_json(&self.api.list_apps())), + "apps" => Some(response::as_json(&self.api.apps)), "ping" => Some(response::ping()), "content" => self.resolve_content(hash, path, control), _ => None diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index c2607fe43..a824134cb 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -47,7 +47,8 @@ pub trait Fetcher: Send + Sync + 'static { fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box; } -pub struct ContentFetcher { +#[derive(Clone)] +pub struct ContentFetcher { dapps_path: PathBuf, resolver: R, cache: Arc>, @@ -57,14 +58,14 @@ pub struct ContentFetcher Drop for ContentFetcher { +impl Drop for ContentFetcher { fn drop(&mut self) { // Clear cache path let _ = fs::remove_dir_all(&self.dapps_path); } } -impl ContentFetcher { +impl ContentFetcher { pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self { let mut dapps_path = env::temp_dir(); @@ -97,7 +98,7 @@ impl ContentFetcher { } } -impl Fetcher for ContentFetcher { +impl Fetcher for ContentFetcher { fn contains(&self, content_id: &str) -> bool { { let mut cache = self.cache.lock(); @@ -233,6 +234,7 @@ mod tests { use page::LocalPageEndpoint; use super::{ContentFetcher, Fetcher}; + #[derive(Clone)] struct FakeResolver; impl URLHint for FakeResolver { fn resolve(&self, _id: Bytes) -> Option { diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index 648d82ff8..ea5825b74 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -16,9 +16,10 @@ //! URL Endpoint traits -use hyper::{self, server, net}; use std::collections::BTreeMap; +use hyper::{self, server, net}; + #[derive(Debug, PartialEq, Default, Clone)] pub struct EndpointPath { pub app_id: String, diff --git a/dapps/src/handlers/auth.rs b/dapps/src/handlers/auth.rs deleted file mode 100644 index db6018e0d..000000000 --- a/dapps/src/handlers/auth.rs +++ /dev/null @@ -1,44 +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 . - -//! Authorization Handlers - -use hyper::{server, Decoder, Encoder, Next}; -use hyper::net::HttpStream; -use hyper::status::StatusCode; - -pub struct AuthRequiredHandler; - -impl server::Handler for AuthRequiredHandler { - fn on_request(&mut self, _request: server::Request) -> Next { - Next::write() - } - - fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { - Next::write() - } - - fn on_response(&mut self, res: &mut server::Response) -> Next { - res.set_status(StatusCode::Unauthorized); - res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]); - Next::write() - } - - fn on_response_writable(&mut self, _encoder: &mut Encoder) -> Next { - Next::end() - } -} - diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index cec7be631..3e2daf462 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -16,14 +16,12 @@ //! Hyper handlers implementations. -mod auth; mod content; mod echo; mod fetch; mod redirect; mod streaming; -pub use self::auth::AuthRequiredHandler; pub use self::content::ContentHandler; pub use self::echo::EchoHandler; pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse}; diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 252e1c3bb..60aba30a4 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -34,9 +34,7 @@ extern crate zip; extern crate jsonrpc_core; extern crate jsonrpc_http_server; -extern crate jsonrpc_server_utils; -extern crate ethcore_rpc; extern crate ethcore_util as util; extern crate fetch; extern crate parity_dapps_glue as parity_dapps; @@ -61,7 +59,6 @@ mod apps; mod page; mod router; mod handlers; -mod rpc; mod api; mod proxypac; mod url; @@ -69,23 +66,16 @@ mod web; #[cfg(test)] mod tests; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::Arc; -use std::net::SocketAddr; use std::collections::HashMap; -use jsonrpc_core::{Middleware, MetaIoHandler}; -use jsonrpc_http_server::tokio_core::reactor::Remote as TokioRemote; -pub use jsonrpc_http_server::{DomainsValidation, Host, AccessControlAllowOrigin}; -pub use jsonrpc_http_server::hyper; +use jsonrpc_http_server::{self as http, hyper, AccessControlAllowOrigin}; -use ethcore_rpc::Metadata; -use fetch::{Fetch, Client as FetchClient}; -use hash_fetch::urlhint::ContractClient; +use fetch::Fetch; use parity_reactor::Remote; -use router::auth::{Authorization, NoAuth, HttpBasicAuth}; -use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; +pub use hash_fetch::urlhint::ContractClient; /// Indicates sync status pub trait SyncStatus: Send + Sync { @@ -107,296 +97,92 @@ impl WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync { fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) } } -/// Webapps HTTP+RPC server build. -pub struct ServerBuilder { - dapps_path: PathBuf, - extra_dapps: Vec, - registrar: Arc, - sync_status: Arc, - web_proxy_tokens: Arc, - signer_address: Option<(String, u16)>, - allowed_hosts: Option>, - extra_cors: Option>, - remote: Remote, - fetch: Option, +/// Dapps server as `jsonrpc-http-server` request middleware. +pub struct Middleware { + router: router::Router>, } -impl ServerBuilder { - /// Construct new dapps server - pub fn new>(dapps_path: P, registrar: Arc, remote: Remote) -> Self { - ServerBuilder { - dapps_path: dapps_path.as_ref().to_owned(), - extra_dapps: vec![], - registrar: registrar, - sync_status: Arc::new(|| false), - web_proxy_tokens: Arc::new(|_| false), - signer_address: None, - allowed_hosts: Some(vec![]), - extra_cors: None, - remote: remote, - fetch: None, - } - } -} - -impl ServerBuilder { - /// Set a fetch client to use. - pub fn fetch(self, fetch: X) -> ServerBuilder { - ServerBuilder { - dapps_path: self.dapps_path, - extra_dapps: vec![], - registrar: self.registrar, - sync_status: self.sync_status, - web_proxy_tokens: self.web_proxy_tokens, - signer_address: self.signer_address, - allowed_hosts: self.allowed_hosts, - extra_cors: self.extra_cors, - remote: self.remote, - fetch: Some(fetch), - } - } - - /// Change default sync status. - pub fn sync_status(mut self, status: Arc) -> Self { - self.sync_status = status; - self - } - - /// Change default web proxy tokens validator. - pub fn web_proxy_tokens(mut self, tokens: Arc) -> Self { - self.web_proxy_tokens = tokens; - self - } - - /// Change default signer port. - pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self { - self.signer_address = signer_address; - self - } - - /// Change allowed hosts. - /// `None` - All hosts are allowed - /// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address) - pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation) -> Self { - self.allowed_hosts = allowed_hosts.into(); - self - } - - /// Extra cors headers. - /// `None` - no additional CORS URLs - pub fn extra_cors_headers(mut self, cors: DomainsValidation) -> Self { - self.extra_cors = cors.into(); - self - } - - /// Change extra dapps paths (apart from `dapps_path`) - pub fn extra_dapps>(mut self, extra_dapps: &[P]) -> Self { - self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); - self - } - - /// Asynchronously start server with no authentication, - /// returns result with `Server` handle on success or an error. - pub fn start_unsecured_http>(self, addr: &SocketAddr, handler: MetaIoHandler, tokio_remote: TokioRemote) -> Result { - let fetch = self.fetch_client()?; - Server::start_http( - addr, - self.allowed_hosts, - self.extra_cors, - NoAuth, - handler, - self.dapps_path, - self.extra_dapps, - self.signer_address, - self.registrar, - self.sync_status, - self.web_proxy_tokens, - self.remote, - tokio_remote, - fetch, - ) - } - - /// Asynchronously start server with `HTTP Basic Authentication`, - /// return result with `Server` handle on success or an error. - pub fn start_basic_auth_http>(self, addr: &SocketAddr, username: &str, password: &str, handler: MetaIoHandler, tokio_remote: TokioRemote) -> Result { - let fetch = self.fetch_client()?; - Server::start_http( - addr, - self.allowed_hosts, - self.extra_cors, - HttpBasicAuth::single_user(username, password), - handler, - self.dapps_path, - self.extra_dapps, - self.signer_address, - self.registrar, - self.sync_status, - self.web_proxy_tokens, - self.remote, - tokio_remote, - fetch, - ) - } - - fn fetch_client(&self) -> Result { - match self.fetch.clone() { - Some(fetch) => Ok(fetch), - None => T::new().map_err(|_| ServerError::FetchInitialization), - } - } -} - -/// Webapps HTTP server. -pub struct Server { - server: Option, -} - -impl Server { - /// Returns a list of allowed hosts or `None` if all hosts are allowed. - fn allowed_hosts(hosts: Option>, bind_address: String) -> Option> { - let mut allowed = Vec::new(); - - match hosts { - Some(hosts) => allowed.extend_from_slice(&hosts), - None => return None, - } - - // Add localhost domain as valid too if listening on loopback interface. - allowed.push(bind_address.replace("127.0.0.1", "localhost").into()); - allowed.push(bind_address.into()); - Some(allowed) - } - - /// Returns a list of CORS domains for API endpoint. - fn cors_domains( +impl Middleware { + /// Creates new Dapps server middleware. + pub fn new( + remote: Remote, signer_address: Option<(String, u16)>, - extra_cors: Option>, - ) -> Vec { - let basic_cors = match signer_address { - Some(signer_address) => [ - format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), - format!("http://{}", address(&signer_address)), - format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), - format!("https://{}", address(&signer_address)), - ].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(), - None => vec![], - }; - - match extra_cors { - None => basic_cors, - Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(), - } - } - - fn start_http>( - addr: &SocketAddr, - hosts: Option>, - extra_cors: Option>, - authorization: A, - handler: MetaIoHandler, dapps_path: PathBuf, extra_dapps: Vec, - signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, web_proxy_tokens: Arc, - remote: Remote, - tokio_remote: TokioRemote, fetch: F, - ) -> Result { - let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( + ) -> Self { + let content_fetcher = apps::fetcher::ContentFetcher::new( hash_fetch::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone(), remote.clone(), fetch.clone(), - )); - let endpoints = Arc::new(apps::all_endpoints( + ); + let endpoints = apps::all_endpoints( dapps_path, extra_dapps, signer_address.clone(), web_proxy_tokens, remote.clone(), fetch.clone(), - )); - let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors); + ); - let special = Arc::new({ + let cors_domains = cors_domains(signer_address.clone()); + + let special = { let mut special = HashMap::new(); - special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, tokio_remote, cors_domains.clone())); - special.insert(router::SpecialEndpoint::Utils, apps::utils()); + special.insert(router::SpecialEndpoint::Rpc, None); + special.insert(router::SpecialEndpoint::Utils, Some(apps::utils())); special.insert( router::SpecialEndpoint::Api, - api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone()) + Some(api::RestApi::new(cors_domains.clone(), &endpoints, content_fetcher.clone())), ); special - }); - let hosts = Self::allowed_hosts(hosts, format!("{}", addr)); + }; - hyper::Server::http(addr)? - .handle(move |ctrl| router::Router::new( - ctrl, - signer_address.clone(), - content_fetcher.clone(), - endpoints.clone(), - special.clone(), - authorization.clone(), - hosts.clone(), - )) - .map(|(l, srv)| { + let router = router::Router::new( + signer_address, + content_fetcher, + endpoints, + special, + ); - ::std::thread::spawn(move || { - srv.run(); - }); - - Server { - server: Some(l), - } - }) - .map_err(ServerError::from) - } - - #[cfg(test)] - /// Returns address that this server is bound to. - pub fn addr(&self) -> &SocketAddr { - self.server.as_ref() - .expect("server is always Some at the start; it's consumed only when object is dropped; qed") - .addrs() - .first() - .expect("You cannot start the server without binding to at least one address; qed") - } -} - -impl Drop for Server { - fn drop(&mut self) { - self.server.take().unwrap().close() - } -} - -/// Webapp Server startup error -#[derive(Debug)] -pub enum ServerError { - /// Wrapped `std::io::Error` - IoError(std::io::Error), - /// Other `hyper` error - Other(hyper::error::Error), - /// Fetch service initialization error - FetchInitialization, -} - -impl From for ServerError { - fn from(err: hyper::error::Error) -> Self { - match err { - hyper::error::Error::Io(e) => ServerError::IoError(e), - e => ServerError::Other(e), + Middleware { + router: router, } } } +impl http::RequestMiddleware for Middleware { + fn on_request(&self, req: &hyper::server::Request, control: &hyper::Control) -> http::RequestMiddlewareAction { + self.router.on_request(req, control) + } +} + +/// Returns a list of CORS domains for API endpoint. +fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { + use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; + + match signer_address { + Some(signer_address) => [ + format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), + format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), + format!("http://{}", address(&signer_address)), + format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), + format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), + format!("https://{}", address(&signer_address)), + ].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(), + None => vec![], + } +} + +fn address(address: &(String, u16)) -> String { + format!("{}:{}", address.0, address.1) +} + /// Random filename fn random_filename() -> String { use ::rand::Rng; @@ -404,39 +190,18 @@ fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } -fn address(address: &(String, u16)) -> String { - format!("{}:{}", address.0, address.1) -} - #[cfg(test)] mod util_tests { - use super::Server; + use super::cors_domains; use jsonrpc_http_server::AccessControlAllowOrigin; - #[test] - fn should_return_allowed_hosts() { - // given - let bind_address = "127.0.0.1".to_owned(); - - // when - let all = Server::allowed_hosts(None, bind_address.clone()); - let address = Server::allowed_hosts(Some(Vec::new()), bind_address.clone()); - let some = Server::allowed_hosts(Some(vec!["ethcore.io".into()]), bind_address.clone()); - - // then - assert_eq!(all, None); - assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()])); - assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()])); - } - #[test] fn should_return_cors_domains() { // given // when - let none = Server::cors_domains(None, None); - let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None); - let extra = Server::cors_domains(None, Some(vec!["all".into()])); + let none = cors_domains(None); + let some = cors_domains(Some(("127.0.0.1".into(), 18180))); // then assert_eq!(none, Vec::::new()); @@ -448,6 +213,5 @@ mod util_tests { "https://parity.web3.site:18180".into(), "https://127.0.0.1:18180".into(), ]); - assert_eq!(extra, vec![AccessControlAllowOrigin::Any]); } } diff --git a/dapps/src/router/mod.rs b/dapps/src/router.rs similarity index 71% rename from dapps/src/router/mod.rs rename to dapps/src/router.rs index 0b4e632a6..995565f26 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router.rs @@ -15,24 +15,20 @@ // along with Parity. If not, see . //! Router implementation -//! Processes request handling authorization and dispatching it to proper application. - -pub mod auth; -mod host_validation; +//! Dispatch requests to proper application. use address; use std::cmp; -use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; -use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode}; +use hyper::{self, server, header, Control, StatusCode}; use hyper::net::HttpStream; -use jsonrpc_server_utils::hosts; +use jsonrpc_http_server as http; use apps::{self, DAPPS_DOMAIN}; use apps::fetcher::Fetcher; -use endpoint::{Endpoint, Endpoints, EndpointPath}; +use endpoint::{Endpoint, Endpoints, EndpointPath, Handler}; use handlers::{self, Redirection, ContentHandler}; /// Special endpoints are accessible on every domain (every dapp) @@ -44,51 +40,29 @@ pub enum SpecialEndpoint { None, } -pub struct Router { - control: Option, +pub struct Router { signer_address: Option<(String, u16)>, - endpoints: Arc, - fetch: Arc, - special: Arc>>, - authorization: Arc, - allowed_hosts: Option>, - handler: Box + Send>, + endpoints: Endpoints, + fetch: F, + special: HashMap>>, } -impl server::Handler for Router { - - fn on_request(&mut self, req: server::Request) -> Next { +impl http::RequestMiddleware for Router { + fn on_request(&self, req: &server::Request, control: &Control) -> http::RequestMiddlewareAction { // 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 referer = extract_referer_endpoint(&req); + let referer = extract_referer_endpoint(req); 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::().is_some(); let is_get_request = *req.method() == hyper::Method::Get; trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); - // Validate Host header - trace!(target: "dapps", "Validating host headers against: {:?}", self.allowed_hosts); - let is_valid = is_utils || host_validation::is_valid(&req, &self.allowed_hosts); - if !is_valid { - debug!(target: "dapps", "Rejecting invalid host header."); - self.handler = host_validation::host_invalid_response(); - return self.handler.on_request(req); - } - - trace!(target: "dapps", "Checking authorization."); - // Check authorization - let auth = self.authorization.is_authorized(&req); - if let auth::Authorized::No(handler) = auth { - debug!(target: "dapps", "Authorization denied."); - self.handler = handler; - return self.handler.on_request(req); - } - - - let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed"); + let control = control.clone(); debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint); - self.handler = match (endpoint.0, endpoint.1, referer) { + let handler: Option> = match (endpoint.0, endpoint.1, referer) { // Handle invalid web requests that we can recover from (ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url))) if referer.app_id == apps::WEB_PATH @@ -100,26 +74,27 @@ impl server::Handler for Router let len = cmp::min(referer_url.path.len(), 2); // /web// let base = referer_url.path[..len].join("/"); let requested = url.map(|u| u.path.join("/")).unwrap_or_default(); - Redirection::boxed(&format!("/{}/{}", base, requested)) + Some(Redirection::boxed(&format!("/{}/{}", base, requested))) }, // First check special endpoints (ref path, ref endpoint, _) if self.special.contains_key(endpoint) => { trace!(target: "dapps", "Resolving to special endpoint."); self.special.get(endpoint) .expect("special known to contain key; qed") - .to_async_handler(path.clone().unwrap_or_default(), control) + .as_ref() + .map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control)) }, // Then delegate to dapp (Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => { trace!(target: "dapps", "Resolving to local/builtin dapp."); - self.endpoints.get(&path.app_id) + Some(self.endpoints.get(&path.app_id) .expect("endpoints known to contain key; qed") - .to_async_handler(path.clone(), control) + .to_async_handler(path.clone(), control)) }, // Try to resolve and fetch the dapp (Some(ref path), _, _) if self.fetch.contains(&path.app_id) => { trace!(target: "dapps", "Resolving to fetchable content."); - 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 // (in the past we used 301 instead of 302) @@ -128,82 +103,61 @@ impl server::Handler for Router // 404 for non-existent content (Some(ref path), _, _) if is_get_request && path.app_id != "home" => { trace!(target: "dapps", "Resolving to 404."); - Box::new(ContentHandler::error( + Some(Box::new(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested content was not found.", None, self.signer_address.clone(), - )) + ))) }, // Redirect any other GET request to signer. _ if is_get_request => { if let Some(ref signer_address) = self.signer_address { trace!(target: "dapps", "Redirecting to signer interface."); - Redirection::boxed(&format!("http://{}", address(signer_address))) + Some(Redirection::boxed(&format!("http://{}", address(signer_address)))) } else { trace!(target: "dapps", "Signer disabled, returning 404."); - Box::new(ContentHandler::error( + 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 _ => { trace!(target: "dapps", "Resolving to RPC call."); - self.special.get(&SpecialEndpoint::Rpc) - .expect("RPC endpoint always stored; qed") - .to_async_handler(EndpointPath::default(), control) + None } }; - // Delegate on_request to proper handler - self.handler.on_request(req) - } - - /// This event occurs each time the `Request` is ready to be read from. - fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { - self.handler.on_request_readable(decoder) - } - - /// This event occurs after the first time this handled signals `Next::write()`. - fn on_response(&mut self, response: &mut server::Response) -> Next { - self.handler.on_response(response) - } - - /// This event occurs each time the `Response` is ready to be written to. - fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { - self.handler.on_response_writable(encoder) + match handler { + Some(handler) => http::RequestMiddlewareAction::Respond { + should_validate_hosts: !(is_utils || is_dapps_domain), + handler: handler, + }, + None => http::RequestMiddlewareAction::Proceed { + should_continue_on_invalid_cors: !is_origin_set, + }, + } } } -impl Router { +impl Router { pub fn new( - control: Control, signer_address: Option<(String, u16)>, - content_fetcher: Arc, - endpoints: Arc, - special: Arc>>, - authorization: Arc, - allowed_hosts: Option>, - ) -> Self { - - let handler = special.get(&SpecialEndpoint::Utils) - .expect("Utils endpoint always stored; qed") - .to_handler(EndpointPath::default()); + content_fetcher: F, + endpoints: Endpoints, + special: HashMap>>, + ) -> Self { Router { - control: Some(control), signer_address: signer_address, endpoints: endpoints, fetch: content_fetcher, special: special, - authorization: authorization, - allowed_hosts: allowed_hosts, - handler: handler, } } } diff --git a/dapps/src/router/auth.rs b/dapps/src/router/auth.rs deleted file mode 100644 index 007ebb96d..000000000 --- a/dapps/src/router/auth.rs +++ /dev/null @@ -1,106 +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 Authorization implementations - -use std::collections::HashMap; -use hyper::{server, net, header, status}; -use endpoint::Handler; -use handlers::{AuthRequiredHandler, ContentHandler}; - -/// Authorization result -pub enum Authorized { - /// Authorization was successful. - Yes, - /// Unsuccessful authorization. Handler for further work is returned. - No(Box), -} - -/// Authorization interface -pub trait Authorization : Send + Sync { - /// Checks if authorization is valid. - fn is_authorized(&self, req: &server::Request)-> Authorized; -} - -/// HTTP Basic Authorization handler -pub struct HttpBasicAuth { - users: HashMap, -} - -/// No-authorization implementation (authorization disabled) -pub struct NoAuth; - -impl Authorization for NoAuth { - fn is_authorized(&self, _req: &server::Request)-> Authorized { - Authorized::Yes - } -} - -impl Authorization for HttpBasicAuth { - fn is_authorized(&self, req: &server::Request) -> Authorized { - let auth = self.check_auth(&req); - - match auth { - Access::Denied => { - Authorized::No(Box::new(ContentHandler::error( - status::StatusCode::Unauthorized, - "Unauthorized", - "You need to provide valid credentials to access this page.", - None, - None, - ))) - }, - Access::AuthRequired => { - Authorized::No(Box::new(AuthRequiredHandler)) - }, - Access::Granted => { - Authorized::Yes - }, - } - } -} - -#[derive(Debug)] -enum Access { - Granted, - Denied, - AuthRequired, -} - -impl HttpBasicAuth { - /// Creates `HttpBasicAuth` instance with only one user. - pub fn single_user(username: &str, password: &str) -> Self { - let mut users = HashMap::new(); - users.insert(username.to_owned(), password.to_owned()); - HttpBasicAuth { - users: users - } - } - - fn is_authorized(&self, username: &str, password: &str) -> bool { - self.users.get(&username.to_owned()).map_or(false, |pass| pass == password) - } - - fn check_auth(&self, req: &server::Request) -> Access { - match req.headers().get::>() { - Some(&header::Authorization( - header::Basic { ref username, password: Some(ref password) } - )) if self.is_authorized(username, password) => Access::Granted, - Some(_) => Access::Denied, - None => Access::AuthRequired, - } - } -} diff --git a/dapps/src/router/host_validation.rs b/dapps/src/router/host_validation.rs deleted file mode 100644 index e5fcedd94..000000000 --- a/dapps/src/router/host_validation.rs +++ /dev/null @@ -1,42 +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 . - - -use apps::DAPPS_DOMAIN; -use hyper::{server, header, StatusCode}; -use hyper::net::HttpStream; - -use handlers::ContentHandler; -use jsonrpc_http_server; -use jsonrpc_server_utils::hosts; - -pub fn is_valid(req: &server::Request, allowed_hosts: &Option>) -> bool { - let header_valid = jsonrpc_http_server::is_host_allowed(req, allowed_hosts); - match (header_valid, req.headers().get::()) { - (true, _) => true, - (_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN), - _ => false, - } -} - -pub fn host_invalid_response() -> Box + Send> { - Box::new(ContentHandler::error(StatusCode::Forbidden, - "Current Host Is Disallowed", - "You are trying to access your node using incorrect address.", - Some("Use allowed URL or specify different hosts CLI options."), - None, - )) -} diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index 6ddb31db0..b743408dc 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -66,14 +66,14 @@ impl> Endpoint for RpcEndpoint { #[derive(Default)] struct NoopMiddleware; impl http::RequestMiddleware for NoopMiddleware { - fn on_request(&self, request: &http::hyper::server::Request) -> http::RequestMiddlewareAction { + fn on_request(&self, request: &http::hyper::server::Request, _control: &http::hyper::Control) -> http::RequestMiddlewareAction { http::RequestMiddlewareAction::Proceed { should_continue_on_invalid_cors: request.headers().get::().is_none(), } } } -struct MetadataExtractor; +pub struct MetadataExtractor; impl HttpMetaExtractor for MetadataExtractor { fn read_metadata(&self, request: &http::hyper::server::Request) -> Metadata { let dapp_id = request.headers().get::() diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index 73467e854..043814377 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers}; +use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; #[test] fn should_return_error() { @@ -195,26 +195,3 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() { response.assert_status("HTTP/1.1 200 OK"); response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site:18180"); } - -#[test] -fn should_return_extra_cors_headers() { - // given - let server = serve_extra_cors(Some(vec!["all".to_owned()])); - - // when - let response = request(server, - "\ - POST /api/ping HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: http://somedomain.io\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://somedomain.io"); -} - diff --git a/dapps/src/tests/authorization.rs b/dapps/src/tests/authorization.rs deleted file mode 100644 index 346f8f2fb..000000000 --- a/dapps/src/tests/authorization.rs +++ /dev/null @@ -1,80 +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 . - -use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed}; - -#[test] -fn should_require_authorization() { - // given - let server = serve_with_auth("test", "test"); - - // when - let response = request(server, - "\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - \r\n\ - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "WWW-Authenticate: Basic realm=\"Parity\""); -} - -#[test] -fn should_reject_on_invalid_auth() { - // given - let server = serve_with_auth("test", "test"); - - // when - let response = request(server, - "\ - GET / HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n - \r\n\ - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned()); - assert!(response.body.contains("Unauthorized"), response.body); - assert_eq!(response.headers_raw.contains("WWW-Authenticate"), false); -} - -#[test] -fn should_allow_on_valid_auth() { - // given - let server = serve_with_auth("Aladdin", "OpenSesame"); - - // when - let response = request(server, - "\ - GET /ui/ HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n - \r\n\ - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_security_headers_for_embed(&response.headers); -} diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 036933995..e6c032549 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -16,18 +16,20 @@ use std::env; use std::str; -use std::ops::Deref; +use std::net::SocketAddr; +use std::path::{Path, PathBuf}; use std::sync::Arc; use env_logger::LogBuilder; -use ethcore_rpc::Metadata; -use jsonrpc_core::MetaIoHandler; +use jsonrpc_core::IoHandler; +use jsonrpc_http_server::{self as http, Host, DomainsValidation}; -use ServerBuilder; -use Server; -use fetch::Fetch; use devtools::http_client; +use hash_fetch::urlhint::ContractClient; +use fetch::{Fetch, Client as FetchClient}; use parity_reactor::{EventLoop, Remote}; +use {Middleware, SyncStatus, WebProxyTokens}; + mod registrar; mod fetch; @@ -50,7 +52,7 @@ pub struct ServerLoop { pub event_loop: EventLoop, } -impl Deref for ServerLoop { +impl ::std::ops::Deref for ServerLoop { type Target = Server; fn deref(&self) -> &Self::Target { @@ -58,7 +60,7 @@ impl Deref for ServerLoop { } } -pub fn init_server(process: F, io: MetaIoHandler, remote: Remote) -> (ServerLoop, Arc) where +pub fn init_server(process: F, io: IoHandler, remote: Remote) -> (ServerLoop, Arc) where F: FnOnce(ServerBuilder) -> ServerBuilder, B: Fetch, { @@ -74,33 +76,15 @@ pub fn init_server(process: F, io: MetaIoHandler, remote: Remote &dapps_path, registrar.clone(), remote, )) .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) - .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io, event_loop.raw_remote()).unwrap(); + .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap(); ( ServerLoop { server: server, event_loop: event_loop }, registrar, ) } -pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop { - init_logger(); - let registrar = Arc::new(FakeRegistrar::new()); - let mut dapps_path = env::temp_dir(); - dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - - let event_loop = EventLoop::spawn(); - let io = MetaIoHandler::default(); - let server = ServerBuilder::new(&dapps_path, registrar, event_loop.remote()) - .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) - .allowed_hosts(None.into()) - .start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), user, pass, io, event_loop.raw_remote()).unwrap(); - ServerLoop { - server: server, - event_loop: event_loop, - } -} - -pub fn serve_with_rpc(io: MetaIoHandler) -> ServerLoop { - init_server(|builder| builder.allowed_hosts(None.into()), io, Remote::new_sync()).0 +pub fn serve_with_rpc(io: IoHandler) -> ServerLoop { + init_server(|builder| builder, io, Remote::new_sync()).0 } pub fn serve_hosts(hosts: Option>) -> ServerLoop { @@ -108,20 +92,13 @@ pub fn serve_hosts(hosts: Option>) -> ServerLoop { init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0 } -pub fn serve_extra_cors(extra_cors: Option>) -> ServerLoop { - let extra_cors = extra_cors.map(|cors| cors.into_iter().map(Into::into).collect()); - init_server(|builder| builder.allowed_hosts(None.into()).extra_cors_headers(extra_cors.into()), Default::default(), Remote::new_sync()).0 -} - pub fn serve_with_registrar() -> (ServerLoop, Arc) { - init_server(|builder| builder.allowed_hosts(None.into()), Default::default(), Remote::new_sync()) + init_server(|builder| builder, Default::default(), Remote::new_sync()) } pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc) { init_server(|builder| { - builder - .sync_status(Arc::new(|| true)) - .allowed_hosts(None.into()) + builder.sync_status(Arc::new(|| true)) }, Default::default(), Remote::new_sync()) } @@ -133,7 +110,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv let fetch = FakeFetch::default(); let f = fetch.clone(); let (server, reg) = init_server(move |builder| { - builder.allowed_hosts(None.into()).fetch(f.clone()) + builder.fetch(f.clone()) }, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() }); (server, fetch, reg) @@ -144,7 +121,6 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) { let f = fetch.clone(); let (server, _) = init_server(move |builder| { builder - .allowed_hosts(None.into()) .fetch(f.clone()) .web_proxy_tokens(Arc::new(move |token| &token == web_token)) }, Default::default(), Remote::new_sync()); @@ -153,7 +129,7 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) { } pub fn serve() -> ServerLoop { - init_server(|builder| builder.allowed_hosts(None.into()), 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 { @@ -166,3 +142,157 @@ pub fn assert_security_headers(headers: &[String]) { pub fn assert_security_headers_for_embed(headers: &[String]) { http_client::assert_security_headers_present(headers, Some(SIGNER_PORT)) } + + +/// Webapps HTTP+RPC server build. +pub struct ServerBuilder { + dapps_path: PathBuf, + registrar: Arc, + sync_status: Arc, + web_proxy_tokens: Arc, + signer_address: Option<(String, u16)>, + allowed_hosts: DomainsValidation, + remote: Remote, + fetch: Option, +} + +impl ServerBuilder { + /// Construct new dapps server + pub fn new>(dapps_path: P, registrar: Arc, remote: Remote) -> Self { + ServerBuilder { + dapps_path: dapps_path.as_ref().to_owned(), + registrar: registrar, + sync_status: Arc::new(|| false), + web_proxy_tokens: Arc::new(|_| false), + signer_address: None, + allowed_hosts: DomainsValidation::Disabled, + remote: remote, + fetch: None, + } + } +} + +impl ServerBuilder { + /// Set a fetch client to use. + pub fn fetch(self, fetch: X) -> ServerBuilder { + ServerBuilder { + dapps_path: self.dapps_path, + registrar: self.registrar, + sync_status: self.sync_status, + web_proxy_tokens: self.web_proxy_tokens, + signer_address: self.signer_address, + allowed_hosts: self.allowed_hosts, + remote: self.remote, + fetch: Some(fetch), + } + } + + /// Change default sync status. + pub fn sync_status(mut self, status: Arc) -> Self { + self.sync_status = status; + self + } + + /// Change default web proxy tokens validator. + pub fn web_proxy_tokens(mut self, tokens: Arc) -> Self { + self.web_proxy_tokens = tokens; + self + } + + /// Change default signer port. + pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self { + self.signer_address = signer_address; + self + } + + /// Change allowed hosts. + /// `None` - All hosts are allowed + /// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address) + pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation) -> Self { + self.allowed_hosts = allowed_hosts; + self + } + + /// Asynchronously start server with no authentication, + /// returns result with `Server` handle on success or an error. + pub fn start_unsecured_http(self, addr: &SocketAddr, io: IoHandler) -> Result { + let fetch = self.fetch_client(); + Server::start_http( + addr, + io, + self.allowed_hosts, + self.signer_address, + self.dapps_path, + vec![], + self.registrar, + self.sync_status, + self.web_proxy_tokens, + self.remote, + fetch, + ) + } + + fn fetch_client(&self) -> T { + match self.fetch.clone() { + Some(fetch) => fetch, + None => T::new().unwrap(), + } + } +} + + +/// Webapps HTTP server. +pub struct Server { + server: Option, +} + +impl Server { + fn start_http( + addr: &SocketAddr, + io: IoHandler, + allowed_hosts: DomainsValidation, + signer_address: Option<(String, u16)>, + dapps_path: PathBuf, + extra_dapps: Vec, + registrar: Arc, + sync_status: Arc, + web_proxy_tokens: Arc, + remote: Remote, + fetch: F, + ) -> Result { + let middleware = Middleware::new( + remote, + signer_address, + dapps_path, + extra_dapps, + registrar, + sync_status, + web_proxy_tokens, + fetch, + ); + http::ServerBuilder::new(io) + .request_middleware(middleware) + .allowed_hosts(allowed_hosts) + .cors(http::DomainsValidation::Disabled) + .start_http(addr) + .map(|server| Server { + server: Some(server), + }) + } + + /// Returns address that this server is bound to. + pub fn addr(&self) -> &SocketAddr { + self.server.as_ref() + .expect("server is always Some at the start; it's consumed only when object is dropped; qed") + .addrs() + .first() + .expect("You cannot start the server without binding to at least one address; qed") + } +} + +impl Drop for Server { + fn drop(&mut self) { + self.server.take().unwrap().close() + } +} + diff --git a/dapps/src/tests/mod.rs b/dapps/src/tests/mod.rs index ced211d53..089318483 100644 --- a/dapps/src/tests/mod.rs +++ b/dapps/src/tests/mod.rs @@ -19,7 +19,6 @@ mod helpers; mod api; -mod authorization; mod fetch; mod redirection; mod rpc; diff --git a/dapps/src/tests/rpc.rs b/dapps/src/tests/rpc.rs index 2cc4ccb24..0cfc2c5a8 100644 --- a/dapps/src/tests/rpc.rs +++ b/dapps/src/tests/rpc.rs @@ -14,16 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use futures::{future, Future}; -use ethcore_rpc::{Metadata, Origin}; -use jsonrpc_core::{MetaIoHandler, Value}; +use jsonrpc_core::{IoHandler, Value}; use tests::helpers::{serve_with_rpc, request}; #[test] fn should_serve_rpc() { // given - let mut io = MetaIoHandler::default(); + let mut io = IoHandler::default(); io.add_method("rpc_test", |_| { Ok(Value::String("Hello World!".into())) }); @@ -49,70 +47,3 @@ fn should_serve_rpc() { response.assert_status("HTTP/1.1 200 OK"); assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); } - -#[test] -fn should_extract_metadata() { - // given - let mut io = MetaIoHandler::default(); - io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.origin, Origin::Dapps("".into())); - assert_eq!(meta.dapp_id(), "".into()); - future::ok(Value::String("Hello World!".into())).boxed() - }); - let server = serve_with_rpc(io); - - // when - let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#; - let response = request(server, &format!( - "\ - POST /rpc/ HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - X-Parity-Origin: https://this.should.be.ignored\r\n\ - Content-Type: application/json\r\n\ - Content-Length: {}\r\n\ - \r\n\ - {}\r\n\ - ", - req.as_bytes().len(), - req, - )); - - // then - response.assert_status("HTTP/1.1 200 OK"); - assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); -} - -#[test] -fn should_extract_metadata_from_custom_header() { - // given - let mut io = MetaIoHandler::default(); - io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); - assert_eq!(meta.dapp_id(), "https://parity.io/".into()); - future::ok(Value::String("Hello World!".into())).boxed() - }); - let server = serve_with_rpc(io); - - // when - let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#; - let response = request(server, &format!( - "\ - POST /rpc/ HTTP/1.1\r\n\ - Host: 127.0.0.1:8080\r\n\ - Connection: close\r\n\ - Origin: null\r\n\ - X-Parity-Origin: https://parity.io/\r\n\ - Content-Type: application/json\r\n\ - Content-Length: {}\r\n\ - \r\n\ - {}\r\n\ - ", - req.as_bytes().len(), - req, - )); - - // then - response.assert_status("HTTP/1.1 200 OK"); - assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); -} diff --git a/dapps/src/tests/validation.rs b/dapps/src/tests/validation.rs index afeb7b5ef..fb68cf5ed 100644 --- a/dapps/src/tests/validation.rs +++ b/dapps/src/tests/validation.rs @@ -34,7 +34,7 @@ fn should_reject_invalid_host() { // then assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned()); - assert!(response.body.contains("Current Host Is Disallowed"), response.body); + assert!(response.body.contains("Provided Host header is not whitelisted."), response.body); } #[test] @@ -97,31 +97,3 @@ fn should_allow_parity_utils_even_on_invalid_domain() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); } - -#[test] -fn should_not_return_cors_headers_for_rpc() { - // given - let server = serve_hosts(Some(vec!["localhost:8080".into()])); - - // when - let response = request(server, - "\ - POST /rpc HTTP/1.1\r\n\ - Host: localhost:8080\r\n\ - Origin: null\r\n\ - Content-Type: application/json\r\n\ - Connection: close\r\n\ - \r\n\ - {} - " - ); - - // then - assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert!( - !response.headers_raw.contains("Access-Control-Allow-Origin"), - "CORS headers were not expected: {:?}", - response.headers - ); -} - diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 1588b5482..579c83845 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -92,12 +92,13 @@ pub enum URLHintResult { } /// URLHint Contract interface -pub trait URLHint { +pub trait URLHint: Send + Sync { /// Resolves given id to registrar entry. fn resolve(&self, id: Bytes) -> Option; } /// `URLHintContract` API +#[derive(Clone)] pub struct URLHintContract { urlhint: Contract, registrar: Contract, diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index df03b6cd7..eeac2431b 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -32,12 +32,13 @@ use std::sync::Arc; use std::net::{SocketAddr, IpAddr}; use error::ServerError; use route::Out; -use http::hyper::server::{Listening, Handler, Request, Response}; +use http::hyper::server::{Handler, Request, Response}; use http::hyper::net::HttpStream; use http::hyper::header::{self, Vary, ContentLength, ContentType}; use http::hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; +pub use http::hyper::server::Listening; pub use http::{AccessControlAllowOrigin, Host, DomainsValidation}; /// Request/response handler diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 65b1cfea4..7576c063f 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -164,6 +164,8 @@ usage! { or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")), + flag_jsonrpc_threads: Option = None, + or |c: &Config| otry!(c.rpc).threads.map(Some), // IPC flag_no_ipc: bool = false, @@ -176,21 +178,8 @@ usage! { // DAPPS flag_no_dapps: bool = false, or |c: &Config| otry!(c.dapps).disable.clone(), - flag_dapps_port: u16 = 8080u16, - or |c: &Config| otry!(c.dapps).port.clone(), - flag_dapps_interface: String = "local", - or |c: &Config| otry!(c.dapps).interface.clone(), - flag_dapps_hosts: String = "none", - or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")), - flag_dapps_cors: Option = None, - or |c: &Config| otry!(c.dapps).cors.clone().map(Some), flag_dapps_path: String = "$BASE/dapps", or |c: &Config| otry!(c.dapps).path.clone(), - flag_dapps_user: Option = None, - or |c: &Config| otry!(c.dapps).user.clone().map(Some), - flag_dapps_pass: Option = None, - or |c: &Config| otry!(c.dapps).pass.clone().map(Some), - flag_dapps_apis_all: bool = false, or |_| None, // Secret Store flag_no_secretstore: bool = false, @@ -330,6 +319,22 @@ usage! { or |c: &Config| otry!(c.misc).log_file.clone().map(Some), flag_no_color: bool = false, or |c: &Config| otry!(c.misc).color.map(|c| !c).clone(), + + + // -- Legacy Options supported in configs + flag_dapps_port: Option = None, + or |c: &Config| otry!(c.dapps).port.clone().map(Some), + flag_dapps_interface: Option = None, + or |c: &Config| otry!(c.dapps).interface.clone().map(Some), + flag_dapps_hosts: Option = None, + or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| Some(vec.join(","))), + flag_dapps_cors: Option = None, + or |c: &Config| otry!(c.dapps).cors.clone().map(Some), + flag_dapps_user: Option = None, + or |c: &Config| otry!(c.dapps).user.clone().map(Some), + flag_dapps_pass: Option = None, + or |c: &Config| otry!(c.dapps).pass.clone().map(Some), + flag_dapps_apis_all: Option = None, or |_| None, } { // Values with optional default value. @@ -419,6 +424,7 @@ struct Rpc { cors: Option, apis: Option>, hosts: Option>, + threads: Option, } #[derive(Default, Debug, PartialEq, RustcDecodable)] @@ -672,6 +678,7 @@ mod tests { flag_jsonrpc_cors: Some("null".into()), flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".into(), flag_jsonrpc_hosts: "none".into(), + flag_jsonrpc_threads: None, // IPC flag_no_ipc: false, @@ -679,15 +686,8 @@ mod tests { flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(), // DAPPS - flag_no_dapps: false, - flag_dapps_port: 8080u16, - flag_dapps_interface: "local".into(), - flag_dapps_hosts: "none".into(), - flag_dapps_cors: None, flag_dapps_path: "$HOME/.parity/dapps".into(), - flag_dapps_user: Some("test_user".into()), - flag_dapps_pass: Some("test_pass".into()), - flag_dapps_apis_all: false, + flag_no_dapps: false, flag_no_secretstore: false, flag_secretstore_port: 8082u16, @@ -792,6 +792,14 @@ mod tests { flag_extradata: None, flag_cache: None, flag_warp: Some(true), + // Legacy-Dapps + flag_dapps_port: Some(8080), + flag_dapps_interface: Some("local".into()), + flag_dapps_hosts: Some("none".into()), + flag_dapps_cors: None, + flag_dapps_user: Some("test_user".into()), + flag_dapps_pass: Some("test_pass".into()), + flag_dapps_apis_all: None, // -- Miscellaneous Options flag_version: false, @@ -873,6 +881,7 @@ mod tests { cors: None, apis: None, hosts: None, + threads: None, }), ipc: Some(Ipc { disable: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 4c1abafbe..1ebeffef9 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -149,6 +149,8 @@ API and Console Options: is additional security against some attack vectors. Special options: "all", "none", (default: {flag_jsonrpc_hosts}). + --jsonrpc-threads THREADS Enables experimental faster implementation of JSON-RPC server. + Requires Dapps server to be disabled using --no-dapps. (default: {flag_jsonrpc_threads:?}) --no-ipc Disable JSON-RPC over IPC service. (default: {flag_no_ipc}) --ipc-path PATH Specify custom path for JSON-RPC over IPC service @@ -157,29 +159,8 @@ API and Console Options: IPC (default: {flag_ipc_apis}). --no-dapps Disable the Dapps server (e.g. status page). (default: {flag_no_dapps}) - --dapps-port PORT Specify the port portion of the Dapps server - (default: {flag_dapps_port}). - --dapps-interface IP Specify the hostname portion of the Dapps - server, IP should be an interface's IP address, - or local (default: {flag_dapps_interface}). - --dapps-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_dapps_hosts}). - --dapps-cors URL Specify CORS headers for Dapps server APIs. - (default: {flag_dapps_cors:?}) - --dapps-user USERNAME Specify username for Dapps server. It will be - used in HTTP Basic Authentication Scheme. - If --dapps-pass is not specified you will be - asked for password on startup. (default: {flag_dapps_user:?}) - --dapps-pass PASSWORD Specify password for Dapps server. Use only in - conjunction with --dapps-user. (default: {flag_dapps_pass:?}) --dapps-path PATH Specify directory where dapps should be installed. (default: {flag_dapps_path}) - --dapps-apis-all Expose all possible RPC APIs on Dapps port. - WARNING: INSECURE. Used only for development. - (default: {flag_dapps_apis_all}) --ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api}) --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. (default: {flag_ipfs_api_port}) @@ -392,6 +373,13 @@ Legacy Options: --jsonrpc-off Equivalent to --no-jsonrpc. -w --webapp Does nothing; dapps server is on by default now. --dapps-off Equivalent to --no-dapps. + --dapps-user USERNAME Dapps server authentication has been removed. (default: {flag_dapps_user:?}) + --dapps-pass PASSWORD Dapps server authentication has been removed. (default: {flag_dapps_pass:?}) + --dapps-apis-all Dapps server is merged with RPC server. Use --jsonrpc-apis. (default: {flag_dapps_apis_all:?}) + --dapps-cors URL Dapps server is merged with RPC server. Use --jsonrpc-cors. (default: {flag_dapps_cors:?}) + --dapps-hosts HOSTS Dapps server is merged with RPC server. Use --jsonrpc-hosts. (default: {flag_dapps_hosts:?}) + --dapps-interface IP Dapps server is merged with RPC server. Use --jsonrpc-interface. (default: {flag_dapps_interface:?}) + --dapps-port PORT Dapps server is merged with RPC server. Use --jsonrpc-port. (default: {flag_dapps_port:?}) --rpc Does nothing; JSON-RPC is on by default now. --warp Does nothing; Warp sync is on by default. (default: {flag_warp}) --rpcaddr IP Equivalent to --jsonrpc-interface IP. diff --git a/parity/configuration.rs b/parity/configuration.rs index 5dd11bd90..1eb4c1848 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -132,12 +132,17 @@ impl Configuration { 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 ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); - let dapps_conf = self.dapps_config(); + let mut dapps_conf = self.dapps_config(); let ipfs_conf = self.ipfs_config(); let signer_conf = self.signer_config(); let secretstore_conf = self.secretstore_config(); let format = self.format()?; + if self.args.flag_jsonrpc_threads.is_some() && dapps_conf.enabled { + dapps_conf.enabled = false; + writeln!(&mut stderr(), "Warning: Disabling Dapps server because fast RPC server was enabled.").expect("Error writing to stderr.") + } + let cmd = if self.args.flag_version { Cmd::Version } else if self.args.cmd_signer { @@ -554,19 +559,12 @@ impl Configuration { fn dapps_config(&self) -> DappsConfiguration { DappsConfiguration { enabled: self.dapps_enabled(), - interface: self.dapps_interface(), - port: self.args.flag_dapps_port, - hosts: self.dapps_hosts(), - cors: self.dapps_cors(), - user: self.args.flag_dapps_user.clone(), - pass: self.args.flag_dapps_pass.clone(), dapps_path: PathBuf::from(self.directories().dapps), extra_dapps: if self.args.cmd_dapp { self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect() } else { vec![] }, - all_apis: self.args.flag_dapps_apis_all, } } @@ -746,14 +744,10 @@ impl Configuration { Self::cors(self.args.flag_ipfs_api_cors.as_ref()) } - fn dapps_cors(&self) -> Option> { - Self::cors(self.args.flag_dapps_cors.as_ref()) - } - fn hosts(hosts: &str) -> Option> { match hosts { "none" => return Some(Vec::new()), - "all" => return None, + "*" | "all" | "any" => return None, _ => {} } let hosts = hosts.split(',').map(Into::into).collect(); @@ -764,10 +758,6 @@ impl Configuration { Self::hosts(&self.args.flag_jsonrpc_hosts) } - fn dapps_hosts(&self) -> Option> { - Self::hosts(&self.args.flag_dapps_hosts) - } - fn ipfs_hosts(&self) -> Option> { Self::hosts(&self.args.flag_ipfs_api_hosts) } @@ -793,12 +783,17 @@ impl Configuration { fn http_config(&self) -> Result { let conf = HttpConfiguration { - enabled: !self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc, + enabled: self.rpc_enabled(), interface: self.rpc_interface(), port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port), apis: self.rpc_apis().parse()?, hosts: self.rpc_hosts(), cors: self.rpc_cors(), + threads: match self.args.flag_jsonrpc_threads { + Some(threads) if threads > 0 => Some(threads), + None => None, + _ => return Err("--jsonrpc-threads number needs to be positive.".into()), + } }; Ok(conf) @@ -809,7 +804,7 @@ impl Configuration { name: self.args.flag_identity.clone(), chain: self.chain(), network_port: self.args.flag_port, - rpc_enabled: !self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc, + rpc_enabled: self.rpc_enabled(), rpc_interface: self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone()), rpc_port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port), } @@ -916,13 +911,6 @@ impl Configuration { Self::interface(&self.network_settings().rpc_interface) } - fn dapps_interface(&self) -> String { - match self.args.flag_dapps_interface.as_str() { - "local" => "127.0.0.1", - x => x, - }.into() - } - fn ipfs_interface(&self) -> String { Self::interface(&self.args.flag_ipfs_api_interface) } @@ -938,8 +926,12 @@ impl Configuration { Self::interface(&self.args.flag_stratum_interface) } + fn rpc_enabled(&self) -> bool { + !self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc + } + fn dapps_enabled(&self) -> bool { - !self.args.flag_dapps_off && !self.args.flag_no_dapps && cfg!(feature = "dapps") + !self.args.flag_dapps_off && !self.args.flag_no_dapps && self.rpc_enabled() && cfg!(feature = "dapps") } fn secretstore_enabled(&self) -> bool { @@ -1317,23 +1309,6 @@ mod tests { assert_eq!(conf3.rpc_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); } - #[test] - fn should_parse_dapps_hosts() { - // given - - // when - let conf0 = parse(&["parity"]); - let conf1 = parse(&["parity", "--dapps-hosts", "none"]); - let conf2 = parse(&["parity", "--dapps-hosts", "all"]); - let conf3 = parse(&["parity", "--dapps-hosts", "ethcore.io,something.io"]); - - // then - assert_eq!(conf0.dapps_hosts(), Some(Vec::new())); - assert_eq!(conf1.dapps_hosts(), Some(Vec::new())); - assert_eq!(conf2.dapps_hosts(), None); - assert_eq!(conf3.dapps_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); - } - #[test] fn should_parse_ipfs_hosts() { // given diff --git a/parity/dapps.rs b/parity/dapps.rs index bbd5f4960..e0e97c08f 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -19,25 +19,17 @@ use std::sync::Arc; use dir::default_data_path; use ethcore::client::Client; -use ethcore_rpc::informant::RpcStats; use ethsync::SyncProvider; use hash_fetch::fetch::Client as FetchClient; use helpers::replace_home; -use rpc_apis::{self, SignerService}; +use rpc_apis::SignerService; use parity_reactor; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { pub enabled: bool, - pub interface: String, - pub port: u16, - pub hosts: Option>, - pub cors: Option>, - pub user: Option, - pub pass: Option, pub dapps_path: PathBuf, pub extra_dapps: Vec, - pub all_apis: bool, } impl Default for Configuration { @@ -45,80 +37,56 @@ impl Default for Configuration { let data_dir = default_data_path(); Configuration { enabled: true, - interface: "127.0.0.1".into(), - port: 8080, - hosts: Some(Vec::new()), - cors: None, - user: None, - pass: None, dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), extra_dapps: vec![], - all_apis: false, } } } pub struct Dependencies { - pub apis: Arc, pub client: Arc, pub sync: Arc, pub remote: parity_reactor::TokioRemote, pub fetch: FetchClient, pub signer: Arc, - pub stats: Arc, } -pub fn new(configuration: Configuration, deps: Dependencies) -> Result, String> { +pub fn new(configuration: Configuration, deps: Dependencies) -> Result, String> { if !configuration.enabled { return Ok(None); } - let url = format!("{}:{}", configuration.interface, configuration.port); - let addr = url.parse().map_err(|_| format!("Invalid Webapps listen host/port given: {}", url))?; - - let auth = configuration.user.as_ref().map(|username| { - let password = configuration.pass.as_ref().map_or_else(|| { - use rpassword::read_password; - println!("Type password for WebApps server (user: {}): ", username); - let pass = read_password().unwrap(); - println!("OK, got it. Starting server..."); - pass - }, |pass| pass.to_owned()); - (username.to_owned(), password) - }); - - Ok(Some(setup_dapps_server( + dapps_middleware( deps, configuration.dapps_path, configuration.extra_dapps, - &addr, - configuration.hosts, - configuration.cors, - auth, - configuration.all_apis, - )?)) + ).map(Some) } -pub use self::server::WebappServer; -pub use self::server::setup_dapps_server; +pub use self::server::Middleware; +pub use self::server::dapps_middleware; #[cfg(not(feature = "dapps"))] mod server { use super::Dependencies; - use std::net::SocketAddr; use std::path::PathBuf; + use ethcore_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction}; - pub struct WebappServer; - pub fn setup_dapps_server( + pub struct Middleware; + + impl RequestMiddleware for Middleware { + fn on_request( + &self, req: &hyper::server::Request, control: &hyper::Control + ) -> RequestMiddlewareAction { + unreachable!() + } + } + + pub fn dapps_middleware( _deps: Dependencies, _dapps_path: PathBuf, _extra_dapps: Vec, - _url: &SocketAddr, - _allowed_hosts: Option>, - _cors: Option>, - _auth: Option<(String, String)>, - _all_apis: bool, - ) -> Result { + ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } } @@ -128,78 +96,41 @@ mod server { use super::Dependencies; use std::path::PathBuf; use std::sync::Arc; - use std::net::SocketAddr; - use std::io; use util::{Bytes, Address, U256}; - use ansi_term::Colour; use ethcore::transaction::{Transaction, Action}; use ethcore::client::{Client, BlockChainClient, BlockId}; - use ethcore_dapps::{AccessControlAllowOrigin, Host}; use ethcore_rpc::is_major_importing; + use hash_fetch::fetch::Client as FetchClient; use hash_fetch::urlhint::ContractClient; + use parity_dapps; use parity_reactor; - use rpc_apis; - pub use ethcore_dapps::Server as WebappServer; + pub type Middleware = parity_dapps::Middleware; - pub fn setup_dapps_server( + pub fn dapps_middleware( deps: Dependencies, dapps_path: PathBuf, extra_dapps: Vec, - url: &SocketAddr, - allowed_hosts: Option>, - cors: Option>, - auth: Option<(String, String)>, - all_apis: bool, - ) -> Result { - use ethcore_dapps as dapps; - - let server = dapps::ServerBuilder::new( - &dapps_path, - Arc::new(Registrar { client: deps.client.clone() }), - parity_reactor::Remote::new(deps.remote.clone()), - ); - let allowed_hosts: Option> = allowed_hosts.map(|hosts| hosts.into_iter().map(Host::from).collect()); - let cors: Option> = cors.map(|cors| cors.into_iter().map(AccessControlAllowOrigin::from).collect()); - - let sync = deps.sync.clone(); - let client = deps.client.clone(); + ) -> Result { + let sync = deps.sync; let signer = deps.signer.clone(); - let server = server - .fetch(deps.fetch.clone()) - .sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))) - .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) - .extra_dapps(&extra_dapps) - .signer_address(deps.signer.address()) - .allowed_hosts(allowed_hosts.into()) - .extra_cors_headers(cors.into()); + let client = deps.client; + let parity_remote = parity_reactor::Remote::new(deps.remote.clone()); + let registrar = Arc::new(Registrar { client: client.clone() }); + let sync_status = Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())); + let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)); - let api_set = if all_apis { - warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed.")); - info!("If you do not intend this, exit now."); - rpc_apis::ApiSet::SafeContext - } else { - rpc_apis::ApiSet::UnsafeContext - }; - let apis = rpc_apis::setup_rpc(deps.stats, deps.apis.clone(), api_set); - let start_result = match auth { - None => { - server.start_unsecured_http(url, apis, deps.remote) - }, - Some((username, password)) => { - server.start_basic_auth_http(url, &username, &password, apis, deps.remote) - }, - }; - - match start_result { - Err(dapps::ServerError::IoError(err)) => match err.kind() { - io::ErrorKind::AddrInUse => Err(format!("WebApps address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --dapps-port and --dapps-interface options.", url)), - _ => Err(format!("WebApps io error: {}", err)), - }, - Err(e) => Err(format!("WebApps error: {:?}", e)), - Ok(server) => Ok(server), - } + Ok(parity_dapps::Middleware::new( + parity_remote, + deps.signer.address(), + dapps_path, + extra_dapps, + registrar, + sync_status, + web_proxy_tokens, + deps.fetch.clone(), + )) } struct Registrar { diff --git a/parity/deprecated.rs b/parity/deprecated.rs index 97c6ffe4a..820181efa 100644 --- a/parity/deprecated.rs +++ b/parity/deprecated.rs @@ -21,94 +21,89 @@ use cli::Args; pub enum Deprecated { DoesNothing(&'static str), Replaced(&'static str, &'static str), + Removed(&'static str), } impl fmt::Display for Deprecated { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Deprecated::DoesNothing(s) => write!(f, "Option '{}' does nothing. It's on by default", s), - Deprecated::Replaced(old, new) => write!(f, "Option '{}' is deprecated. Please use '{}' instead", old, new), + Deprecated::DoesNothing(s) => write!(f, "Option '{}' does nothing. It's on by default.", s), + Deprecated::Replaced(old, new) => write!(f, "Option '{}' is deprecated. Please use '{}' instead.", old, new), + Deprecated::Removed(s) => write!(f, "Option '{}' has been removed and is no longer supported.", s) } } } -impl Deprecated { - fn jsonrpc() -> Self { - Deprecated::DoesNothing("--jsonrpc") - } - - fn rpc() -> Self { - Deprecated::DoesNothing("--rpc") - } - - fn jsonrpc_off() -> Self { - Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc") - } - - fn webapp() -> Self { - Deprecated::DoesNothing("--webapp") - } - - fn dapps_off() -> Self { - Deprecated::Replaced("--dapps-off", "--no-dapps") - } - - fn ipcdisable() -> Self { - Deprecated::Replaced("--ipcdisable", "--no-ipc") - } - - fn ipc_off() -> Self { - Deprecated::Replaced("--ipc-off", "--no-ipc") - } - - fn etherbase() -> Self { - Deprecated::Replaced("--etherbase", "--author") - } - - fn extradata() -> Self { - Deprecated::Replaced("--extradata", "--extra-data") - } -} - pub fn find_deprecated(args: &Args) -> Vec { let mut result = vec![]; if args.flag_jsonrpc { - result.push(Deprecated::jsonrpc()); + result.push(Deprecated::DoesNothing("--jsonrpc")); } if args.flag_rpc { - result.push(Deprecated::rpc()); + result.push(Deprecated::DoesNothing("--rpc")); } if args.flag_jsonrpc_off { - result.push(Deprecated::jsonrpc_off()); + result.push(Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc")); } if args.flag_webapp { - result.push(Deprecated::webapp()) + result.push(Deprecated::DoesNothing("--webapp")); } if args.flag_dapps_off { - result.push(Deprecated::dapps_off()); + result.push(Deprecated::Replaced("--dapps-off", "--no-dapps")); } if args.flag_ipcdisable { - result.push(Deprecated::ipcdisable()); + result.push(Deprecated::Replaced("--ipcdisable", "--no-ipc")); } if args.flag_ipc_off { - result.push(Deprecated::ipc_off()); + result.push(Deprecated::Replaced("--ipc-off", "--no-ipc")); } if args.flag_etherbase.is_some() { - result.push(Deprecated::etherbase()); + result.push(Deprecated::Replaced("--etherbase", "--author")); } if args.flag_extradata.is_some() { - result.push(Deprecated::extradata()); + result.push(Deprecated::Replaced("--extradata", "--extra-data")); } + // Removed in 1.7 + if args.flag_dapps_port.is_some() { + result.push(Deprecated::Replaced("--dapps-port", "--jsonrpc-port")); + } + + if args.flag_dapps_interface.is_some() { + result.push(Deprecated::Replaced("--dapps-interface", "--jsonrpc-interface")); + } + + if args.flag_dapps_hosts.is_some() { + result.push(Deprecated::Replaced("--dapps-hosts", "--jsonrpc-hosts")); + } + + if args.flag_dapps_cors.is_some() { + result.push(Deprecated::Replaced("--dapps-cors", "--jsonrpc-cors")); + } + + if args.flag_dapps_user.is_some() { + result.push(Deprecated::Removed("--dapps-user")); + } + + if args.flag_dapps_pass.is_some() { + result.push(Deprecated::Removed("--dapps-pass")); + } + + if args.flag_dapps_apis_all.is_some() { + result.push(Deprecated::Replaced("--dapps-apis-all", "--jsonrpc-apis")); + } + + // Removed in 1.8 + result } @@ -131,17 +126,31 @@ mod tests { args.flag_ipc_off = true; args.flag_etherbase = Some(Default::default()); args.flag_extradata = Some(Default::default()); + args.flag_dapps_port = Some(Default::default()); + args.flag_dapps_interface = Some(Default::default()); + args.flag_dapps_hosts = Some(Default::default()); + args.flag_dapps_cors = Some(Default::default()); + args.flag_dapps_user = Some(Default::default()); + args.flag_dapps_pass = Some(Default::default()); + args.flag_dapps_apis_all = Some(Default::default()); args }), vec![ - Deprecated::jsonrpc(), - Deprecated::rpc(), - Deprecated::jsonrpc_off(), - Deprecated::webapp(), - Deprecated::dapps_off(), - Deprecated::ipcdisable(), - Deprecated::ipc_off(), - Deprecated::etherbase(), - Deprecated::extradata(), + Deprecated::DoesNothing("--jsonrpc"), + Deprecated::DoesNothing("--rpc"), + Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc"), + Deprecated::DoesNothing("--webapp"), + Deprecated::Replaced("--dapps-off", "--no-dapps"), + Deprecated::Replaced("--ipcdisable", "--no-ipc"), + Deprecated::Replaced("--ipc-off", "--no-ipc"), + Deprecated::Replaced("--etherbase", "--author"), + Deprecated::Replaced("--extradata", "--extra-data"), + Deprecated::Replaced("--dapps-port", "--jsonrpc-port"), + Deprecated::Replaced("--dapps-interface", "--jsonrpc-interface"), + Deprecated::Replaced("--dapps-hosts", "--jsonrpc-hosts"), + Deprecated::Replaced("--dapps-cors", "--jsonrpc-cors"), + Deprecated::Removed("--dapps-user"), + Deprecated::Removed("--dapps-pass"), + Deprecated::Replaced("--dapps-apis-all", "--jsonrpc-apis"), ]); } } diff --git a/parity/ipfs.rs b/parity/ipfs.rs index 760868f91..45c3f7062 100644 --- a/parity/ipfs.rs +++ b/parity/ipfs.rs @@ -15,10 +15,9 @@ // along with Parity. If not, see . use std::sync::Arc; -use parity_ipfs_api::{self, AccessControlAllowOrigin, Host}; +use parity_ipfs_api::{self, AccessControlAllowOrigin, Host, Listening}; use parity_ipfs_api::error::ServerError; use ethcore::client::BlockChainClient; -use hyper::server::Listening; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { diff --git a/parity/main.rs b/parity/main.rs index 2044b3ee0..4b6dc6dab 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -28,7 +28,6 @@ extern crate ctrlc; extern crate docopt; extern crate env_logger; extern crate fdlimit; -extern crate hyper; extern crate isatty; extern crate jsonrpc_core; extern crate num_cpus; @@ -73,7 +72,11 @@ extern crate ethcore_stratum; extern crate ethcore_secretstore; #[cfg(feature = "dapps")] -extern crate ethcore_dapps; +extern crate parity_dapps; + +#[cfg(test)] +#[macro_use] +extern crate pretty_assertions; #[cfg(windows)] extern crate ws2_32; #[cfg(windows)] extern crate winapi; diff --git a/parity/rpc.rs b/parity/rpc.rs index a435f24db..254bb782e 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -14,24 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::fmt; +use std::{io, fmt}; use std::sync::Arc; -use std::net::SocketAddr; -use std::io; +use dapps; use dir::default_data_path; -use ethcore_rpc::{self as rpc, HttpServerError, Metadata, Origin, AccessControlAllowOrigin, Host}; use ethcore_rpc::informant::{RpcStats, Middleware}; +use ethcore_rpc::{self as rpc, HttpServerError, Metadata, Origin, AccessControlAllowOrigin, Host}; use helpers::parity_ipc_path; -use hyper; use jsonrpc_core::MetaIoHandler; -use rpc_apis; -use rpc_apis::ApiSet; use parity_reactor::TokioRemote; +use rpc_apis::{self, ApiSet}; -pub use ethcore_rpc::{IpcServer, HttpServer}; +pub use ethcore_rpc::{IpcServer, HttpServer, RequestMiddleware}; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct HttpConfiguration { pub enabled: bool, pub interface: String, @@ -39,6 +36,7 @@ pub struct HttpConfiguration { pub apis: ApiSet, pub cors: Option>, pub hosts: Option>, + pub threads: Option, } impl Default for HttpConfiguration { @@ -50,6 +48,7 @@ impl Default for HttpConfiguration { apis: ApiSet::UnsafeContext, cors: None, hosts: Some(Vec::new()), + threads: None, } } } @@ -89,13 +88,17 @@ pub struct Dependencies { } pub struct RpcExtractor; -impl rpc::HttpMetaExtractor for RpcExtractor { - fn read_metadata(&self, req: &hyper::server::Request) -> Metadata { - let origin = req.headers().get::() - .map(|origin| format!("{}://{}", origin.scheme, origin.host)) - .unwrap_or_else(|| "unknown".into()); +impl rpc::HttpMetaExtractor for RpcExtractor { + type Metadata = Metadata; + + fn read_metadata(&self, origin: String, dapps_origin: Option) -> Metadata { let mut metadata = Metadata::default(); - metadata.origin = Origin::Rpc(origin); + + metadata.origin = match (origin.as_str(), dapps_origin) { + ("null", Some(dapp)) => Origin::Dapps(dapp.into()), + _ => Origin::Rpc(origin), + }; + metadata } } @@ -109,52 +112,92 @@ impl rpc::IpcMetaExtractor for RpcExtractor { } } -pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result, String> { +fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler { + rpc_apis::setup_rpc(deps.stats.clone(), deps.apis.clone(), apis) +} + +pub fn new_http(conf: HttpConfiguration, deps: &Dependencies, middleware: Option) -> Result, String> { if !conf.enabled { return Ok(None); } let url = format!("{}:{}", conf.interface, conf.port); let addr = url.parse().map_err(|_| format!("Invalid JSONRPC listen host/port given: {}", url))?; - Ok(Some(setup_http_rpc_server(deps, &addr, conf.cors, conf.hosts, conf.apis)?)) -} + let handler = setup_apis(conf.apis, deps); + let remote = deps.remote.clone(); -fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler { - rpc_apis::setup_rpc(deps.stats.clone(), deps.apis.clone(), apis) -} + let cors_domains: Option> = conf.cors.map(|domains| domains.into_iter().map(AccessControlAllowOrigin::from).collect()); + let allowed_hosts: Option> = conf.hosts.map(|hosts| hosts.into_iter().map(Host::from).collect()); + + let start_result = rpc::start_http( + &addr, + cors_domains.into(), + allowed_hosts.into(), + handler, + remote, + RpcExtractor, + match (conf.threads, middleware) { + (Some(threads), None) => rpc::HttpSettings::Threads(threads), + (None, middleware) => rpc::HttpSettings::Dapps(middleware), + (Some(_), Some(_)) => { + return Err("Dapps and fast multi-threaded RPC server cannot be enabled at the same time.".into()) + }, + } + ); -pub fn setup_http_rpc_server( - dependencies: &Dependencies, - url: &SocketAddr, - cors_domains: Option>, - allowed_hosts: Option>, - apis: ApiSet -) -> Result { - let handler = setup_apis(apis, dependencies); - let remote = dependencies.remote.clone(); - let cors_domains: Option> = cors_domains.map(|domains| domains.into_iter().map(AccessControlAllowOrigin::from).collect()); - let allowed_hosts: Option> = allowed_hosts.map(|hosts| hosts.into_iter().map(Host::from).collect()); - let start_result = rpc::start_http(url, cors_domains.into(), allowed_hosts.into(), handler, remote, RpcExtractor); match start_result { - Err(HttpServerError::IoError(err)) => match err.kind() { - io::ErrorKind::AddrInUse => Err(format!("RPC 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)), + Ok(server) => Ok(Some(server)), + Err(HttpServerError::Io(err)) => match err.kind() { + io::ErrorKind::AddrInUse => Err( + format!("RPC 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) + ), _ => Err(format!("RPC io error: {}", err)), }, Err(e) => Err(format!("RPC error: {:?}", e)), - Ok(server) => Ok(server), } } -pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Result, String> { - if !conf.enabled { return Ok(None); } - Ok(Some(setup_ipc_rpc_server(deps, &conf.socket_addr, conf.apis)?)) -} - -pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: ApiSet) -> Result { - let handler = setup_apis(apis, dependencies); +pub fn new_ipc(conf: IpcConfiguration, dependencies: &Dependencies) -> Result, String> { + if !conf.enabled { + return Ok(None); + } + let handler = setup_apis(conf.apis, dependencies); let remote = dependencies.remote.clone(); - match rpc::start_ipc(addr, handler, remote, RpcExtractor) { + match rpc::start_ipc(&conf.socket_addr, handler, remote, RpcExtractor) { + Ok(server) => Ok(Some(server)), Err(io_error) => Err(format!("RPC io error: {}", io_error)), - Ok(server) => Ok(server) + } +} + +#[cfg(test)] +mod tests { + use super::RpcExtractor; + use ethcore_rpc::{HttpMetaExtractor, Origin}; + + #[test] + fn should_extract_rpc_origin() { + // given + let extractor = RpcExtractor; + + // when + let meta = extractor.read_metadata("http://parity.io".into(), None); + let meta1 = extractor.read_metadata("http://parity.io".into(), Some("ignored".into())); + + // then + assert_eq!(meta.origin, Origin::Rpc("http://parity.io".into())); + assert_eq!(meta1.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("null".into(), Some(dapp.clone())); + + // then + assert_eq!(meta.origin, Origin::Dapps(dapp.into())); } } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index e168f029c..dbeeea962 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -81,7 +81,7 @@ impl FromStr for Api { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ApiSet { SafeContext, UnsafeContext, diff --git a/parity/run.rs b/parity/run.rs index c438c25a5..a85bcc39b 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -36,7 +36,6 @@ use updater::{UpdatePolicy, Updater}; use parity_reactor::EventLoop; use hash_fetch::fetch::{Fetch, Client as FetchClient}; -use rpc::{HttpConfiguration, IpcConfiguration}; use params::{ SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool @@ -76,8 +75,8 @@ pub struct RunCmd { pub daemon: Option, pub logger_config: LogConfig, pub miner_options: MinerOptions, - pub http_conf: HttpConfiguration, - pub ipc_conf: IpcConfiguration, + pub http_conf: rpc::HttpConfiguration, + pub ipc_conf: rpc::IpcConfiguration, pub net_conf: NetworkConfiguration, pub network_id: Option, pub warp_sync: bool, @@ -110,11 +109,7 @@ pub struct RunCmd { pub verifier_settings: VerifierSettings, } -pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> { - if !dapps_conf.enabled { - return Err("Cannot use UI command with Dapps turned off.".into()) - } - +pub fn open_ui(signer_conf: &signer::Configuration) -> Result<(), String> { if !signer_conf.enabled { return Err("Cannot use UI command with UI turned off.".into()) } @@ -127,12 +122,12 @@ pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configur Ok(()) } -pub fn open_dapp(dapps_conf: &dapps::Configuration, dapp: &str) -> Result<(), String> { +pub fn open_dapp(dapps_conf: &dapps::Configuration, rpc_conf: &rpc::HttpConfiguration, dapp: &str) -> Result<(), String> { if !dapps_conf.enabled { return Err("Cannot use DAPP command with Dapps turned off.".into()) } - let url = format!("http://{}:{}/{}/", dapps_conf.interface, dapps_conf.port, dapp); + let url = format!("http://{}:{}/{}/", rpc_conf.interface, rpc_conf.port, dapp); url::open(&url); Ok(()) } @@ -156,9 +151,9 @@ impl ::local_store::NodeInfo for FullNodeInfo { pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> Result<(bool, Option), String> { if cmd.ui && cmd.dapps_conf.enabled { // Check if Parity is already running - let addr = format!("{}:{}", cmd.dapps_conf.interface, cmd.dapps_conf.port); + let addr = format!("{}:{}", cmd.signer_conf.interface, cmd.signer_conf.port); if !TcpListener::bind(&addr as &str).is_ok() { - return open_ui(&cmd.dapps_conf, &cmd.signer_conf).map(|_| (false, None)); + return open_ui(&cmd.signer_conf).map(|_| (false, None)); } } @@ -429,11 +424,11 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R updater: updater.clone(), geth_compatibility: cmd.geth_compatibility, dapps_interface: match cmd.dapps_conf.enabled { - true => Some(cmd.dapps_conf.interface.clone()), + true => Some(cmd.http_conf.interface.clone()), false => None, }, dapps_port: match cmd.dapps_conf.enabled { - true => Some(cmd.dapps_conf.port), + true => Some(cmd.http_conf.port), false => None, }, fetch: fetch.clone(), @@ -445,21 +440,19 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R stats: rpc_stats.clone(), }; - // start rpc servers - let http_server = rpc::new_http(cmd.http_conf, &dependencies)?; - let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; - - // the dapps server + // the dapps middleware let dapps_deps = dapps::Dependencies { - apis: deps_for_rpc_apis.clone(), client: client.clone(), sync: sync_provider.clone(), remote: event_loop.raw_remote(), fetch: fetch.clone(), signer: deps_for_rpc_apis.signer_service.clone(), - stats: rpc_stats.clone(), }; - let dapps_server = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; + let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; + + // start rpc servers + let http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?; + let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; // the signer server let signer_deps = signer::Dependencies { @@ -524,18 +517,18 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R // start ui if cmd.ui { - open_ui(&cmd.dapps_conf, &cmd.signer_conf)?; + open_ui(&cmd.signer_conf)?; } if let Some(dapp) = cmd.dapp { - open_dapp(&cmd.dapps_conf, &dapp)?; + open_dapp(&cmd.dapps_conf, &cmd.http_conf, &dapp)?; } // Handle exit let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); + drop((http_server, ipc_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); info!("Finishing work, please wait..."); diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 960dc5102..cbfecf366 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -21,6 +21,7 @@ transient-hashmap = "0.4" 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-minihttp-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index abc51f2ed..247edd8f7 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -29,8 +29,9 @@ extern crate time; extern crate transient_hashmap; extern crate jsonrpc_core; -pub extern crate jsonrpc_http_server as http; -pub extern crate jsonrpc_ipc_server as ipc; +extern crate jsonrpc_http_server as http; +extern crate jsonrpc_minihttp_server as minihttp; +extern crate jsonrpc_ipc_server as ipc; extern crate ethash; extern crate ethcore; @@ -62,10 +63,15 @@ extern crate ethjson; #[cfg(test)] extern crate ethcore_devtools as devtools; +mod metadata; pub mod v1; pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext}; -pub use http::{HttpMetaExtractor, Server as HttpServer, Error as HttpServerError, AccessControlAllowOrigin, Host}; +pub use http::{ + hyper, + RequestMiddleware, RequestMiddlewareAction, + AccessControlAllowOrigin, Host, +}; pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch}; pub use v1::block_import::is_major_importing; @@ -73,26 +79,98 @@ pub use v1::block_import::is_major_importing; use std::net::SocketAddr; use http::tokio_core; +/// RPC HTTP Server instance +pub enum HttpServer { + /// Fast MiniHTTP variant + Mini(minihttp::Server), + /// Hyper variant + Hyper(http::Server), +} + +/// RPC HTTP Server error +#[derive(Debug)] +pub enum HttpServerError { + /// IO error + Io(::std::io::Error), + /// Other hyper error + Hyper(hyper::Error), +} + +impl From for HttpServerError { + fn from(e: http::Error) -> Self { + use self::HttpServerError::*; + match e { + http::Error::Io(io) => Io(io), + http::Error::Other(hyper) => Hyper(hyper), + } + } +} + +impl From for HttpServerError { + fn from(e: minihttp::Error) -> Self { + use self::HttpServerError::*; + match e { + minihttp::Error::Io(io) => Io(io), + } + } +} + +/// 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) -> Self::Metadata; +} + +/// HTTP server implementation-specific settings. +pub enum HttpSettings { + /// Enable fast minihttp server with given number of threads. + Threads(usize), + /// Enable standard server with optional dapps middleware. + Dapps(Option), +} + /// Start http server asynchronously and returns result with `Server` handle on success or an error. -pub fn start_http( +pub fn start_http( addr: &SocketAddr, cors_domains: http::DomainsValidation, allowed_hosts: http::DomainsValidation, handler: H, remote: tokio_core::reactor::Remote, extractor: T, + settings: HttpSettings, ) -> Result where M: jsonrpc_core::Metadata, S: jsonrpc_core::Middleware, H: Into>, - T: HttpMetaExtractor, + T: HttpMetaExtractor, + R: RequestMiddleware, { - http::ServerBuilder::new(handler) - .event_loop_remote(remote) - .meta_extractor(extractor) - .cors(cors_domains.into()) - .allowed_hosts(allowed_hosts.into()) - .start_http(addr) + Ok(match settings { + HttpSettings::Dapps(middleware) => { + let mut builder = http::ServerBuilder::new(handler) + .event_loop_remote(remote) + .meta_extractor(metadata::HyperMetaExtractor::new(extractor)) + .cors(cors_domains.into()) + .allowed_hosts(allowed_hosts.into()); + + if let Some(dapps) = middleware { + builder = builder.request_middleware(dapps) + } + builder.start_http(addr) + .map(HttpServer::Hyper)? + }, + HttpSettings::Threads(threads) => { + minihttp::ServerBuilder::new(handler) + .threads(threads) + .meta_extractor(metadata::MiniMetaExtractor::new(extractor)) + .cors(cors_domains.into()) + .allowed_hosts(allowed_hosts.into()) + .start_http(addr) + .map(HttpServer::Mini)? + }, + }) } /// Start ipc server asynchronously and returns result with `Server` handle on success or an error. diff --git a/rpc/src/metadata.rs b/rpc/src/metadata.rs new file mode 100644 index 000000000..af3a5d183 --- /dev/null +++ b/rpc/src/metadata.rs @@ -0,0 +1,74 @@ +// 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 . + +use jsonrpc_core; +use http; +use hyper; +use minihttp; +use HttpMetaExtractor; + +pub struct HyperMetaExtractor { + extractor: T, +} + +impl HyperMetaExtractor { + pub fn new(extractor: T) -> Self { + HyperMetaExtractor { + extractor: extractor, + } + } +} + +impl http::MetaExtractor for HyperMetaExtractor where + T: HttpMetaExtractor, + M: jsonrpc_core::Metadata, +{ + fn read_metadata(&self, req: &hyper::server::Request) -> M { + let origin = req.headers().get::() + .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()) + .map(|raw| String::from_utf8_lossy(raw).into_owned()); + self.extractor.read_metadata(origin, dapps_origin) + } +} + +pub struct MiniMetaExtractor { + extractor: T, +} + +impl MiniMetaExtractor { + pub fn new(extractor: T) -> Self { + MiniMetaExtractor { + extractor: extractor, + } + } +} + +impl minihttp::MetaExtractor for MiniMetaExtractor where + T: HttpMetaExtractor, + M: jsonrpc_core::Metadata, +{ + fn read_metadata(&self, req: &minihttp::Req) -> M { + let origin = req.header("origin") + .unwrap_or_else(|| "unknown") + .to_owned(); + let dapps_origin = req.header("x-parity-origin").map(|h| h.to_owned()); + + self.extractor.read_metadata(origin, dapps_origin) + } +} diff --git a/scripts/targets.sh b/scripts/targets.sh index 505875336..040485d85 100644 --- a/scripts/targets.sh +++ b/scripts/targets.sh @@ -5,7 +5,7 @@ export TARGETS=" -p ethash \ -p ethcore \ -p ethcore-bigint\ - -p ethcore-dapps \ + -p parity-dapps \ -p ethcore-rpc \ -p ethcore-signer \ -p ethcore-util \