Rewriting webapps to use hyper-mio branch

This commit is contained in:
Tomasz Drwięga 2016-04-14 20:38:48 +02:00
parent 9892e43c4e
commit f81914351d
13 changed files with 532 additions and 232 deletions

14
Cargo.lock generated
View File

@ -300,9 +300,9 @@ dependencies = [
"clippy 0.0.63 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.63 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-rpc 1.1.0", "ethcore-rpc 1.1.0",
"ethcore-util 1.1.0", "ethcore-util 1.1.0",
"hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.0-mio (git+https://github.com/hyperium/hyper?branch=mio)",
"jsonrpc-core 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 2.0.2 (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)", "jsonrpc-http-server 5.0.1 (git+https://github.com/debris/jsonrpc-http-server.git)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-status 0.1.6 (git+https://github.com/tomusdrw/parity-status.git)", "parity-status 0.1.6 (git+https://github.com/tomusdrw/parity-status.git)",
"parity-wallet 0.1.1 (git+https://github.com/tomusdrw/parity-wallet.git)", "parity-wallet 0.1.1 (git+https://github.com/tomusdrw/parity-wallet.git)",
@ -491,16 +491,6 @@ dependencies = [
"syntex 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "syntex 0.31.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.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "jsonrpc-http-server" name = "jsonrpc-http-server"
version = "5.0.1" version = "5.0.1"

View File

@ -69,7 +69,7 @@ use number_prefix::{binary_prefix, Standalone, Prefixed};
#[cfg(feature = "rpc")] #[cfg(feature = "rpc")]
use rpc::Server as RpcServer; use rpc::Server as RpcServer;
#[cfg(feature = "webapp")] #[cfg(feature = "webapp")]
use webapp::Listening as WebappServer; use webapp::Server as WebappServer;
mod price_info; mod price_info;
mod upgrade; mod upgrade;
@ -342,12 +342,12 @@ fn setup_webapp_server(
sync: Arc<EthSync>, sync: Arc<EthSync>,
secret_store: Arc<AccountService>, secret_store: Arc<AccountService>,
miner: Arc<Miner>, miner: Arc<Miner>,
url: &str, url: &SocketAddr,
auth: Option<(String, String)>, auth: Option<(String, String)>,
) -> WebappServer { ) -> WebappServer {
use rpc::v1::*; use rpc::v1::*;
let server = webapp::WebappServer::new(); let server = webapp::ServerBuilder::new();
server.add_delegate(Web3Client::new().to_delegate()); server.add_delegate(Web3Client::new().to_delegate());
server.add_delegate(NetClient::new(&sync).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(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate());
@ -356,14 +356,14 @@ fn setup_webapp_server(
server.add_delegate(EthcoreClient::new(&miner).to_delegate()); server.add_delegate(EthcoreClient::new(&miner).to_delegate());
let start_result = match auth { let start_result = match auth {
None => { None => {
server.start_unsecure_http(url, ::num_cpus::get()) server.start_unsecure_http(url)
}, },
Some((username, password)) => { Some((username, password)) => {
server.start_basic_auth_http(url, ::num_cpus::get(), &username, &password) server.start_basic_auth_http(url, &username, &password)
}, },
}; };
match start_result { match start_result {
Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), Err(webapp::ServerError::IoError(err)) => die_with_io_error(err),
Err(e) => die!("{:?}", e), Err(e) => die!("{:?}", e),
Ok(handle) => handle, Ok(handle) => handle,
} }
@ -379,7 +379,7 @@ fn setup_rpc_server(
_sync: Arc<EthSync>, _sync: Arc<EthSync>,
_secret_store: Arc<AccountService>, _secret_store: Arc<AccountService>,
_miner: Arc<Miner>, _miner: Arc<Miner>,
_url: &str, _url: &SocketAddr,
_cors_domain: &str, _cors_domain: &str,
_apis: Vec<&str>, _apis: Vec<&str>,
) -> ! { ) -> ! {
@ -395,7 +395,7 @@ fn setup_webapp_server(
_sync: Arc<EthSync>, _sync: Arc<EthSync>,
_secret_store: Arc<AccountService>, _secret_store: Arc<AccountService>,
_miner: Arc<Miner>, _miner: Arc<Miner>,
_url: &str, _url: &SocketAddr,
_auth: Option<(String, String)>, _auth: Option<(String, String)>,
) -> ! { ) -> ! {
die!("Your Parity version has been compiled without WebApps support.") die!("Your Parity version has been compiled without WebApps support.")
@ -736,6 +736,7 @@ impl Configuration {
}, },
self.args.flag_webapp_port self.args.flag_webapp_port
); );
let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid Webapps listen host/port given.", url));
let auth = self.args.flag_webapp_user.as_ref().map(|username| { let auth = self.args.flag_webapp_user.as_ref().map(|username| {
let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| { let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| {
use rpassword::read_password; use rpassword::read_password;
@ -752,7 +753,7 @@ impl Configuration {
sync.clone(), sync.clone(),
account_service.clone(), account_service.clone(),
miner.clone(), miner.clone(),
&url, &addr,
auth, auth,
)) ))
} else { } else {

View File

@ -10,8 +10,8 @@ authors = ["Ethcore <admin@ethcore.io"]
[dependencies] [dependencies]
log = "0.3" log = "0.3"
jsonrpc-core = "2.0" jsonrpc-core = "2.0"
jsonrpc-http-server = { git = "https://github.com/tomusdrw/jsonrpc-http-server.git", branch="old-hyper" } jsonrpc-http-server = { git = "https://github.com/debris/jsonrpc-http-server.git" }
hyper = { version = "0.8", default-features = false } hyper = { default-features = false, git = "https://github.com/hyperium/hyper", branch = "mio" }
url = "0.5" url = "0.5"
ethcore-rpc = { path = "../rpc" } ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }

93
webapp/src/api.rs Normal file
View File

@ -0,0 +1,93 @@
// 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 <http://www.gnu.org/licenses/>.
//! Simple REST API
use std::io::Write;
use std::sync::Arc;
use hyper::status::StatusCode;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use endpoint::{Endpoint, Endpoints};
pub struct RestApi {
endpoints: Arc<Endpoints>,
}
impl RestApi {
pub fn new(endpoints: Arc<Endpoints>) -> Box<Endpoint> {
Box::new(RestApi {
endpoints: endpoints
})
}
fn list_pages(&self) -> String {
let mut s = "[".to_owned();
for name in self.endpoints.keys() {
s.push_str(&format!("\"{}\",", name));
}
s.push_str("\"rpc\"");
s.push_str("]");
s
}
}
impl Endpoint for RestApi {
fn to_handler(&self, _prefix: &str) -> Box<server::Handler<HttpStream>> {
Box::new(RestApiHandler {
pages: self.list_pages(),
write_pos: 0,
})
}
}
struct RestApiHandler {
pages: String,
write_pos: usize,
}
impl server::Handler<HttpStream> for RestApiHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType("application/json".parse().unwrap()));
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let bytes = self.pages.as_bytes();
if self.write_pos == bytes.len() {
return Next::end();
}
match encoder.write(&bytes[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}

View File

@ -14,28 +14,28 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::HashMap; use endpoint::Endpoints;
use page::{Page, PageHandler}; use page::PageEndpoint;
extern crate parity_status; extern crate parity_status;
extern crate parity_wallet; extern crate parity_wallet;
pub type Pages = HashMap<String, Box<Page>>;
pub fn main_page() -> Box<Page> { pub fn main_page() -> &'static str {
Box::new(PageHandler { app: parity_status::App::default() }) "/status/"
} }
pub fn all_pages() -> Pages { pub fn all_endpoints() -> Endpoints {
let mut pages = Pages::new(); let mut pages = Endpoints::new();
pages.insert("status".to_owned(), Box::new(PageEndpoint::new(parity_status::App::default())));
wallet_page(&mut pages); wallet_page(&mut pages);
pages pages
} }
#[cfg(feature = "parity-wallet")] #[cfg(feature = "parity-wallet")]
fn wallet_page(pages: &mut Pages) { fn wallet_page(pages: &mut Endpoints) {
pages.insert("wallet".to_owned(), Box::new(PageHandler { app: parity_wallet::App::default() })); pages.insert("wallet".to_owned(), Box::new(PageEndpoint::new(parity_wallet::App::default())));
} }
#[cfg(not(feature = "parity-wallet"))] #[cfg(not(feature = "parity-wallet"))]
fn wallet_page(_pages: &mut Pages) {} fn wallet_page(_pages: &mut Endpoints) {}

27
webapp/src/endpoint.rs Normal file
View File

@ -0,0 +1,27 @@
// 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 <http://www.gnu.org/licenses/>.
//! URL Endpoint traits
use hyper::server;
use hyper::net::HttpStream;
use std::collections::HashMap;
pub trait Endpoint : Send + Sync {
fn to_handler(&self, prefix: &str) -> Box<server::Handler<HttpStream>>;
}
pub type Endpoints = HashMap<String, Box<Endpoint>>;

View File

@ -15,6 +15,31 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore Webapplications for Parity //! Ethcore Webapplications for Parity
//! ```
//! extern crate jsonrpc_core;
//! extern crate ethcore_webapp;
//!
//! use std::sync::Arc;
//! use jsonrpc_core::IoHandler;
//! use ethcore_webapp::*;
//!
//! struct SayHello;
//! impl MethodCommand for SayHello {
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
//! Ok(Value::String("hello".to_string()))
//! }
//! }
//!
//! fn main() {
//! let io = IoHandler::new();
//! io.add_method("say_hello", SayHello);
//! let _server = Server::start_unsecure_http(
//! &"127.0.0.1:3030".parse().unwrap(),
//! Arc::new(io)
//! );
//! }
//! ```
//!
#![warn(missing_docs)] #![warn(missing_docs)]
#![cfg_attr(feature="nightly", plugin(clippy))] #![cfg_attr(feature="nightly", plugin(clippy))]
@ -24,29 +49,30 @@ extern crate url;
extern crate hyper; extern crate hyper;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate jsonrpc_http_server; extern crate jsonrpc_http_server;
extern crate ethcore_rpc as rpc;
extern crate parity_webapp; extern crate parity_webapp;
use std::sync::Arc; mod endpoint;
use self::jsonrpc_core::{IoHandler, IoDelegate};
use jsonrpc_http_server::ServerHandler;
mod apps; mod apps;
mod page; mod page;
mod router; mod router;
mod rpc;
mod api;
use std::sync::Arc;
use std::net::SocketAddr;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use router::auth::{Authorization, NoAuth, HttpBasicAuth};
/// Http server. /// Webapps HTTP+RPC server build.
pub struct WebappServer { pub struct ServerBuilder {
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
} }
impl WebappServer { impl ServerBuilder {
/// Construct new http server object /// Construct new webapps
pub fn new() -> Self { pub fn new() -> Self {
WebappServer { ServerBuilder {
handler: Arc::new(IoHandler::new()), handler: Arc::new(IoHandler::new())
} }
} }
@ -56,57 +82,63 @@ impl WebappServer {
} }
/// Asynchronously start server with no authentication, /// Asynchronously start server with no authentication,
/// return result with `Listening` handle on success or an error. /// returns result with `Server` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &str, threads: usize) -> Result<Listening, WebappServerError> { pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> {
self.start_http(addr, threads, NoAuth) Server::start_http(addr, NoAuth, self.handler.clone())
} }
/// Asynchronously start server with `HTTP Basic Authentication`, /// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Listening` handle on success or an error. /// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http(&self, addr: &str, threads: usize, username: &str, password: &str) -> Result<Listening, WebappServerError> { pub fn start_basic_auth_http(&self, addr: &SocketAddr, username: &str, password: &str) -> Result<Server, ServerError> {
self.start_http(addr, threads, HttpBasicAuth::single_user(username, password)) Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone())
}
fn start_http<A: Authorization + 'static>(&self, addr: &str, threads: usize, authorization: A) -> Result<Listening, WebappServerError> {
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(), authorization);
try!(hyper::Server::http(addr.as_ref() as &str))
.handle_threads(router, threads)
.map(|l| Listening { listening: l })
.map_err(WebappServerError::from)
} }
} }
/// Listening handle /// Webapps HTTP server.
pub struct Listening { pub struct Server {
listening: hyper::server::Listening server: Option<hyper::server::Listening>,
} }
impl Drop for Listening { impl Server {
fn start_http<A: Authorization + 'static>(addr: &SocketAddr, authorization: A, handler: Arc<IoHandler>) -> Result<Server, ServerError> {
let endpoints = Arc::new(apps::all_endpoints());
let authorization = Arc::new(authorization);
let rpc_endpoint = Arc::new(rpc::rpc(handler));
let api = Arc::new(api::RestApi::new(endpoints.clone()));
try!(hyper::Server::http(addr))
.handle(move |_| router::Router::new(
apps::main_page(),
endpoints.clone(),
rpc_endpoint.clone(),
api.clone(),
authorization.clone(),
))
.map(|l| Server { server: Some(l) })
.map_err(ServerError::from)
}
}
impl Drop for Server {
fn drop(&mut self) { fn drop(&mut self) {
self.listening.close().unwrap(); self.server.take().unwrap().close()
} }
} }
/// Webapp Server startup error /// Webapp Server startup error
#[derive(Debug)] #[derive(Debug)]
pub enum WebappServerError { pub enum ServerError {
/// Wrapped `std::io::Error` /// Wrapped `std::io::Error`
IoError(std::io::Error), IoError(std::io::Error),
/// Other `hyper` error /// Other `hyper` error
Other(hyper::error::Error), Other(hyper::error::Error),
} }
impl From<hyper::error::Error> for WebappServerError { impl From<hyper::error::Error> for ServerError {
fn from(err: hyper::error::Error) -> Self { fn from(err: hyper::error::Error) -> Self {
match err { match err {
hyper::error::Error::Io(e) => WebappServerError::IoError(e), hyper::error::Error::Io(e) => ServerError::IoError(e),
e => WebappServerError::Other(e), e => ServerError::Other(e),
} }
} }
} }

View File

@ -14,54 +14,97 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use std::io::Write; use std::io::Write;
use hyper::uri::RequestUri; use hyper::uri::RequestUri;
use hyper::server; use hyper::server;
use hyper::header; use hyper::header;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use hyper::net::HttpStream;
use hyper::{Decoder, Encoder, Next};
use endpoint::Endpoint;
use parity_webapp::WebApp; use parity_webapp::WebApp;
pub trait Page : Send + Sync { pub struct PageEndpoint<T : WebApp + 'static> {
fn serve_file(&self, mut path: &str, mut res: server::Response); pub app: Arc<T>,
} }
pub struct PageHandler<T : WebApp> { impl<T: WebApp + 'static> PageEndpoint<T> {
pub app: T, pub fn new(app: T) -> Self {
PageEndpoint {
app: Arc::new(app)
}
}
} }
impl<T: WebApp> Page for PageHandler<T> { impl<T: WebApp> Endpoint for PageEndpoint<T> {
fn serve_file(&self, mut path: &str, mut res: server::Response) { fn to_handler(&self, prefix: &str) -> Box<server::Handler<HttpStream>> {
Box::new(PageHandler {
app: self.app.clone(),
prefix: prefix.to_owned(),
prefix2: prefix.to_owned() + "/",
path: None,
write_pos: 0,
})
}
}
struct PageHandler<T: WebApp + 'static> {
app: Arc<T>,
prefix: String,
prefix2: String,
path: Option<String>,
write_pos: usize,
}
impl<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request) -> Next {
if let RequestUri::AbsolutePath(ref path) = *req.uri() {
// Support index file // Support index file
if path == "" { if path == &self.prefix || path == &self.prefix2 {
path = "index.html" self.path = Some("index.html".to_owned());
} else {
self.path = Some(path[self.prefix2.len()..].to_owned());
} }
let file = self.app.file(path); }
if let Some(f) = file { Next::write()
*res.status_mut() = StatusCode::Ok; }
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
if let Some(f) = self.path.as_ref().and_then(|f| self.app.file(f)) {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap())); res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap()));
Next::write()
let _ = match res.start() { } else {
Ok(mut raw_res) => { res.set_status(StatusCode::NotFound);
for chunk in f.content.chunks(1024 * 20) { Next::write()
let _ = raw_res.write(chunk);
} }
raw_res.end() }
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let (wrote, res) = {
let file = self.path.as_ref().and_then(|f| self.app.file(f));
match file {
None => (None, Next::end()),
Some(f) if self.write_pos == f.content.len() => (None, Next::end()),
Some(f) => match encoder.write(&f.content[self.write_pos..]) {
Ok(bytes) => {
(Some(bytes), Next::write())
}, },
Err(_) => { Err(e) => match e.kind() {
println!("Error while writing response."); ::std::io::ErrorKind::WouldBlock => (None, Next::write()),
Ok(()) _ => (None, Next::end())
}, },
}
}
}; };
} if let Some(bytes) = wrote {
} self.write_pos += bytes;
} }
res
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);
}
} }
} }

View File

@ -1,53 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! 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<Pages>,
}
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());
},
_ => (),
}
}
}

View File

@ -16,22 +16,24 @@
//! HTTP Authorization implementations //! HTTP Authorization implementations
use std::io::Write;
use std::collections::HashMap; use std::collections::HashMap;
use hyper::{header, server}; use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode; use hyper::status::StatusCode;
/// Authorization result /// Authorization result
pub enum Authorized<'a, 'b> where 'b : 'a { pub enum Authorized {
/// Authorization was successful. Request and Response are returned for further processing. /// Authorization was successful.
Yes(server::Request<'a, 'b>, server::Response<'a>), Yes,
/// Unsuccessful authorization. Request and Response has been consumed. /// Unsuccessful authorization. Handler for further work is returned.
No, No(Box<server::Handler<HttpStream>>),
} }
/// Authorization interface /// Authorization interface
pub trait Authorization : Send + Sync { pub trait Authorization : Send + Sync {
/// Handle authorization process and return `Request` and `Response` when authorization is successful. /// Checks if authorization is valid.
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>; fn is_authorized(&self, req: &server::Request)-> Authorized;
} }
/// HTTP Basic Authorization handler /// HTTP Basic Authorization handler
@ -43,27 +45,24 @@ pub struct HttpBasicAuth {
pub struct NoAuth; pub struct NoAuth;
impl Authorization for NoAuth { impl Authorization for NoAuth {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> { fn is_authorized(&self, _req: &server::Request)-> Authorized {
Authorized::Yes(req, res) Authorized::Yes
} }
} }
impl Authorization for HttpBasicAuth { impl Authorization for HttpBasicAuth {
fn is_authorized(&self, req: &server::Request) -> Authorized {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> {
let auth = self.check_auth(&req); let auth = self.check_auth(&req);
match auth { match auth {
Access::Denied => { Access::Denied => {
self.respond_with_unauthorized(res); Authorized::No(Box::new(UnauthorizedHandler { write_pos: 0 }))
Authorized::No
}, },
Access::AuthRequired => { Access::AuthRequired => {
self.respond_with_auth_required(res); Authorized::No(Box::new(AuthRequiredHandler))
Authorized::No
}, },
Access::Granted => { Access::Granted => {
Authorized::Yes(req, res) Authorized::Yes
}, },
} }
} }
@ -90,7 +89,7 @@ impl HttpBasicAuth {
} }
fn check_auth(&self, req: &server::Request) -> Access { fn check_auth(&self, req: &server::Request) -> Access {
match req.headers.get::<header::Authorization<header::Basic>>() { match req.headers().get::<header::Authorization<header::Basic>>() {
Some(&header::Authorization( Some(&header::Authorization(
header::Basic { ref username, password: Some(ref password) } header::Basic { ref username, password: Some(ref password) }
)) if self.is_authorized(username, password) => Access::Granted, )) if self.is_authorized(username, password) => Access::Granted,
@ -98,15 +97,63 @@ impl HttpBasicAuth {
None => Access::AuthRequired, None => Access::AuthRequired,
} }
} }
fn respond_with_unauthorized(&self, mut res: server::Response) {
*res.status_mut() = StatusCode::Unauthorized;
let _ = res.send(b"Unauthorized");
} }
fn respond_with_auth_required(&self, mut res: server::Response) { pub struct UnauthorizedHandler {
*res.status_mut() = StatusCode::Unauthorized; write_pos: usize,
}
impl server::Handler<HttpStream> for UnauthorizedHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Unauthorized);
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let response = "Unauthorized".as_bytes();
if self.write_pos == response.len() {
return Next::end();
}
match encoder.write(&response[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}
pub struct AuthRequiredHandler;
impl server::Handler<HttpStream> for AuthRequiredHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> 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()]); res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
} Next::end()
} }
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

View File

@ -17,67 +17,97 @@
//! Router implementation //! Router implementation
//! Processes request handling authorization and dispatching it to proper application. //! Processes request handling authorization and dispatching it to proper application.
mod api;
mod url; mod url;
mod redirect;
pub mod auth; pub mod auth;
use std::sync::Arc; use std::sync::Arc;
use hyper; use hyper;
use hyper::{server, uri, header}; use hyper::{server, uri, header};
use page::Page; use hyper::{Next, Encoder, Decoder};
use apps::Pages; use hyper::net::HttpStream;
use endpoint::{Endpoint, Endpoints};
use self::url::Url; use self::url::Url;
use jsonrpc_http_server::ServerHandler;
use self::auth::{Authorization, Authorized}; use self::auth::{Authorization, Authorized};
use self::redirect::Redirection;
pub struct Router<A: Authorization> { pub struct Router<A: Authorization + 'static> {
authorization: A, main_page: &'static str,
rpc: ServerHandler, endpoints: Arc<Endpoints>,
api: api::RestApi, rpc: Arc<Box<Endpoint>>,
main_page: Box<Page>, api: Arc<Box<Endpoint>>,
pages: Arc<Pages>, authorization: Arc<A>,
handler: Box<server::Handler<HttpStream>>,
} }
impl<A: Authorization> server::Handler for Router<A> { impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) {
let auth = self.authorization.handle(req, res);
if let Authorized::Yes(req, res) = auth { fn on_request(&mut self, req: server::Request) -> Next {
let (path, req) = self.extract_request_path(req); let auth = self.authorization.is_authorized(&req);
self.handler = match auth {
Authorized::No(handler) => handler,
Authorized::Yes => {
let path = self.extract_request_path(&req);
match path { match path {
Some(ref url) if self.pages.contains_key(url) => { Some(ref url) if self.endpoints.contains_key(url) => {
self.pages.get(url).unwrap().handle(req, res); let prefix = "/".to_owned() + url;
self.endpoints.get(url).unwrap().to_handler(&prefix)
}, },
Some(ref url) if url == "api" => { Some(ref url) if url == "api" => {
self.api.handle(req, res); self.api.to_handler("/api")
}, },
_ if req.method == hyper::method::Method::Post => { _ if *req.method() == hyper::method::Method::Get => {
self.rpc.handle(req, res) Redirection::new(self.main_page)
}, },
_ => self.main_page.handle(req, res), _ => {
self.rpc.to_handler(&"/")
} }
} }
} }
};
self.handler.on_request(req)
// Check authorization
// Choose proper handler depending on path
// Delegate on_request to proper handler
}
/// This event occurs each time the `Request` is ready to be read from.
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> 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<HttpStream>) -> Next {
self.handler.on_response_writable(encoder)
}
} }
impl<A: Authorization> Router<A> { impl<A: Authorization> Router<A> {
pub fn new( pub fn new(
rpc: ServerHandler, main_page: &'static str,
main_page: Box<Page>, endpoints: Arc<Endpoints>,
pages: Pages, rpc: Arc<Box<Endpoint>>,
authorization: A) -> Self { api: Arc<Box<Endpoint>>,
let pages = Arc::new(pages); authorization: Arc<A>) -> Self {
let handler = rpc.to_handler(&"/");
Router { Router {
authorization: authorization,
rpc: rpc,
api: api::RestApi { pages: pages.clone() },
main_page: main_page, main_page: main_page,
pages: pages, endpoints: endpoints,
rpc: rpc,
api: api,
authorization: authorization,
handler: handler,
} }
} }
fn extract_url(&self, req: &server::Request) -> Option<Url> { fn extract_url(&self, req: &server::Request) -> Option<Url> {
match req.uri { match *req.uri() {
uri::RequestUri::AbsoluteUri(ref url) => { uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) { match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url), Ok(url) => Some(url),
@ -86,7 +116,7 @@ impl<A: Authorization> Router<A> {
}, },
uri::RequestUri::AbsolutePath(ref path) => { uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1) // Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match req.headers.get::<header::Host>() { let url_string = match req.headers().get::<header::Host>() {
Some(ref host) => { Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
}, },
@ -102,22 +132,15 @@ impl<A: Authorization> Router<A> {
} }
} }
fn extract_request_path<'a, 'b>(&self, mut req: server::Request<'a, 'b>) -> (Option<String>, server::Request<'a, 'b>) { fn extract_request_path(&self, req: &server::Request) -> Option<String> {
let url = self.extract_url(&req); let url = self.extract_url(&req);
match url { match url {
Some(ref url) if url.path.len() > 1 => { Some(ref url) if url.path.len() > 1 => {
let part = url.path[0].clone(); let part = url.path[0].clone();
let url = url.path[1..].join("/"); Some(part)
req.uri = uri::RequestUri::AbsolutePath(url);
(Some(part), req)
},
Some(url) => {
let url = url.path.join("/");
req.uri = uri::RequestUri::AbsolutePath(url);
(None, req)
}, },
_ => { _ => {
(None, req) None
}, },
} }
} }

View File

@ -0,0 +1,56 @@
// 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 <http://www.gnu.org/licenses/>.
//! HTTP Redirection hyper handler
use std::io::Write;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
pub struct Redirection {
to_url: &'static str
}
impl Redirection {
pub fn new(url: &'static str) -> Box<Self> {
Box::new(Redirection {
to_url: url
})
}
}
impl server::Handler<HttpStream> for Redirection {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::MovedPermanently);
res.headers_mut().set(header::Location(self.to_url.to_owned()));
Next::end()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

41
webapp/src/rpc.rs Normal file
View File

@ -0,0 +1,41 @@
// 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 <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use hyper::server;
use hyper::net::HttpStream;
use jsonrpc_core::IoHandler;
use jsonrpc_http_server::ServerHandler;
use jsonrpc_http_server::AccessControlAllowOrigin;
use endpoint::Endpoint;
pub fn rpc(handler: Arc<IoHandler>) -> Box<Endpoint> {
Box::new(RpcEndpoint {
handler: handler,
cors_domain: AccessControlAllowOrigin::Null
})
}
struct RpcEndpoint {
handler: Arc<IoHandler>,
cors_domain: AccessControlAllowOrigin,
}
impl Endpoint for RpcEndpoint {
fn to_handler(&self, _prefix: &str) -> Box<server::Handler<HttpStream>> {
Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone()))
}
}