Subdomains router for dapps

This commit is contained in:
Tomasz Drwięga 2016-05-12 15:05:59 +02:00
parent f62d058186
commit 3fc7faf24c
5 changed files with 141 additions and 52 deletions

View File

@ -18,27 +18,35 @@ use std::net::SocketAddr;
use endpoint::Endpoints; use endpoint::Endpoints;
use page::PageEndpoint; use page::PageEndpoint;
use proxypac::ProxyPac; use proxypac::ProxyPac;
use parity_webapp::WebApp;
extern crate parity_status; extern crate parity_status;
#[cfg(feature = "parity-wallet")] #[cfg(feature = "parity-wallet")]
extern crate parity_wallet; extern crate parity_wallet;
pub fn main_page() -> &'static str { pub fn main_page() -> &'static str {
"/status/" "/status/"
} }
pub fn all_endpoints(addr: &SocketAddr) -> Endpoints { pub fn all_endpoints(addr: &SocketAddr) -> Endpoints {
let mut pages = Endpoints::new(); 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)); pages.insert("proxy".to_owned(), ProxyPac::new(addr));
insert::<parity_status::App>(&mut pages, "status");
insert::<parity_status::App>(&mut pages, "parity");
wallet_page(&mut pages); wallet_page(&mut pages);
pages pages
} }
#[cfg(feature = "parity-wallet")]
fn wallet_page(pages: &mut Endpoints) { fn wallet_page(pages: &mut Endpoints) {
if cfg!(feature = "parity-wallet") { insert::<parity_wallet::App>(pages, "wallet");
pages.insert("wallet".to_owned(), Box::new(PageEndpoint::new(parity_wallet::App::default()))); }
}
#[cfg(not(feature = "parity-wallet"))]
fn wallet_page(_pages: &mut Endpoints) {}
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
} }

View File

@ -64,7 +64,7 @@ use std::net::SocketAddr;
use jsonrpc_core::{IoHandler, IoDelegate}; use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use router::auth::{Authorization, NoAuth, HttpBasicAuth};
static DAPPS_DOMAIN : &'static str = "dapp"; static DAPPS_DOMAIN : &'static str = ".dapp";
/// Webapps HTTP+RPC server build. /// Webapps HTTP+RPC server build.
pub struct ServerBuilder { pub struct ServerBuilder {

View File

@ -57,15 +57,27 @@ struct PageHandler<T: WebApp + 'static> {
write_pos: usize, write_pos: usize,
} }
impl<T: WebApp + 'static> PageHandler<T> {
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<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> { impl<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request) -> Next { fn on_request(&mut self, req: server::Request) -> Next {
if let RequestUri::AbsolutePath(ref path) = *req.uri() { self.path = match *req.uri() {
// Index file support RequestUri::AbsolutePath(ref path) => {
self.path = match path == &self.prefix || path == &self.prefix_with_slash { Some(self.extract_path(path))
true => Some("index.html".to_owned()), },
false => Some(path[self.prefix_with_slash.len()..].to_owned()), RequestUri::AbsoluteUri(ref url) => {
}; Some(self.extract_path(url.path()))
} },
_ => None,
};
Next::write() Next::write()
} }

View File

@ -38,7 +38,7 @@ impl Endpoint for ProxyPac {
let content = format!( let content = format!(
r#" r#"
function FindProxyForURL(url, host) {{ function FindProxyForURL(url, host) {{
if (shExpMatch(host, "*.{0}")) if (shExpMatch(host, "*{0}"))
{{ {{
return "PROXY {1}"; return "PROXY {1}";
}} }}

View File

@ -21,7 +21,9 @@ mod url;
mod redirect; mod redirect;
pub mod auth; pub mod auth;
use DAPPS_DOMAIN;
use std::sync::Arc; use std::sync::Arc;
use url::Host;
use hyper; use hyper;
use hyper::{server, uri, header}; use hyper::{server, uri, header};
use hyper::{Next, Encoder, Decoder}; use hyper::{Next, Encoder, Decoder};
@ -40,6 +42,13 @@ pub struct Router<A: Authorization + 'static> {
handler: Box<server::Handler<HttpStream>>, handler: Box<server::Handler<HttpStream>>,
} }
#[derive(Debug, PartialEq)]
struct AppId {
id: String,
prefix: String,
is_rpc: bool,
}
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> { impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
fn on_request(&mut self, req: server::Request) -> Next { fn on_request(&mut self, req: server::Request) -> Next {
@ -50,25 +59,32 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
self.handler = match auth { self.handler = match auth {
Authorized::No(handler) => handler, Authorized::No(handler) => handler,
Authorized::Yes => { Authorized::Yes => {
let url = self.extract_url(&req); let url = extract_url(&req);
let app_id = self.extract_app_id(&url); let app_id = extract_app_id(&url);
let host = url.map(|u| HostInfo { let host = url.map(|u| HostInfo {
host: u.host, host: u.host,
port: u.port port: u.port
}); });
match app_id { 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) => { 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) self.endpoints.get(&app_id.id).unwrap().to_handler(&app_id.prefix, host)
}, },
Some(ref app_id) if app_id.id == "api" => { Some(ref app_id) if app_id.id == "api" => {
self.api.to_handler(&app_id.prefix, host) self.api.to_handler(&app_id.prefix, host)
}, },
// Redirection to main page
_ if *req.method() == hyper::method::Method::Get => { _ if *req.method() == hyper::method::Method::Get => {
Redirection::new(self.main_page) Redirection::new(self.main_page)
}, },
// RPC by default
_ => { _ => {
self.rpc.to_handler("/", host) self.rpc.to_handler("", host)
} }
} }
} }
@ -102,7 +118,7 @@ impl<A: Authorization> Router<A> {
api: Arc<Box<Endpoint>>, api: Arc<Box<Endpoint>>,
authorization: Arc<A>) -> Self { authorization: Arc<A>) -> Self {
let handler = rpc.to_handler("/", None); let handler = rpc.to_handler("", None);
Router { Router {
main_page: main_page, main_page: main_page,
endpoints: endpoints, endpoints: endpoints,
@ -112,50 +128,103 @@ impl<A: Authorization> Router<A> {
handler: handler, handler: handler,
} }
} }
}
fn extract_url(&self, req: &server::Request) -> Option<Url> { fn extract_url(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),
_ => None, _ => None,
} }
}, },
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)
}, },
None => return None, None => return None,
}; };
match Url::parse(&url_string) { match Url::parse(&url_string) {
Ok(url) => Some(url), Ok(url) => Some(url),
_ => None, _ => None,
} }
}, },
_ => None, _ => None,
} }
}
fn extract_app_id(url: &Option<Url>) -> Option<AppId> {
fn is_rpc(url: &Url) -> bool {
url.path.len() > 1 && url.path[0] == "rpc"
} }
fn extract_app_id(&self, url: &Option<Url>) -> Option<AppId> { match *url {
match *url { Some(ref url) => match url.host {
Some(ref url) if url.path.len() > 1 => { 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(); let id = url.path[0].clone();
Some(AppId { Some(AppId {
id: id.clone(), id: id.clone(),
prefix: "/".to_owned() + &id prefix: "/".to_owned() + &id,
is_rpc: is_rpc(url),
}) })
}, },
_ => { _ => None,
None },
}, _ => None
}
} }
} }
struct AppId { #[test]
id: String, fn should_extract_app_id() {
prefix: String 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,
}));
} }