From 12a2be6e28cc2f93d46c92006d2fb137356ef242 Mon Sep 17 00:00:00 2001 From: steveklabnik Date: Thu, 30 Mar 2017 18:06:08 -0400 Subject: [PATCH] Chapter 20: build a web server --- second-edition/src/ch20-00-unnamed-project.md | 496 ++++++++++++++++++ second-edition/src/img/hello.png | Bin 0 -> 8491 bytes 2 files changed, 496 insertions(+) create mode 100644 second-edition/src/img/hello.png diff --git a/second-edition/src/ch20-00-unnamed-project.md b/second-edition/src/ch20-00-unnamed-project.md index a2898c6b7..c8f267904 100644 --- a/second-edition/src/ch20-00-unnamed-project.md +++ b/second-edition/src/ch20-00-unnamed-project.md @@ -1 +1,497 @@ # Un-named project + +It's been a long journey, but here we are! It's the end of the book. Parting is +such sweet sorrow. But before we go, let's build one more project together, to +show off some of the things we learned in these final chapters, as well as +re-cap some of the earlier ones. + +Here's what we're going to make: a web server that says hello: + +![hello from rust](hello.png) + +Before we get started, however, there's one thing we should mention: if you were +writing this code in production, there are a lot of better ways to write it. +Specifically, there are a number of robust crates on crates.io that would make +writing this easier. However, for this chapter, our intention is to learn, not +to take the easy route. So we'll be writing a basic implementation ourselves. + +# Accepting a TCP connection + +The HTTP protocol is built on top of the TCP protocol. So the first thing we need +to build our webserver is to be able to listen to a TCP connection. The standard +library has a `std::net` module that lets us do this. Let's make a new project: + +```bash +$ cargo new hello --bin + Created binary (application) `hello` project +$ cd hello +``` + +We'll put this in `src/main.rs`: + +```rust,no_run +use std::net::TcpListener; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + + println!("Connection established!"); + } +} +``` + +Let's talk about each part in turn: + +```rust,ignore +use std::net::TcpListener; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + +``` + +A `TcpListener` allows us to listen for TCP connections. We've chosen to listen +to the address `127.0.0.1:8008`. The first four digits are an IP address +representing our own computer, and `8080` is the port. We've chosen this port +becuase HTTP is normally accepted on port 80, but connecting to port 80 requires +administrator privledges. Regular users can listen on ports higher than 1024; +8080 is easy to remember since it's port 80, but twice. + +The `bind` method is sort of like `new`, but with a more descriptive name. In +networking, people will often talk about "binding to a port", and so the +function is called `bind`. Finally, it returns a `Result`; binding may +fail. For example, if we had tried to connect to port 80 without being an +administrator. Since we're writing a basic client here, we're not going to worry +about handling these kinds of errors, and so `unwrap` lets us ignore them. + +```rust,ignore +for stream in listener.incoming() { +``` + +The `incoming` method on `TcpListener` gives us an iterator that gives us a +sequence of streams, more specifically, `TcpStream`s. This struct represents an +open connection, and will let us read from and write to it. So this `for` loop +will process each connection in turn, and produce a series of streams. We can +then handle each one in turn. + +```rust,ignore +let stream = stream.unwrap(); + +println!("Connection established!"); +``` + +Right now, "handling" a stream means `unwrap`ping it to ignore any futher +errors, and then printing a message. Let's try this code out! First invoke +`cargo run`: + +```bash +$ cargo run + Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs +warning: unused variable: `stream`, #[warn(unused_variables)] on by default + --> src\main.rs:8:13 + | +8 | let stream = stream.unwrap(); + | ^^^^^^ + + Running `target\debug\hello.exe` +``` + +And then load up `127.0.0.1:8080` in your web browser. Your browser will +show an error message, something like "Connection reset", but if you look +at your terminal... + +```bash + Running `target\debug\hello.exe` +Connection established! +Connection established! +Connection established! +``` + +A bunch of messages! Why did we get multiple ones? Well, our browser is +expecting to speak HTTP, but we aren't replying with anything, just closing the +connection. This might be the browser making a request for the page and a +request for a `favicon.ico`, it might be retrying on its own... the important +thing is that we've successfully gotten a handle on a TCP connection! + +In order to keep things clean, let's move our processing of the connection out +to a function. Modify your code to look like this: + +```rust +use std::net::TcpListener; +use std::net::TcpStream; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + + handle_connection(stream); + } +} + +fn handle_connection(stream: TcpStream) { + println!("Connection established!"); +} +``` + +Now we can worry about handling the `TcpStream` in `handle_connection` only, and +not worry about all of the connection processing stuff. + +# Reading the request + +Let's read in the request from our browser! Modify our code like this: + +```rust +use std::io::prelude::*; +use std::net::TcpListener; +use std::net::TcpStream; + +fn main() { + // no changes in here! +} + +fn handle_connection(mut stream: TcpStream) { + let mut buffer = [0; 512]; + + stream.read(&mut buffer).unwrap(); + + println!("Request: {}", String::from_utf8_lossy(&buffer[..])); +} +``` + +We've added one new `use` declaration, importing the `std::io` module's +`prelude`. This will bring important traits into scope that let us read from and +write to the stream. + +In `handle_connection`, we had to make `stream` mutable with the `mut` keyword. +We're going to be reading data from the stream, so it's going to get modified. +Next, we need to actually read from the stream; we do this in two steps: + +```rust,ignore +let mut buffer = [0; 512]; + +stream.read(&mut buffer).unwrap(); +``` + +First, we declare a `buffer` on the stack; we've made it 512 bytes. Why 512? +It's big enough to get a basic request, but not super huge. If we wanted to +handle requests of an arbitrary size, this would need to be more +complicated, but we're keeping it simple for now! We then pass that buffer +to `stream.read`. This will read bytes from the `TcpStream` and put them in +the buffer. + +Next, we print that stream out: + +```rust +println!("Request: {}", String::from_utf8_lossy(&buffer[..])); +```` + +The `String::from_utf8_lossy` function will take a `&[u8]` and produce a `String`. The +'lossy' part of its name comes from its behavior when it sees invalid UTF-8 sequences; +it replaces them with �, `U+FFFD REPLACEMENT CHARACTER`. + +Let's give this a try! + +```bash +$ cargo run + Compiling hello v0.1.0 (file:///C:/Users/steve/src/hello) + Finished dev [unoptimized + debuginfo] target(s) in 0.42 secs + Running `target\debug\hello.exe` +Request: GET / HTTP/1.1 +Host: 127.0.0.1:8080 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Language: en-US,en;q=0.5 +Accept-Encoding: gzip, deflate +Connection: keep-alive +Upgrade-Insecure-Requests: 1 +������������������������������������ +``` + +You'll probably get slightly different output depending on your browser! You +also might see this request repeated; now we can tell that the reason we +have multiple connections is because the browser is trying to fetch `/` +repeatedly. Let's break this request down. HTTP is a text-based protocol, a +request looks like this: + +```text +Request-Line headers CRLF message-body +``` + +First there's a 'request line'. Then, any headers. Next, a CRLF sequence, and then, the body +of the message. A request line looks like this: + +```text +Request-Line = Method Request-URI HTTP-Version CRLF +``` + +First, we have a method, like `GET` or `POST`. Then, the request's URI, which is +often a URL. Next, we have the HTTP version, and then a CRLF sequence. That's +`\r\n` is the CRLF sequence; `\r` is a "carriage return" and `\n` is a "line +feed"; these terms come from the typewriter days! + +If we apply this to our request: + +```text +GET / HTTP/1.1 +Host: 127.0.0.1:8080 + +``` + +`GET` is our method, `/` is our Request URI, and `HTTP/1.1` is our version. +All the stuff from `Host` and after are headers. `GET` requests have no body. +Neat! + +# Writing a response + + Let's respond to our browser with a response. Responses look like this: + +```text +Status-Line headers CRLF message-body +``` + +First, we need a status line. Then, any headers. Next, a CRLF sequence, and then, the body +of the message. What's a status line? Here's an example of one: + +```text +HTTP/1.1 200 OK\r\n\r\n +``` + +Status lines look like this: + +```text +Status-Line = HTTP-Version Status-Code Reason-Phrase CRLF +``` + +We're using version 1.1 of the protocol, and `200` is the status code. `OK` is +the "reason phrase", it's like a text description of the stauts code. Finally, +`\r\n` is the CRLF sequence; `\r` is a "carriage return" and `\n` is a "line +feed"; these terms come from the typewriter days! + +We don't have any headers, so there's nothing to put there. Next, another CRLF +to separate the headers from the body, which is empty. Whew! With this text, +we've got a successful, tiny, HTTP response. We have to write it to the stream +though! Let's modify our code to write out a response. Remove the `println!` +line, and add these below: + +```rust,ignore +let response = "HTTP/1.1 200 OK\r\n\r\n"; + +stream.write(header.as_bytes()).unwrap(); +stream.flush().unwrap(); +``` + +The first line defines our response. Then, we call `as_bytes` on our `response`, +as the `write` method on `stream` takes a `&[u8]`, and writes those bytes +directly down the connection. This could fail, so `write` returns a `Result`; we continue to use `unwrap` to make progress here. Finally, `flush()` will +wait until all of the underlying bytes are written to the connection; +`TcpStream` contains an internal buffer to minimize calls into the underlying +operating system. + +With these changes, let's run our code! + +```bash +> cargo run + Compiling hello v0.1.0 (file:///C:/Users/steve/src/hello) + Finished dev [unoptimized + debuginfo] target(s) in 0.39 secs + Running `target\debug\hello.exe` +``` + +Once we've loaded `127.0.0.1:8080` in our web browser... we get a blank page! +How exciting! You've just hand-coded an HTTP request and response. From here on +out, it's all just details. + +## Returning real HTML + +Let's return more than a blank page. Create a new file, `hello.html`, in the +root of the project; that is, not in the `src` directory. You can put any +HTML you want in it, here's what I put in mine: + +```html + + + + + Hello! + + +

Hello!

+

Hi from Rust

+ + +``` + +This is a minimal HTML 5 document, with a header and a little paragraph. Let's +modify `handle_connection` to read that file, append it to our header, and send +it as the response: + +```rust,ignore +// add this import at the top +use std::fs::File; + +// our new handle_connection +fn handle_connection(mut stream: TcpStream) { + let mut buffer = [0; 512]; + stream.read(&mut buffer).unwrap(); + + let mut file = File::open("hello.html").unwrap(); + let mut contents = String::new(); + + file.read_to_string(&mut contents).unwrap(); + + let header = "HTTP/1.1 200 OK\r\n\r\n"; + let response = format!("{}{}", header, contents); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); +} +``` + +Here's opening and reading the file: + +```rust,ignore +let mut file = File::open("hello.html").unwrap(); +let mut contents = String::new(); + +file.read_to_string(&mut contents).unwrap(); +``` + +We talked about this in the I/O project chapter, so this should +look fairly familliar. We open the file with `File::open`, and +the read it into a `String` with `file.read_to_string`. + +Next, we write our response out: + +```rust,ignore +let header = "HTTP/1.1 200 OK\r\n\r\n"; +let response = format!("{}{}", header, contents); + +stream.write(response.as_bytes()).unwrap(); +stream.flush().unwrap(); +``` + +We use `format!` to concatenate our header onto the body, +and then change `write` to write `response`. Easy! Run it with +`cargo run`, load up `127.0.0.1:8080` in your browser, and you +should see your HTML rendered! + +# Validating the request + +Right now, our web server will return this HTML no matter what the request. +Let's check that the browser is requesting `/`, and then return an error if +it's not. First, modify `handle_connection` to look like this: + +```rust +fn handle_connection(mut stream: TcpStream) { + let mut buffer = [0; 512]; + stream.read(&mut buffer).unwrap(); + + let get = b"GET / HTTP/1.1\r\n"; + + let start = &buffer[..get.len()]; + + if start == get { + // success! + } else { + // error :( + }; +``` + +Here, we defined the HTTP request we're looking for with `get`. Because we are +reading raw bytes into the buffer, we use a byte string, with `b"`, to make this +a byte string too. Then, we take a slice of the `buffer` that's the same length +as `get`, and compare them. If they're identical, we've gotten a good request. +If not, we've gotten a bad request. + +Let's add in the code to handle each side: + +```rust,ignore +if start == get { + let header = "HTTP/1.1 200 OK\r\n\r\n"; + let mut file = File::open("hello.html").unwrap(); + let mut contents = String::new(); + + file.read_to_string(&mut contents).unwrap(); + + let response = format!("{}{}", header, contents); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); +} else { + let header = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; + let mut file = File::open("404.html").unwrap(); + let mut contents = String::new(); + + file.read_to_string(&mut contents).unwrap(); + + let response = format!("{}{}", header, contents); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); +}; +``` + +The interesting bit is in the else case: + +```rust +let header = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; +let mut file = File::open("404.html").unwrap(); +``` + +`404 NOT FOUND` is the proper error code here. And we need to make a new file, +`404.html`, to go along with `hello.html`. Here's its contents: + +```html + + + + + Hello! + + +

Oops!

+

Sorry, I don't know what you're asking for.

+ + +``` + +With these changes, try running your server again. Requesting `127.0.0.1:8080` should +return our `hello.html`, and any other request, like `127.0.0.1:8080/foo`, should return +our error! + +There's a lot of repetition in this function; let's pull it out: + +```rust + let (header, filename) = if start == get { + ("HTTP/1.1 200 OK\r\n\r\n", "hello.html") + } else { + ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html") + }; + + let mut file = File::open(filename).unwrap(); + let mut contents = String::new(); + + file.read_to_string(&mut contents).unwrap(); + + let response = format!("{}{}", header, contents); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); +} +``` + +Here, the only thing in our `if` is the header and the filename; we then use +destructuring to assign these two bits to `filename` and `header`. We have to +change the call to `File::open` to use this new variable. + +Awesome! We have a simple little web server in ~40 lines of Rust code. So far, +this project has been relatively straightforward as far as Rust code goes; we +haven't done much of the more advanced things yet. Let's kick it up a notch +and add a feature to our web server: a threadpool. + +# Threadpools \ No newline at end of file diff --git a/second-edition/src/img/hello.png b/second-edition/src/img/hello.png new file mode 100644 index 0000000000000000000000000000000000000000..19e2cbc0ac4a1fc44e39f9c470e44b81dfe2f1f4 GIT binary patch literal 8491 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 F{{vktfA9bR literal 0 HcmV?d00001