Adding fetch API to the crate

This commit is contained in:
Tomasz Drwięga 2016-11-20 18:30:54 +01:00
parent 845bc52e36
commit cc8a9d410b
7 changed files with 155 additions and 20 deletions

1
Cargo.lock generated
View File

@ -372,6 +372,7 @@ version = "1.5.0"
dependencies = [ dependencies = [
"ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-util 1.5.0", "ethcore-util 1.5.0",
"fetch 0.1.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -11,4 +11,5 @@ log = "0.3"
rustc-serialize = "0.3" rustc-serialize = "0.3"
ethabi = "0.2.2" ethabi = "0.2.2"
mime_guess = "1.6.1" mime_guess = "1.6.1"
fetch = { path = "../../util/fetch" }
ethcore-util = { path = "../../util" } ethcore-util = { path = "../../util" }

View File

@ -0,0 +1,114 @@
// 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/>.
//! Hash-addressed content resolver & fetcher.
use std::{io, fs};
use std::sync::Arc;
use std::path::PathBuf;
use util::{Mutex, H256, sha3};
use fetch::{Fetch, FetchError, Client as FetchClient};
use urlhint::{ContractClient, URLHintContract, URLHint, URLHintResult};
/// API for fetching by hash.
pub trait HashFetch {
/// Fetch hash-addressed content.
/// Parameters:
/// 1. `hash` - content hash
/// 2. `on_done` - callback function invoked when the content is ready (or there was error during fetch)
///
/// This function may fail immediately when fetch cannot be initialized or content cannot be resolved.
fn fetch(&self, hash: H256, on_done: Box<Fn(Result<PathBuf, Error>) + Send>) -> Result<(), Error>;
}
/// Hash-fetching error.
#[derive(Debug)]
pub enum Error {
/// Hash could not be resolved to a valid content address.
NoResolution,
/// Downloaded content hash does not match.
HashMismatch { expected: H256, got: H256 },
/// IO Error while validating hash.
IO(io::Error),
/// Error during fetch.
Fetch(FetchError),
}
impl From<FetchError> for Error {
fn from(error: FetchError) -> Self {
Error::Fetch(error)
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::IO(error)
}
}
/// Default Hash-fetching client using on-chain contract to resolve hashes to URLs.
pub struct Client {
contract: URLHintContract,
fetch: Mutex<FetchClient>,
}
impl Client {
/// Creates new instance of the `Client` given on-chain contract client.
pub fn new(contract: Arc<ContractClient>) -> Self {
Client {
contract: URLHintContract::new(contract),
fetch: Mutex::new(FetchClient::default()),
}
}
}
impl HashFetch for Client {
fn fetch(&self, hash: H256, on_done: Box<Fn(Result<PathBuf, Error>) + Send>) -> Result<(), Error> {
debug!(target: "dapps", "Fetching: {:?}", hash);
let url = try!(
self.contract.resolve(hash.to_vec()).map(|content| match content {
URLHintResult::Dapp(dapp) => {
dapp.url()
},
URLHintResult::Content(content) => {
content.url
},
}).ok_or_else(|| Error::NoResolution)
);
debug!(target: "dapps", "Resolved {:?} to {:?}. Fetching...", hash, url);
self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| {
fn validate_hash(hash: H256, result: Result<PathBuf, FetchError>) -> Result<PathBuf, Error> {
let path = try!(result);
let mut file_reader = io::BufReader::new(try!(fs::File::open(&path)));
let content_hash = try!(sha3(&mut file_reader));
if content_hash != hash {
Err(Error::HashMismatch{ got: content_hash, expected: hash })
} else {
Ok(path)
}
}
debug!(target: "dapps", "Content fetched, validating hash ({:?})", hash);
on_done(validate_hash(hash, result))
})).map_err(Into::into)
}
}

View File

@ -14,12 +14,20 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Hash-addressed content resolver & fetcher.
#![warn(missing_docs)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate rustc_serialize; extern crate rustc_serialize;
extern crate mime_guess; extern crate mime_guess;
extern crate ethabi; extern crate ethabi;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate fetch;
mod client;
pub mod urlhint; pub mod urlhint;
pub use client::{HashFetch, Client};

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! URLHint Contract
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use rustc_serialize::hex::ToHex; use rustc_serialize::hex::ToHex;
@ -24,15 +26,30 @@ use util::{Address, Bytes, Hashable};
const COMMIT_LEN: usize = 20; const COMMIT_LEN: usize = 20;
/// RAW Contract interface.
/// Should execute transaction using current blockchain state.
pub trait ContractClient: Send + Sync {
/// Get registrar address
fn registrar(&self) -> Result<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
}
/// Github-hosted dapp.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct GithubApp { pub struct GithubApp {
/// Github Account
pub account: String, pub account: String,
/// Github Repository
pub repo: String, pub repo: String,
/// Commit on Github
pub commit: [u8;COMMIT_LEN], pub commit: [u8;COMMIT_LEN],
/// Dapp owner address
pub owner: Address, pub owner: Address,
} }
impl GithubApp { impl GithubApp {
/// Returns URL of this Github-hosted dapp package.
pub fn url(&self) -> String { pub fn url(&self) -> String {
// Since https fetcher doesn't support redirections we use direct link // Since https fetcher doesn't support redirections we use direct link
// format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex()) // format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex())
@ -53,22 +70,17 @@ impl GithubApp {
} }
} }
/// Hash-Addressed Content
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Content { pub struct Content {
/// URL of the content
pub url: String, pub url: String,
/// MIME type of the content
pub mime: String, pub mime: String,
/// Content owner address
pub owner: Address, pub owner: Address,
} }
/// RAW Contract interface.
/// Should execute transaction using current blockchain state.
pub trait ContractClient: Send + Sync {
/// Get registrar address
fn registrar(&self) -> Result<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
}
/// Result of resolving id to URL /// Result of resolving id to URL
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum URLHintResult { pub enum URLHintResult {
@ -84,6 +96,7 @@ pub trait URLHint {
fn resolve(&self, id: Bytes) -> Option<URLHintResult>; fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
} }
/// `URLHintContract` API
pub struct URLHintContract { pub struct URLHintContract {
urlhint: Contract, urlhint: Contract,
registrar: Contract, registrar: Contract,
@ -91,6 +104,7 @@ pub struct URLHintContract {
} }
impl URLHintContract { impl URLHintContract {
/// Creates new `URLHintContract`
pub fn new(client: Arc<ContractClient>) -> Self { pub fn new(client: Arc<ContractClient>) -> Self {
let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI");
let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI");
@ -244,11 +258,6 @@ fn guess_mime_type(url: &str) -> Option<String> {
}) })
} }
#[cfg(test)]
pub fn test_guess_mime_type(url: &str) -> Option<String> {
guess_mime_type(url)
}
fn as_string<T: fmt::Debug>(e: T) -> String { fn as_string<T: fmt::Debug>(e: T) -> String {
format!("{:?}", e) format!("{:?}", e)
} }
@ -260,6 +269,7 @@ mod tests {
use rustc_serialize::hex::FromHex; use rustc_serialize::hex::FromHex;
use super::*; use super::*;
use super::guess_mime_type;
use util::{Bytes, Address, Mutex, ToPretty}; use util::{Bytes, Address, Mutex, ToPretty};
struct FakeRegistrar { struct FakeRegistrar {
@ -390,10 +400,10 @@ mod tests {
let url5 = "https://ethcore.io/parity.png"; let url5 = "https://ethcore.io/parity.png";
assert_eq!(test_guess_mime_type(url1), None); assert_eq!(guess_mime_type(url1), None);
assert_eq!(test_guess_mime_type(url2), Some("image/png".into())); assert_eq!(guess_mime_type(url2), Some("image/png".into()));
assert_eq!(test_guess_mime_type(url3), Some("image/png".into())); assert_eq!(guess_mime_type(url3), Some("image/png".into()));
assert_eq!(test_guess_mime_type(url4), Some("image/jpeg".into())); assert_eq!(guess_mime_type(url4), Some("image/jpeg".into()));
assert_eq!(test_guess_mime_type(url5), Some("image/png".into())); assert_eq!(guess_mime_type(url5), Some("image/png".into()));
} }
} }

View File

@ -683,6 +683,7 @@ macro_rules! construct_uint {
bytes[i] = (arr[pos] >> ((rev % 8) * 8)) as u8; bytes[i] = (arr[pos] >> ((rev % 8) * 8)) as u8;
} }
} }
#[inline] #[inline]
fn exp10(n: usize) -> Self { fn exp10(n: usize) -> Self {
match n { match n {