Skip to content

Commit b2388fa

Browse files
committed
Clean up TODO and implement an echo test case
1 parent 054bcf2 commit b2388fa

File tree

2 files changed

+92
-29
lines changed

2 files changed

+92
-29
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ features = [
6868

6969
[dev-dependencies]
7070
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
71-
tide = { version = "0.9.0" }
7271
portpicker = "0.1.0"
72+
tide = { version = "0.9.0" }
73+
tokio = { version = "0.2.21", features = ["macros"] }

src/hyper.rs

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
//! http-client implementation for reqwest
2+
23
use super::{Error, HttpClient, Request, Response};
3-
use http_types::StatusCode;
4+
use http_types::headers::{HeaderName, HeaderValue};
5+
use http_types::{Method, StatusCode, Version};
46
use hyper::body::HttpBody;
57
use hyper_tls::HttpsConnector;
68
use std::convert::{TryFrom, TryInto};
79
use std::str::FromStr;
810

911
/// Hyper-based HTTP Client.
1012
#[derive(Debug)]
11-
struct HyperClient {}
13+
pub struct HyperClient {}
14+
15+
impl HyperClient {
16+
/// Create a new client.
17+
///
18+
/// There is no specific benefit to reusing instances of this client.
19+
pub fn new() -> Self {
20+
HyperClient {}
21+
}
22+
}
1223

1324
impl HttpClient for HyperClient {
1425
fn send(&self, req: Request) -> futures::future::BoxFuture<'static, Result<Response, Error>> {
@@ -41,23 +52,32 @@ struct HyperHttpRequest {
4152
}
4253

4354
impl HyperHttpRequest {
44-
async fn try_from(mut value: http_types::Request) -> Result<Self, Error> {
45-
// Note: Much of this code was taken from the `http-types` compat implementation. Trying to
46-
// figure out the feature flags to conditionally compile with compat support was rather
47-
// difficult, so copying code was deemed a reasonable intermediate solution.
48-
// Also, because converting the `http_types` body to bytes is async, we can't implement `TryFrom`
49-
50-
// TODO: Do this without a `String` allocation
51-
let method = hyper::Method::from_str(&value.method().to_string()).unwrap();
55+
async fn try_from(mut value: Request) -> Result<Self, Error> {
56+
let method = match value.method() {
57+
Method::Get => hyper::Method::GET,
58+
Method::Head => hyper::Method::HEAD,
59+
Method::Post => hyper::Method::POST,
60+
Method::Put => hyper::Method::PUT,
61+
Method::Patch => hyper::Method::PATCH,
62+
Method::Options => hyper::Method::OPTIONS,
63+
Method::Trace => hyper::Method::TRACE,
64+
Method::Connect => hyper::Method::CONNECT,
65+
_ => {
66+
return Err(Error::from_str(
67+
StatusCode::BadRequest,
68+
"unrecognized HTTP method",
69+
))
70+
}
71+
};
5272

5373
let version = value
5474
.version()
5575
.map(|v| match v {
56-
http_types::Version::Http0_9 => Ok(hyper::Version::HTTP_09),
57-
http_types::Version::Http1_0 => Ok(hyper::Version::HTTP_10),
58-
http_types::Version::Http1_1 => Ok(hyper::Version::HTTP_11),
59-
http_types::Version::Http2_0 => Ok(hyper::Version::HTTP_2),
60-
http_types::Version::Http3_0 => Ok(hyper::Version::HTTP_3),
76+
Version::Http0_9 => Ok(hyper::Version::HTTP_09),
77+
Version::Http1_0 => Ok(hyper::Version::HTTP_10),
78+
Version::Http1_1 => Ok(hyper::Version::HTTP_11),
79+
Version::Http2_0 => Ok(hyper::Version::HTTP_2),
80+
Version::Http3_0 => Ok(hyper::Version::HTTP_3),
6181
_ => Err(Error::from_str(
6282
StatusCode::BadRequest,
6383
"unrecognized HTTP version",
@@ -109,14 +129,11 @@ impl HyperHttpRequest {
109129
}
110130

111131
struct HttpTypesResponse {
112-
inner: http_types::Response,
132+
inner: Response,
113133
}
114134

115135
impl HttpTypesResponse {
116136
async fn try_from(value: hyper::Response<hyper::Body>) -> Result<Self, Error> {
117-
// Note: Much of this code was taken from the `http-types` compat implementation. Trying to
118-
// figure out the feature flags to conditionally compile with compat support was rather
119-
// difficult, so copying code was deemed a reasonable intermediate solution.
120137
let (parts, mut body) = value.into_parts();
121138

122139
// UNWRAP: http and http-types implement the same status codes
@@ -128,9 +145,8 @@ impl HttpTypesResponse {
128145
hyper::Version::HTTP_11 => Ok(http_types::Version::Http1_1),
129146
hyper::Version::HTTP_2 => Ok(http_types::Version::Http2_0),
130147
hyper::Version::HTTP_3 => Ok(http_types::Version::Http3_0),
131-
// TODO: Is this realistically reachable, and should it be marked BadRequest?
132148
_ => Err(Error::from_str(
133-
StatusCode::BadRequest,
149+
StatusCode::BadGateway,
134150
"unrecognized HTTP response version",
135151
)),
136152
}?;
@@ -140,26 +156,24 @@ impl HttpTypesResponse {
140156
Some(Ok(b)) => Some(b),
141157
Some(Err(_)) => {
142158
return Err(Error::from_str(
143-
StatusCode::BadRequest,
159+
StatusCode::BadGateway,
144160
"unable to read HTTP response body",
145161
))
146162
}
147163
}
148164
.map(|b| http_types::Body::from_bytes(b.to_vec()))
149-
// TODO: How does `http-types` handle responses without bodies?
150-
.unwrap_or(http_types::Body::from_bytes(Vec::new()));
165+
.unwrap_or(http_types::Body::empty());
151166

152167
let mut res = Response::new(status);
153168
res.set_version(Some(version));
154169

155170
for (name, value) in parts.headers {
156-
// TODO: http_types uses an `unsafe` block here, should it be allowed for `hyper` as well?
157171
let value = value.as_bytes().to_owned();
158-
let value = http_types::headers::HeaderValue::from_bytes(value)?;
172+
let value = HeaderValue::from_bytes(value)?;
159173

160174
if let Some(name) = name {
161175
let name = name.as_str();
162-
let name = http_types::headers::HeaderName::from_str(name)?;
176+
let name = HeaderName::from_str(name)?;
163177
res.insert_header(name, value);
164178
}
165179
}
@@ -168,7 +182,55 @@ impl HttpTypesResponse {
168182
Ok(HttpTypesResponse { inner: res })
169183
}
170184

171-
fn into_inner(self) -> http_types::Response {
185+
fn into_inner(self) -> Response {
172186
self.inner
173187
}
174188
}
189+
190+
#[cfg(test)]
191+
mod tests {
192+
use crate::{Error, HttpClient};
193+
use http_types::{Method, Request, Url};
194+
use hyper::service::{make_service_fn, service_fn};
195+
use std::time::Duration;
196+
use tokio::sync::oneshot::channel;
197+
198+
use super::HyperClient;
199+
200+
async fn echo(
201+
req: hyper::Request<hyper::Body>,
202+
) -> Result<hyper::Response<hyper::Body>, hyper::Error> {
203+
Ok(hyper::Response::new(req.into_body()))
204+
}
205+
206+
#[tokio::test]
207+
async fn basic_functionality() {
208+
let (send, recv) = channel::<()>();
209+
210+
let recv = async move { recv.await.unwrap_or(()) };
211+
212+
let addr = ([127, 0, 0, 1], portpicker::pick_unused_port().unwrap()).into();
213+
let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(echo)) });
214+
let server = hyper::Server::bind(&addr)
215+
.serve(service)
216+
.with_graceful_shutdown(recv);
217+
218+
let client = HyperClient::new();
219+
let url = Url::parse(&format!("http://localhost:{}", addr.port())).unwrap();
220+
let mut req = Request::new(Method::Get, url);
221+
req.set_body("hello");
222+
223+
let client = async move {
224+
tokio::time::delay_for(Duration::from_millis(100)).await;
225+
let mut resp = client.send(req).await?;
226+
send.send(()).unwrap();
227+
assert_eq!(resp.body_string().await?, "hello");
228+
229+
Result::<(), Error>::Ok(())
230+
};
231+
232+
let (client_res, server_res) = tokio::join!(client, server);
233+
assert!(client_res.is_ok());
234+
assert!(server_res.is_ok());
235+
}
236+
}

0 commit comments

Comments
 (0)