diff --git a/Cargo.lock b/Cargo.lock index 71b6873f6..210b23666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "ethcore-devtools 1.1.0", "ethcore-rpc 1.1.0", "ethcore-util 1.1.0", + "ethcore-webapp 1.1.0", "ethminer 1.1.0", "ethsync 1.1.0", "fdlimit 0.1.0", @@ -107,6 +108,14 @@ dependencies = [ "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "conduit-mime-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.1.21" @@ -185,6 +194,15 @@ dependencies = [ "regex 0.1.61 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth-secp256k1" version = "0.5.4" @@ -291,6 +309,23 @@ dependencies = [ "vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-webapp" +version = "1.1.0" +dependencies = [ + "clippy 0.0.61 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-rpc 1.1.0", + "ethcore-util 1.1.0", + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)", + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-status 0.1.4 (git+https://github.com/tomusdrw/parity-status.git)", + "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", +] + [[package]] name = "ethjson" version = "0.1.0" @@ -448,6 +483,23 @@ dependencies = [ "xmltree 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "iron" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.4.11" @@ -472,6 +524,16 @@ dependencies = [ "syntex 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-http-server" +version = "4.0.0" +source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15" +dependencies = [ + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jsonrpc-http-server" version = "5.0.0" @@ -594,6 +656,11 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "net2" version = "0.2.23" @@ -658,6 +725,35 @@ name = "odds" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "parity-status" +version = "0.1.4" +source = "git+https://github.com/tomusdrw/parity-status.git#380d13c8aafc3847a731968a6532edb09c78f2cf" +dependencies = [ + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", +] + +[[package]] +name = "parity-wallet" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/parity-wallet.git#9b0253f5cb88b31417450ca8be708cab2e437dfc" +dependencies = [ + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", +] + +[[package]] +name = "parity-webapp" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/parity-webapp.git#a24297256bae0ae0712c6478cd1ad681828b3800" + +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "primal" version = "0.2.3" @@ -976,6 +1072,14 @@ name = "typeable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicase" version = "1.4.0" @@ -1002,6 +1106,14 @@ name = "unicode-xid" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unsafe-any" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.38" diff --git a/Cargo.toml b/Cargo.toml index f38fe5b10..276159f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ ethsync = { path = "sync" } ethminer = { path = "miner" } ethcore-devtools = { path = "devtools" } ethcore-rpc = { path = "rpc", optional = true } +ethcore-webapp = { path = "webapp", optional = true } + [dependencies.hyper] version = "0.8" @@ -36,7 +38,9 @@ default-features = false [features] default = ["rpc"] rpc = ["ethcore-rpc"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev"] +webapp = ["ethcore-webapp"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev", +"ethcore-webapp/dev"] travis-beta = ["ethcore/json-tests"] travis-nightly = ["ethcore/json-tests", "dev"] diff --git a/cov.sh b/cov.sh index d60ef223d..1698d6f36 100755 --- a/cov.sh +++ b/cov.sh @@ -23,6 +23,7 @@ cargo test \ -p ethcore-rpc \ -p parity \ -p ethminer \ + -p ethcore-webapp \ --no-run || exit $? rm -rf target/coverage mkdir -p target/coverage @@ -33,5 +34,6 @@ kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage t kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethsync-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-* +kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_webapp-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethminer-* xdg-open target/coverage/index.html diff --git a/doc.sh b/doc.sh index a5e5e2e13..9b0f13817 100755 --- a/doc.sh +++ b/doc.sh @@ -7,5 +7,6 @@ cargo doc --no-deps --verbose \ -p ethcore \ -p ethsync \ -p ethcore-rpc \ + -p ethcore-webapp \ -p parity \ -p ethminer diff --git a/fmt.sh b/fmt.sh index a16d5ac1f..4d835967f 100755 --- a/fmt.sh +++ b/fmt.sh @@ -9,6 +9,7 @@ $RUSTFMT ./json/src/lib.rs $RUSTFMT ./miner/src/lib.rs $RUSTFMT ./parity/main.rs $RUSTFMT ./rpc/src/lib.rs +$RUSTFMT ./webapp/src/lib.rs $RUSTFMT ./sync/src/lib.rs $RUSTFMT ./util/src/lib.rs diff --git a/hook.sh b/hook.sh index 58bff20ab..d98297835 100755 --- a/hook.sh +++ b/hook.sh @@ -7,6 +7,6 @@ echo "set -e" >> $FILE echo "cargo build --release --features dev" >> $FILE # Build tests echo "cargo test --no-run --features dev \\" >> $FILE -echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer" >> $FILE +echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer -p ethcore-webapp" >> $FILE echo "" >> $FILE chmod +x $FILE diff --git a/parity/main.rs b/parity/main.rs index 192f88132..b9f95eee3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -42,6 +42,8 @@ extern crate rpassword; #[cfg(feature = "rpc")] extern crate ethcore_rpc as rpc; +#[cfg(feature = "webapp")] +extern crate ethcore_webapp as webapp; use std::io::{BufRead, BufReader}; use std::fs::File; @@ -65,6 +67,8 @@ use daemonize::Daemonize; use number_prefix::{binary_prefix, Standalone, Prefixed}; #[cfg(feature = "rpc")] use rpc::Server as RpcServer; +#[cfg(feature = "webapp")] +use webapp::Listening as WebappServer; mod price_info; @@ -120,7 +124,7 @@ Networking Options: string or input to SHA3 operation. API and Console Options: - -j --jsonrpc Enable the JSON-RPC API sever. + -j --jsonrpc Enable the JSON-RPC API server. --jsonrpc-interface IP Specify the hostname portion of the JSONRPC API server, IP should be an interface's IP address, or all (all interfaces) or local [default: local]. @@ -132,6 +136,13 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth and net. [default: web3,eth,net,personal]. + -w --webapp Enable the web applications server (e.g. status page). + --webapp-port PORT Specify the port portion of the WebApps server + [default: 8080]. + --webapp-interface IP Specify the hostname portion of the WebApps + server, IP should be an interface's IP address, or + all (all interfaces) or local [default: local]. + Sealing/Mining Options: --usd-per-tx USD Amount of USD to be paid for a basic transaction @@ -216,6 +227,9 @@ struct Args { flag_jsonrpc_port: u16, flag_jsonrpc_cors: String, flag_jsonrpc_apis: String, + flag_webapp: bool, + flag_webapp_port: u16, + flag_webapp_interface: String, flag_author: String, flag_usd_per_tx: String, flag_usd_per_eth: String, @@ -301,6 +315,31 @@ fn setup_rpc_server( } } +#[cfg(feature = "webapp")] +fn setup_webapp_server( + client: Arc, + sync: Arc, + secret_store: Arc, + miner: Arc, + url: &str +) -> WebappServer { + use rpc::v1::*; + + let server = webapp::WebappServer::new(); + server.add_delegate(Web3Client::new().to_delegate()); + server.add_delegate(NetClient::new(&sync).to_delegate()); + server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); + server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); + let start_result = server.start_http(url, ::num_cpus::get()); + match start_result { + Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), + Err(e) => die!("{:?}", e), + Ok(handle) => handle, + } + +} + #[cfg(not(feature = "rpc"))] struct RpcServer; @@ -317,6 +356,20 @@ fn setup_rpc_server( die!("Your Parity version has been compiled without JSON-RPC support.") } +#[cfg(not(feature = "webapp"))] +struct WebappServer; + +#[cfg(not(feature = "webapp"))] +fn setup_webapp_server( + _client: Arc, + _sync: Arc, + _secret_store: Arc, + _miner: Arc, + _url: &str +) -> ! { + die!("Your Parity version has been compiled without WebApps support.") +} + fn print_version() { println!("\ Parity @@ -621,6 +674,26 @@ impl Configuration { None }; + let webapp_server = if self.args.flag_webapp { + let url = format!("{}:{}", + match self.args.flag_webapp_interface.as_str() { + "all" => "0.0.0.0", + "local" => "127.0.0.1", + x => x, + }, + self.args.flag_webapp_port + ); + Some(setup_webapp_server( + service.client(), + sync.clone(), + account_service.clone(), + miner.clone(), + &url, + )) + } else { + None + }; + // Register IO handler let io_handler = Arc::new(ClientIoHandler { client: service.client(), @@ -631,11 +704,11 @@ impl Configuration { service.io().register_handler(io_handler).expect("Error registering IO handler"); // Handle exit - wait_for_exit(panic_handler, rpc_server); + wait_for_exit(panic_handler, rpc_server, webapp_server); } } -fn wait_for_exit(panic_handler: Arc, _rpc_server: Option) { +fn wait_for_exit(panic_handler: Arc, _rpc_server: Option, _webapp_server: Option) { let exit = Arc::new(Condvar::new()); // Handle possible exits diff --git a/test.sh b/test.sh index 4957fd762..5094158cc 100755 --- a/test.sh +++ b/test.sh @@ -7,6 +7,7 @@ cargo test --features ethcore/json-tests $1 \ -p ethcore \ -p ethsync \ -p ethcore-rpc \ + -p ethcore-webapp \ -p parity \ -p ethminer \ -p bigint diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml new file mode 100644 index 000000000..59452f32d --- /dev/null +++ b/webapp/Cargo.toml @@ -0,0 +1,26 @@ +[package] +description = "Parity WebApplications crate" +name = "ethcore-webapp" +version = "1.1.0" +license = "GPL-3.0" +authors = ["Ethcore . + +use std::collections::HashMap; +use page::{Page, PageHandler}; + +extern crate parity_status; +extern crate parity_wallet; + +pub type Pages = HashMap>; + +pub fn main_page() -> Box { + Box::new(PageHandler { app: parity_status::App::default() }) +} + +pub fn all_pages() -> Pages { + let mut pages = Pages::new(); + wallet_page(&mut pages); + pages +} + +#[cfg(feature = "parity-wallet")] +fn wallet_page(pages: &mut Pages) { + pages.insert("wallet".to_owned(), Box::new(PageHandler { app: parity_wallet::App::default() })); +} + +#[cfg(not(feature = "parity-wallet"))] +fn wallet_page(_pages: &mut Pages) {} diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs new file mode 100644 index 000000000..35ebb4a44 --- /dev/null +++ b/webapp/src/lib.rs @@ -0,0 +1,99 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Ethcore Webapplications for Parity +#![warn(missing_docs)] +#![cfg_attr(feature="nightly", plugin(clippy))] + +#[macro_use] +extern crate log; +extern crate hyper; +extern crate iron; +extern crate jsonrpc_core; +extern crate jsonrpc_http_server; +extern crate ethcore_rpc as rpc; +extern crate parity_webapp; + +use std::sync::Arc; +use self::jsonrpc_core::{IoHandler, IoDelegate}; +use jsonrpc_http_server::ServerHandler; + +mod apps; +mod page; +mod router; + +/// Http server. +pub struct WebappServer { + handler: Arc, +} + +impl WebappServer { + /// Construct new http server object + pub fn new() -> Self { + WebappServer { + handler: Arc::new(IoHandler::new()), + } + } + + /// Add io delegate. + pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { + self.handler.add_delegate(delegate); + } + + /// Start server asynchronously and returns result with `Listening` handle on success or an error. + pub fn start_http(&self, addr: &str, threads: usize) -> Result { + let addr = addr.to_owned(); + let handler = self.handler.clone(); + + let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; + let rpc = ServerHandler::new(handler, cors_domain); + let router = router::Router::new(rpc, apps::main_page(), apps::all_pages()); + + try!(hyper::Server::http(addr.as_ref() as &str)) + .handle_threads(router, threads) + .map(|l| Listening { listening: l }) + .map_err(WebappServerError::from) + } +} + +/// Listening handle +pub struct Listening { + listening: hyper::server::Listening +} + +impl Drop for Listening { + fn drop(&mut self) { + self.listening.close().unwrap(); + } +} + +/// Webapp Server startup error +#[derive(Debug)] +pub enum WebappServerError { + /// Wrapped `std::io::Error` + IoError(std::io::Error), + /// Other `hyper` error + Other(hyper::error::Error), +} + +impl From for WebappServerError { + fn from(err: hyper::error::Error) -> Self { + match err { + hyper::error::Error::Io(e) => WebappServerError::IoError(e), + e => WebappServerError::Other(e), + } + } +} diff --git a/webapp/src/page/mod.rs b/webapp/src/page/mod.rs new file mode 100644 index 000000000..ce7b32947 --- /dev/null +++ b/webapp/src/page/mod.rs @@ -0,0 +1,67 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::io::Write; +use hyper::uri::RequestUri; +use hyper::server; +use hyper::header; +use hyper::status::StatusCode; +use parity_webapp::WebApp; + +pub trait Page : Send + Sync { + fn serve_file(&self, mut path: &str, mut res: server::Response); +} + +pub struct PageHandler { + pub app: T, +} + +impl Page for PageHandler { + fn serve_file(&self, mut path: &str, mut res: server::Response) { + // Support index file + if path == "" { + path = "index.html" + } + let file = self.app.file(path); + if let Some(f) = file { + *res.status_mut() = StatusCode::Ok; + res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap())); + + let _ = match res.start() { + Ok(mut raw_res) => { + for chunk in f.content.chunks(1024 * 20) { + let _ = raw_res.write(chunk); + } + raw_res.end() + }, + Err(_) => { + println!("Error while writing response."); + Ok(()) + }, + }; + } + } +} + +impl server::Handler for Page { + fn handle(&self, req: server::Request, mut res: server::Response) { + *res.status_mut() = StatusCode::NotFound; + + if let RequestUri::AbsolutePath(ref path) = req.uri { + self.serve_file(path, res); + } + } +} diff --git a/webapp/src/router/api.rs b/webapp/src/router/api.rs new file mode 100644 index 000000000..046b75957 --- /dev/null +++ b/webapp/src/router/api.rs @@ -0,0 +1,53 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Simple REST API + +use std::sync::Arc; +use hyper; +use hyper::status::StatusCode; +use hyper::header; +use hyper::uri::RequestUri::AbsolutePath as Path; +use apps::Pages; + +pub struct RestApi { + pub pages: Arc, +} + +impl RestApi { + fn list_pages(&self) -> String { + let mut s = "[".to_owned(); + for name in self.pages.keys() { + s.push_str(&format!("\"{}\",", name)); + } + s.push_str("\"rpc\""); + s.push_str("]"); + s + } +} + +impl hyper::server::Handler for RestApi { + fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, mut res: hyper::server::Response<'a>) { + match req.uri { + Path(ref path) if path == "apps" => { + *res.status_mut() = StatusCode::Ok; + res.headers_mut().set(header::ContentType("application/json".parse().unwrap())); + let _ = res.send(self.list_pages().as_bytes()); + }, + _ => (), + } + } +} diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs new file mode 100644 index 000000000..bd0d2ff18 --- /dev/null +++ b/webapp/src/router/mod.rs @@ -0,0 +1,109 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Router implementation + +use std::sync::Arc; +use hyper; +use page::Page; +use apps::Pages; +use iron::request::Url; +use jsonrpc_http_server::ServerHandler; + +mod api; + +pub struct Router { + rpc: ServerHandler, + api: api::RestApi, + main_page: Box, + pages: Arc, +} + +impl hyper::server::Handler for Router { + fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) { + let (path, req) = Router::extract_request_path(req); + match path { + Some(ref url) if self.pages.contains_key(url) => { + self.pages.get(url).unwrap().handle(req, res); + }, + Some(ref url) if url == "api" => { + self.api.handle(req, res); + }, + _ if req.method == hyper::method::Method::Post => { + self.rpc.handle(req, res) + }, + _ => self.main_page.handle(req, res), + } + } +} + +impl Router { + pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { + let pages = Arc::new(pages); + Router { + rpc: rpc, + api: api::RestApi { pages: pages.clone() }, + main_page: main_page, + pages: pages, + } + } + + fn extract_url(req: &hyper::server::Request) -> Option { + match req.uri { + hyper::uri::RequestUri::AbsoluteUri(ref url) => { + match Url::from_generic_url(url.clone()) { + Ok(url) => Some(url), + _ => None, + } + }, + hyper::uri::RequestUri::AbsolutePath(ref path) => { + // Attempt to prepend the Host header (mandatory in HTTP/1.1) + let url_string = match req.headers.get::() { + Some(ref host) => { + format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) + }, + None => return None, + }; + + match Url::parse(&url_string) { + Ok(url) => Some(url), + _ => None, + } + }, + _ => None, + } + } + + fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { + let url = Router::extract_url(&req); + match url { + Some(ref url) if url.path.len() > 1 => { + let part = url.path[0].clone(); + let url = url.path[1..].join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (Some(part), req) + }, + Some(url) => { + let url = url.path.join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (None, req) + }, + _ => { + (None, req) + }, + } + } +}