Skip to content

Commit dd9f09d

Browse files
committed
Futures implementation
1 parent ebb2eb6 commit dd9f09d

4 files changed

Lines changed: 70 additions & 43 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "http_client"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Vladimir Burdukov <chippcheg@gmail.com>"]
55
edition = "2018"
66

src/lib.rs

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
use curl::easy::Easy;
1+
use std::borrow::Borrow;
2+
use std::thread;
3+
4+
use futures::channel::oneshot;
5+
26
use serde::de::DeserializeOwned;
37
use serde_json;
4-
use std::borrow::Borrow;
8+
9+
use curl::easy::Easy;
510
use url::{ParseError, Url};
611

712
#[derive(Debug)]
@@ -28,28 +33,26 @@ pub struct HttpClient {
2833
}
2934

3035
impl HttpClient {
31-
// Public API
32-
3336
pub fn new(base_url: &str) -> Result<HttpClient, ParseError> {
3437
let base_url = Url::parse(base_url)?;
3538
Ok(HttpClient { base_url })
3639
}
3740

38-
pub fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
39-
self.do_get(self.prepare_url_with_path(path))
41+
pub async fn get<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Result<T, Error> {
42+
self.do_get(self.prepare_url_with_path(path)).await
4043
}
4144

42-
pub fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
45+
pub async fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
4346
where
44-
T: DeserializeOwned,
47+
T: DeserializeOwned + Send + 'static,
4548
I: IntoIterator,
4649
I::Item: Borrow<(K, V)>,
4750
K: AsRef<str>,
4851
V: AsRef<str>,
4952
{
5053
let mut url = self.prepare_url_with_path(path);
5154
url.query_pairs_mut().extend_pairs(iter);
52-
self.do_get(url)
55+
self.do_get(url).await
5356
}
5457

5558
// Private API
@@ -60,33 +63,39 @@ impl HttpClient {
6063
url
6164
}
6265

63-
fn do_get<T: DeserializeOwned>(&self, url: Url) -> Result<T, Error> {
64-
let mut response = Vec::new();
65-
let mut easy = Easy::new();
66-
easy.url(url.as_str()).unwrap();
67-
68-
{
69-
let mut transfer = easy.transfer();
70-
transfer
71-
.write_function(|data| {
72-
response.extend_from_slice(data);
73-
Ok(data.len())
74-
})
75-
.unwrap();
76-
transfer.perform().unwrap();
77-
}
78-
79-
let code = easy.response_code().unwrap();
80-
81-
if code >= 200 && code < 300 {
82-
let response: T = serde_json::from_slice(&response)
83-
.map_err(|err| Error::from(err))
84-
.unwrap();
85-
86-
Ok(response)
87-
} else {
88-
eprintln!("{}", String::from_utf8_lossy(&response));
89-
Err(Error::HttpError(code))
90-
}
66+
async fn do_get<T: DeserializeOwned + Send + 'static>(&self, url: Url) -> Result<T, Error> {
67+
let (tx, rx) = oneshot::channel::<Result<T, Error>>();
68+
69+
thread::spawn(move || {
70+
let mut response = Vec::new();
71+
let mut easy = Easy::new();
72+
easy.url(url.as_str()).unwrap();
73+
74+
{
75+
let mut transfer = easy.transfer();
76+
transfer
77+
.write_function(|data| {
78+
response.extend_from_slice(data);
79+
Ok(data.len())
80+
})
81+
.unwrap();
82+
transfer.perform().unwrap();
83+
}
84+
85+
let code = easy.response_code().unwrap();
86+
87+
if code >= 200 && code < 300 {
88+
let response: T = serde_json::from_slice(&response)
89+
.map_err(|err| Error::from(err))
90+
.unwrap();
91+
92+
let _ = tx.send(Ok(response));
93+
} else {
94+
eprintln!("{}", String::from_utf8_lossy(&response));
95+
let _ = tx.send(Err(Error::HttpError(code)));
96+
}
97+
});
98+
99+
rx.await.unwrap()
91100
}
92101
}

tests/get.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use futures::executor::block_on;
12
use http_client::HttpClient;
23
use serde_derive::Deserialize;
34

@@ -11,11 +12,28 @@ fn test_get() {
1112
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
1213

1314
assert_eq!(
14-
http_client.get::<Response>("/get").unwrap().url,
15+
block_on(http_client.get::<Response>("/get")).unwrap().url,
1516
"https://httpbin.org/get"
1617
);
1718
}
1819

20+
#[test]
21+
fn test_get_join() {
22+
#[derive(Deserialize)]
23+
struct Response {
24+
url: String,
25+
}
26+
27+
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
28+
let results = block_on(futures::future::join(
29+
http_client.get::<Response>("/delay/2"),
30+
http_client.get::<Response>("/delay/1"),
31+
));
32+
33+
assert_eq!(results.0.unwrap().url, "https://httpbin.org/delay/2");
34+
assert_eq!(results.1.unwrap().url, "https://httpbin.org/delay/1");
35+
}
36+
1937
#[test]
2038
fn test_get_with_params() {
2139
use std::collections::HashMap;
@@ -29,9 +47,8 @@ fn test_get_with_params() {
2947
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
3048

3149
let params = vec![("key1", "value1"), ("key2", "value2")];
32-
let response = http_client
33-
.get_with_params::<Response, _, _, _>("/get", params)
34-
.unwrap();
50+
let response =
51+
block_on(http_client.get_with_params::<Response, _, _, _>("/get", params)).unwrap();
3552

3653
assert_eq!(
3754
response.url,

tests/http_error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use futures::executor::block_on;
12
use http_client::{Error, HttpClient};
23
use serde_derive::Deserialize;
34

@@ -8,7 +9,7 @@ fn test_404() {
89

910
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
1011

11-
match http_client.get::<Response>("/status/404").unwrap_err() {
12+
match block_on(http_client.get::<Response>("/status/404")).unwrap_err() {
1213
Error::HttpError(404) => (),
1314
error => panic!(
1415
r#"assertion failed:

0 commit comments

Comments
 (0)