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 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::<parity_status::App>(&mut pages, "status");
insert::<parity_status::App>(&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::<parity_wallet::App>(pages, "wallet");
}
#[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 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 {

View File

@ -57,15 +57,27 @@ struct PageHandler<T: WebApp + 'static> {
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> {
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()
}

View File

@ -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}";
}}

View File

@ -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<A: Authorization + 'static> {
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> {
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 {
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<A: Authorization> Router<A> {
api: Arc<Box<Endpoint>>,
authorization: Arc<A>) -> 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<A: Authorization> Router<A> {
handler: handler,
}
}
}
fn extract_url(&self, req: &server::Request) -> Option<Url> {
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::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};
fn extract_url(req: &server::Request) -> Option<Url> {
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::<header::Host>() {
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<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 {
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,
}));
}