hyper_stub/lib.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! hyper-stub provides functions to create [hyper] clients that convert requests
6//! to responses using predefined functions, without doing any actual
7//! networking. This means the entire request/response lifecycle happens in a
8//! single process, and should have performance and stability improvements over,
9//! for example, binding to a port. One potential use case for this is stubbing
10//! HTTP interactions in tests to avoid slow/flakey/absent internet connections.
11//!
12//! The simplest case uses [`proxy_client_fn_ok`] to create a client bound to a
13//! simple function that directly maps from a request to a response:
14//!
15//! ```
16//! # extern crate futures;
17//! # extern crate hyper;
18//! # extern crate hyper_stub;
19//! # extern crate tokio;
20//! #
21//! use futures::{Future, Stream};
22//! use hyper::{Request, Response, Uri};
23//! use hyper_stub::proxy_client_fn_ok;
24//! use tokio::runtime::current_thread::Runtime;
25//!
26//! let echo_client = proxy_client_fn_ok(|request| {
27//! let body = request.into_body();
28//! Response::new(body)
29//! });
30//!
31//! let url: Uri = "http://example.com".parse().unwrap();
32//! let mut builder = Request::post(url);
33//! let request = builder.body("hello world".into()).unwrap();
34//! let future = echo_client.request(request)
35//! .and_then(|res| res.into_body().concat2())
36//! .map(|bytes| {
37//! let body = String::from_utf8(bytes.to_vec()).unwrap();
38//! println!("{}", body);
39//! })
40//! .map_err(|error| panic!("ERROR: {:?}", error));
41//!
42//! Runtime::new().unwrap().block_on(future).unwrap();
43//! ```
44//!
45//! If the function needs to return an error, or respond to the request
46//! asynchronously, [`proxy_client_fn`] can be used.
47//!
48//! Finally, an advanced use case is using hyper [`services`] instead of simple
49//! functions. This can be done with the [`proxy_client`] function.
50//!
51//! [hyper]: https://hyper.rs
52//! [services]: https://docs.rs/hyper/0.12.1/hyper/service/index.html
53//! [`proxy_client_fn_ok`]: fn.proxy_client_fn_ok.html
54//! [`proxy_client_fn`]: fn.proxy_client_fn.html
55//! [`proxy_client`]: fn.proxy_client.html
56
57extern crate futures;
58extern crate hyper;
59extern crate memsocket;
60extern crate tokio;
61
62mod connector;
63mod never;
64
65use connector::Connector;
66use futures::prelude::*;
67use hyper::body::{Body, Payload};
68use hyper::client::connect::Connect;
69use hyper::service::{NewService, Service};
70use hyper::{Client, Request, Response};
71use never::Never;
72use std::error::Error;
73
74/// Creates a hyper client whose requests are converted to responses by being
75/// passed through a hyper [`Service`] instantiated by and returned from the given
76/// [`NewService`].
77///
78/// [`proxy_client_fn`] is much more simple and almost as powerful, so should
79/// generally be preferred.
80///
81/// [`Service`]: https://docs.rs/hyper/0.12.1/hyper/service/index.html
82/// [`NewService`]: https://docs.rs/hyper/0.12.1/hyper/service/trait.NewService.html
83/// [`proxy_client_fn`]: fn.proxy_client_fn.html
84pub fn proxy_client<ResBody, ResponseError, ServiceError, ResponseFuture, ServiceFuture, S, N>(
85 new_service: N,
86) -> Client<Connector<N>>
87where
88 ResBody: Payload,
89 ResponseError: Error + Send + Sync + 'static,
90 ServiceError: Error + Send + Sync + 'static,
91 ResponseFuture: Future<Item = Response<S::ResBody>, Error = ResponseError> + Send + 'static,
92 ServiceFuture: Future<Item = S, Error = ServiceError> + Send + 'static,
93 S: Service<ReqBody = Body, ResBody = ResBody, Error = ResponseError, Future = ResponseFuture>
94 + Send
95 + 'static,
96 N: NewService<
97 ReqBody = S::ReqBody,
98 ResBody = S::ResBody,
99 Future = ServiceFuture,
100 Error = ResponseError,
101 Service = S,
102 InitError = ServiceError,
103 >
104 + Sync
105 + Send,
106{
107 Client::builder()
108 .set_host(true)
109 .build(Connector::new(new_service))
110}
111
112/// Creates a hyper client whose requests are converted to responses by being
113/// passed through the given handler function, which returns a future.
114pub fn proxy_client_fn<E, Fut, F>(handler: F) -> Client<impl Connect>
115where
116 E: Error + Send + Sync + 'static,
117 Fut: Future<Item = Response<Body>, Error = E> + Send + 'static,
118 F: Fn(Request<Body>) -> Fut + Send + Sync + Copy + 'static,
119{
120 use futures::future;
121 use hyper::service::service_fn;
122
123 proxy_client(move || future::ok::<_, Never>(service_fn(handler)))
124}
125
126/// Creates a hyper client whose requests are converted to responses by being
127/// passed through the given handler function.
128///
129/// See [`proxy_client_fn`] if errors or asynchronous processing are required.
130///
131/// [`proxy_client_fn`]: fn.proxy_client_fn.html
132pub fn proxy_client_fn_ok<F>(handler: F) -> Client<impl Connect>
133where
134 F: Fn(Request<Body>) -> Response<Body> + Send + Sync + Copy + 'static,
135{
136 use futures::future;
137
138 proxy_client_fn(move |req| future::ok::<_, Never>(handler(req)))
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_ok() {
147 use futures::prelude::*;
148 use tokio::runtime::current_thread::Runtime;
149
150 let client = proxy_client_fn_ok(|req| {
151 let query = req.uri().query().unwrap().to_string();
152 Response::new(query.into())
153 });
154
155 Runtime::new()
156 .unwrap()
157 .block_on({
158 client
159 .get("https://example.com?foo=bar".parse().unwrap())
160 .and_then(|res| res.into_body().concat2())
161 .map(|bytes| {
162 let body = String::from_utf8(bytes.to_vec()).unwrap();
163 assert_eq!(body, "foo=bar");
164 })
165 .map_err(|err| panic!("{:?}", err))
166 })
167 .unwrap();
168 }
169
170 #[test]
171 fn test_err() {
172 use futures::future::{self, FutureResult};
173 use futures::prelude::*;
174 use std::fmt::{self, Display, Formatter};
175 use tokio::runtime::current_thread::Runtime;
176
177 #[derive(Debug)]
178 struct NewServiceError;
179
180 impl Display for NewServiceError {
181 fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
182 write!(fmt, "correct error for test")
183 }
184 }
185
186 impl Error for NewServiceError {
187 fn description(&self) -> &str {
188 "It broke"
189 }
190 }
191
192 impl Service for Never {
193 type ReqBody = Body;
194 type ResBody = Body;
195 type Error = Self;
196 type Future = FutureResult<Response<Self::ResBody>, Self>;
197
198 fn call(&mut self, _: Request<Self::ReqBody>) -> Self::Future {
199 unreachable!()
200 }
201 }
202
203 let client = proxy_client(|| future::err::<Never, _>(NewServiceError));
204
205 let _ = Runtime::new().unwrap().block_on({
206 client
207 .get("https://example.com".parse().unwrap())
208 .map(|res| panic!("didn't error: {:?}", res))
209 .map_err(|err| assert!(err.to_string().contains("correct error for test")))
210 });
211 }
212}