Merge pull request #873 from hyperium/guide
docs(guide): add meat to the Server Guide
This commit is contained in:
		| @@ -22,7 +22,7 @@ script: | |||||||
|   - ./.travis/readme.py |   - ./.travis/readme.py | ||||||
|   - cargo build --verbose $FEATURES |   - cargo build --verbose $FEATURES | ||||||
|   - cargo test --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: | addons: | ||||||
|   apt: |   apt: | ||||||
|   | |||||||
| @@ -1,30 +1,384 @@ | |||||||
| % Server Guide | % 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; | 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; | struct Text(&'static [u8]); | ||||||
|  |  | ||||||
| impl Handler<Http> for Hello { |  | ||||||
|     fn on_request(&mut self, req: Request<Http>) -> Next { |  | ||||||
|  |  | ||||||
|  | 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 { |     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 { |     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() {} | # 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 | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ | |||||||
| //! Hyper provides both a [Client](client/index.html) and a | //! Hyper provides both a [Client](client/index.html) and a | ||||||
| //! [Server](server/index.html), along with a | //! [Server](server/index.html), along with a | ||||||
| //! [typed Headers system](header/index.html). | //! [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 rustc_serialize as serialize; | ||||||
| extern crate time; | extern crate time; | ||||||
| #[macro_use] extern crate url; | #[macro_use] extern crate url; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user