Skip to content

Commit

Permalink
feat(lib): implement compatibility with http crate
Browse files Browse the repository at this point in the history
  • Loading branch information
srijs authored and seanmonstar committed Sep 22, 2017
1 parent 92595e8 commit 0c7d375
Show file tree
Hide file tree
Showing 17 changed files with 535 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ matrix:
env: FEATURES="--features nightly"
- rust: beta
- rust: stable
- rust: stable
env: FEATURES="--features compat"
- rust: 1.15.0

cache:
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ base64 = "0.6"
bytes = "0.4.4"
futures = "0.1.14"
futures-cpupool = "0.1"
http = { version = "0.1", optional = true }
httparse = "1.0"
language-tags = "0.2"
log = "0.3"
Expand All @@ -45,3 +46,4 @@ spmc = "0.2"
default = []
nightly = []
raw_status = []
compat = [ "http" ]
6 changes: 6 additions & 0 deletions src/client/compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Wrappers to build compatibility with the `http` crate.
pub use super::compat_impl::{
CompatClient,
CompatFutureResponse
};
53 changes: 53 additions & 0 deletions src/client/compat_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use futures::{Future, Poll, Stream};
use http_types;
use tokio_service::Service;

use client::{Connect, Client, FutureResponse};
use error::Error;
use http::Body;

/// A Client to make outgoing HTTP requests.
#[derive(Debug)]
pub struct CompatClient<C, B = Body> {
inner: Client<C, B>
}

pub fn client<C, B>(client: Client<C, B>) -> CompatClient<C, B> {
CompatClient { inner: client }
}

impl<C, B> Service for CompatClient<C, B>
where C: Connect,
B: Stream<Error=Error> + 'static,
B::Item: AsRef<[u8]>,
{
type Request = http_types::Request<B>;
type Response = http_types::Response<Body>;
type Error = Error;
type Future = CompatFutureResponse;

fn call(&self, req: Self::Request) -> Self::Future {
future(self.inner.call(req.into()))
}
}

/// A `Future` that will resolve to an `http::Response`.
#[must_use = "futures do nothing unless polled"]
#[derive(Debug)]
pub struct CompatFutureResponse {
inner: FutureResponse
}

pub fn future(fut: FutureResponse) -> CompatFutureResponse {
CompatFutureResponse { inner: fut }
}

impl Future for CompatFutureResponse {
type Item = http_types::Response<Body>;
type Error = Error;

fn poll(&mut self) -> Poll<Self::Item, Error> {
self.inner.poll()
.map(|a| a.map(|r| r.into()))
}
}
19 changes: 19 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use std::time::Duration;

use futures::{future, Poll, Async, Future, Stream};
use futures::unsync::oneshot;
#[cfg(feature = "compat")]
use http_types;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio::reactor::Handle;
use tokio_proto::BindClient;
Expand All @@ -33,6 +35,10 @@ pub use self::connect::{HttpConnector, Connect};
mod connect;
mod dns;
mod pool;
#[cfg(feature = "compat")]
mod compat_impl;
#[cfg(feature = "compat")]
pub mod compat;

/// A Client to make outgoing HTTP requests.
// If the Connector is clone, then the Client can be clone easily.
Expand Down Expand Up @@ -108,6 +114,19 @@ where C: Connect,
pub fn request(&self, req: Request<B>) -> FutureResponse {
self.call(req)
}

/// Send an `http::Request` using this Client.
#[inline]
#[cfg(feature = "compat")]
pub fn request_compat(&self, req: http_types::Request<B>) -> compat::CompatFutureResponse {
self::compat_impl::future(self.call(req.into()))
}

/// Convert into a client accepting `http::Request`.
#[cfg(feature = "compat")]
pub fn into_compat(self) -> compat::CompatClient<C, B> {
self::compat_impl::client(self)
}
}

/// A `Future` that will resolve to an HTTP Response.
Expand Down
5 changes: 5 additions & 0 deletions src/common/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ impl ByteStr {
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.0.as_ref()) }
}

#[cfg(feature = "compat")]
pub fn into_bytes(self) -> Bytes {
self.0
}
}

impl Deref for ByteStr {
Expand Down
76 changes: 76 additions & 0 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,14 @@
//! }
//! ```
use std::borrow::{Cow, ToOwned};
#[cfg(feature = "compat")]
use std::convert::From;
use std::iter::{FromIterator, IntoIterator};
use std::{mem, fmt};

#[cfg(feature = "compat")]
use http_types;

use unicase::Ascii;

use self::internals::{Item, VecMap, Entry};
Expand Down Expand Up @@ -546,6 +551,54 @@ impl fmt::Debug for Headers {
}
}

#[cfg(feature = "compat")]
impl From<http_types::HeaderMap> for Headers {
fn from(mut header_map: http_types::HeaderMap) -> Headers {
let mut headers = Headers::new();
for (name, mut value_drain) in header_map.drain() {
if let Some(first_value) = value_drain.next() {
let mut raw: Raw = first_value.as_bytes().into();
for value in value_drain {
raw.push(value.as_bytes());
}
headers.append_raw(name.as_str().to_string(), raw);
}
}
headers
}
}

#[cfg(feature = "compat")]
impl From<Headers> for http_types::HeaderMap {
fn from(headers: Headers) -> http_types::HeaderMap {
let mut header_map = http_types::HeaderMap::new();
for header in headers.iter() {
let entry = header_map.entry(header.name())
.expect("attempted to convert invalid header name");
let mut value_iter = header.raw().iter().map(|line| {
http_types::header::HeaderValue::from_bytes(line)
.expect("attempted to convert invalid header value")
});
match entry {
http_types::header::Entry::Occupied(mut occupied) => {
for value in value_iter {
occupied.append(value);
}
},
http_types::header::Entry::Vacant(vacant) => {
if let Some(first_value) = value_iter.next() {
let mut occupied = vacant.insert_entry(first_value);
for value in value_iter {
occupied.append(value);
}
}
}
}
}
header_map
}
}

/// An `Iterator` over the fields in a `Headers` map.
#[allow(missing_debug_implementations)]
pub struct HeadersItems<'a> {
Expand Down Expand Up @@ -940,6 +993,29 @@ mod tests {
assert_ne!(headers1, headers2);
}

#[test]
#[cfg(feature = "compat")]
fn test_compat() {
use http_types;

let mut orig_hyper_headers = Headers::new();
orig_hyper_headers.set(ContentLength(11));
orig_hyper_headers.set(Host::new("foo.bar", None));
orig_hyper_headers.append_raw("x-foo", b"bar".to_vec());
orig_hyper_headers.append_raw("x-foo", b"quux".to_vec());

let mut orig_http_headers = http_types::HeaderMap::new();
orig_http_headers.insert(http_types::header::CONTENT_LENGTH, "11".parse().unwrap());
orig_http_headers.insert(http_types::header::HOST, "foo.bar".parse().unwrap());
orig_http_headers.append("x-foo", "bar".parse().unwrap());
orig_http_headers.append("x-foo", "quux".parse().unwrap());

let conv_hyper_headers: Headers = orig_http_headers.clone().into();
let conv_http_headers: http_types::HeaderMap = orig_hyper_headers.clone().into();
assert_eq!(orig_hyper_headers, conv_hyper_headers);
assert_eq!(orig_http_headers, conv_http_headers);
}

#[cfg(feature = "nightly")]
#[bench]
fn bench_headers_new(b: &mut Bencher) {
Expand Down
37 changes: 36 additions & 1 deletion src/http/request.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::fmt;
#[cfg(feature = "compat")]
use std::mem::replace;
use std::net::SocketAddr;

#[cfg(feature = "compat")]
use http_types;

use header::Headers;
use http::{Body, MessageHead, RequestHead, RequestLine};
use method::Method;
use uri::{self, Uri};
use version::HttpVersion;
use std::net::SocketAddr;

/// An HTTP Request
pub struct Request<B = Body> {
Expand Down Expand Up @@ -132,6 +137,36 @@ impl<B> fmt::Debug for Request<B> {
}
}

#[cfg(feature = "compat")]
impl From<Request> for http_types::Request<Body> {
fn from(from_req: Request) -> http_types::Request<Body> {
let (m, u, v, h, b) = from_req.deconstruct();

let to_req = http_types::Request::new(());
let (mut to_parts, _) = to_req.into_parts();

to_parts.method = m.into();
to_parts.uri = u.into();
to_parts.version = v.into();
to_parts.headers = h.into();

http_types::Request::from_parts(to_parts, b)
}
}

#[cfg(feature = "compat")]
impl<B> From<http_types::Request<B>> for Request<B> {
fn from(from_req: http_types::Request<B>) -> Request<B> {
let (from_parts, body) = from_req.into_parts();

let mut to_req = Request::new(from_parts.method.into(), from_parts.uri.into());
to_req.set_version(from_parts.version.into());
replace(to_req.headers_mut(), from_parts.headers.into());
to_req.set_body(body);
to_req
}
}

/// Constructs a request using a received ResponseHead and optional body
pub fn from_wire<B>(addr: Option<SocketAddr>, incoming: RequestHead, body: B) -> Request<B> {
let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming;
Expand Down
29 changes: 29 additions & 0 deletions src/http/response.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::fmt;
#[cfg(feature = "compat")]
use std::mem::replace;

#[cfg(feature = "compat")]
use http_types;

use header::{Header, Headers};
use http::{MessageHead, ResponseHead, Body};
Expand Down Expand Up @@ -142,6 +147,30 @@ impl fmt::Debug for Response {
}
}

#[cfg(feature = "compat")]
impl<B> From<http_types::Response<B>> for Response<B> {
fn from(from_res: http_types::Response<B>) -> Response<B> {
let (from_parts, body) = from_res.into_parts();
let mut to_res = Response::new();
to_res.version = from_parts.version.into();
to_res.set_status(from_parts.status.into());
replace(to_res.headers_mut(), from_parts.headers.into());
to_res.with_body(body)
}
}

#[cfg(feature = "compat")]
impl From<Response> for http_types::Response<Body> {
fn from(mut from_res: Response) -> http_types::Response<Body> {
let (mut to_parts, ()) = http_types::Response::new(()).into_parts();
to_parts.version = from_res.version().into();
to_parts.status = from_res.status().into();
let from_headers = replace(from_res.headers_mut(), Headers::new());
to_parts.headers = from_headers.into();
http_types::Response::from_parts(to_parts, from_res.body())
}
}

/// Constructs a response using a received ResponseHead and optional body
#[inline]
#[cfg(not(feature = "raw_status"))]
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ extern crate base64;
extern crate bytes;
#[macro_use] extern crate futures;
extern crate futures_cpupool;
#[cfg(feature = "compat")]
extern crate http as http_types;
extern crate httparse;
extern crate language_tags;
#[macro_use] extern crate log;
Expand Down
Loading

0 comments on commit 0c7d375

Please sign in to comment.