diff --git a/dapps/build.rs b/dapps/build.rs index 2bef33059..0776c03ec 100644 --- a/dapps/build.rs +++ b/dapps/build.rs @@ -25,8 +25,8 @@ mod inner { pub fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); - let src = Path::new("./src/api/mod.rs.in"); - let dst = Path::new(&out_dir).join("mod.rs"); + let src = Path::new("./src/api/types.rs.in"); + let dst = Path::new(&out_dir).join("types.rs"); let mut registry = syntex::Registry::new(); diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 95b01d442..ab3632074 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -15,38 +15,16 @@ // along with Parity. If not, see . use std::sync::Arc; -use endpoint::{Endpoint, Endpoints, EndpointInfo, Handler, EndpointPath}; - -use api::response::as_json; +use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; +use api::types::{App, ApiError}; +use api::response::{as_json, as_json_error}; +use hyper::{server, net, Decoder, Encoder, Next}; +#[derive(Clone)] pub struct RestApi { endpoints: Arc, } -#[derive(Debug, PartialEq, Serialize, Deserialize)] -pub struct App { - pub id: String, - pub name: String, - pub description: String, - pub version: String, - pub author: String, - #[serde(rename="iconUrl")] - pub icon_url: String, -} - -impl App { - fn from_info(id: &str, info: &EndpointInfo) -> Self { - App { - id: id.to_owned(), - name: info.name.to_owned(), - description: info.description.to_owned(), - version: info.version.to_owned(), - author: info.author.to_owned(), - icon_url: info.icon_url.to_owned(), - } - } -} - impl RestApi { pub fn new(endpoints: Arc) -> Box { Box::new(RestApi { @@ -63,7 +41,39 @@ impl RestApi { impl Endpoint for RestApi { fn to_handler(&self, _path: EndpointPath) -> Box { - as_json(&self.list_apps()) + Box::new(RestApiRouter { + api: self.clone(), + handler: as_json_error(&ApiError { + code: "404".into(), + title: "Not Found".into(), + detail: "Resource you requested has not been found.".into(), + }), + }) } } +struct RestApiRouter { + api: RestApi, + handler: Box, +} + +impl server::Handler for RestApiRouter { + + fn on_request(&mut self, _request: server::Request) -> Next { + self.handler = as_json(&self.api.list_apps()); + Next::write() + } + + fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { + self.handler.on_request_readable(decoder) + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + self.handler.on_response(res) + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + self.handler.on_response_writable(encoder) + } + +} diff --git a/dapps/src/api/mod.rs b/dapps/src/api/mod.rs index 088d7f6b2..402e84257 100644 --- a/dapps/src/api/mod.rs +++ b/dapps/src/api/mod.rs @@ -16,13 +16,12 @@ //! REST API -#![warn(missing_docs)] #![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))] #![cfg_attr(feature="nightly", plugin(serde_macros, clippy))] -#[cfg(feature = "serde_macros")] -include!("mod.rs.in"); - -#[cfg(not(feature = "serde_macros"))] -include!(concat!(env!("OUT_DIR"), "/mod.rs")); +mod api; +mod response; +mod types; +pub use self::api::RestApi; +pub use self::types::App; diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index 345b8a6ee..ba0922e7a 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -16,8 +16,13 @@ use serde::Serialize; use serde_json; -use endpoint::{ContentHandler, Handler}; +use endpoint::Handler; +use handlers::ContentHandler; pub fn as_json(val: &T) -> Box { - Box::new(ContentHandler::new(serde_json::to_string(val).unwrap(), "application/json".to_owned())) + Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned())) +} + +pub fn as_json_error(val: &T) -> Box { + Box::new(ContentHandler::not_found(serde_json::to_string(val).unwrap(), "application/json".to_owned())) } diff --git a/dapps/src/api/mod.rs.in b/dapps/src/api/types.rs similarity index 82% rename from dapps/src/api/mod.rs.in rename to dapps/src/api/types.rs index a069c06b0..d99d767eb 100644 --- a/dapps/src/api/mod.rs.in +++ b/dapps/src/api/types.rs @@ -14,8 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -mod api; -mod response; +#[cfg(feature = "serde_macros")] +include!("types.rs.in"); + +#[cfg(not(feature = "serde_macros"))] +include!(concat!(env!("OUT_DIR"), "/types.rs")); + -pub use self::api::RestApi; -pub use self::api::App; diff --git a/dapps/src/api/types.rs.in b/dapps/src/api/types.rs.in new file mode 100644 index 000000000..72a8cd221 --- /dev/null +++ b/dapps/src/api/types.rs.in @@ -0,0 +1,51 @@ +// 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 endpoint::EndpointInfo; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct App { + pub id: String, + pub name: String, + pub description: String, + pub version: String, + pub author: String, + #[serde(rename="iconUrl")] + pub icon_url: String, +} + +impl App { + /// Creates `App` instance from `EndpointInfo` and `id`. + pub fn from_info(id: &str, info: &EndpointInfo) -> Self { + App { + id: id.to_owned(), + name: info.name.to_owned(), + description: info.description.to_owned(), + version: info.version.to_owned(), + author: info.author.to_owned(), + icon_url: info.icon_url.to_owned(), + } + } +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ApiError { + pub code: String, + pub title: String, + pub detail: String, +} + + diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index 075211e58..ba2116e1c 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -16,11 +16,7 @@ //! URL Endpoint traits -use hyper::status::StatusCode; -use hyper::{header, server, Decoder, Encoder, Next}; -use hyper::net::HttpStream; - -use std::io::Write; +use hyper::{server, net}; use std::collections::BTreeMap; #[derive(Debug, PartialEq, Default, Clone)] @@ -42,58 +38,8 @@ pub struct EndpointInfo { pub trait Endpoint : Send + Sync { fn info(&self) -> Option<&EndpointInfo> { None } - fn to_handler(&self, path: EndpointPath) -> Box + Send>; + fn to_handler(&self, path: EndpointPath) -> Box + Send>; } pub type Endpoints = BTreeMap>; -pub type Handler = server::Handler + Send; - -pub struct ContentHandler { - content: String, - mimetype: String, - write_pos: usize, -} - -impl ContentHandler { - pub fn new(content: String, mimetype: String) -> Self { - ContentHandler { - content: content, - mimetype: mimetype, - write_pos: 0 - } - } -} - -impl server::Handler for ContentHandler { - 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::Ok); - res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap())); - Next::write() - } - - fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { - let bytes = self.content.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() - }, - } - } -} +pub type Handler = server::Handler + Send; diff --git a/dapps/src/handlers/auth.rs b/dapps/src/handlers/auth.rs new file mode 100644 index 000000000..7f72f7cf8 --- /dev/null +++ b/dapps/src/handlers/auth.rs @@ -0,0 +1,44 @@ +// 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 . + +//! 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/content.rs b/dapps/src/handlers/content.rs new file mode 100644 index 000000000..a589e5492 --- /dev/null +++ b/dapps/src/handlers/content.rs @@ -0,0 +1,92 @@ +// 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 Content Handler + +use std::io::Write; +use hyper::{header, server, Decoder, Encoder, Next}; +use hyper::net::HttpStream; +use hyper::status::StatusCode; + +pub struct ContentHandler { + code: StatusCode, + content: String, + mimetype: String, + write_pos: usize, +} + +impl ContentHandler { + pub fn ok(content: String, mimetype: String) -> Self { + ContentHandler { + code: StatusCode::Ok, + content: content, + mimetype: mimetype, + write_pos: 0 + } + } + + pub fn not_found(content: String, mimetype: String) -> Self { + ContentHandler { + code: StatusCode::NotFound, + content: content, + mimetype: mimetype, + write_pos: 0 + } + } + + pub fn new(code: StatusCode, content: String, mimetype: String) -> Self { + ContentHandler { + code: code, + content: content, + mimetype: mimetype, + write_pos: 0, + } + } +} + +impl server::Handler for ContentHandler { + 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(self.code); + res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap())); + Next::write() + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + let bytes = self.content.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() + }, + } + } +} diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs new file mode 100644 index 000000000..933538655 --- /dev/null +++ b/dapps/src/handlers/mod.rs @@ -0,0 +1,25 @@ +// 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 . + +//! Hyper handlers implementations. + +mod auth; +mod content; +mod redirect; + +pub use self::auth::AuthRequiredHandler; +pub use self::content::ContentHandler; +pub use self::redirect::Redirection; diff --git a/dapps/src/router/redirect.rs b/dapps/src/handlers/redirect.rs similarity index 100% rename from dapps/src/router/redirect.rs rename to dapps/src/handlers/redirect.rs diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 22b075afd..1c550fb07 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -59,6 +59,7 @@ mod endpoint; mod apps; mod page; mod router; +mod handlers; mod rpc; mod api; mod proxypac; diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index 9d91d58f0..aaa68dc0c 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -16,7 +16,8 @@ //! Serving ProxyPac file -use endpoint::{Endpoint, Handler, ContentHandler, EndpointPath}; +use endpoint::{Endpoint, Handler, EndpointPath}; +use handlers::ContentHandler; use apps::DAPPS_DOMAIN; pub struct ProxyPac; @@ -41,7 +42,7 @@ function FindProxyForURL(url, host) {{ }} "#, DAPPS_DOMAIN, path.host, path.port); - Box::new(ContentHandler::new(content, "application/javascript".to_owned())) + Box::new(ContentHandler::ok(content, "application/javascript".to_owned())) } } diff --git a/dapps/src/router/auth.rs b/dapps/src/router/auth.rs index 9459d0719..d18424a00 100644 --- a/dapps/src/router/auth.rs +++ b/dapps/src/router/auth.rs @@ -16,24 +16,23 @@ //! HTTP Authorization implementations -use std::io::Write; use std::collections::HashMap; -use hyper::{header, server, Decoder, Encoder, Next}; -use hyper::net::HttpStream; -use hyper::status::StatusCode; +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 + Send>), + No(Box), } /// Authorization interface pub trait Authorization : Send + Sync { /// Checks if authorization is valid. - fn is_authorized(&self, req: &server::Request)-> Authorized; + fn is_authorized(&self, req: &server::Request)-> Authorized; } /// HTTP Basic Authorization handler @@ -45,18 +44,22 @@ pub struct HttpBasicAuth { pub struct NoAuth; impl Authorization for NoAuth { - fn is_authorized(&self, _req: &server::Request)-> Authorized { + fn is_authorized(&self, _req: &server::Request)-> Authorized { Authorized::Yes } } impl Authorization for HttpBasicAuth { - fn is_authorized(&self, req: &server::Request) -> Authorized { + fn is_authorized(&self, req: &server::Request) -> Authorized { let auth = self.check_auth(&req); match auth { Access::Denied => { - Authorized::No(Box::new(UnauthorizedHandler { write_pos: 0 })) + Authorized::No(Box::new(ContentHandler::new( + status::StatusCode::Unauthorized, + "

Unauthorized

".into(), + "text/html".into(), + ))) }, Access::AuthRequired => { Authorized::No(Box::new(AuthRequiredHandler)) @@ -89,7 +92,7 @@ impl HttpBasicAuth { self.users.get(&username.to_owned()).map_or(false, |pass| pass == password) } - fn check_auth(&self, req: &server::Request) -> Access { + fn check_auth(&self, req: &server::Request) -> Access { match req.headers().get::>() { Some(&header::Authorization( header::Basic { ref username, password: Some(ref password) } @@ -99,63 +102,3 @@ impl HttpBasicAuth { } } } - -pub struct UnauthorizedHandler { - write_pos: usize, -} - -impl server::Handler for UnauthorizedHandler { - 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); - Next::write() - } - - fn on_response_writable(&mut self, encoder: &mut Encoder) -> 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 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/router/mod.rs b/dapps/src/router/mod.rs index 0cb0d38d0..f04c8b614 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -18,7 +18,6 @@ //! Processes request handling authorization and dispatching it to proper application. mod url; -mod redirect; pub mod auth; use DAPPS_DOMAIN; @@ -33,7 +32,7 @@ use apps; use endpoint::{Endpoint, Endpoints, EndpointPath}; use self::url::Url; use self::auth::{Authorization, Authorized}; -use self::redirect::Redirection; +use handlers::Redirection; /// Special endpoints are accessible on every domain (every dapp) #[derive(Debug, PartialEq, Hash, Eq)]