1
1
#![ deny( warnings, clippy:: all, missing_debug_implementations) ]
2
2
3
+ //! A wrapper for integrating `hyper 0.12` with a `conduit 0.8` blocking application stack.
4
+ //!
5
+ //! A `conduit::Handler` is allowed to block so the `Server` must be spawned on the (default)
6
+ //! multi-threaded `Runtime` which allows (by default) 100 concurrent blocking threads. Any excess
7
+ //! requests will asynchronously wait for an available blocking thread.
8
+ //!
9
+ //! # Examples
10
+ //!
11
+ //! Try out the example with `cargo run --example server`.
12
+ //!
13
+ //! Typical usage:
14
+ //!
15
+ //! ```no_run
16
+ //! use conduit::Handler;
17
+ //! use conduit_hyper::Server;
18
+ //! use futures::Future;
19
+ //! use tokio::runtime::Runtime;
20
+ //!
21
+ //! fn main() {
22
+ //! let app = build_conduit_handler();
23
+ //! let addr = ([127, 0, 0, 1], 12345).into();
24
+ //! let server = Server::bind(&addr, app).map_err(|e| {
25
+ //! eprintln!("server error: {}", e);
26
+ //! });
27
+ //!
28
+ //! let mut rt = Runtime::new().unwrap();
29
+ //! rt.spawn(server);
30
+ //! rt.shutdown_on_idle().wait().unwrap();
31
+ //! }
32
+ //!
33
+ //! fn build_conduit_handler() -> impl Handler {
34
+ //! // ...
35
+ //! # Endpoint()
36
+ //! }
37
+ //! #
38
+ //! # use std::{collections, error, io};
39
+ //! #
40
+ //! # use conduit::{Request, Response};
41
+ //! #
42
+ //! # struct Endpoint();
43
+ //! #
44
+ //! # impl Handler for Endpoint {
45
+ //! # fn call(&self, _: &mut dyn Request) -> Result<Response, Box<dyn error::Error + Send>> {
46
+ //! # Ok(Response {
47
+ //! # status: (200, "OK"),
48
+ //! # headers: collections::HashMap::new(),
49
+ //! # body: Box::new(io::Cursor::new("")),
50
+ //! # })
51
+ //! # }
52
+ //! # }
53
+ //! ```
54
+
3
55
#[ cfg( test) ]
4
56
mod tests;
5
57
@@ -9,13 +61,27 @@ use std::path::{Component, Path, PathBuf};
9
61
use std:: sync:: Arc ;
10
62
11
63
use futures:: { future, Future , Stream } ;
12
- use futures_cpupool:: CpuPool ;
13
- use hyper:: { Body , Chunk , Method , Request , Response , Server , StatusCode , Version } ;
64
+ use hyper:: { Body , Chunk , Method , Request , Response , StatusCode , Version } ;
14
65
use log:: error;
15
66
16
67
// Consumers of this library need access to this particular version of `semver`
17
68
pub use semver;
18
69
70
+ /// A builder for a `hyper::Server`
71
+ #[ derive( Debug ) ]
72
+ pub struct Server ;
73
+
74
+ impl Server {
75
+ /// Bind a handler to an address
76
+ pub fn bind < H : conduit:: Handler > (
77
+ addr : & SocketAddr ,
78
+ handler : H ,
79
+ ) -> hyper:: Server < hyper:: server:: conn:: AddrIncoming , Service < H > > {
80
+ let service = Service :: new ( handler) ;
81
+ hyper:: Server :: bind ( & addr) . serve ( service)
82
+ }
83
+ }
84
+
19
85
#[ derive( Debug ) ]
20
86
struct Parts ( http:: request:: Parts ) ;
21
87
@@ -67,7 +133,7 @@ struct ConduitRequest {
67
133
parts : Parts ,
68
134
path : String ,
69
135
body : Cursor < Chunk > ,
70
- extensions : conduit:: Extensions ,
136
+ extensions : conduit:: Extensions , // makes struct non-Send
71
137
}
72
138
73
139
impl conduit:: Request for ConduitRequest {
@@ -157,8 +223,34 @@ impl conduit::Request for ConduitRequest {
157
223
}
158
224
}
159
225
226
+ /// Owned data consumed by the worker thread
227
+ ///
228
+ /// `ConduitRequest` cannot be sent between threads, so the input data is
229
+ /// captured on a core thread and taken by the worker thread.
230
+ struct RequestInfo ( Option < ( Parts , Chunk ) > ) ;
231
+
232
+ impl RequestInfo {
233
+ /// Save the request info that can be sent between threads
234
+ fn new ( parts : http:: request:: Parts , body : Chunk ) -> Self {
235
+ let tuple = ( Parts ( parts) , body) ;
236
+ Self ( Some ( tuple) )
237
+ }
238
+
239
+ /// Take back the request info
240
+ ///
241
+ /// Call this from the worker thread to obtain ownership of the `Send` data
242
+ ///
243
+ /// # Panics
244
+ ///
245
+ /// Panics if called more than once on a value
246
+ fn take ( & mut self ) -> ( Parts , Chunk ) {
247
+ self . 0 . take ( ) . expect ( "called take multiple times" )
248
+ }
249
+ }
250
+
160
251
impl ConduitRequest {
161
- fn new ( parts : Parts , body : Chunk ) -> ConduitRequest {
252
+ fn new ( info : & mut RequestInfo ) -> Self {
253
+ let ( parts, body) = info. take ( ) ;
162
254
let path = parts. 0 . uri . path ( ) . to_string ( ) ;
163
255
let path = Path :: new ( & path) ;
164
256
let path = path
@@ -183,7 +275,7 @@ impl ConduitRequest {
183
275
. to_string_lossy ( )
184
276
. to_string ( ) ; // non-Unicode is replaced with U+FFFD REPLACEMENT CHARACTER
185
277
186
- ConduitRequest {
278
+ Self {
187
279
parts,
188
280
path,
189
281
body : Cursor :: new ( body) ,
@@ -195,15 +287,13 @@ impl ConduitRequest {
195
287
/// Serve a `conduit::Handler` on a thread pool
196
288
#[ derive( Debug ) ]
197
289
pub struct Service < H > {
198
- pool : CpuPool ,
199
290
handler : Arc < H > ,
200
291
}
201
292
202
293
// #[derive(Clone)] results in cloning a ref, and not the Service
203
294
impl < H > Clone for Service < H > {
204
295
fn clone ( & self ) -> Self {
205
296
Service {
206
- pool : self . pool . clone ( ) ,
207
297
handler : self . handler . clone ( ) ,
208
298
}
209
299
}
@@ -230,39 +320,32 @@ impl<H: conduit::Handler> hyper::service::Service for Service<H> {
230
320
231
321
/// Returns a future which buffers the response body and then calls the conduit handler from a thread pool
232
322
fn call ( & mut self , request : Request < Self :: ReqBody > ) -> Self :: Future {
233
- let pool = self . pool . clone ( ) ;
234
323
let handler = self . handler . clone ( ) ;
235
324
236
325
let ( parts, body) = request. into_parts ( ) ;
237
326
let response = body. concat2 ( ) . and_then ( move |full_body| {
238
- pool. spawn_fn ( move || {
239
- let mut request = ConduitRequest :: new ( Parts ( parts) , full_body) ;
240
- let response = handler
241
- . call ( & mut request)
242
- . map ( good_response)
243
- . unwrap_or_else ( |e| error_response ( e. description ( ) ) ) ;
244
-
245
- Ok ( response)
327
+ let mut request_info = RequestInfo :: new ( parts, full_body) ;
328
+ future:: poll_fn ( move || {
329
+ tokio_threadpool:: blocking ( || {
330
+ let mut request = ConduitRequest :: new ( & mut request_info) ;
331
+ handler
332
+ . call ( & mut request)
333
+ . map ( good_response)
334
+ . unwrap_or_else ( |e| error_response ( e. description ( ) ) )
335
+ } )
336
+ . map_err ( |_| panic ! ( "the threadpool shut down" ) )
246
337
} )
247
338
} ) ;
248
339
Box :: new ( response)
249
340
}
250
341
}
251
342
252
343
impl < H : conduit:: Handler > Service < H > {
253
- /// Create a multi-threaded `Service` from a `Handler`
254
- pub fn new ( handler : H , threads : usize ) -> Service < H > {
344
+ fn new ( handler : H ) -> Self {
255
345
Service {
256
- pool : CpuPool :: new ( threads) ,
257
346
handler : Arc :: new ( handler) ,
258
347
}
259
348
}
260
-
261
- /// Run the `Service` bound to a given `SocketAddr`
262
- pub fn run ( & self , addr : SocketAddr ) {
263
- let server = Server :: bind ( & addr) . serve ( self . clone ( ) ) ;
264
- hyper:: rt:: run ( server. map_err ( |e| error ! ( "Server error: {}" , e) ) ) ;
265
- }
266
349
}
267
350
268
351
/// Builds a `hyper::Response` given a `conduit:Response`
0 commit comments