diff --git a/.travis.yml b/.travis.yml index 75244fb1..86839084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ script: - ./.travis/readme.py - cargo build --verbose $FEATURES - cargo test --verbose $FEATURES - - 'for f in ./doc/**/*.md; do rustdoc --test $f; done' + - 'for f in ./doc/**/*.md; do rustdoc -L ./target/debug -L ./target/debug/deps --test $f; done' addons: apt: diff --git a/doc/guide/server.md b/doc/guide/server.md index bd24181c..49c49ab9 100644 --- a/doc/guide/server.md +++ b/doc/guide/server.md @@ -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 for Hello { - fn on_request(&mut self, req: Request) -> Next { +struct Text(&'static [u8]); +impl Handler for Text { + fn on_request(&mut self, _req: Request) -> Next { + Next::write() } - fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { - + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> 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) -> 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) -> 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 for Text { + fn on_request(&mut self, req: Request) -> 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) -> 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) -> 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, + worker_tx: mpsc::Sender<(Control, mpsc::Sender<&'static [u8]>)>, + worker_rx: Option>, +} + +impl Handler for Text { + fn on_request(&mut self, req: Request) -> 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) -> 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) -> 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 diff --git a/src/lib.rs b/src/lib.rs index 5e56d1ef..5213c7fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ //! Hyper provides both a [Client](client/index.html) and a //! [Server](server/index.html), along with a //! [typed Headers system](header/index.html). +//! +//! If just getting started, consider looking over the [Server Guide](./guide/server.html). extern crate rustc_serialize as serialize; extern crate time; #[macro_use] extern crate url;