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 | ||||
|   - 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: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user