Skip to content

Commit 2dc6202

Browse files
committed
feat(service): introduce hyper-specific Service
This introduces the `hyper::service` module, which replaces `tokio-service`. Since the trait is specific to hyper, its associated types have been adjusted. It didn't make sense to need to define `Service<Request=http::Request>`, since we already know the context is HTTP. Instead, the request and response bodies are associated types now, and slightly stricter bounds have been placed on `Error`. The helpers `service_fn` and `service_fn_ok` should be sufficient for now to ease creating `Service`s. The `NewService` trait now allows service creation to also be asynchronous. These traits are similar to `tower` in nature, and possibly will be replaced completely by it in the future. For now, hyper defining its own allows the traits to have better context, and prevents breaking changes in `tower` from affecting hyper. Closes #1461 BREAKING CHANGE: The `Service` trait has changed: it has some changed associated types, and `call` is now bound to `&mut self`. The `NewService` trait has changed: it has some changed associated types, and `new_service` now returns a `Future`. `Client` no longer implements `Service` for now. `hyper::server::conn::Serve` now returns `Connecting` instead of `Connection`s, since `new_service` can now return a `Future`. The `Connecting` is a future wrapping the new service future, returning a `Connection` afterwards. In many cases, `Future::flatten` can be used.
1 parent 71a15c2 commit 2dc6202

24 files changed

+749
-582
lines changed

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ net2 = "0.2.32"
3434
time = "0.1"
3535
tokio = "0.1.5"
3636
tokio-executor = "0.1.0"
37-
tokio-service = "0.1"
3837
tokio-io = "0.1"
3938
want = "0.0.3"
4039

benches/end_to_end.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -71,29 +71,28 @@ fn post_one_at_a_time(b: &mut test::Bencher) {
7171
static PHRASE: &'static [u8] = include_bytes!("../CHANGELOG.md"); //b"Hello, World!";
7272

7373
fn spawn_hello(rt: &mut Runtime) -> SocketAddr {
74-
use hyper::server::{const_service, service_fn, NewService};
74+
use hyper::service::{service_fn};
7575
let addr = "127.0.0.1:0".parse().unwrap();
7676
let listener = TcpListener::bind(&addr).unwrap();
7777
let addr = listener.local_addr().unwrap();
7878

7979
let http = Http::new();
8080

81-
let service = const_service(service_fn(|req: Request<Body>| {
81+
let service = service_fn(|req: Request<Body>| {
8282
req.into_body()
8383
.concat2()
8484
.map(|_| {
8585
Response::new(Body::from(PHRASE))
8686
})
87-
}));
87+
});
8888

8989
// Specifically only accept 1 connection.
9090
let srv = listener.incoming()
9191
.into_future()
9292
.map_err(|(e, _inc)| panic!("accept error: {}", e))
9393
.and_then(move |(accepted, _inc)| {
9494
let socket = accepted.expect("accepted socket");
95-
http.serve_connection(socket, service.new_service().expect("new_service"))
96-
.map(|_| ())
95+
http.serve_connection(socket, service)
9796
.map_err(|_| ())
9897
});
9998
rt.spawn(srv);

benches/server.rs

+14-30
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ use std::io::{Read, Write};
1111
use std::net::{TcpListener, TcpStream};
1212
use std::sync::mpsc;
1313

14-
use futures::{future, stream, Future, Stream};
14+
use futures::{stream, Future, Stream};
1515
use futures::sync::oneshot;
1616

17-
use hyper::{Body, Request, Response, Server};
18-
use hyper::server::Service;
17+
use hyper::{Body, Response, Server};
18+
use hyper::service::service_fn_ok;
1919

2020
macro_rules! bench_server {
2121
($b:ident, $header:expr, $body:expr) => ({
@@ -26,10 +26,17 @@ macro_rules! bench_server {
2626
::std::thread::spawn(move || {
2727
let addr = "127.0.0.1:0".parse().unwrap();
2828
let srv = Server::bind(&addr)
29-
.serve(|| Ok(BenchPayload {
30-
header: $header,
31-
body: $body,
32-
}));
29+
.serve(|| {
30+
let header = $header;
31+
let body = $body;
32+
service_fn_ok(move |_| {
33+
Response::builder()
34+
.header(header.0, header.1)
35+
.header("content-type", "text/plain")
36+
.body(body())
37+
.unwrap()
38+
})
39+
});
3340
addr_tx.send(srv.local_addr()).unwrap();
3441
let fut = srv
3542
.map_err(|e| panic!("server error: {}", e))
@@ -182,26 +189,3 @@ fn raw_tcp_throughput_large_payload(b: &mut test::Bencher) {
182189
tx.send(()).unwrap();
183190
}
184191

185-
struct BenchPayload<F> {
186-
header: (&'static str, &'static str),
187-
body: F,
188-
}
189-
190-
impl<F, B> Service for BenchPayload<F>
191-
where
192-
F: Fn() -> B,
193-
{
194-
type Request = Request<Body>;
195-
type Response = Response<B>;
196-
type Error = hyper::Error;
197-
type Future = future::FutureResult<Self::Response, hyper::Error>;
198-
fn call(&self, _req: Self::Request) -> Self::Future {
199-
future::ok(
200-
Response::builder()
201-
.header(self.header.0, self.header.1)
202-
.header("content-type", "text/plain")
203-
.body((self.body)())
204-
.unwrap()
205-
)
206-
}
207-
}

examples/hello.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ extern crate tokio;
66

77
use futures::Future;
88

9-
use hyper::{Body, Response};
10-
use hyper::server::{Server, const_service, service_fn};
9+
use hyper::{Body, Response, Server};
10+
use hyper::service::service_fn_ok;
1111

1212
static PHRASE: &'static [u8] = b"Hello World!";
1313

@@ -16,10 +16,16 @@ fn main() {
1616

1717
let addr = ([127, 0, 0, 1], 3000).into();
1818

19-
let new_service = const_service(service_fn(|_| {
20-
//TODO: when `!` is stable, replace error type
21-
Ok::<_, hyper::Error>(Response::new(Body::from(PHRASE)))
22-
}));
19+
// new_service is run for each connection, creating a 'service'
20+
// to handle requests for that specific connection.
21+
let new_service = || {
22+
// This is the `Service` that will handle the connection.
23+
// `service_fn_ok` is a helper to convert a function that
24+
// returns a Response into a `Service`.
25+
service_fn_ok(|_| {
26+
Response::new(Body::from(PHRASE))
27+
})
28+
};
2329

2430
let server = Server::bind(&addr)
2531
.serve(new_service)

examples/multi_server.rs

+5-30
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,14 @@ extern crate pretty_env_logger;
55
extern crate tokio;
66

77
use futures::{Future};
8-
use futures::future::{FutureResult, lazy};
8+
use futures::future::{lazy};
99

10-
use hyper::{Body, Method, Request, Response, StatusCode};
11-
use hyper::server::{Server, Service};
10+
use hyper::{Body, Response, Server};
11+
use hyper::service::service_fn_ok;
1212

1313
static INDEX1: &'static [u8] = b"The 1st service!";
1414
static INDEX2: &'static [u8] = b"The 2nd service!";
1515

16-
struct Srv(&'static [u8]);
17-
18-
impl Service for Srv {
19-
type Request = Request<Body>;
20-
type Response = Response<Body>;
21-
type Error = hyper::Error;
22-
type Future = FutureResult<Response<Body>, hyper::Error>;
23-
24-
fn call(&self, req: Request<Body>) -> Self::Future {
25-
futures::future::ok(match (req.method(), req.uri().path()) {
26-
(&Method::GET, "/") => {
27-
Response::new(self.0.into())
28-
},
29-
_ => {
30-
Response::builder()
31-
.status(StatusCode::NOT_FOUND)
32-
.body(Body::empty())
33-
.unwrap()
34-
}
35-
})
36-
}
37-
38-
}
39-
40-
4116
fn main() {
4217
pretty_env_logger::init();
4318

@@ -46,11 +21,11 @@ fn main() {
4621

4722
tokio::run(lazy(move || {
4823
let srv1 = Server::bind(&addr1)
49-
.serve(|| Ok(Srv(INDEX1)))
24+
.serve(|| service_fn_ok(|_| Response::new(Body::from(INDEX1))))
5025
.map_err(|e| eprintln!("server 1 error: {}", e));
5126

5227
let srv2 = Server::bind(&addr2)
53-
.serve(|| Ok(Srv(INDEX2)))
28+
.serve(|| service_fn_ok(|_| Response::new(Body::from(INDEX2))))
5429
.map_err(|e| eprintln!("server 2 error: {}", e));
5530

5631
println!("Listening on http://{} and http://{}", addr1, addr2);

examples/params.rs

+60-69
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ extern crate pretty_env_logger;
55
extern crate tokio;
66
extern crate url;
77

8-
use futures::{Future, Stream};
8+
use futures::{future, Future, Stream};
99

10-
use hyper::{Body, Method, Request, Response, StatusCode};
11-
use hyper::server::{Server, Service};
10+
use hyper::{Body, Method, Request, Response, Server, StatusCode};
11+
use hyper::service::service_fn;
1212

1313
use std::collections::HashMap;
1414
use url::form_urlencoded;
@@ -17,89 +17,80 @@ static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name:
1717
static MISSING: &[u8] = b"Missing field";
1818
static NOTNUMERIC: &[u8] = b"Number field is not numeric";
1919

20-
struct ParamExample;
20+
// Using service_fn, we can turn this function into a `Service`.
21+
fn param_example(req: Request<Body>) -> Box<Future<Item=Response<Body>, Error=hyper::Error> + Send> {
22+
match (req.method(), req.uri().path()) {
23+
(&Method::GET, "/") | (&Method::GET, "/post") => {
24+
Box::new(future::ok(Response::new(INDEX.into())))
25+
},
26+
(&Method::POST, "/post") => {
27+
Box::new(req.into_body().concat2().map(|b| {
28+
// Parse the request body. form_urlencoded::parse
29+
// always succeeds, but in general parsing may
30+
// fail (for example, an invalid post of json), so
31+
// returning early with BadRequest may be
32+
// necessary.
33+
//
34+
// Warning: this is a simplified use case. In
35+
// principle names can appear multiple times in a
36+
// form, and the values should be rolled up into a
37+
// HashMap<String, Vec<String>>. However in this
38+
// example the simpler approach is sufficient.
39+
let params = form_urlencoded::parse(b.as_ref()).into_owned().collect::<HashMap<String, String>>();
2140

22-
impl Service for ParamExample {
23-
type Request = Request<Body>;
24-
type Response = Response<Body>;
25-
type Error = hyper::Error;
26-
type Future = Box<Future<Item = Self::Response, Error = Self::Error> + Send>;
27-
28-
fn call(&self, req: Request<Body>) -> Self::Future {
29-
match (req.method(), req.uri().path()) {
30-
(&Method::GET, "/") | (&Method::GET, "/post") => {
31-
Box::new(futures::future::ok(Response::new(INDEX.into())))
32-
},
33-
(&Method::POST, "/post") => {
34-
Box::new(req.into_body().concat2().map(|b| {
35-
// Parse the request body. form_urlencoded::parse
36-
// always succeeds, but in general parsing may
37-
// fail (for example, an invalid post of json), so
38-
// returning early with BadRequest may be
39-
// necessary.
40-
//
41-
// Warning: this is a simplified use case. In
42-
// principle names can appear multiple times in a
43-
// form, and the values should be rolled up into a
44-
// HashMap<String, Vec<String>>. However in this
45-
// example the simpler approach is sufficient.
46-
let params = form_urlencoded::parse(b.as_ref()).into_owned().collect::<HashMap<String, String>>();
47-
48-
// Validate the request parameters, returning
49-
// early if an invalid input is detected.
50-
let name = if let Some(n) = params.get("name") {
51-
n
41+
// Validate the request parameters, returning
42+
// early if an invalid input is detected.
43+
let name = if let Some(n) = params.get("name") {
44+
n
45+
} else {
46+
return Response::builder()
47+
.status(StatusCode::UNPROCESSABLE_ENTITY)
48+
.body(MISSING.into())
49+
.unwrap();
50+
};
51+
let number = if let Some(n) = params.get("number") {
52+
if let Ok(v) = n.parse::<f64>() {
53+
v
5254
} else {
5355
return Response::builder()
5456
.status(StatusCode::UNPROCESSABLE_ENTITY)
55-
.body(MISSING.into())
57+
.body(NOTNUMERIC.into())
5658
.unwrap();
57-
};
58-
let number = if let Some(n) = params.get("number") {
59-
if let Ok(v) = n.parse::<f64>() {
60-
v
61-
} else {
62-
return Response::builder()
63-
.status(StatusCode::UNPROCESSABLE_ENTITY)
64-
.body(NOTNUMERIC.into())
65-
.unwrap();
66-
}
67-
} else {
68-
return Response::builder()
69-
.status(StatusCode::UNPROCESSABLE_ENTITY)
70-
.body(MISSING.into())
71-
.unwrap();
72-
};
59+
}
60+
} else {
61+
return Response::builder()
62+
.status(StatusCode::UNPROCESSABLE_ENTITY)
63+
.body(MISSING.into())
64+
.unwrap();
65+
};
7366

74-
// Render the response. This will often involve
75-
// calls to a database or web service, which will
76-
// require creating a new stream for the response
77-
// body. Since those may fail, other error
78-
// responses such as InternalServiceError may be
79-
// needed here, too.
80-
let body = format!("Hello {}, your number is {}", name, number);
81-
Response::new(body.into())
82-
}))
83-
},
84-
_ => {
85-
Box::new(futures::future::ok(Response::builder()
86-
.status(StatusCode::NOT_FOUND)
87-
.body(Body::empty())
88-
.unwrap()))
89-
}
67+
// Render the response. This will often involve
68+
// calls to a database or web service, which will
69+
// require creating a new stream for the response
70+
// body. Since those may fail, other error
71+
// responses such as InternalServiceError may be
72+
// needed here, too.
73+
let body = format!("Hello {}, your number is {}", name, number);
74+
Response::new(body.into())
75+
}))
76+
},
77+
_ => {
78+
Box::new(future::ok(Response::builder()
79+
.status(StatusCode::NOT_FOUND)
80+
.body(Body::empty())
81+
.unwrap()))
9082
}
9183
}
9284

9385
}
9486

95-
9687
fn main() {
9788
pretty_env_logger::init();
9889

9990
let addr = ([127, 0, 0, 1], 1337).into();
10091

10192
let server = Server::bind(&addr)
102-
.serve(|| Ok(ParamExample))
93+
.serve(|| service_fn(param_example))
10394
.map_err(|e| eprintln!("server error: {}", e));
10495

10596
tokio::run(server);

0 commit comments

Comments
 (0)