simple_hyper_client/blocking/
client.rs1use super::body::Body;
8use super::Response;
9use crate::async_client::{ClientBuilder as AsyncClientBuilder, RequestDetails};
10use crate::body::RequestBody;
11use crate::connector::NetworkConnector;
12use crate::error::Error;
13
14use futures_executor::block_on;
15use headers::{Header, HeaderMap, HeaderMapExt};
16use hyper::{Method, Uri};
17use hyper_util::client::legacy::Builder as HyperClientBuilder;
18use tokio::runtime;
19use tokio::sync::{mpsc, oneshot};
20
21use std::convert::{TryFrom, TryInto};
22use std::sync::Arc;
23use std::thread::{self, JoinHandle};
24use std::time::Duration;
25
26#[derive(Clone)]
37pub struct Client {
38 inner: Arc<ClientInner>,
39}
40
41type ResponseSender = oneshot::Sender<Result<Response, Error>>;
42
43struct ClientInner {
44 tx: Option<mpsc::UnboundedSender<(RequestDetails, ResponseSender)>>,
45 thread: Option<JoinHandle<()>>,
46}
47
48impl Drop for ClientInner {
49 fn drop(&mut self) {
50 self.tx.take();
52 self.thread.take().map(|h| h.join());
53 }
54}
55
56macro_rules! define_method_fn {
57 (@internal $name:ident, $method:ident, $method_str:expr) => {
58 #[doc = "Initiate a "]
59 #[doc = $method_str]
60 #[doc = " request with the specified URI."]
61 pub fn $name<U>(&self, uri: U) -> Result<RequestBuilder<'_>, Error>
64 where
65 Uri: TryFrom<U>,
66 <Uri as TryFrom<U>>::Error: Into<http::Error>,
67 {
68 self.request(Method::$method, uri)
69 }
70 };
71
72 ($name:ident, $method:ident) => {
73 define_method_fn!(@internal $name, $method, stringify!($method));
74 };
75}
76
77impl Client {
78 pub fn builder() -> ClientBuilder {
79 ClientBuilder::default()
80 }
81
82 pub fn with_connector<C: NetworkConnector>(connector: C) -> Self {
83 ClientBuilder::default().build(connector)
84 }
85
86 pub fn request<U>(&self, method: Method, uri: U) -> Result<RequestBuilder<'_>, Error>
90 where
91 Uri: TryFrom<U>,
92 <Uri as TryFrom<U>>::Error: Into<http::Error>,
93 {
94 let uri = uri.try_into().map_err(Into::into).map_err(Error::Http)?;
95 Ok(RequestBuilder {
96 client: self,
97 details: RequestDetails::new(method, uri),
98 })
99 }
100
101 define_method_fn!(get, GET);
102 define_method_fn!(head, HEAD);
103 define_method_fn!(post, POST);
104 define_method_fn!(patch, PATCH);
105 define_method_fn!(put, PUT);
106 define_method_fn!(delete, DELETE);
107}
108
109#[derive(Default, Clone)]
113pub struct ClientBuilder(AsyncClientBuilder);
114
115impl ClientBuilder {
116 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn from_hyper_client_builder(inner: HyperClientBuilder) -> Self {
122 Self(AsyncClientBuilder::from_hyper_client_builder(inner))
123 }
124
125 pub fn pool_max_idle_per_host(&mut self, max_idle: usize) -> &mut Self {
129 self.0.pool_max_idle_per_host(max_idle);
130 self
131 }
132
133 pub fn pool_idle_timeout(&mut self, val: Option<Duration>) -> &mut Self {
139 self.0.pool_idle_timeout(val);
140 self
141 }
142
143 pub fn http2_only(&mut self, val: bool) -> &mut Self {
149 self.0.http2_only(val);
150 self
151 }
152
153 pub fn build<C: NetworkConnector>(&self, connector: C) -> Client {
156 let async_client = self.0.build(connector);
157 let (tx, mut rx) = mpsc::unbounded_channel::<(RequestDetails, ResponseSender)>();
158
159 let thread = thread::spawn(move || {
160 let rt = runtime::Builder::new_current_thread()
161 .enable_all()
162 .build()
163 .unwrap(); rt.block_on(async move {
166 while let Some((req_details, resp_tx)) = rx.recv().await {
167 let async_client = async_client.clone();
168 tokio::spawn(async move {
169 match req_details.send(&async_client).await {
170 Ok(resp) => {
171 let (parts, hyper_body) = resp.into_parts();
172 let (fut, body) = Body::new(hyper_body);
173 let _ = resp_tx.send(Ok(Response::from_parts(parts, body)));
174 fut.await;
175 }
176 Err(e) => {
177 let _: Result<_, _> = resp_tx.send(Err(e));
178 }
179 }
180 });
181 }
182 })
183 });
184
185 Client {
186 inner: Arc::new(ClientInner {
187 tx: Some(tx),
188 thread: Some(thread),
189 }),
190 }
191 }
192}
193
194pub struct RequestBuilder<'a> {
203 client: &'a Client,
204 details: RequestDetails,
205}
206
207impl<'a> RequestBuilder<'a> {
208 pub fn body<B: Into<RequestBody>>(mut self, body: B) -> Self {
210 self.details.body = Some(body.into());
211 self
212 }
213
214 pub fn headers(mut self, headers: HeaderMap) -> Self {
216 self.details.headers = headers;
217 self
218 }
219
220 pub fn header<H: Header>(mut self, header: H) -> Self {
224 self.details.headers.typed_insert(header);
225 self
226 }
227
228 pub fn send(self) -> Result<Response, Error> {
233 let RequestBuilder { client, details } = self;
234 let (tx, rx) = oneshot::channel();
235 client
236 .inner
237 .tx
238 .as_ref()
239 .expect("runtime thread exited early")
240 .send((details, tx))
241 .expect("runtime thread panicked");
242
243 block_on(async move {
245 match rx.await {
246 Ok(res) => res,
247 Err(_) => panic!("event loop panicked"),
248 }
249 })
250 .map(|mut resp| {
251 resp.body_mut().keep_client_alive = KeepClientAlive(Some(client.inner.clone()));
252 resp
253 })
254 }
255}
256
257pub(super) struct KeepClientAlive(#[allow(unused)] Option<Arc<ClientInner>>);
258
259impl KeepClientAlive {
260 pub fn empty() -> Self {
261 KeepClientAlive(None)
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use crate::connector::HttpConnector;
269 use headers::ContentType;
270 use hyper::StatusCode;
271 use std::io::{Read, Write};
272 use std::net::{SocketAddr, TcpListener};
273 use std::thread;
274
275 const RESPONSE_OK: &str = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!\r\n";
276 const RESPONSE_404: &str =
277 "HTTP/1.1 404 Not Found\r\nContent-Length: 23\r\n\r\nResource was not found.\r\n";
278
279 fn test_http_server(resp: &'static str) -> SocketAddr {
280 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
281 let addr = listener.local_addr().unwrap();
282 thread::spawn(move || {
283 let (mut stream, _) = listener.accept().unwrap();
284 let mut input = Vec::new();
285 stream.read(&mut input).unwrap();
286 stream.write_all(resp.as_bytes()).unwrap();
287 });
288 addr
289 }
290
291 #[test]
292 fn http_client_ok() {
293 let addr = test_http_server(RESPONSE_OK);
294 let url = format!("http://{}/", addr);
295
296 let connector = HttpConnector::new();
297 let client = Client::with_connector(connector);
298 let mut response = client
299 .request(Method::POST, url)
300 .unwrap()
301 .header(ContentType::json())
302 .body(r#"{"key":"value"}"#)
303 .send()
304 .unwrap();
305
306 assert_eq!(response.status(), StatusCode::OK);
307 let mut body = String::new();
308 response.body_mut().read_to_string(&mut body).unwrap();
309 assert_eq!(body, "Hello, world!");
310 }
311
312 #[test]
313 fn drop_client_before_response() {
314 let addr = test_http_server(RESPONSE_404);
315 let url = format!("http://{}/", addr);
316
317 let connector = HttpConnector::new();
318 let client = Client::with_connector(connector);
319 let mut response = client.get(url).unwrap().send().unwrap();
320 drop(client);
321
322 assert_eq!(response.status(), StatusCode::NOT_FOUND);
323 assert_eq!(response.headers().len(), 1);
324 let mut body = String::new();
325 response.body_mut().read_to_string(&mut body).unwrap();
326 assert_eq!(body, "Resource was not found.");
327 }
328}