From ad37b7fd2a5b3297904aa4d90974390d01e196fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:10:26 +0200 Subject: [PATCH] Adding webapps router --- Cargo.lock | 15 +++++ parity/main.rs | 5 +- webapp/Cargo.toml | 4 ++ webapp/src/apps.rs | 37 ++++++++++++ webapp/src/lib.rs | 118 ++++++--------------------------------- webapp/src/page/mod.rs | 67 ++++++++++++++++++++++ webapp/src/router/mod.rs | 89 +++++++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 102 deletions(-) create mode 100644 webapp/src/apps.rs create mode 100644 webapp/src/page/mod.rs create mode 100644 webapp/src/router/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9783413d4..62093c293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,8 @@ dependencies = [ "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)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "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]] @@ -701,6 +703,19 @@ name = "odds" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[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" diff --git a/parity/main.rs b/parity/main.rs index ecd72e0ff..b8498d520 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -132,7 +132,7 @@ 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 --webap Enable the web applications server (e.g. status page). + -w --webapp Enable the web applications server (e.g. status page). --webapp-port PORT Specify the port portion of the WebApps server [default: 8080]. @@ -305,7 +305,10 @@ fn setup_rpc_server( fn setup_webapp_server( url: &str ) -> Option> { + use rpc::v1::*; + let server = webapp::WebappServer::new(); + server.add_delegate(Web3Client::new().to_delegate()); Some(server.start_http(url, ::num_cpus::get())) } diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index f6503e163..377b0b556 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -15,7 +15,11 @@ hyper = { version = "0.8", default-features = false } iron = { version = "0.3" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } +parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" } +# List of apps +parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true } clippy = { version = "0.0.61", optional = true} [features] +default = ["parity-wallet"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] diff --git a/webapp/src/apps.rs b/webapp/src/apps.rs new file mode 100644 index 000000000..6a43decab --- /dev/null +++ b/webapp/src/apps.rs @@ -0,0 +1,37 @@ +// 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::collections::HashMap; +use page::{Page, PageHandler}; + +extern crate parity_wallet; + +pub type Pages = HashMap>; + +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 index 8fd20fdeb..7a3620e1a 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -26,16 +26,17 @@ extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate ethcore_rpc as rpc; extern crate ethcore_util as util; +extern crate parity_webapp; -use rpc::v1::*; use std::sync::Arc; -use std::thread; - use util::panics::PanicHandler; -use iron::request::Url; use self::jsonrpc_core::{IoHandler, IoDelegate}; use jsonrpc_http_server::ServerHandler; +mod apps; +mod page; +mod router; + /// Http server. pub struct WebappServer { handler: Arc, @@ -44,117 +45,32 @@ pub struct WebappServer { impl WebappServer { /// Construct new http server object pub fn new() -> Self { - let server = WebappServer { + WebappServer { handler: Arc::new(IoHandler::new()), - }; - // TODO add more - server.add_delegate(Web3Client::new().to_delegate()); - - server + } } /// Add io delegate. - fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { + pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { self.handler.add_delegate(delegate); } - /// Start server asynchronously in new thread and returns panic handler. + /// Start server asynchronously and returns panic handler. pub fn start_http(&self, addr: &str, threads: usize) -> Arc { let addr = addr.to_owned(); let panic_handler = PanicHandler::new_in_arc(); - let ph = panic_handler.clone(); let handler = self.handler.clone(); - thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || { - let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; - let rpc = ServerHandler::new(handler, cors_domain); - let router = Router::new(rpc); + let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; + let rpc = ServerHandler::new(handler, cors_domain); + let router = router::Router::new(rpc, apps::all_pages()); - ph.catch_panic(move || { - hyper::Server::http(addr.as_ref() as &str).unwrap() - .handle_threads(router, threads) - .unwrap(); - }).unwrap() - }).expect("Error while creating jsonrpc http thread"); + panic_handler.catch_panic(move || { + hyper::Server::http(addr.as_ref() as &str).unwrap() + .handle_threads(router, threads) + .unwrap(); + }).unwrap(); panic_handler } } - - -struct Router { - rpc: ServerHandler, - // admin: Page, - // wallet: Page, - // mist: Page, -} - -impl Router { - pub fn new(rpc: ServerHandler) -> Self { - Router { - rpc: rpc, - // admin: Page { app: AdminApp::default() }, - // wallet: Page { app: WalletApp::default() }, - // mist: Page { app: MistApp::default() }, - } - } - - 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(url) => { - let part = url.path[0].clone(); - let url = url.path[1..].join("/"); - req.uri = hyper::uri::RequestUri::AbsolutePath(url); - (Some(part), req) - }, - None => { - (None, req) - } - } - } -} - -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 url == "admin" => { - // self.admin.handle(req, res); - // }, - // Some(ref url) if url == "wallet" => { - // self.wallet.handle(req, res); - // }, - // Some(ref url) if url == "mist" => { - // self.mist.handle(req, res); - // }, - _ => self.rpc.handle(req, res), - } - } -} diff --git a/webapp/src/page/mod.rs b/webapp/src/page/mod.rs new file mode 100644 index 000000000..94a25769e --- /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/mod.rs b/webapp/src/router/mod.rs new file mode 100644 index 000000000..4ed32fab9 --- /dev/null +++ b/webapp/src/router/mod.rs @@ -0,0 +1,89 @@ +// 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 hyper; +use apps::Pages; +use iron::request::Url; +use jsonrpc_http_server::ServerHandler; + +pub struct Router { + rpc: ServerHandler, + pages: Pages, +} + +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); + } + _ => self.rpc.handle(req, res), + } + } +} + +impl Router { + pub fn new(rpc: ServerHandler, pages: Pages) -> Self { + Router { + rpc: rpc, + 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(url) => { + let part = url.path[0].clone(); + let url = url.path[1..].join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (Some(part), req) + }, + None => { + (None, req) + } + } + } +}