diff --git a/content/meetups/2024-09-17.md b/content/meetups/2024-09-17.md index abb5926..56886d6 100644 --- a/content/meetups/2024-09-17.md +++ b/content/meetups/2024-09-17.md @@ -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) diff --git a/static/content/2024-09-17/http-for-lunch/Cargo.lock b/static/content/2024-09-17/http-for-lunch/Cargo.lock new file mode 100644 index 0000000..7d37cb2 --- /dev/null +++ b/static/content/2024-09-17/http-for-lunch/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "http-for-lunch" +version = "0.1.0" diff --git a/static/content/2024-09-17/http-for-lunch/Cargo.toml b/static/content/2024-09-17/http-for-lunch/Cargo.toml new file mode 100644 index 0000000..5a06dfe --- /dev/null +++ b/static/content/2024-09-17/http-for-lunch/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "http-for-lunch" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/static/content/2024-09-17/http-for-lunch/README.md b/static/content/2024-09-17/http-for-lunch/README.md new file mode 100644 index 0000000..547218f --- /dev/null +++ b/static/content/2024-09-17/http-for-lunch/README.md @@ -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 +``` diff --git a/static/content/2024-09-17/http-for-lunch/src/main.rs b/static/content/2024-09-17/http-for-lunch/src/main.rs new file mode 100644 index 0000000..3d49574 --- /dev/null +++ b/static/content/2024-09-17/http-for-lunch/src/main.rs @@ -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::>(); + + 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(()) +}