From 8b1fbec3b299e744ecef6a98f7ee442d12c3b9e3 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Mon, 20 Mar 2023 17:40:43 -0700 Subject: [PATCH] Introducing the documentation, Shichao's style. Shichao is one of those compulsive documentarians, but unlike myself he has a much more disciplined style. I intend to try and match it during these tutorials. --- docs/01-installing-rust.md | 49 ++++++++ docs/02-testing-hello-world.md | 202 +++++++++++++++++++++++++++++++++ docs/_index.md | 20 ++++ 3 files changed, 271 insertions(+) create mode 100644 docs/01-installing-rust.md create mode 100644 docs/02-testing-hello-world.md create mode 100644 docs/_index.md diff --git a/docs/01-installing-rust.md b/docs/01-installing-rust.md new file mode 100644 index 0000000..73cb572 --- /dev/null +++ b/docs/01-installing-rust.md @@ -0,0 +1,49 @@ ++++ +title = "Installing Rust" +date = 2023-03-20T17:37:40Z +weight = 1 ++++ +Since this book is about learning Rust, primarily in a microservices +environment, this chapter focuses on installing Rust and describing the tools +available to the developer. + +The easiest way to install Rust is to install the [Rustup](https://rustup.rs/) +tool. It is one of those blind-trust-in-the-safety-of-the-toolchain things. For +Linux and Mac users, the command is a shell script that installs to a user's +local account: + +``` +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Once installed, you can install Rust itself: + +``` +$ rustup install toolchain stable +``` + +You should now have Rust compiler and the Rust build and packaging tool, known +as Cargo: + +``` +$ rustc --version +rustc 1.68.0 (2c8cc3432 2023-03-06) +$ cargo --version +cargo 1.68.0 (115f34552 2023-02-26) +``` + +I also installed the following tools: + +``` +$ rustup component add clippy rust-src rust-docs +$ cargo install rustfmt rust-analyzer +``` + +- clippy: A powerful linter that provides useful advice above and beyond the + compiler's basic error checking. +- rustfmt: A formatting tool that provides a common format for most developers +- rust-analyzer: For your IDE, rust-analyzer provides the LSP (Language Server + Protocol) for Rust, giving you code completion, on-the-fly error definition, + and other luxuries. + + diff --git a/docs/02-testing-hello-world.md b/docs/02-testing-hello-world.md new file mode 100644 index 0000000..bed3378 --- /dev/null +++ b/docs/02-testing-hello-world.md @@ -0,0 +1,202 @@ ++++ +title = "Hello World, You Need Testing" +date = 2023-03-20T17:38:12Z +weight = 2 ++++ +Zero-to-Production's project is writing a web service that signs people up for +an email newsletter. The first task in the book is to set up a "Hello World!" +application server. + +The book uses the [Actix-web](https://actix.rs/) web framework, but I've chosen +to implement it using [Axum](https://github.com/tokio-rs/axum) server, the +default server provided by the [Tokio](https://github.com/tokio-rs/tokio) +asynchronous runtime. + +Although the book is only two years old, it is already out-of-date with respect +to some commands. `cargo add` is now provided by default. The following +commands installed the tools I'll be using: + +``` +cargo add --features tokio/full --features hyper/full tokio hyper \ + axum tracing tracing-subscriber +``` + + +- axum: The web server framework for Tokio. +- tokio: The Rust asynchronous runtime. Has single-threaded (select) and + multi-threaded variants. +- [hyper](https://hyper.rs/): An HTTPS request/response library, used for testing. +- [tracing](https://crates.io/crates/tracing): A debugging library that works + with Tokio. + +We start by defining the core services. In the book, they're a greeter ("Hello, +World"), a greeter with a parameter ("Hello, {name}"), and a health check +(returns a HTTP 200 Code, but no body). Actix-web hands a generic Request and +expects a generic request, but Axum is more straightforward, providing +`IntoResponse` handlers for most of the basic Rust types, as well as some for +formats via Serde, Rust's standard serializing/deserializing library for +converting data from one format to another. + +All of these go into `src/lib.rs`: + +``` +async fn health_check() -> impl IntoResponse { + (StatusCode::OK, ()) +} + +async fn anon_greet() -> &'static str { + "Hello World!\n" +} + +async fn greet(Path(name): Path) -> impl IntoResponse { + let greeting = String::from("He's dead, ") + name.as_str(); + let greeting = greeting + &String::from("!\n"); + (StatusCode::OK, greeting) +} +``` + + + +We then define the routes that our server will recognize. This is +straightforward and familiar territory: + +``` +fn app() -> Router { + Router::new() + .route("/", get(anon_greet)) + .route("/:name", get(greet)) + .route("/health_check", get(health_check)) +} +``` + +We then define a function to *run* the core server: + +``` +pub async fn run() { + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::info!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app().into_make_service()) + .await + .unwrap() +} +``` + +And finally, in a file named `src/main.rs`, we instantiate the server: + +``` +use ztp::run; + +#[tokio::main] +async fn main() { + run().await +} +``` + +To make this "work," we need to define what `ztp` means, and make a distinction +between the library and the CLI program. + +In the project root's `Cargo.toml` file, the first three sections are needed to +define these relationships: + +``` +[package] +name = "ztp" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" + +[[bin]] +path = "src/main.rs" +name = "ztp" +``` + +It is the `[package.name]` feature that defines how the `use` statement in +`main.rs` will find the library. The `[[bin]]` clause defines the name of the +binary when it is generated. + +This project should now be runnable. In one window, type: + +``` +$ cargo run +``` + +And in another, type and see the replies: + +``` +$ curl http://localhost:3000/ +Hello, World! +$ curl http://localhost:3000/Jim +He's dead, Jim! +$ curl -v http://localhost:3000/health_check +> GET /health_check HTTP/1.1 +> Host: localhost:3000 +> User-Agent: curl/7.81.0 +> Accept: */* +< HTTP/1.1 200 OK +< content-length: 0 +< date: Tue, 21 Mar 2023 00:16:43 GMT +``` + +In the last command, the *verbose* flag shows us what we sent to the server, and +what came back. We expected a "200 OK" flag and a zero-length body, and that's +what we got. + +## Testing + +In order to unit-test a web server, we must spawn a copy of it in order to +exercise its functions. We'll use Tokio's `spawn` function to create a new +server, use hyper to request data from the server, and finally Rust's own native +test asserts to check that we got what we expected. + +``` +#[cfg(test)] +mod tests { + use super::*; + use axum::{ + body::Body, + http::{Request, StatusCode}, + }; + use std::net::{SocketAddr, TcpListener}; + + #[tokio::test] + async fn the_real_deal() { + let listener = TcpListener::bind("127.0.0.1:0".parse::() + .unwrap()).unwrap(); + let addr = listener.local_addr().unwrap(); + + tokio::spawn(async move { + axum::Server::from_tcp(listener) + .unwrap()serve(app().into_make_service()).await.unwrap(); + }); + + let response = hyper::Client::new() + .request( + Request::builder().uri(format!("http://{}/", addr)) + .body(Body::empty()).unwrap(), + ) + .await + .unwrap(); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + assert_eq!(&body[..], b"Hello World!\n"); + } +} +``` + +One interesting trick to observe in this testing is the port number specified in +the `TcpListener` call. It's zero. When the port is zero, the `TcpListener` will +request from the kernel the first-free-port. Normally, you'd want to know +exactly what port to call the server on, but in this case both ends of the +communication are aware of the port to use and we want to ensure that port isn't +hard-coded and inconveniently already in-use by someone else. diff --git a/docs/_index.md b/docs/_index.md new file mode 100644 index 0000000..3d53518 --- /dev/null +++ b/docs/_index.md @@ -0,0 +1,20 @@ ++++ +title = "Zero to Production in Rust" +date = 2023-03-20T17:33:13Z +[taxonomies] +categories = ["programming language", "rust"] +[extra] +status = "Incomplete" ++++ +[Zero to Production in Rust](https://www.zero2prod.com/) by Luca Palmieri is a +book that proposes to teach readers how to write a simple and straightforward +microservice using Rust. + +The book was written in 2021 and is, already, somewhat out of date. Where +applicable I have documented the changes I made while working my way through the +book. Those changes include: + +- Neglecting CI/CD, since I'm a sole developer +- Developing entirely in a laptop environment +- Using Axum instead of Actix-Web for the server framework +