Skip to content

Commit 2c24239

Browse files
committed
The HTTP server that can be terminated (/quit, Ctrl+C, docker stop, all tested).
1 parent d6ac7df commit 2c24239

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

step02_server/Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Build the binaries statically, and then run them from a separate container.
2+
FROM alpine AS build
3+
4+
# Intentionally not installing `cargo`, etc. But it does need `clang`.
5+
RUN apk add rustup clang musl
6+
7+
# Init local rust via `rustup`, including static targets builder.
8+
RUN rustup-init -y -t x86_64-unknown-linux-musl --no-modify-path
9+
RUN mv /root/.cargo/bin/* /usr/local/bin/
10+
11+
# Build the project.
12+
COPY ./code /code
13+
WORKDIR /code
14+
RUN cargo build --release --target x86_64-unknown-linux-musl
15+
16+
# Prepare the static binary.
17+
RUN cp ./target/x86_64-unknown-linux-musl/release/httpserver /
18+
19+
# The resulting container with the static binary only.
20+
FROM scratch
21+
COPY --from=build /httpserver /httpserver
22+
EXPOSE 3000
23+
ENTRYPOINT ["/httpserver"]

step02_server/code/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "httpserver"
3+
edition = "2021"
4+
5+
[dependencies]
6+
axum = "0.7"
7+
hyper = { version = "1", features = ["server", "http1"] }
8+
tokio = { version = "1", features = ["full"] }

step02_server/code/src/main.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use axum::{routing::get, Router, serve};
2+
use std::net::SocketAddr;
3+
use tokio::{net::TcpListener, sync::mpsc};
4+
use tokio::signal::unix::{signal, SignalKind};
5+
6+
#[tokio::main]
7+
async fn main() {
8+
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
9+
10+
let app = Router::new()
11+
.route("/healthz", get(|| async { "OK\n" }))
12+
.route("/", get(|| async { "hello this is a rust http server\n" }))
13+
.route(
14+
"/quit",
15+
get({
16+
let shutdown_tx = shutdown_tx.clone();
17+
move || {
18+
let shutdown_tx = shutdown_tx.clone();
19+
async move {
20+
let _ = shutdown_tx.send(()).await;
21+
"yes i am shutting down\n"
22+
}
23+
}
24+
}));
25+
26+
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
27+
let listener = TcpListener::bind(addr).await.unwrap();
28+
29+
println!("rust http server ready on {}", addr);
30+
31+
let server = serve(listener, app);
32+
33+
let mut term_signal = signal(SignalKind::terminate()).expect("failed to register SIGTERM handler");
34+
let mut int_signal = signal(SignalKind::interrupt()).expect("failed to register SIGINT handler");
35+
36+
tokio::select! {
37+
_ = server.with_graceful_shutdown(async move { shutdown_rx.recv().await; }) => { println! ("done"); }
38+
_ = tokio::signal::ctrl_c() => { println!("terminating due to Ctrl+C"); }
39+
_ = term_signal.recv() => { println!("terminating due to SIGTERM"); }
40+
_ = int_signal.recv() => { println!("terminating due to SIGINT"); }
41+
}
42+
43+
println!("rust http server down");
44+
}

step02_server/run.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
docker build . -t demo
6+
7+
docker run --rm --network=bridge -p 3000:3000 -t demo &
8+
PID=$!
9+
10+
while true ; do
11+
R="$(curl -s localhost:3000/healthz || echo NOPE)"
12+
if [ "$R" = "OK" ] ; then
13+
echo "server healthy"
14+
break
15+
fi
16+
sleep 0.5
17+
echo "server not yet healthy"
18+
done
19+
20+
curl -s localhost:3000
21+
22+
curl -s localhost:3000/quit
23+
24+
wait $PID

0 commit comments

Comments
 (0)