1
1
//! http-client implementation for reqwest
2
+
2
3
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 } ;
4
6
use hyper:: body:: HttpBody ;
5
7
use hyper_tls:: HttpsConnector ;
6
8
use std:: convert:: { TryFrom , TryInto } ;
7
9
use std:: str:: FromStr ;
8
10
9
11
/// Hyper-based HTTP Client.
10
12
#[ 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
+ }
12
23
13
24
impl HttpClient for HyperClient {
14
25
fn send ( & self , req : Request ) -> futures:: future:: BoxFuture < ' static , Result < Response , Error > > {
@@ -41,23 +52,32 @@ struct HyperHttpRequest {
41
52
}
42
53
43
54
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
+ } ;
52
72
53
73
let version = value
54
74
. version ( )
55
75
. 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 ) ,
61
81
_ => Err ( Error :: from_str (
62
82
StatusCode :: BadRequest ,
63
83
"unrecognized HTTP version" ,
@@ -109,14 +129,11 @@ impl HyperHttpRequest {
109
129
}
110
130
111
131
struct HttpTypesResponse {
112
- inner : http_types :: Response ,
132
+ inner : Response ,
113
133
}
114
134
115
135
impl HttpTypesResponse {
116
136
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.
120
137
let ( parts, mut body) = value. into_parts ( ) ;
121
138
122
139
// UNWRAP: http and http-types implement the same status codes
@@ -128,9 +145,8 @@ impl HttpTypesResponse {
128
145
hyper:: Version :: HTTP_11 => Ok ( http_types:: Version :: Http1_1 ) ,
129
146
hyper:: Version :: HTTP_2 => Ok ( http_types:: Version :: Http2_0 ) ,
130
147
hyper:: Version :: HTTP_3 => Ok ( http_types:: Version :: Http3_0 ) ,
131
- // TODO: Is this realistically reachable, and should it be marked BadRequest?
132
148
_ => Err ( Error :: from_str (
133
- StatusCode :: BadRequest ,
149
+ StatusCode :: BadGateway ,
134
150
"unrecognized HTTP response version" ,
135
151
) ) ,
136
152
} ?;
@@ -140,26 +156,24 @@ impl HttpTypesResponse {
140
156
Some ( Ok ( b) ) => Some ( b) ,
141
157
Some ( Err ( _) ) => {
142
158
return Err ( Error :: from_str (
143
- StatusCode :: BadRequest ,
159
+ StatusCode :: BadGateway ,
144
160
"unable to read HTTP response body" ,
145
161
) )
146
162
}
147
163
}
148
164
. 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 ( ) ) ;
151
166
152
167
let mut res = Response :: new ( status) ;
153
168
res. set_version ( Some ( version) ) ;
154
169
155
170
for ( name, value) in parts. headers {
156
- // TODO: http_types uses an `unsafe` block here, should it be allowed for `hyper` as well?
157
171
let value = value. as_bytes ( ) . to_owned ( ) ;
158
- let value = http_types :: headers :: HeaderValue :: from_bytes ( value) ?;
172
+ let value = HeaderValue :: from_bytes ( value) ?;
159
173
160
174
if let Some ( name) = name {
161
175
let name = name. as_str ( ) ;
162
- let name = http_types :: headers :: HeaderName :: from_str ( name) ?;
176
+ let name = HeaderName :: from_str ( name) ?;
163
177
res. insert_header ( name, value) ;
164
178
}
165
179
}
@@ -168,7 +182,55 @@ impl HttpTypesResponse {
168
182
Ok ( HttpTypesResponse { inner : res } )
169
183
}
170
184
171
- fn into_inner ( self ) -> http_types :: Response {
185
+ fn into_inner ( self ) -> Response {
172
186
self . inner
173
187
}
174
188
}
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