Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

meetup: Add HTTP server code we wrote during September meet-up #36

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions content/meetups/2024-09-17.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@ an HTTP server!

This will be a group implementation session, where we'll do our best to get a `curl`-compliant HTTP
server responding with something simple by the end of our lunch break.

#### The result

We managed to get a server working with some basic error handling as well as responding to a happy
path request with a body. The code has been left exactly as it was at the end of the meet-up, `dbg!`
statements and all!

The code can be found on GitHub [hds/lunch.rs](https://github.com/hds/lunch.rs):

- [http-for-lunch](https://github.com/hds/lunch.rs/tree/main/static/content/2024-09-17/http-for-lunch)
7 changes: 7 additions & 0 deletions static/content/2024-09-17/http-for-lunch/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions static/content/2024-09-17/http-for-lunch/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "http-for-lunch"
version = "0.1.0"
edition = "2021"

[dependencies]
48 changes: 48 additions & 0 deletions static/content/2024-09-17/http-for-lunch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# HTTP for Lunch

This is an HTTP server which was built during the [September Rust for Lunch meetup](https://lunch.rs/meetups/2024-09-17/).

The code has been left exactly as it was at the end of the meet-up, `dbg!` statements and all!

## Building & running

Normal `cargo` behaviour:

```sh
cargo run
```

## Testing

We ran the following requests against the server.

Happy path:

```sh
$ curl -D - http://127.0.0.1:8080/hello
HTTP/1.1 200 Rust for Lunch
Content-Length: 13

Hello, World!
```

Method not allowed:

```sh
$ curl -X POST -D - http://127.0.0.1:8080/hello
HTTP/1.1 405 Method Not Allowed
```

HTTP version not supported:

```sh
$ curl --http1.0 -D - http://127.0.0.1:8080/hello
HTTP/1.1 505 HTTP Version not supported
```

Not found:

```sh
$ curl -D - http://127.0.0.1:8080/bye
HTTP/1.1 404 Not Found
```
80 changes: 80 additions & 0 deletions static/content/2024-09-17/http-for-lunch/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::{
io::{self, Read, Write},
net::{TcpListener, TcpStream},
};

fn write_response_no_body(
stream: &mut TcpStream,
status_code: &str,
reason_phrase: &str,
) -> io::Result<()> {
write_response(stream, status_code, reason_phrase, None)
}

fn write_response(
stream: &mut TcpStream,
status_code: &str,
reason_phrase: &str,
body: Option<&str>,
) -> io::Result<()> {
let http_version = "HTTP/1.1";
write!(
stream,
"{http_version} {status_code} {reason_phrase}\r\n",
)?;

match body {
Some(body) => {
// Content length header
write!(stream, "Content-Length: {len}\r\n\r\n", len = body.len())?;

write!(stream, "{body}")
}
None => write!(stream, "\r\n"),
}
}

fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
let mut buffer = vec![0_u8; 1024];

let read_len = stream.read(&mut buffer)?;
println!("Read some bytes: {read_len}");

let request = std::str::from_utf8(&buffer).unwrap();
println!("Read this data: {request}");
let mut lines = request.lines();
let request_line = lines.next().unwrap();
let request_parts = request_line.split_whitespace().collect::<Vec<_>>();

let [method, uri, http_version] = request_parts.as_slice() else {
panic!("we'll sort this out later");
};

dbg!(method);
dbg!(uri);
dbg!(http_version);

if *http_version != "HTTP/1.1" {
return write_response_no_body(&mut stream, "505", "HTTP Version not supported");
}

if *method != "GET" {
return write_response_no_body(&mut stream, "405", "Method Not Allowed");
}

if *uri != "/hello" {
return write_response_no_body(&mut stream, "404", "Not Found");
}

write_response(&mut stream, "200", "Rust for Lunch", Some("Hello, World!"))
}

fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;

// accept connections and process them serially
for stream in listener.incoming() {
handle_client(stream?).unwrap();
}
Ok(())
}