From 3fc7faf24c08b6ea88ab4cea53f4830772e15c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 12 May 2016 15:05:59 +0200 Subject: [PATCH] Subdomains router for dapps --- webapp/src/apps.rs | 18 +++-- webapp/src/lib.rs | 2 +- webapp/src/page/mod.rs | 26 +++++-- webapp/src/proxypac.rs | 2 +- webapp/src/router/mod.rs | 145 +++++++++++++++++++++++++++++---------- 5 files changed, 141 insertions(+), 52 deletions(-) diff --git a/webapp/src/apps.rs b/webapp/src/apps.rs index 21e1102ad..7470786a5 100644 --- a/webapp/src/apps.rs +++ b/webapp/src/apps.rs @@ -18,27 +18,35 @@ use std::net::SocketAddr; use endpoint::Endpoints; use page::PageEndpoint; use proxypac::ProxyPac; +use parity_webapp::WebApp; extern crate parity_status; #[cfg(feature = "parity-wallet")] extern crate parity_wallet; - pub fn main_page() -> &'static str { "/status/" } pub fn all_endpoints(addr: &SocketAddr) -> Endpoints { let mut pages = Endpoints::new(); - pages.insert("status".to_owned(), Box::new(PageEndpoint::new(parity_status::App::default()))); pages.insert("proxy".to_owned(), ProxyPac::new(addr)); + insert::(&mut pages, "status"); + insert::(&mut pages, "parity"); + wallet_page(&mut pages); pages } +#[cfg(feature = "parity-wallet")] fn wallet_page(pages: &mut Endpoints) { - if cfg!(feature = "parity-wallet") { - pages.insert("wallet".to_owned(), Box::new(PageEndpoint::new(parity_wallet::App::default()))); - } + insert::(pages, "wallet"); +} + +#[cfg(not(feature = "parity-wallet"))] +fn wallet_page(_pages: &mut Endpoints) {} + +fn insert(pages: &mut Endpoints, id: &str) { + pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default()))); } diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 5ee93acc7..18b088005 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -64,7 +64,7 @@ use std::net::SocketAddr; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; -static DAPPS_DOMAIN : &'static str = "dapp"; +static DAPPS_DOMAIN : &'static str = ".dapp"; /// Webapps HTTP+RPC server build. pub struct ServerBuilder { diff --git a/webapp/src/page/mod.rs b/webapp/src/page/mod.rs index 68d8a14bd..b2cf9615b 100644 --- a/webapp/src/page/mod.rs +++ b/webapp/src/page/mod.rs @@ -57,15 +57,27 @@ struct PageHandler { write_pos: usize, } +impl PageHandler { + fn extract_path(&self, path: &str) -> String { + // Index file support + match path == &self.prefix || path == &self.prefix_with_slash { + true => "index.html".to_owned(), + false => path[self.prefix_with_slash.len()..].to_owned(), + } + } +} + impl server::Handler for PageHandler { fn on_request(&mut self, req: server::Request) -> Next { - if let RequestUri::AbsolutePath(ref path) = *req.uri() { - // Index file support - self.path = match path == &self.prefix || path == &self.prefix_with_slash { - true => Some("index.html".to_owned()), - false => Some(path[self.prefix_with_slash.len()..].to_owned()), - }; - } + self.path = match *req.uri() { + RequestUri::AbsolutePath(ref path) => { + Some(self.extract_path(path)) + }, + RequestUri::AbsoluteUri(ref url) => { + Some(self.extract_path(url.path())) + }, + _ => None, + }; Next::write() } diff --git a/webapp/src/proxypac.rs b/webapp/src/proxypac.rs index b8162fadf..b39c72500 100644 --- a/webapp/src/proxypac.rs +++ b/webapp/src/proxypac.rs @@ -38,7 +38,7 @@ impl Endpoint for ProxyPac { let content = format!( r#" function FindProxyForURL(url, host) {{ - if (shExpMatch(host, "*.{0}")) + if (shExpMatch(host, "*{0}")) {{ return "PROXY {1}"; }} diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index 1d8a24585..dc8abdada 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -21,7 +21,9 @@ mod url; mod redirect; pub mod auth; +use DAPPS_DOMAIN; use std::sync::Arc; +use url::Host; use hyper; use hyper::{server, uri, header}; use hyper::{Next, Encoder, Decoder}; @@ -40,6 +42,13 @@ pub struct Router { handler: Box>, } +#[derive(Debug, PartialEq)] +struct AppId { + id: String, + prefix: String, + is_rpc: bool, +} + impl server::Handler for Router { fn on_request(&mut self, req: server::Request) -> Next { @@ -50,25 +59,32 @@ impl server::Handler for Router { self.handler = match auth { Authorized::No(handler) => handler, Authorized::Yes => { - let url = self.extract_url(&req); - let app_id = self.extract_app_id(&url); + let url = extract_url(&req); + let app_id = extract_app_id(&url); let host = url.map(|u| HostInfo { host: u.host, port: u.port }); match app_id { + // First check RPC requests + Some(ref app_id) if app_id.is_rpc && *req.method() != hyper::method::Method::Get => { + self.rpc.to_handler("", host) + }, + // Then delegate to dapp Some(ref app_id) if self.endpoints.contains_key(&app_id.id) => { self.endpoints.get(&app_id.id).unwrap().to_handler(&app_id.prefix, host) }, Some(ref app_id) if app_id.id == "api" => { self.api.to_handler(&app_id.prefix, host) }, + // Redirection to main page _ if *req.method() == hyper::method::Method::Get => { Redirection::new(self.main_page) }, + // RPC by default _ => { - self.rpc.to_handler("/", host) + self.rpc.to_handler("", host) } } } @@ -102,7 +118,7 @@ impl Router { api: Arc>, authorization: Arc) -> Self { - let handler = rpc.to_handler("/", None); + let handler = rpc.to_handler("", None); Router { main_page: main_page, endpoints: endpoints, @@ -112,50 +128,103 @@ impl Router { handler: handler, } } +} - fn extract_url(&self, req: &server::Request) -> Option { - match *req.uri() { - uri::RequestUri::AbsoluteUri(ref url) => { - match Url::from_generic_url(url.clone()) { - Ok(url) => Some(url), - _ => None, - } - }, - 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, - }; +fn extract_url(req: &server::Request) -> Option { + match *req.uri() { + uri::RequestUri::AbsoluteUri(ref url) => { + match Url::from_generic_url(url.clone()) { + Ok(url) => Some(url), + _ => None, + } + }, + 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, - } + match Url::parse(&url_string) { + Ok(url) => Some(url), + _ => None, + } + }, + _ => None, + } +} + +fn extract_app_id(url: &Option) -> Option { + fn is_rpc(url: &Url) -> bool { + url.path.len() > 1 && url.path[0] == "rpc" } - fn extract_app_id(&self, url: &Option) -> Option { - match *url { - Some(ref url) if url.path.len() > 1 => { + match *url { + Some(ref url) => match url.host { + Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => { + let len = domain.len() - DAPPS_DOMAIN.len(); + let id = domain[0..len].to_owned(); + + Some(AppId { + id: id, + prefix: "".to_owned(), + is_rpc: is_rpc(url), + }) + }, + _ if url.path.len() > 1 => { let id = url.path[0].clone(); Some(AppId { id: id.clone(), - prefix: "/".to_owned() + &id + prefix: "/".to_owned() + &id, + is_rpc: is_rpc(url), }) }, - _ => { - None - }, - } + _ => None, + }, + _ => None } } -struct AppId { - id: String, - prefix: String +#[test] +fn should_extract_app_id() { + assert_eq!(extract_app_id(&None), None); + + // With path prefix + assert_eq!( + extract_app_id(&Url::parse("http://localhost:8080/status/index.html").ok()), + Some(AppId { + id: "status".to_owned(), + prefix: "/status".to_owned(), + is_rpc: false, + })); + + // With path prefix + assert_eq!( + extract_app_id(&Url::parse("http://localhost:8080/rpc/").ok()), + Some(AppId { + id: "rpc".to_owned(), + prefix: "/rpc".to_owned(), + is_rpc: true, + })); + + // By Subdomain + assert_eq!( + extract_app_id(&Url::parse("http://my.status.dapp/test.html").ok()), + Some(AppId { + id: "my.status".to_owned(), + prefix: "".to_owned(), + is_rpc: false, + })); + + // RPC by subdomain + assert_eq!( + extract_app_id(&Url::parse("http://my.status.dapp/rpc/").ok()), + Some(AppId { + id: "my.status".to_owned(), + prefix: "".to_owned(), + is_rpc: true, + })); + }