use std::cell::RefCell;
use futures::Future;
use futures::future::FutureResult;
use hina;
use hina::result::ResultExt;
use hina::task::OneOfFuture;
use hyper::Body;
use hyper::client::Client;
use hyper::client::HttpConnector;
use tokio_core::reactor::CoreId;
use tokio_core::reactor::Remote;
use simplist::HttpContent;
use simplist::HttpError;
use simplist::HttpMethod;
use simplist::HttpRequest;
use simplist::HttpResponse;
use simplist::HttpSyncResponse;
use simplist::IntoUrl;
use simplist::Url;
thread_local! {
static THREAD_LOCAL_DATA: RefCell<Option<ThreadData>> = RefCell::new(None);
}
struct ThreadData {
core: CoreId,
client: Client<HttpConnector, Body>,
}
#[derive(Clone, Debug)]
pub struct HttpClient {
remote: Remote,
}
impl HttpClient {
pub fn new(remote: Remote) -> HttpClient {
HttpClient { remote }
}
pub fn request(&self, request: HttpRequest) -> Box<Future<Item = HttpResponse, Error = HttpError>> {
let request = request.into();
let (send, receive) = hina::task::sync::source();
self.remote.spawn(|handle| {
THREAD_LOCAL_DATA.with(|data| {
let core = handle.id();
let mut storage = data.borrow_mut();
if storage.is_none() || storage.as_ref().unwrap().core != core {
*storage = Some(ThreadData {
core: core,
client: Client::new(handle),
})
}
let data = storage
.as_ref()
.expect(d!["invariant broken: thread local data was none."]);
data.client.request(request)
.map (HttpResponse::new)
.map_err(From::from)
.then (|x| { send.done(x).consume(); Ok(()) })
})
});
Box::new(receive)
}
fn run_bytes<TBody>(&self, url: Url, method: HttpMethod, content: Option<TBody>) -> Box<Future<Item = Vec<u8>, Error = HttpError>>
where TBody: Into<HttpContent> {
let request = HttpRequest::new()
.with_url(url)
.with_method(method)
.with_body(content);
let future = self.request(request).and_then(|response| {
match response.status_code() {
0...399 => OneOfFuture::A(response.read_as_bytes()),
_ => OneOfFuture::B(hina::task::failed(HttpError::Status(response.status()))),
}
});
Box::new(future)
}
fn run_string<TBody>(&self, url: Url, method: HttpMethod, content: Option<TBody>) -> Box<Future<Item = String, Error = HttpError>>
where TBody: Into<HttpContent> {
Box::new(
self.run_bytes(url, method, content)
.and_then (|x| String::from_utf8(x).map_err(From::from)))
}
}
#[derive(Clone, Debug)]
pub struct HttpSyncClient {
underlying: HttpClient,
}
impl HttpSyncClient {
pub fn new(remote: Remote) -> HttpSyncClient {
HttpSyncClient {
underlying: HttpClient::new(remote),
}
}
pub fn request(&self, request: HttpRequest) -> Result<HttpSyncResponse, HttpError> {
self.underlying.request(request)
.wait()
.map (HttpSyncResponse::new)
}
}
macro_rules! http_client_methods {
() => {};
(no-content, $method_variant: path, $method_name: ident, $string_method_name: ident) => {
impl HttpClient {
pub fn $method_name<TUrl>(&self, url: TUrl) -> OneOfFuture<Box<Future<Item = Vec<u8>, Error = HttpError>>, FutureResult<Vec<u8>, HttpError>, Vec<u8>, HttpError>
where TUrl: IntoUrl {
match url.as_url() {
Ok(url) => OneOfFuture::A(self.run_bytes(url, $method_variant, Option::None::<&'static [u8]>)),
Err(e) => OneOfFuture::B(hina::task::failed(e)),
}
}
pub fn $string_method_name<TUrl>(&self, url: TUrl) -> OneOfFuture<Box<Future<Item = String, Error = HttpError>>, FutureResult<String, HttpError>, String, HttpError>
where TUrl: IntoUrl {
match url.as_url() {
Ok(url) => OneOfFuture::A(self.run_string(url, $method_variant, Option::None::<&'static [u8]>)),
Err(e) => OneOfFuture::B(hina::task::failed(e)),
}
}
}
impl HttpSyncClient {
pub fn $method_name<TUrl>(&self, url: TUrl) -> Result<Vec<u8>, HttpError>
where TUrl: IntoUrl {
self.underlying.$method_name(url).wait()
}
pub fn $string_method_name<TUrl>(&self, url: TUrl) -> Result<String, HttpError>
where TUrl: IntoUrl {
self.underlying.$string_method_name(url).wait()
}
}
};
(x, $method_variant: path, $method_name: ident, $string_method_name: ident) => {
impl HttpClient {
pub fn $method_name<TUrl, TContent>(&self, url: TUrl, content: Option<TContent>) -> OneOfFuture<Box<Future<Item = Vec<u8>, Error = HttpError>>, FutureResult<Vec<u8>, HttpError>, Vec<u8>, HttpError>
where TUrl: IntoUrl,
TContent: Into<HttpContent>, {
match url.as_url() {
Ok(url) => OneOfFuture::A(self.run_bytes(url, $method_variant, content)),
Err(e) => OneOfFuture::B(hina::task::failed(e)),
}
}
pub fn $string_method_name<TUrl, TContent>(&self, url: TUrl, content: Option<TContent>) -> OneOfFuture<Box<Future<Item = String, Error = HttpError>>, FutureResult<String, HttpError>, String, HttpError>
where TUrl: IntoUrl,
TContent: Into<HttpContent>, {
match url.as_url() {
Ok(url) => OneOfFuture::A(self.run_string(url, $method_variant, content)),
Err(e) => OneOfFuture::B(hina::task::failed(e)),
}
}
}
impl HttpSyncClient {
pub fn $method_name<TUrl, TContent>(&self, url: TUrl, content: Option<TContent>) -> Result<Vec<u8>, HttpError>
where TUrl: IntoUrl,
TContent: Into<HttpContent>, {
self.underlying.$method_name(url, content).wait()
}
pub fn $string_method_name<TUrl, TContent>(&self, url: TUrl, content: Option<TContent>) -> Result<String, HttpError>
where TUrl: IntoUrl,
TContent: Into<HttpContent>, {
self.underlying.$string_method_name(url, content).wait()
}
}
};
([no-content, $method_variant: path, $method_name: ident, $string_method_name: ident], $($rest:tt)*) => {
http_client_methods!(no-content, $method_variant, $method_name, $string_method_name);
http_client_methods!($($rest)*);
};
([x, $method_variant: path, $method_name: ident, $string_method_name: ident], $($rest:tt)*) => {
http_client_methods!(x, $method_variant, $method_name, $string_method_name);
http_client_methods!($($rest)*);
};
}
http_client_methods!(
[x, HttpMethod::Options, options, options_string],
[no-content, HttpMethod::Get, get, get_string ],
[x, HttpMethod::Post, post, post_string ],
[x, HttpMethod::Put, put, put_string ],
[no-content, HttpMethod::Delete, delete, delete_string ],
[no-content, HttpMethod::Head, head, head_string ],
[no-content, HttpMethod::Trace, trace, trace_string ],
[no-content, HttpMethod::Connect, connect, connect_string],
[x, HttpMethod::Patch, patch, patch_string ],
);