Skip to content

Commit

Permalink
Update hyper to version 0.12. (paritytech#303)
Browse files Browse the repository at this point in the history
* Update hyper to version 0.12.

* Replace `tokio_core` with `tokio`.

* `server_utils::reactor::Remote` has been renamed to `Executor`.
  The `Shared` variant now contains a `tokio::runtime::TaskExecutor`.
  This may need to be changed to a trait object (of
  `tokio::executor::Executor`) or be otherwise abstracted to conceal the
  type in the public API.

* Bump crate versions to 0.9

* Update hyper to version 0.12.

* Replace `tokio_core` with `tokio`.

* `server_utils::reactor::Remote` has been renamed to `Executor`.
  The `Shared` variant now contains a `tokio::runtime::TaskExecutor`.
  This may need to be changed to a trait object (of
  `tokio::executor::Executor`) or be otherwise abstracted to conceal the
  type in the public API.

* Bump crate versions to 0.9

* Fix compilation of hyper-0.2

* Address grumbles.

* Re-export `AllowCors` from `http`.
  • Loading branch information
c0gent authored and CriesofCarrots committed Sep 28, 2018
1 parent 9569728 commit 64188da
Show file tree
Hide file tree
Showing 26 changed files with 679 additions and 668 deletions.
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ categories = [

[dependencies]
log = "0.4"
futures = "0.1.6"
futures = "~0.1.6"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "server"]
documentation = "https://paritytech.github.io/jsonrpc/jsonrpc_http_server/index.html"

[dependencies]
hyper = "0.11"
hyper = "0.12"
solana-jsonrpc-core = { version = "0.1", path = "../core" }
solana-jsonrpc-server-utils = { version = "0.1", path = "../server-utils" }
log = "0.4"
Expand Down
12 changes: 5 additions & 7 deletions http/examples/http_meta.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
extern crate solana_jsonrpc_http_server as jsonrpc_http_server;
extern crate unicase;

use jsonrpc_http_server::{ServerBuilder, hyper, RestApi, AccessControlAllowHeaders};
use jsonrpc_http_server::{ServerBuilder, hyper, RestApi, cors::AccessControlAllowHeaders};
use jsonrpc_http_server::jsonrpc_core::*;
use self::hyper::header;

#[derive(Default, Clone)]
struct Meta {
Expand All @@ -27,15 +26,14 @@ fn main() {
let server = ServerBuilder::new(io)
.cors_allow_headers(AccessControlAllowHeaders::Only(
vec![
"Authorization",
"Authorization".to_owned(),
])
)
.rest_api(RestApi::Unsecure)
// You can also implement `MetaExtractor` trait and pass a struct here.
.meta_extractor(|req: &hyper::Request| {
let auth = req.headers()
.get::<header::Authorization<header::Bearer>>();
let auth = auth.map(|h| h.token.clone());
.meta_extractor(|req: &hyper::Request<hyper::Body>| {
let auth = req.headers().get(hyper::header::AUTHORIZATION)
.map(|h| h.to_str().unwrap_or("").to_owned());

Meta { auth }
})
Expand Down
4 changes: 2 additions & 2 deletions http/examples/http_middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ fn main() {

let server = ServerBuilder::new(io)
.cors(DomainsValidation::AllowOnly(vec![AccessControlAllowOrigin::Null]))
.request_middleware(|request: hyper::server::Request| {
if request.path() == "/status" {
.request_middleware(|request: hyper::Request<hyper::Body>| {
if request.uri() == "/status" {
Response::ok("Server running OK.").into()
} else {
request.into()
Expand Down
157 changes: 80 additions & 77 deletions http/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ use Rpc;
use std::{fmt, mem, str};
use std::sync::Arc;

use hyper::{self, mime, server, Method};
use hyper::header::{self, Headers};
use unicase::Ascii;
use hyper::{self, service::Service, Body, Method};
use hyper::header::{self, HeaderMap, HeaderValue};

use jsonrpc::{self as core, FutureResult, Metadata, Middleware, NoopMiddleware};
use jsonrpc::{self as core, FutureResult, Metadata, Middleware, NoopMiddleware, FutureRpcResult};
use jsonrpc::futures::{Future, Poll, Async, Stream, future};
use jsonrpc::serde_json;
use response::Response;
use server_utils::cors;
use server_utils::cors::AllowHeaders;

use {utils, RequestMiddleware, RequestMiddlewareAction, CorsDomains, AllowedHosts, RestApi};

Expand All @@ -22,7 +20,7 @@ pub struct ServerHandler<M: Metadata = (), S: Middleware<M> = NoopMiddleware> {
allowed_hosts: AllowedHosts,
cors_domains: CorsDomains,
cors_max_age: Option<u32>,
allowed_headers: cors::AccessControlAllowHeadersUnicase,
cors_allowed_headers: cors::AccessControlAllowHeaders,
middleware: Arc<RequestMiddleware>,
rest_api: RestApi,
health_api: Option<(String, String)>,
Expand All @@ -35,7 +33,7 @@ impl<M: Metadata, S: Middleware<M>> ServerHandler<M, S> {
jsonrpc_handler: Rpc<M, S>,
cors_domains: CorsDomains,
cors_max_age: Option<u32>,
allowed_headers: cors::AccessControlAllowHeadersUnicase,
cors_allowed_headers: cors::AccessControlAllowHeaders,
allowed_hosts: AllowedHosts,
middleware: Arc<RequestMiddleware>,
rest_api: RestApi,
Expand All @@ -47,7 +45,7 @@ impl<M: Metadata, S: Middleware<M>> ServerHandler<M, S> {
allowed_hosts,
cors_domains,
cors_max_age,
allowed_headers,
cors_allowed_headers,
middleware,
rest_api,
health_api,
Expand All @@ -56,13 +54,13 @@ impl<M: Metadata, S: Middleware<M>> ServerHandler<M, S> {
}
}

impl<M: Metadata, S: Middleware<M>> server::Service for ServerHandler<M, S> {
type Request = server::Request;
type Response = server::Response;
impl<M: Metadata, S: Middleware<M>> Service for ServerHandler<M, S> {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
type Future = Handler<M, S>;

fn call(&self, request: Self::Request) -> Self::Future {
fn call(&mut self, request: hyper::Request<Self::ReqBody>) -> Self::Future {
let is_host_allowed = utils::is_host_allowed(&request, &self.allowed_hosts);
let action = self.middleware.on_request(request);

Expand All @@ -87,17 +85,17 @@ impl<M: Metadata, S: Middleware<M>> server::Service for ServerHandler<M, S> {
Handler::Rpc(RpcHandler {
jsonrpc_handler: self.jsonrpc_handler.clone(),
state: RpcHandlerState::ReadingHeaders {
request: request,
request,
cors_domains: self.cors_domains.clone(),
cors_headers: self.cors_allowed_headers.clone(),
continue_on_invalid_cors: should_continue_on_invalid_cors,
},
is_options: false,
cors_allow_origin: cors::AllowOrigin::NotRequired,
cors_max_age: self.cors_max_age,
cors_allow_origin: cors::AllowCors::NotRequired,
cors_allow_headers: cors::AllowCors::NotRequired,
rest_api: self.rest_api,
health_api: self.health_api.clone(),
cors_max_age: self.cors_max_age,
allowed_headers: self.allowed_headers.clone(),
cors_allow_headers: cors::AllowHeaders::NotRequired,
max_request_body_size: self.max_request_body_size,
})
}
Expand All @@ -108,11 +106,11 @@ impl<M: Metadata, S: Middleware<M>> server::Service for ServerHandler<M, S> {
pub enum Handler<M: Metadata, S: Middleware<M>> {
Rpc(RpcHandler<M, S>),
Error(Option<Response>),
Middleware(Box<Future<Item=server::Response, Error=hyper::Error> + Send>),
Middleware(Box<Future<Item = hyper::Response<Body>, Error = hyper::Error> + Send>),
}

impl<M: Metadata, S: Middleware<M>> Future for Handler<M, S> {
type Item = server::Response;
type Item = hyper::Response<Body>;
type Error = hyper::Error;

fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
Expand Down Expand Up @@ -146,7 +144,7 @@ impl<M, F> RpcPollState<M, F> where
}

type FutureResponse<F> = future::Map<
future::Either<future::FutureResult<Option<core::Response>, ()>, core::FutureRpcResult<F>>,
future::Either<future::FutureResult<Option<core::Response>, ()>, FutureRpcResult<F>>,
fn(Option<core::Response>) -> Response,
>;

Expand All @@ -155,8 +153,9 @@ enum RpcHandlerState<M, F> where
F: Future<Item = Option<core::Response>, Error = ()>,
{
ReadingHeaders {
request: server::Request,
request: hyper::Request<Body>,
cors_domains: CorsDomains,
cors_headers: cors::AccessControlAllowHeaders,
continue_on_invalid_cors: bool,
},
ReadingBody {
Expand Down Expand Up @@ -202,28 +201,25 @@ pub struct RpcHandler<M: Metadata, S: Middleware<M>> {
jsonrpc_handler: Rpc<M, S>,
state: RpcHandlerState<M, S::Future>,
is_options: bool,
cors_allow_origin: cors::AllowOrigin<header::AccessControlAllowOrigin>,
cors_allow_origin: cors::AllowCors<header::HeaderValue>,
cors_allow_headers: cors::AllowCors<Vec<header::HeaderValue>>,
cors_max_age: Option<u32>,
cors_allow_headers: cors::AllowHeaders<header::AccessControlAllowHeaders>,
allowed_headers: cors::AccessControlAllowHeadersUnicase,
rest_api: RestApi,
health_api: Option<(String, String)>,
max_request_body_size: usize,
}

impl<M: Metadata, S: Middleware<M>> Future for RpcHandler<M, S> {
type Item = server::Response;
type Item = hyper::Response<Body>;
type Error = hyper::Error;

fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let allowed_headers = self.allowed_headers.clone();

let new_state = match mem::replace(&mut self.state, RpcHandlerState::Done) {
RpcHandlerState::ReadingHeaders { request, cors_domains, continue_on_invalid_cors, } => {
RpcHandlerState::ReadingHeaders { request, cors_domains, cors_headers, continue_on_invalid_cors, } => {
// Read cors header
self.cors_allow_origin = utils::cors_allow_origin(&request, &cors_domains);
self.cors_allow_headers = utils::cors_allow_headers(&request, &allowed_headers);
self.is_options = *request.method() == Method::Options;
self.cors_allow_headers = utils::cors_allow_headers(&request, &cors_headers);
self.is_options = *request.method() == Method::OPTIONS;
// Read other headers
RpcPollState::Ready(self.read_headers(request, continue_on_invalid_cors))
},
Expand Down Expand Up @@ -279,16 +275,16 @@ impl<M: Metadata, S: Middleware<M>> Future for RpcHandler<M, S> {
let (new_state, is_ready) = new_state.decompose();
match new_state {
RpcHandlerState::Writing(res) => {
let mut response: server::Response = res.into();
let cors_allow_origin = mem::replace(&mut self.cors_allow_origin, cors::AllowOrigin::Invalid);
let cors_allow_headers = mem::replace(&mut self.cors_allow_headers, cors::AllowHeaders::Invalid);
let mut response: hyper::Response<Body> = res.into();
let cors_allow_origin = mem::replace(&mut self.cors_allow_origin, cors::AllowCors::Invalid);
let cors_allow_headers = mem::replace(&mut self.cors_allow_headers, cors::AllowCors::Invalid);

Self::set_response_headers(
response.headers_mut(),
self.is_options,
cors_allow_origin.into(),
self.cors_max_age,
cors_allow_headers,
cors_allow_origin.into(),
cors_allow_headers.into(),
);
Ok(Async::Ready(response))
},
Expand All @@ -305,7 +301,7 @@ impl<M: Metadata, S: Middleware<M>> Future for RpcHandler<M, S> {
}

// Intermediate and internal error type to better distinguish
// error cases occuring during request body processing.
// error cases occurring during request body processing.
enum BodyError {
Hyper(hyper::Error),
Utf8(str::Utf8Error),
Expand All @@ -321,13 +317,13 @@ impl From<hyper::Error> for BodyError {
impl<M: Metadata, S: Middleware<M>> RpcHandler<M, S> {
fn read_headers(
&self,
request: server::Request,
request: hyper::Request<Body>,
continue_on_invalid_cors: bool,
) -> RpcHandlerState<M, S::Future> {
if self.cors_allow_origin == cors::AllowOrigin::Invalid && !continue_on_invalid_cors {
if self.cors_allow_origin == cors::AllowCors::Invalid && !continue_on_invalid_cors {
return RpcHandlerState::Writing(Response::invalid_allow_origin());
}
if self.cors_allow_headers == cors::AllowHeaders::Invalid && !continue_on_invalid_cors {
if self.cors_allow_headers == cors::AllowCors::Invalid && !continue_on_invalid_cors {
return RpcHandlerState::Writing(Response::invalid_allow_headers());
}

Expand All @@ -338,31 +334,31 @@ impl<M: Metadata, S: Middleware<M>> RpcHandler<M, S> {
match *request.method() {
// Validate the ContentType header
// to prevent Cross-Origin XHRs with text/plain
Method::Post if Self::is_json(request.headers().get::<header::ContentType>()) => {
Method::POST if Self::is_json(request.headers().get("content-type")) => {
let uri = if self.rest_api != RestApi::Disabled { Some(request.uri().clone()) } else { None };
RpcHandlerState::ReadingBody {
metadata,
request: Default::default(),
uri,
body: request.body(),
body: request.into_body(),
}
},
Method::Post if self.rest_api == RestApi::Unsecure && request.uri().path().split('/').count() > 2 => {
Method::POST if self.rest_api == RestApi::Unsecure && request.uri().path().split('/').count() > 2 => {
RpcHandlerState::ProcessRest {
metadata,
uri: request.uri().clone(),
}
},
// Just return error for unsupported content type
Method::Post => {
Method::POST => {
RpcHandlerState::Writing(Response::unsupported_content_type())
},
// Don't validate content type on options
Method::Options => {
Method::OPTIONS => {
RpcHandlerState::Writing(Response::empty())
},
// Respond to health API request if there is one configured.
Method::Get if self.health_api.as_ref().map(|x| &*x.0) == Some(request.uri().path()) => {
Method::GET if self.health_api.as_ref().map(|x| &*x.0) == Some(request.uri().path()) => {
RpcHandlerState::ProcessHealth {
metadata,
method: self.health_api.as_ref()
Expand Down Expand Up @@ -497,51 +493,58 @@ impl<M: Metadata, S: Middleware<M>> RpcHandler<M, S> {
}

fn set_response_headers(
headers: &mut Headers,
headers: &mut HeaderMap,
is_options: bool,
cors_allow_origin: Option<header::AccessControlAllowOrigin>,
cors_max_age: Option<u32>,
cors_allow_headers: AllowHeaders<header::AccessControlAllowHeaders>,
cors_allow_origin: Option<HeaderValue>,
cors_allow_headers: Option<Vec<HeaderValue>>,
) {
let as_header = |m: Method| m.as_str().parse().expect("`Method` will always parse; qed");
let concat = |headers: &[HeaderValue]| {
let separator = b", ";
let val = headers
.iter()
.flat_map(|h| h.as_bytes().iter().chain(separator.iter()))
.cloned()
.collect::<Vec<_>>();
let max_len = if val.is_empty() { 0 } else { val.len() - 2 };
HeaderValue::from_bytes(&val[..max_len]).expect("Concatenation of valid headers with `, ` is still valid; qed")
};

let allowed = concat(&[as_header(Method::OPTIONS), as_header(Method::POST)]);

if is_options {
headers.set(header::Allow(vec![
Method::Options,
Method::Post,
]));
headers.set(header::Accept(vec![
header::qitem(mime::APPLICATION_JSON)
]));
headers.append(header::ALLOW, allowed.clone());
headers.append(header::ACCEPT, HeaderValue::from_static("application/json"));
}

if let Some(cors_domain) = cors_allow_origin {
headers.set(header::AccessControlAllowMethods(vec![
Method::Options,
Method::Post
]));
if let Some(cors_allow_origin) = cors_allow_origin {
headers.append(header::VARY, HeaderValue::from_static("origin"));
headers.append(header::ACCESS_CONTROL_ALLOW_METHODS, allowed);
headers.append(header::ACCESS_CONTROL_ALLOW_ORIGIN, cors_allow_origin);

if let AllowHeaders::Ok(cors_allow_headers) = cors_allow_headers {
if !cors_allow_headers.is_empty() {
headers.set(cors_allow_headers);
}
if let Some(cma) = cors_max_age {
headers.append(
header::ACCESS_CONTROL_MAX_AGE,
HeaderValue::from_str(&cma.to_string()).expect("`u32` will always parse; qed")
);
}

if let Some(cors_max_age) = cors_max_age {
headers.set(header::AccessControlMaxAge(cors_max_age));
if let Some(cors_allow_headers) = cors_allow_headers {
if !cors_allow_headers.is_empty() {
headers.append(header::ACCESS_CONTROL_ALLOW_HEADERS, concat(&cors_allow_headers));
}
}
headers.set(cors_domain);
headers.set(header::Vary::Items(vec![
Ascii::new("origin".to_owned())
]));
}
}

fn is_json(content_type: Option<&header::ContentType>) -> bool {
const APPLICATION_JSON_UTF_8: &str = "application/json; charset=utf-8";

match content_type {
Some(&header::ContentType(ref mime))
if *mime == mime::APPLICATION_JSON || *mime == APPLICATION_JSON_UTF_8 => true,
_ => false
/// Returns true if the `content_type` header indicates a valid JSON
/// message.
fn is_json(content_type: Option<&header::HeaderValue>) -> bool {
match content_type.and_then(|val| val.to_str().ok()) {
Some("application/json") => true,
Some("application/json; charset=utf-8") => true,
_ => false,
}
}
}
Loading

0 comments on commit 64188da

Please sign in to comment.