@@ -1,30 +1,384 @@
|
||||
% Server Guide
|
||||
|
||||
# The `Handler`
|
||||
# Hello, World
|
||||
|
||||
```ignore,no_run
|
||||
Let's start off by creating a simple server to just serve a text response
|
||||
of "Hello, World!" to every request.
|
||||
|
||||
```no_run
|
||||
extern crate hyper;
|
||||
use hyper::server::{Handler, Request, Response, Decoder, Encoder, Next, HttpStream as Http};
|
||||
use hyper::{Decoder, Encoder, HttpStream as Http, Next};
|
||||
use hyper::server::{Server, Handler, Request, Response};
|
||||
|
||||
struct Hello;
|
||||
|
||||
impl Handler<Http> for Hello {
|
||||
fn on_request(&mut self, req: Request<Http>) -> Next {
|
||||
struct Text(&'static [u8]);
|
||||
|
||||
impl Handler<Http> for Text {
|
||||
fn on_request(&mut self, _req: Request<Http>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<Http>) -> Next {
|
||||
|
||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||
|
||||
use hyper::header::ContentLength;
|
||||
res.headers_mut().set(ContentLength(self.0.len() as u64));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
||||
|
||||
encoder.write(self.0).unwrap(); // for now
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let (listening, server) = Server::http(&addr).unwrap()
|
||||
.handle(|_| Text(b"Hello, World")).unwrap();
|
||||
|
||||
println!("Listening on http://{}", listening);
|
||||
server.run()
|
||||
}
|
||||
```
|
||||
|
||||
There is quite a few concepts here, so let's tackle them one by one.
|
||||
|
||||
## Handler
|
||||
|
||||
The [`Handler`][Handler] is how you define what should happen during the lifetime
|
||||
of an HTTP message.
|
||||
|
||||
## Next
|
||||
|
||||
Every event in the [`Handler`][Handler] returns a [`Next`][Next]. This signals
|
||||
to hyper what the `Handler` would wishes to do next, and hyper will call the
|
||||
appropriate method of the `Handler` when the action is ready again.
|
||||
|
||||
So, in our "Hello World" server, you'll notice that when a request comes in, we
|
||||
have no interest in the `Request` or its body. We immediately just wish to write
|
||||
"Hello, World!", and be done. So, in `on_request`, we return `Next::write()`,
|
||||
which tells hyper we wish to write the response.
|
||||
|
||||
After `on_response` is called, we ask for `Next::write()` again, because we
|
||||
still need to write the response body. hyper knows that the next time the
|
||||
transport is ready to be written, since it already called `on_response`, it
|
||||
will call `on_response_writable`, which is where we can write the text body.
|
||||
|
||||
Once we're all done with the response, we can tell hyper to finish by returning
|
||||
`Next::end()`. hyper will try to finish flushing all the output, and if the
|
||||
conditions are met, it may try to use the underlying transport for another
|
||||
request. This is also known as "keep-alive".
|
||||
|
||||
## Server
|
||||
|
||||
In the `main` function, a [`Server`][Server] is created that will utilize our
|
||||
`Hello` handler. We use the default options, though you may wish to peruse
|
||||
them, especially the `max_sockets` option, as it is conservative by default.
|
||||
|
||||
We pass a constructor closure to `Server.handle`, which constructs a `Handler`
|
||||
to be used for each incoming request.
|
||||
|
||||
# Non-blocking IO
|
||||
|
||||
## Don't Panic
|
||||
|
||||
There is actually a very bad practice in the "Hello, World" example. The usage
|
||||
of `decoder.write(x).unwrap()` will panic if the write operation fails. A panic
|
||||
will take down the whole thread, which means the event loop and all other
|
||||
in-progress requests. So don't do it. It's bad.
|
||||
|
||||
What makes it worse, is that the write operation is much more likely to fail
|
||||
when using non-blocking IO. If the write would block the thread, instead of
|
||||
doing so, it will return an `io::Error` with the `kind` of `WouldBlock`. These
|
||||
are expected errors.
|
||||
|
||||
## WouldBlock
|
||||
|
||||
Instead, we should inspect when there is a read or write error to see if the
|
||||
`kind` was a `WouldBlock` error. Since `WouldBlock` is so common when using
|
||||
non-blocking IO, the `Encoder` and `Decoder` provide `try_` methods that will
|
||||
special case `WouldBlock`, allowing you to treat all `Err` cases as actual
|
||||
errors.
|
||||
|
||||
Additionally, it's possible there was a partial write of the response body, so
|
||||
we should probably change the example to keep track of it's progress. Can you
|
||||
see how we should change the example to better handle these conditions?
|
||||
|
||||
This will just show the updated `on_response_writable` method, the rest stays
|
||||
the same:
|
||||
|
||||
```no_run
|
||||
# extern crate hyper;
|
||||
# use hyper::{Encoder, HttpStream as Http, Next};
|
||||
|
||||
# struct Text(&'static [u8]);
|
||||
|
||||
# impl Text {
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
||||
match encoder.try_write(self.0) {
|
||||
Ok(Some(n)) => {
|
||||
if n == self.0.len() {
|
||||
// all done!
|
||||
Next::end()
|
||||
} else {
|
||||
// a partial write!
|
||||
// with a static array, we can just move our pointer
|
||||
// another option could be to store a separate index field
|
||||
self.0 = &self.0[n..];
|
||||
// there's still more to write, so ask to write again
|
||||
Next::write()
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
// would block, ask to write again
|
||||
Next::write()
|
||||
},
|
||||
Err(e) => {
|
||||
println!("oh noes, we cannot say hello! {}", e);
|
||||
// remove (kill) this transport
|
||||
Next::remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
# }
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
# Routing
|
||||
|
||||
What if we wanted to serve different messages depending on the URL of the
|
||||
request? Say, we wanted to respond with "Hello, World!" to `/hello`, but
|
||||
"Good-bye" with `/bye`. Let's adjust our example to do that.
|
||||
|
||||
```no_run
|
||||
extern crate hyper;
|
||||
use hyper::{Decoder, Encoder, HttpStream as Http, Next, StatusCode};
|
||||
use hyper::server::{Server, Handler, Request, Response};
|
||||
|
||||
struct Text(StatusCode, &'static [u8]);
|
||||
|
||||
impl Handler<Http> for Text {
|
||||
fn on_request(&mut self, req: Request<Http>) -> Next {
|
||||
use hyper::RequestUri;
|
||||
let path = match *req.uri() {
|
||||
RequestUri::AbsolutePath(ref p) => p,
|
||||
RequestUri::AbsoluteUri(ref url) => url.path(),
|
||||
// other 2 forms are for CONNECT and OPTIONS methods
|
||||
_ => ""
|
||||
};
|
||||
|
||||
match path {
|
||||
"/hello" => {
|
||||
self.1 = b"Hello, World!";
|
||||
},
|
||||
"/bye" => {
|
||||
self.1 = b"Good-bye";
|
||||
},
|
||||
_ => {
|
||||
self.0 = StatusCode::NotFound;
|
||||
self.1 = b"Not Found";
|
||||
}
|
||||
}
|
||||
Next::write()
|
||||
}
|
||||
|
||||
# fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
||||
# Next::write()
|
||||
# }
|
||||
|
||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||
use hyper::header::ContentLength;
|
||||
// we send an HTTP Status Code, 200 OK, or 404 Not Found
|
||||
res.set_status(self.0);
|
||||
res.headers_mut().set(ContentLength(self.1.len() as u64));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
# fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
||||
# match encoder.try_write(self.1) {
|
||||
# Ok(Some(n)) => {
|
||||
# if n == self.1.len() {
|
||||
# Next::end()
|
||||
# } else {
|
||||
# self.1 = &self.1[n..];
|
||||
# Next::write()
|
||||
# }
|
||||
# },
|
||||
# Ok(None) => {
|
||||
# Next::write()
|
||||
# },
|
||||
# Err(e) => {
|
||||
# println!("oh noes, we cannot say hello! {}", e);
|
||||
# Next::remove()
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let (listening, server) = Server::http(&addr).unwrap()
|
||||
.handle(|_| Text(StatusCode::Ok, b"")).unwrap();
|
||||
|
||||
println!("Listening on http://{}", listening);
|
||||
server.run()
|
||||
}
|
||||
```
|
||||
|
||||
# Waiting
|
||||
|
||||
More often than not, a server needs to something "expensive" before it can
|
||||
provide a response to a request. This may be talking to a database, reading
|
||||
a file, processing an image, sending its own HTTP request to another server,
|
||||
or anything else that would impede the event loop thread. These sorts of actions
|
||||
should be done off the event loop thread, when complete, should notify hyper
|
||||
that it can now proceed. This is done by combining `Next::wait()` and the
|
||||
[`Control`][Control].
|
||||
|
||||
## Control
|
||||
|
||||
The `Control` is provided to the `Handler` constructor; it is the argument we
|
||||
have so far been ignoring. It's not needed if we don't ever need to wait a
|
||||
transport. The `Control` is usually sent to a queue, or another thread, or
|
||||
wherever makes sense to be able to use it when the "blocking" operations are
|
||||
complete.
|
||||
|
||||
To focus on hyper instead of obscure blocking operations, we'll use this useless
|
||||
sleeping thread to show it works.
|
||||
|
||||
```no_run
|
||||
extern crate hyper;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper::{Control, Next};
|
||||
|
||||
fn calculate_ultimate_question(rx: mpsc::Receiver<(Control, mpsc::Sender<&'static [u8]>)>) {
|
||||
thread::spawn(move || {
|
||||
while let Ok((ctrl, tx)) = rx.recv() {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
tx.send(b"42").unwrap();
|
||||
ctrl.ready(Next::write()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Our worker will spawn a thread that waits on messages. When receiving a message,
|
||||
after a short nap, it will send back the "result" of the work, and wake up the
|
||||
waiting transport with a `Next::write()` desire.
|
||||
|
||||
## Wait
|
||||
|
||||
Finally, let's tie in our worker thread into our `Text` handler:
|
||||
|
||||
```no_run
|
||||
extern crate hyper;
|
||||
use hyper::{Control, Decoder, Encoder, HttpStream as Http, Next, StatusCode};
|
||||
use hyper::server::{Server, Handler, Request, Response};
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
struct Text {
|
||||
status: StatusCode,
|
||||
text: &'static [u8],
|
||||
control: Option<Control>,
|
||||
worker_tx: mpsc::Sender<(Control, mpsc::Sender<&'static [u8]>)>,
|
||||
worker_rx: Option<mpsc::Receiver<&'static [u8]>>,
|
||||
}
|
||||
|
||||
impl Handler<Http> for Text {
|
||||
fn on_request(&mut self, req: Request<Http>) -> Next {
|
||||
use hyper::RequestUri;
|
||||
let path = match *req.uri() {
|
||||
RequestUri::AbsolutePath(ref p) => p,
|
||||
RequestUri::AbsoluteUri(ref url) => url.path(),
|
||||
_ => ""
|
||||
};
|
||||
|
||||
match path {
|
||||
"/hello" => {
|
||||
self.text = b"Hello, World!";
|
||||
},
|
||||
"/bye" => {
|
||||
self.text = b"Good-bye";
|
||||
},
|
||||
"/question" => {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
// queue work on our worker
|
||||
self.worker_tx.send((self.control.take().unwrap(), tx));
|
||||
// tell hyper we need to wait until we can continue
|
||||
return Next::wait();
|
||||
}
|
||||
_ => {
|
||||
self.status = StatusCode::NotFound;
|
||||
self.text = b"Not Found";
|
||||
}
|
||||
}
|
||||
Next::write()
|
||||
}
|
||||
|
||||
# fn on_request_readable(&mut self, _decoder: &mut Decoder<Http>) -> Next {
|
||||
# Next::write()
|
||||
# }
|
||||
#
|
||||
|
||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||
use hyper::header::ContentLength;
|
||||
res.set_status(self.status);
|
||||
if let Some(rx) = self.worker_rx.take() {
|
||||
self.text = rx.recv().unwrap();
|
||||
}
|
||||
res.headers_mut().set(ContentLength(self.text.len() as u64));
|
||||
Next::write()
|
||||
}
|
||||
#
|
||||
# fn on_response_writable(&mut self, encoder: &mut Encoder<Http>) -> Next {
|
||||
# unimplemented!()
|
||||
# }
|
||||
}
|
||||
|
||||
# fn calculate_ultimate_question(rx: mpsc::Receiver<(Control, mpsc::Sender<&'static [u8]>)>) {
|
||||
# use std::sync::mpsc;
|
||||
# use std::thread;
|
||||
# use std::time::Duration;
|
||||
# thread::spawn(move || {
|
||||
# while let Ok((ctrl, tx)) = rx.recv() {
|
||||
# thread::sleep(Duration::from_millis(500));
|
||||
# tx.send(b"42").unwrap();
|
||||
# ctrl.ready(Next::write()).unwrap();
|
||||
# }
|
||||
# });
|
||||
# }
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
calculate_ultimate_question(rx);
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let (listening, server) = Server::http(&addr).unwrap()
|
||||
.handle(move |ctrl| Text {
|
||||
status: StatusCode::Ok,
|
||||
text: b"",
|
||||
control: Some(ctrl),
|
||||
worker_tx: tx.clone(),
|
||||
worker_rx: None,
|
||||
}).unwrap();
|
||||
|
||||
println!("Listening on http://{}", listening);
|
||||
server.run()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
[Control]: ../hyper/struct.Control.html
|
||||
[Handler]: ../hyper/server/trait.Handler.html
|
||||
[Next]: ../hyper/struct.Next.html
|
||||
[Server]: ../hyper/server/struct.Server.html
|
||||
|
||||
Reference in New Issue
Block a user