extern crate hyper;
extern crate tokio_core;
extern crate futures;
use hyper::Client as HyperClient;
use hyper::client::{Connect, HttpConnector};
use hyper::{Result, Response as HyperResponse, Request, Method, Headers, HttpVersion, StatusCode};
use tokio_core::reactor::{Core, Handle};
use futures::Stream;
use std::io::Read;
use std::cell::RefCell;
#[cfg(test)]
mod test;
macro_rules! method {
($name: ident, $method: ident) => {
pub fn $name<S: AsRef<str>>(&self, url: S) -> RequestBuilder<C> {
RequestBuilder::new(self, Method::$method, url)
}
};
}
thread_local! {
static CORE: RefCell<Core> = RefCell::new(Core::new().unwrap());
static HANDLE: Handle = CORE.with(|c| c.borrow().handle());
}
pub struct Client<C> {
client: HyperClient<C>
}
impl Client<HttpConnector> {
pub fn new() -> Result<Client<HttpConnector>> {
let handle = HANDLE.with(|h| h.clone());
let client = HyperClient::new(&handle);
Ok(Client {
client: client
})
}
}
impl<C> Client<C>
where C: Connect
{
pub fn create_connector<F: FnOnce(&Core) -> C>(f: F) -> Result<Client<C>> {
let connector = CORE.with(|c| f(&*c.borrow()));
Ok(Self::with_core_and_connector(connector))
}
pub fn with_core_and_connector(connector: C) -> Client<C> {
let handle = HANDLE.with(|h| h.clone());
let client = HyperClient::configure()
.connector(connector)
.build(&handle);
Client {
client: client
}
}
method!(connect, Connect);
method!(delete, Delete);
method!(get, Get);
method!(head, Head);
method!(options, Options);
method!(patch, Patch);
method!(post, Post);
method!(put, Put);
method!(trace, Trace);
pub fn as_hyper(&self) -> &HyperClient<C> {
&self.client
}
}
pub struct RequestBuilder<'a, C: 'a> {
client: &'a Client<C>,
url: String,
method: Method,
headers: Headers,
body: Option<hyper::Body>
}
impl<'a, C> RequestBuilder<'a, C>
where C: Connect
{
pub fn new<S: AsRef<str>>(client: &'a Client<C>, method: Method, url: S) -> RequestBuilder<'a, C> {
RequestBuilder {
client: client,
url: url.as_ref().to_string(),
method: method,
headers: Headers::default(),
body: None
}
}
pub fn header<H: hyper::header::Header>(mut self, header: H) -> RequestBuilder<'a, C> {
self.headers.set(header);
self
}
pub fn body<B: Into<hyper::Body>>(mut self, body: B) -> RequestBuilder<'a, C> {
self.body = Some(body.into());
self
}
pub fn send(self) -> Result<Response> {
let mut request = Request::new(self.method, self.url.parse()?);
request.headers_mut().extend(self.headers.iter());
if let Some(body) = self.body {
request.set_body(body);
}
let request_future = self.client.client.request(request);
let response = CORE.with(move |c| c.borrow_mut().run(request_future))?;
Response::new(response)
}
}
pub struct Response {
version: HttpVersion,
headers: Headers,
status: StatusCode,
content: Vec<u8>,
pointer: usize
}
impl Response {
pub fn new(response: HyperResponse) -> Result<Self> {
let version = response.version();
let headers = response.headers().clone();
let status = response.status();
let collect = response.body().collect();
let chunks = CORE.with(|c| c.borrow_mut().run(collect))?;
let bytes = chunks.into_iter().flat_map(|x| x).collect();
Ok(Response {
version: version,
headers: headers,
status: status,
content: bytes,
pointer: 0
})
}
pub fn version(&self) -> HttpVersion {
self.version
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn status(&self) -> StatusCode {
self.status
}
}
impl Read for Response {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pointer >= self.content.len() {
return Ok(0);
}
let read = (&self.content[self.pointer..]).read(buf)?;
self.pointer += read;
Ok(read)
}
}