Update jsonrpc dependencies and rewrite dapps to futures. (#6522)

* Bump version.

* Fix RPC crate.

* Fix BoxFuture in crates.

* Compiles and passes tests!

* Get rid of .boxed()

* Fixing issues with the UI.

* Remove minihttp. Support threads.

* Reimplement files serving to do it in chunks.

* Increase chunk size.

* Remove some unecessary copying.

* Fix tests.

* Fix stratum warning and ipfs todo.

* Switch to proper branch of jsonrpc.

* Update Cargo.lock.

* Update docs.

* Include dapps-glue in workspace.

* fixed merge artifacts

* Fix test compilation.
This commit is contained in:
Tomasz Drwięga
2017-10-05 12:35:01 +02:00
committed by Arkadiy Paronyan
parent 492da38d67
commit e8b418ca03
118 changed files with 2090 additions and 2908 deletions

View File

@@ -14,71 +14,62 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::io;
use futures::future;
use futures_cpupool::CpuPool;
use hyper::mime::{self, Mime};
use itertools::Itertools;
use parity_dapps::{WebApp, Info};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use page::{handler, PageCache};
use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info};
use Embeddable;
pub struct PageEndpoint<T : WebApp + 'static> {
pub struct Dapp<T: WebApp + 'static> {
/// futures cpu pool
pool: CpuPool,
/// Content of the files
pub app: Arc<T>,
/// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>,
app: T,
/// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Embeddable,
info: EndpointInfo,
fallback_to_index_html: bool,
}
impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` for builtin (compile time) Dapp.
pub fn new(app: T) -> Self {
impl<T: WebApp + 'static> Dapp<T> {
/// Creates new `Dapp` for builtin (compile time) Dapp.
pub fn new(pool: CpuPool, app: T) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
Dapp {
pool,
app,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
/// Creates a new `PageEndpoint` for builtin (compile time) Dapp.
/// Creates a new `Dapp` for builtin (compile time) Dapp.
/// Instead of returning 404 this endpoint will always server index.html.
pub fn with_fallback_to_index(app: T) -> Self {
pub fn with_fallback_to_index(pool: CpuPool, app: T) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
Dapp {
pool,
app,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: true,
}
}
/// Create new `PageEndpoint` and specify prefix that should be removed before looking for a file.
/// It's used only for special endpoints (i.e. `/parity-utils/`)
/// So `/parity-utils/inject.js` will be resolved to `/inject.js` is prefix is set.
pub fn with_prefix(app: T, prefix: String) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: Some(prefix),
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
/// Creates new `PageEndpoint` which can be safely used in iframe
/// Creates new `Dapp` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking).
/// Use wisely!
pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
pub fn new_safe_to_embed(pool: CpuPool, app: T, address: Embeddable) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
Dapp {
pool,
app,
safe_to_embed_on: address,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
@@ -86,21 +77,51 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
}
}
impl<T: WebApp> Endpoint for PageEndpoint<T> {
impl<T: WebApp> Endpoint for Dapp<T> {
fn info(&self) -> Option<&EndpointInfo> {
Some(&self.info)
}
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
Box::new(handler::PageHandler {
app: BuiltinDapp::new(self.app.clone(), self.fallback_to_index_html),
prefix: self.prefix.clone(),
path: path,
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
trace!(target: "dapps", "Builtin file path: {:?}", path);
let file_path = if path.has_no_params() {
"index.html".to_owned()
} else {
path.app_params.into_iter().filter(|x| !x.is_empty()).join("/")
};
trace!(target: "dapps", "Builtin file: {:?}", file_path);
let file = {
let file = |path| self.app.file(path).map(|file| {
let content_type = match file.content_type.parse() {
Ok(mime) => mime,
Err(_) => {
warn!(target: "dapps", "invalid MIME type: {}", file.content_type);
mime::TEXT_HTML
},
};
BuiltinFile {
content_type,
content: io::Cursor::new(file.content),
}
});
let res = file(&file_path);
if self.fallback_to_index_html {
res.or_else(|| file("index.html"))
} else {
res
}
};
let (reader, response) = handler::PageHandler {
file,
cache: PageCache::Disabled,
safe_to_embed_on: self.safe_to_embed_on.clone(),
})
}.into_response();
self.pool.spawn(reader).forget();
Box::new(future::ok(response))
}
}
@@ -116,66 +137,20 @@ impl From<Info> for EndpointInfo {
}
}
struct BuiltinDapp<T: WebApp + 'static> {
app: Arc<T>,
fallback_to_index_html: bool,
struct BuiltinFile {
content_type: Mime,
content: io::Cursor<&'static [u8]>,
}
impl<T: WebApp + 'static> BuiltinDapp<T> {
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
BuiltinDapp {
app: app,
fallback_to_index_html: fallback_to_index_html,
}
}
}
impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
type DappFile = BuiltinDappFile<T>;
fn file(&self, path: &str) -> Option<Self::DappFile> {
let file = |path| self.app.file(path).map(|_| {
BuiltinDappFile {
app: self.app.clone(),
path: path.into(),
write_pos: 0,
}
});
let res = file(path);
if self.fallback_to_index_html {
res.or_else(|| file("index.html"))
} else {
res
}
}
}
struct BuiltinDappFile<T: WebApp + 'static> {
app: Arc<T>,
path: String,
write_pos: usize,
}
impl<T: WebApp + 'static> BuiltinDappFile<T> {
fn file(&self) -> &File {
self.app.file(&self.path).expect("Check is done when structure is created.")
}
}
impl<T: WebApp + 'static> handler::DappFile for BuiltinDappFile<T> {
fn content_type(&self) -> &str {
self.file().content_type
}
fn is_drained(&self) -> bool {
self.write_pos == self.file().content.len()
}
fn next_chunk(&mut self) -> &[u8] {
&self.file().content[self.write_pos..]
}
fn bytes_written(&mut self, bytes: usize) {
self.write_pos += bytes;
impl handler::DappFile for BuiltinFile {
type Reader = io::Cursor<&'static [u8]>;
fn content_type(&self) -> &Mime {
&self.content_type
}
fn into_reader(self) -> Self::Reader {
self.content
}
}

View File

@@ -14,61 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use time::{self, Duration};
use std::io;
use std::time::{Duration, SystemTime};
use hyper::{self, header, StatusCode};
use hyper::mime::Mime;
use hyper::header;
use hyper::server;
use hyper::uri::RequestUri;
use hyper::net::HttpStream;
use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers};
use handlers::{Reader, ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally.
pub trait DappFile: Send {
pub trait DappFile {
/// A reader type returned by this file.
type Reader: io::Read;
/// Returns a content-type of this file.
fn content_type(&self) -> &str;
fn content_type(&self) -> &Mime;
/// Checks if all bytes from that file were written.
fn is_drained(&self) -> bool;
/// Fetch next chunk to write to the client.
fn next_chunk(&mut self) -> &[u8];
/// How many files have been written to the client.
fn bytes_written(&mut self, bytes: usize);
}
/// Dapp as a (dynamic) set of files.
pub trait Dapp: Send + 'static {
/// File type
type DappFile: DappFile;
/// Returns file under given path.
fn file(&self, path: &str) -> Option<Self::DappFile>;
}
/// Currently served by `PageHandler` file
pub enum ServedFile<T: Dapp> {
/// File from dapp
File(T::DappFile),
/// Error (404)
Error(ContentHandler),
}
impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: Embeddable) -> Self {
ServedFile::Error(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Requested dapp resource was not found.",
None,
embeddable_on,
))
}
/// Convert this file into io::Read instance.
fn into_reader(self) -> Self::Reader where Self: Sized;
}
/// Defines what cache headers should be appended to returned resources.
@@ -84,194 +48,55 @@ impl Default for PageCache {
}
}
/// A generic type for `PageHandler` allowing to set the URL.
/// Used by dapps fetching to set the URL after the content was downloaded.
pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send {
fn set_uri(&mut self, uri: &RequestUri);
}
/// A handler for a single webapp.
/// Resolves correct paths and serves as a plumbing code between
/// hyper server and dapp.
pub struct PageHandler<T: Dapp> {
/// A Dapp.
pub app: T,
pub struct PageHandler<T: DappFile> {
/// File currently being served
pub file: ServedFile<T>,
/// Optional prefix to strip from path.
pub prefix: Option<String>,
/// Requested path.
pub path: EndpointPath,
pub file: Option<T>,
/// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Embeddable,
/// Cache settings for this page.
pub cache: PageCache,
}
impl<T: Dapp> PageHandlerWaiting for PageHandler<T> {
fn set_uri(&mut self, uri: &RequestUri) {
trace!(target: "dapps", "Setting URI: {:?}", uri);
self.file = match *uri {
RequestUri::AbsolutePath { ref path, .. } => {
self.app.file(&self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
self.app.file(&self.extract_path(url.path()))
},
_ => None,
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
}
}
impl<T: DappFile> PageHandler<T> {
pub fn into_response(self) -> (Option<Reader<T::Reader>>, hyper::Response) {
let file = match self.file {
None => return (None, ContentHandler::error(
StatusCode::NotFound,
"File not found",
"Requested file has not been found.",
None,
self.safe_to_embed_on,
).into()),
Some(file) => file,
};
impl<T: Dapp> PageHandler<T> {
fn extract_path(&self, path: &str) -> String {
let app_id = &self.path.app_id;
let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
let prefix_with_slash = prefix.clone() + "/";
let query_pos = path.find('?').unwrap_or_else(|| path.len());
let mut res = hyper::Response::new()
.with_status(StatusCode::Ok);
// 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()..query_pos].to_owned()
} else if path.starts_with("/") {
path[1..query_pos].to_owned()
} else {
path[0..query_pos].to_owned()
// headers
{
let mut headers = res.headers_mut();
if let PageCache::Enabled = self.cache {
let validity_secs = 365u32 * 24 * 3600;
let validity = Duration::from_secs(validity_secs as u64);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity_secs),
]));
headers.set(header::Expires(header::HttpDate::from(SystemTime::now() + validity)));
}
headers.set(header::ContentType(file.content_type().to_owned()));
add_security_headers(&mut headers, self.safe_to_embed_on);
}
let (reader, body) = Reader::pair(file.into_reader(), Vec::new());
res.set_body(body);
(Some(reader), res)
}
}
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
self.set_uri(req.uri());
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 {
match self.file {
ServedFile::File(ref f) => {
res.set_status(StatusCode::Ok);
if let PageCache::Enabled = self.cache {
let mut headers = res.headers_mut();
let validity = Duration::days(365);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
]));
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
}
match f.content_type().parse() {
Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
}
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write()
},
ServedFile::Error(ref mut handler) => {
handler.on_response(res)
}
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.file {
ServedFile::Error(ref mut handler) => handler.on_response_writable(encoder),
ServedFile::File(ref f) if f.is_drained() => Next::end(),
ServedFile::File(ref mut f) => match encoder.write(f.next_chunk()) {
Ok(bytes) => {
f.bytes_written(bytes);
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end(),
},
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
pub struct TestWebAppFile;
impl DappFile for TestWebAppFile {
fn content_type(&self) -> &str {
unimplemented!()
}
fn is_drained(&self) -> bool {
unimplemented!()
}
fn next_chunk(&mut self) -> &[u8] {
unimplemented!()
}
fn bytes_written(&mut self, _bytes: usize) {
unimplemented!()
}
}
#[derive(Default)]
pub struct TestWebapp;
impl Dapp for TestWebapp {
type DappFile = TestWebAppFile;
fn file(&self, _path: &str) -> Option<Self::DappFile> {
None
}
}
}
#[test]
fn should_extract_path_with_appid() {
// given
let path1 = "/";
let path2= "/test.css";
let path3 = "/app/myfile.txt";
let path4 = "/app/myfile.txt?query=123";
let page_handler = PageHandler {
app: test::TestWebapp,
prefix: None,
path: EndpointPath {
app_id: "app".to_owned(),
app_params: vec![],
host: "".to_owned(),
port: 8080,
using_dapps_domains: true,
},
file: ServedFile::new(None),
cache: Default::default(),
safe_to_embed_on: None,
};
// when
let res1 = page_handler.extract_path(path1);
let res2 = page_handler.extract_path(path2);
let res3 = page_handler.extract_path(path3);
let res4 = page_handler.extract_path(path4);
// then
assert_eq!(&res1, "index.html");
assert_eq!(&res2, "test.css");
assert_eq!(&res3, "myfile.txt");
assert_eq!(&res4, "myfile.txt");
}

View File

@@ -15,16 +15,18 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use mime_guess;
use std::io::{Seek, Read, SeekFrom};
use std::fs;
use std::{fs, fmt};
use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime;
use futures::{future};
use futures_cpupool::CpuPool;
use page::handler::{self, PageCache};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use hyper::mime::Mime;
use Embeddable;
#[derive(Debug, Clone)]
pub struct LocalPageEndpoint {
#[derive(Clone)]
pub struct Dapp {
pool: CpuPool,
path: PathBuf,
mime: Option<Mime>,
info: Option<EndpointInfo>,
@@ -32,23 +34,37 @@ pub struct LocalPageEndpoint {
embeddable_on: Embeddable,
}
impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
LocalPageEndpoint {
path: path,
impl fmt::Debug for Dapp {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Dapp")
.field("path", &self.path)
.field("mime", &self.mime)
.field("info", &self.info)
.field("cache", &self.cache)
.field("embeddable_on", &self.embeddable_on)
.finish()
}
}
impl Dapp {
pub fn new(pool: CpuPool, path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
Dapp {
pool,
path,
mime: None,
info: Some(info),
cache: cache,
embeddable_on: embeddable_on,
cache,
embeddable_on,
}
}
pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self {
LocalPageEndpoint {
path: path,
pub fn single_file(pool: CpuPool, path: PathBuf, mime: Mime, cache: PageCache) -> Self {
Dapp {
pool,
path,
mime: Some(mime),
info: None,
cache: cache,
cache,
embeddable_on: None,
}
}
@@ -57,125 +73,75 @@ impl LocalPageEndpoint {
self.path.clone()
}
fn page_handler_with_mime(&self, path: EndpointPath, mime: &Mime) -> handler::PageHandler<LocalSingleFile> {
handler::PageHandler {
app: LocalSingleFile { path: self.path.clone(), mime: format!("{}", mime) },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> {
handler::PageHandler {
app: LocalDapp { path: self.path.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> {
fn get_file(&self, path: &EndpointPath) -> Option<LocalFile> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
} else {
Box::new(self.page_handler(path))
return LocalFile::from_path(&self.path, mime.to_owned());
}
let mut file_path = self.path.to_owned();
if path.has_no_params() {
file_path.push("index.html");
} else {
for part in &path.app_params {
file_path.push(part);
}
}
let mime = mime_guess::guess_mime_type(&file_path);
LocalFile::from_path(&file_path, mime)
}
pub fn to_response(&self, path: &EndpointPath) -> Response {
let (reader, response) = handler::PageHandler {
file: self.get_file(path),
cache: self.cache,
safe_to_embed_on: self.embeddable_on.clone(),
}.into_response();
self.pool.spawn(reader).forget();
Box::new(future::ok(response))
}
}
impl Endpoint for LocalPageEndpoint {
impl Endpoint for Dapp {
fn info(&self) -> Option<&EndpointInfo> {
self.info.as_ref()
}
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
} else {
Box::new(self.page_handler(path))
}
}
}
struct LocalSingleFile {
path: PathBuf,
mime: String,
}
impl handler::Dapp for LocalSingleFile {
type DappFile = LocalFile;
fn file(&self, _path: &str) -> Option<Self::DappFile> {
LocalFile::from_path(&self.path, Some(&self.mime))
}
}
struct LocalDapp {
path: PathBuf,
}
impl handler::Dapp for LocalDapp {
type DappFile = LocalFile;
fn file(&self, file_path: &str) -> Option<Self::DappFile> {
let mut path = self.path.clone();
for part in file_path.split('/') {
path.push(part);
}
LocalFile::from_path(&path, None)
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
self.to_response(&path)
}
}
struct LocalFile {
content_type: String,
buffer: [u8; 4096],
content_type: Mime,
file: fs::File,
len: u64,
pos: u64,
}
impl LocalFile {
fn from_path<P: AsRef<Path>>(path: P, mime: Option<&str>) -> Option<Self> {
fn from_path<P: AsRef<Path>>(path: P, content_type: Mime) -> Option<Self> {
trace!(target: "dapps", "Local file: {:?}", path.as_ref());
// Check if file exists
fs::File::open(&path).ok().map(|file| {
let content_type = mime.map(|mime| mime.to_owned())
.unwrap_or_else(|| mime_guess::guess_mime_type(path).to_string());
let len = file.metadata().ok().map_or(0, |meta| meta.len());
LocalFile {
content_type: content_type,
buffer: [0; 4096],
file: file,
pos: 0,
len: len,
content_type,
file,
}
})
}
}
impl handler::DappFile for LocalFile {
fn content_type(&self) -> &str {
type Reader = fs::File;
fn content_type(&self) -> &Mime {
&self.content_type
}
fn is_drained(&self) -> bool {
self.pos == self.len
}
fn next_chunk(&mut self) -> &[u8] {
let _ = self.file.seek(SeekFrom::Start(self.pos));
if let Ok(n) = self.file.read(&mut self.buffer) {
&self.buffer[0..n]
} else {
&self.buffer[0..0]
}
}
fn bytes_written(&mut self, bytes: usize) {
self.pos += bytes as u64;
fn into_reader(self) -> Self::Reader {
self.file
}
}

View File

@@ -15,11 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
mod builtin;
mod local;
pub mod builtin;
pub mod local;
mod handler;
pub use self::local::LocalPageEndpoint;
pub use self::builtin::PageEndpoint;
pub use self::handler::{PageCache, PageHandlerWaiting};
pub use self::handler::PageCache;