Serving content at /api/content/<hash> (#2248)

This commit is contained in:
Tomasz Drwięga 2016-09-22 18:05:36 +02:00 committed by Gav Wood
parent 368aca521b
commit 862feb7172
6 changed files with 71 additions and 22 deletions

View File

@ -15,23 +15,26 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc; use std::sync::Arc;
use hyper::{server, net, Decoder, Encoder, Next}; use hyper::{server, net, Decoder, Encoder, Next, Control};
use api::types::{App, ApiError}; use api::types::{App, ApiError};
use api::response::{as_json, as_json_error, ping_response}; use api::response::{as_json, as_json_error, ping_response};
use handlers::extract_url; use handlers::extract_url;
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
use apps::fetcher::ContentFetcher;
#[derive(Clone)] #[derive(Clone)]
pub struct RestApi { pub struct RestApi {
local_domain: String, local_domain: String,
endpoints: Arc<Endpoints>, endpoints: Arc<Endpoints>,
fetcher: Arc<ContentFetcher>,
} }
impl RestApi { impl RestApi {
pub fn new(local_domain: String, endpoints: Arc<Endpoints>) -> Box<Endpoint> { pub fn new(local_domain: String, endpoints: Arc<Endpoints>, fetcher: Arc<ContentFetcher>) -> Box<Endpoint> {
Box::new(RestApi { Box::new(RestApi {
local_domain: local_domain, local_domain: local_domain,
endpoints: endpoints, endpoints: endpoints,
fetcher: fetcher,
}) })
} }
@ -43,23 +46,42 @@ impl RestApi {
} }
impl Endpoint for RestApi { impl Endpoint for RestApi {
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> { fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
Box::new(RestApiRouter { Box::new(RestApiRouter::new(self.clone(), path, control))
api: self.clone(),
handler: as_json_error(&ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
}),
})
} }
} }
struct RestApiRouter { struct RestApiRouter {
api: RestApi, api: RestApi,
path: Option<EndpointPath>,
control: Option<Control>,
handler: Box<Handler>, handler: Box<Handler>,
} }
impl RestApiRouter {
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
RestApiRouter {
path: Some(path),
control: Some(control),
api: api,
handler: as_json_error(&ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
}),
}
}
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, control: Control) -> Option<Box<Handler>> {
match hash {
Some(hash) if self.api.fetcher.contains(hash) => {
Some(self.api.fetcher.to_async_handler(path, control))
},
_ => None
}
}
}
impl server::Handler<net::HttpStream> for RestApiRouter { impl server::Handler<net::HttpStream> for RestApiRouter {
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next { fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
@ -69,13 +91,18 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
return Next::write(); return Next::write();
} }
let url = url.expect("Check for None is above; qed"); let url = url.expect("Check for None early-exists above; qed");
let path = self.path.take().expect("on_request called only once, and path is always defined in new; qed");
let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed");
let endpoint = url.path.get(1).map(|v| v.as_str()); let endpoint = url.path.get(1).map(|v| v.as_str());
let hash = url.path.get(2).map(|v| v.as_str());
let handler = endpoint.and_then(|v| match v { let handler = endpoint.and_then(|v| match v {
"apps" => Some(as_json(&self.api.list_apps())), "apps" => Some(as_json(&self.api.list_apps())),
"ping" => Some(ping_response(&self.api.local_domain)), "ping" => Some(ping_response(&self.api.local_domain)),
_ => None, "content" => self.resolve_content(hash, path, control),
_ => None
}); });
// Overwrite default // Overwrite default

View File

@ -42,7 +42,9 @@ pub type Handler = server::Handler<net::HttpStream> + Send;
pub trait Endpoint : Send + Sync { pub trait Endpoint : Send + Sync {
fn info(&self) -> Option<&EndpointInfo> { None } fn info(&self) -> Option<&EndpointInfo> { None }
fn to_handler(&self, path: EndpointPath) -> Box<Handler>; fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
panic!("This Endpoint is asynchronous and requires Control object.");
}
fn to_async_handler(&self, path: EndpointPath, _control: hyper::Control) -> Box<Handler> { fn to_async_handler(&self, path: EndpointPath, _control: hyper::Control) -> Box<Handler> {
self.to_handler(path) self.to_handler(path)

View File

@ -196,8 +196,11 @@ impl Server {
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
special.insert(router::SpecialEndpoint::Api, api::RestApi::new(format!("{}", addr), endpoints.clone()));
special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert(router::SpecialEndpoint::Utils, apps::utils());
special.insert(
router::SpecialEndpoint::Api,
api::RestApi::new(format!("{}", addr), endpoints.clone(), content_fetcher.clone())
);
special special
}); });
let hosts = Self::allowed_hosts(hosts, format!("{}", addr)); let hosts = Self::allowed_hosts(hosts, format!("{}", addr));

View File

@ -91,7 +91,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
(Some(ref path), _) if self.fetch.contains(&path.app_id) => { (Some(ref path), _) if self.fetch.contains(&path.app_id) => {
self.fetch.to_async_handler(path.clone(), control) self.fetch.to_async_handler(path.clone(), control)
}, },
// Redirection to main page (maybe 404 instead?) // 404 for non-existent content
(Some(ref path), _) if *req.method() == hyper::method::Method::Get => { (Some(ref path), _) if *req.method() == hyper::method::Method::Get => {
let address = apps::redirection_address(path.using_dapps_domains, self.main_page); let address = apps::redirection_address(path.using_dapps_domains, self.main_page);
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
@ -143,7 +143,7 @@ impl<A: Authorization> Router<A> {
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
) -> Self { ) -> Self {
let handler = special.get(&SpecialEndpoint::Api).unwrap().to_handler(EndpointPath::default()); let handler = special.get(&SpecialEndpoint::Utils).unwrap().to_handler(EndpointPath::default());
Router { Router {
control: Some(control), control: Some(control),
main_page: main_page, main_page: main_page,

View File

@ -38,10 +38,6 @@ struct RpcEndpoint {
} }
impl Endpoint for RpcEndpoint { impl Endpoint for RpcEndpoint {
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
panic!("RPC Endpoint is asynchronous and requires Control object.");
}
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> { fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let panic_handler = PanicHandler { handler: self.panic_handler.clone() }; let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
Box::new(ServerHandler::new( Box::new(ServerHandler::new(

View File

@ -14,7 +14,7 @@
// 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/>.
use tests::helpers::{serve, request}; use tests::helpers::{serve, serve_with_registrar, request};
#[test] #[test]
fn should_return_error() { fn should_return_error() {
@ -82,3 +82,24 @@ fn should_handle_ping() {
assert_eq!(response.body, "0\n\n".to_owned()); assert_eq!(response.body, "0\n\n".to_owned());
} }
#[test]
fn should_try_to_resolve_dapp() {
// given
let (server, registrar) = serve_with_registrar();
// when
let response = request(server,
"\
GET /api/content/1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d HTTP/1.1\r\n\
Host: home.parity\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2);
}