Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(service): introduce hyper-specific Service #1490

Merged
merged 1 commit into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ net2 = "0.2.32"
time = "0.1"
tokio = "0.1.5"
tokio-executor = "0.1.0"
tokio-service = "0.1"
tokio-io = "0.1"
want = "0.0.3"

Expand Down
9 changes: 4 additions & 5 deletions benches/end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,28 @@ fn post_one_at_a_time(b: &mut test::Bencher) {
static PHRASE: &'static [u8] = include_bytes!("../CHANGELOG.md"); //b"Hello, World!";

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

let http = Http::new();

let service = const_service(service_fn(|req: Request<Body>| {
let service = service_fn(|req: Request<Body>| {
req.into_body()
.concat2()
.map(|_| {
Response::new(Body::from(PHRASE))
})
}));
});

// Specifically only accept 1 connection.
let srv = listener.incoming()
.into_future()
.map_err(|(e, _inc)| panic!("accept error: {}", e))
.and_then(move |(accepted, _inc)| {
let socket = accepted.expect("accepted socket");
http.serve_connection(socket, service.new_service().expect("new_service"))
.map(|_| ())
http.serve_connection(socket, service)
.map_err(|_| ())
});
rt.spawn(srv);
Expand Down
44 changes: 14 additions & 30 deletions benches/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc;

use futures::{future, stream, Future, Stream};
use futures::{stream, Future, Stream};
use futures::sync::oneshot;

use hyper::{Body, Request, Response, Server};
use hyper::server::Service;
use hyper::{Body, Response, Server};
use hyper::service::service_fn_ok;

macro_rules! bench_server {
($b:ident, $header:expr, $body:expr) => ({
Expand All @@ -26,10 +26,17 @@ macro_rules! bench_server {
::std::thread::spawn(move || {
let addr = "127.0.0.1:0".parse().unwrap();
let srv = Server::bind(&addr)
.serve(|| Ok(BenchPayload {
header: $header,
body: $body,
}));
.serve(|| {
let header = $header;
let body = $body;
service_fn_ok(move |_| {
Response::builder()
.header(header.0, header.1)
.header("content-type", "text/plain")
.body(body())
.unwrap()
})
});
addr_tx.send(srv.local_addr()).unwrap();
let fut = srv
.map_err(|e| panic!("server error: {}", e))
Expand Down Expand Up @@ -182,26 +189,3 @@ fn raw_tcp_throughput_large_payload(b: &mut test::Bencher) {
tx.send(()).unwrap();
}

struct BenchPayload<F> {
header: (&'static str, &'static str),
body: F,
}

impl<F, B> Service for BenchPayload<F>
where
F: Fn() -> B,
{
type Request = Request<Body>;
type Response = Response<B>;
type Error = hyper::Error;
type Future = future::FutureResult<Self::Response, hyper::Error>;
fn call(&self, _req: Self::Request) -> Self::Future {
future::ok(
Response::builder()
.header(self.header.0, self.header.1)
.header("content-type", "text/plain")
.body((self.body)())
.unwrap()
)
}
}
18 changes: 12 additions & 6 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ extern crate tokio;

use futures::Future;

use hyper::{Body, Response};
use hyper::server::{Server, const_service, service_fn};
use hyper::{Body, Response, Server};
use hyper::service::service_fn_ok;

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

Expand All @@ -16,10 +16,16 @@ fn main() {

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

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

let server = Server::bind(&addr)
.serve(new_service)
Expand Down
35 changes: 5 additions & 30 deletions examples/multi_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,14 @@ extern crate pretty_env_logger;
extern crate tokio;

use futures::{Future};
use futures::future::{FutureResult, lazy};
use futures::future::{lazy};

use hyper::{Body, Method, Request, Response, StatusCode};
use hyper::server::{Server, Service};
use hyper::{Body, Response, Server};
use hyper::service::service_fn_ok;

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

struct Srv(&'static [u8]);

impl Service for Srv {
type Request = Request<Body>;
type Response = Response<Body>;
type Error = hyper::Error;
type Future = FutureResult<Response<Body>, hyper::Error>;

fn call(&self, req: Request<Body>) -> Self::Future {
futures::future::ok(match (req.method(), req.uri().path()) {
(&Method::GET, "/") => {
Response::new(self.0.into())
},
_ => {
Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()
}
})
}

}


fn main() {
pretty_env_logger::init();

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

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

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

println!("Listening on http://{} and http://{}", addr1, addr2);
Expand Down
129 changes: 60 additions & 69 deletions examples/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ extern crate pretty_env_logger;
extern crate tokio;
extern crate url;

use futures::{Future, Stream};
use futures::{future, Future, Stream};

use hyper::{Body, Method, Request, Response, StatusCode};
use hyper::server::{Server, Service};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use hyper::service::service_fn;

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

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

impl Service for ParamExample {
type Request = Request<Body>;
type Response = Response<Body>;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error> + Send>;

fn call(&self, req: Request<Body>) -> Self::Future {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") | (&Method::GET, "/post") => {
Box::new(futures::future::ok(Response::new(INDEX.into())))
},
(&Method::POST, "/post") => {
Box::new(req.into_body().concat2().map(|b| {
// Parse the request body. form_urlencoded::parse
// always succeeds, but in general parsing may
// fail (for example, an invalid post of json), so
// returning early with BadRequest may be
// necessary.
//
// Warning: this is a simplified use case. In
// principle names can appear multiple times in a
// form, and the values should be rolled up into a
// HashMap<String, Vec<String>>. However in this
// example the simpler approach is sufficient.
let params = form_urlencoded::parse(b.as_ref()).into_owned().collect::<HashMap<String, String>>();

// Validate the request parameters, returning
// early if an invalid input is detected.
let name = if let Some(n) = params.get("name") {
n
// Validate the request parameters, returning
// early if an invalid input is detected.
let name = if let Some(n) = params.get("name") {
n
} else {
return Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(MISSING.into())
.unwrap();
};
let number = if let Some(n) = params.get("number") {
if let Ok(v) = n.parse::<f64>() {
v
} else {
return Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(MISSING.into())
.body(NOTNUMERIC.into())
.unwrap();
};
let number = if let Some(n) = params.get("number") {
if let Ok(v) = n.parse::<f64>() {
v
} else {
return Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(NOTNUMERIC.into())
.unwrap();
}
} else {
return Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(MISSING.into())
.unwrap();
};
}
} else {
return Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(MISSING.into())
.unwrap();
};

// Render the response. This will often involve
// calls to a database or web service, which will
// require creating a new stream for the response
// body. Since those may fail, other error
// responses such as InternalServiceError may be
// needed here, too.
let body = format!("Hello {}, your number is {}", name, number);
Response::new(body.into())
}))
},
_ => {
Box::new(futures::future::ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()))
}
// Render the response. This will often involve
// calls to a database or web service, which will
// require creating a new stream for the response
// body. Since those may fail, other error
// responses such as InternalServiceError may be
// needed here, too.
let body = format!("Hello {}, your number is {}", name, number);
Response::new(body.into())
}))
},
_ => {
Box::new(future::ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()))
}
}

}


fn main() {
pretty_env_logger::init();

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

let server = Server::bind(&addr)
.serve(|| Ok(ParamExample))
.serve(|| service_fn(param_example))
.map_err(|e| eprintln!("server error: {}", e));

tokio::run(server);
Expand Down
Loading