Subdomains router for dapps
This commit is contained in:
parent
f62d058186
commit
3fc7faf24c
@ -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())));
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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}";
|
||||
}}
|
||||
|
@ -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,
|
||||
}));
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user