Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Subdomains support in content server (webapps server). #1082

Merged
merged 4 commits into from May 16, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 3 additions & 48 deletions webapp/src/api.rs
Expand Up @@ -16,12 +16,8 @@

//! Simple REST API

use std::io::Write;
use std::sync::Arc;
use hyper::status::StatusCode;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use endpoint::{Endpoint, Endpoints};
use endpoint::{Endpoint, Endpoints, ContentHandler, Handler, EndpointPath};

pub struct RestApi {
endpoints: Arc<Endpoints>,
Expand All @@ -46,49 +42,8 @@ impl RestApi {
}

impl Endpoint for RestApi {
fn to_handler(&self, _prefix: &str) -> Box<server::Handler<HttpStream>> {
Box::new(RestApiHandler {
pages: self.list_pages(),
write_pos: 0,
})
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
Box::new(ContentHandler::new(self.list_pages(), "application/json".to_owned()))
}
}

struct RestApiHandler {
pages: String,
write_pos: usize,
}

impl server::Handler<HttpStream> for RestApiHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}

fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}

fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType("application/json".parse().unwrap()));
Next::write()
}

fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let bytes = self.pages.as_bytes();
if self.write_pos == bytes.len() {
return Next::end();
}

match encoder.write(&bytes[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}
15 changes: 12 additions & 3 deletions webapp/src/apps.rs
Expand Up @@ -16,27 +16,36 @@

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() -> 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::boxed());

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) {
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())));
}
65 changes: 63 additions & 2 deletions webapp/src/endpoint.rs
Expand Up @@ -16,12 +16,73 @@

//! URL Endpoint traits

use hyper::server;
use hyper::status::StatusCode;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;

use std::io::Write;
use std::collections::HashMap;

#[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath {
pub app_id: String,
pub host: String,
pub port: u16,
}

pub trait Endpoint : Send + Sync {
fn to_handler(&self, prefix: &str) -> Box<server::Handler<HttpStream>>;
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<HttpStream>>;
}

pub type Endpoints = HashMap<String, Box<Endpoint>>;
pub type Handler = server::Handler<HttpStream>;

pub struct ContentHandler {
content: String,
mimetype: String,
write_pos: usize,
}

impl ContentHandler {
pub fn new(content: String, mimetype: String) -> Self {
ContentHandler {
content: content,
mimetype: mimetype,
write_pos: 0
}
}
}

impl server::Handler<HttpStream> for ContentHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}

fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}

fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap()));
Next::write()
}

fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let bytes = self.content.as_bytes();
if self.write_pos == bytes.len() {
return Next::end();
}

match encoder.write(&bytes[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}
3 changes: 3 additions & 0 deletions webapp/src/lib.rs
Expand Up @@ -57,12 +57,15 @@ mod page;
mod router;
mod rpc;
mod api;
mod proxypac;

use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};

static DAPPS_DOMAIN : &'static str = ".dapp";

/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
handler: Arc<IoHandler>,
Expand Down
53 changes: 36 additions & 17 deletions webapp/src/page/mod.rs
Expand Up @@ -22,7 +22,7 @@ use hyper::header;
use hyper::status::StatusCode;
use hyper::net::HttpStream;
use hyper::{Decoder, Encoder, Next};
use endpoint::Endpoint;
use endpoint::{Endpoint, EndpointPath};
use parity_webapp::WebApp;

pub struct PageEndpoint<T : WebApp + 'static> {
Expand All @@ -38,34 +38,53 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
}

impl<T: WebApp> Endpoint for PageEndpoint<T> {
fn to_handler(&self, prefix: &str) -> Box<server::Handler<HttpStream>> {
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<HttpStream>> {
Box::new(PageHandler {
app: self.app.clone(),
prefix: prefix.to_owned(),
prefix_with_slash: prefix.to_owned() + "/",
path: None,
path: path,
file: None,
write_pos: 0,
})
}
}

struct PageHandler<T: WebApp + 'static> {
app: Arc<T>,
prefix: String,
prefix_with_slash: String,
path: Option<String>,
path: EndpointPath,
file: Option<String>,
write_pos: usize,
}

impl<T: WebApp + 'static> PageHandler<T> {
fn extract_path(&self, path: &str) -> String {
let prefix = "/".to_owned() + &self.path.app_id;
let prefix_with_slash = prefix.clone() + "/";

// Index file support
match path == "/" || path == &prefix || path == &prefix_with_slash {
true => "index.html".to_owned(),
false => if path.starts_with(&prefix_with_slash) {
path[prefix_with_slash.len()..].to_owned()
} else if path.starts_with("/") {
path[1..].to_owned()
} else {
path.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.file = 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()
}

Expand All @@ -74,7 +93,7 @@ impl<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> {
}

fn on_response(&mut self, res: &mut server::Response) -> Next {
if let Some(f) = self.path.as_ref().and_then(|f| self.app.file(f)) {
if let Some(f) = self.file.as_ref().and_then(|f| self.app.file(f)) {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap()));
Next::write()
Expand All @@ -86,7 +105,7 @@ impl<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> {

fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let (wrote, res) = {
let file = self.path.as_ref().and_then(|f| self.app.file(f));
let file = self.file.as_ref().and_then(|f| self.app.file(f));
match file {
None => (None, Next::end()),
Some(f) if self.write_pos == f.content.len() => (None, Next::end()),
Expand Down
48 changes: 48 additions & 0 deletions webapp/src/proxypac.rs
@@ -0,0 +1,48 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

//! Serving ProxyPac file

use endpoint::{Endpoint, Handler, ContentHandler, EndpointPath};
use DAPPS_DOMAIN;

pub struct ProxyPac;

impl ProxyPac {
pub fn boxed() -> Box<Endpoint> {
Box::new(ProxyPac)
}
}

impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let content = format!(
r#"
function FindProxyForURL(url, host) {{
if (shExpMatch(host, "*{0}"))
{{
return "PROXY {1}:{2}";
}}

return "DIRECT";
}}
"#,
DAPPS_DOMAIN, path.host, path.port);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one DAPPS_DOMAIN? would probably be sensible to have a .parity domain for inbuilt-parity stuff (which would use parity-specific RPCs) and .dapp domain for theoretically standardised, cross-implementation stuff.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be easily extended to support more "domains", but we can still use addresses like:
http://status.parity.dapp or http://myoracle.ethcore.dapp

Copy link
Contributor

@gavofyork gavofyork May 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah; i'm taking direction from the classic about: (and newer chrome:) protocols which don't try to map over http: but rather use a different namespace.

i would reserve the .dapp domain for pages/packages which are, in principle, independent of parity. .parity could be used for our packages which are built in (i.e. all of them at present).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the domain to .parity (since we only have built-in dapps).
Will prepare multi-domain support in separte PR.

Box::new(ContentHandler::new(content, "application/javascript".to_owned()))
}
}