make_hyper_great_again 0.11.1

Wrap async hyper back to the good ol' days.
Documentation
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)
  }
}