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 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())));
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}";
|
||||||
}}
|
}}
|
||||||
|
@ -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,
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user