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.
This commit is contained in:
parent
87efc8ee7c
commit
8b1fbec3b2
|
@ -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.
|
||||
|
||||
|
|
@ -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<String>) -> impl IntoResponse {
|
||||
let greeting = String::from("He's dead, ") + name.as_str();
|
||||
let greeting = greeting + &String::from("!\n");
|
||||
(StatusCode::OK, greeting)
|
||||
}
|
||||
```
|
||||
|
||||
<aside>Axum's documentation says to [avoid using `impl
|
||||
IntoResponse`](https://docs.rs/axum/latest/axum/response/index.html#regarding-impl-intoresponse)
|
||||
until you understand how it really works, as it can result in confusing issues
|
||||
when chaining response handlers, when a handler can return multiple types, or
|
||||
when a handler can return either a type or a [`Result<T,
|
||||
E>`](https://doc.rust-lang.org/std/result/), especially one with an error.</aside>
|
||||
|
||||
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. <aside>The double brackets around the `[[bin]]`
|
||||
clauses is there to emphasize to the TOML parser that there can be more than one
|
||||
binary. There can be only one library per package, but it is possible for a Rust
|
||||
project to have more than one package, called "crates," per project. </aside>
|
||||
|
||||
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::<SocketAddr>()
|
||||
.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.
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue