From 2831c36d1bb9ffc5339a254689e241da70ad2bf8 Mon Sep 17 00:00:00 2001 From: "Kyle L. Jensen" Date: Mon, 23 Sep 2024 16:39:45 -0400 Subject: [PATCH] Switch to zig. Closes #1 --- .gitignore | 3 +- Cargo.lock | 166 ---------------------------------------------------- Cargo.toml | 16 ----- Dockerfile | 26 +++----- Makefile | 3 + README.md | 32 +++++++--- server.zig | 38 ++++++++++++ src/main.rs | 40 ------------- 8 files changed, 73 insertions(+), 251 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 server.zig delete mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..b594a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target +server.o +server diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 4afa626..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,166 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - -[[package]] -name = "ctrlc" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" -dependencies = [ - "nix", - "windows-sys", -] - -[[package]] -name = "hello-world-http" -version = "0.1.0" -dependencies = [ - "ctrlc", - "tiny_http", -] - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 50c459a..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "hello-world-http" -version = "0.1.0" -edition = "2021" - -[dependencies] -tiny_http = "0.12" -ctrlc = "3.4" - -[profile.release] -opt-level = 'z' # Optimize for size -lto = true # Enable Link Time Optimization -codegen-units = 1 # Use a single codegen unit -panic = 'abort' # Abort on panic -strip = true # Strip symbols from binary - diff --git a/Dockerfile b/Dockerfile index c3a1fed..1f7e27a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,7 @@ -# Use a Rust image based on Alpine -FROM rust:1.81.0-alpine3.20 as builder +FROM alpine:3.20.3 as builder # Install build dependencies -RUN apk add --no-cache musl-dev +RUN apk add --no-cache zig upx # Set the working directory WORKDIR /usr/src/app @@ -10,26 +9,15 @@ WORKDIR /usr/src/app # Copy the entire project COPY . . -# Determine the system architecture and set the appropriate Rust target -RUN arch=$(uname -m) && \ - case "$arch" in \ - "x86_64") echo "x86_64-unknown-linux-musl" > /tmp/target ;; \ - "aarch64") echo "aarch64-unknown-linux-musl" > /tmp/target ;; \ - *) echo "Unsupported architecture: $arch" && exit 1 ;; \ - esac - -# Add the target to rustup and build the project -RUN rustup target add $(cat /tmp/target) && \ - cargo build --release --target $(cat /tmp/target) - -# Copy the binary to a known location -RUN cp target/$(cat /tmp/target)/release/hello-world-http /usr/local/bin/hello-world-http +RUN \ + zig build-exe -lc -static -O ReleaseSmall ./server.zig && \ + upx -9 ./server # Start a new stage for a minimal runtime container FROM scratch # Copy the built executable from the builder stage -COPY --from=builder /usr/local/bin/hello-world-http /hello-world-http +COPY --from=builder /usr/src/app/server /server # Set the startup command -CMD ["/hello-world-http"] \ No newline at end of file +CMD ["/server"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7bea258 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ + +all: + zig build-exe -lc -static -O ReleaseSmall ./server.zig \ No newline at end of file diff --git a/README.md b/README.md index 46f409c..2638cb4 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,48 @@ -# Hello world http +# Tiny Hello World HTTP -This is a simple hello world http server written in Rust. -I wrote this to use as a very tiny Docker image for testing -HTTP services. This image is a bit smaller than 600kb. +The goal of this project is to be the _smallest possible_ Docker +image for testing HTTP services. The server responds to all HTTP +requests with a simple "Hello, World!" As of v0.5.0, the Docker +container is just 22.8kB. ## Running with Docker To run this with Docker, do something like ```sh -docker run -e HOST=0.0.0.0 -p 8000:8000 ghcr.io/kljensen/hello-world-http:latest +docker run -e HOST=0.0.0.0 -e PORT=8000 -p 8000:8000 --init ghcr.io/kljensen/hello-world-http:latest ``` -The `HOST` will tell the server to listen for outisde -requests. The `-p 8000:8000` will map the container's -port 8000 to the host's port 8000. +Notice: + +- `HOST` and `PORT` are _required_. +- `HOST` must be in dotted decimal, like `0.0.0.0` +- If `HOST` is something other than `0.0.0.0`, your +container will likely not respond to external requests. +- `PORT` is the port on which the server will listen +inside the container. If you're forwarding from the +host to the container, obviously this needs to match +the port you publish with `-p`. See [the Docker documentation](https://docs.docker.com/engine/network/#published-ports). +- The `--init` flag is optional, but it's a good idea to +use it. It ensures that the server process is stopped +properly when Docker gets a `SIGTERM` signal. (For example, +this will make `docker run` handle `Ctrl-C` properly.) ## Running with Docker Compose -To run this with Docker Compose, you should have a +To run this with Docker Compose, you should have a `docker-compose.yml` file that looks something like ```yaml services: hello-world: image: ghcr.io/kljensen/hello-world-http:latest + init: true ports: - "8000:8000" environment: - HOST=0.0.0.0 + - PORT=8000 ``` Then you can run it with diff --git a/server.zig b/server.zig new file mode 100644 index 0000000..73f3d9d --- /dev/null +++ b/server.zig @@ -0,0 +1,38 @@ +const std = @import("std"); +const net = std.net; +const print = std.debug.print; + +pub fn main() !void { + const allocator = std.heap.c_allocator; + + // Get the HOST environment variable + const host = try std.process.getEnvVarOwned(allocator, "HOST"); + defer allocator.free(host); + + // Get the PORT environment variable + const port_str = try std.process.getEnvVarOwned(allocator, "PORT"); + defer allocator.free(port_str); + + // Parse the port string into a u16 integer + const port_number = try std.fmt.parseInt(u16, port_str, 10); + + // Parse the IP address and port + const addr = try net.Address.parseIp(host, port_number); + + var server = try addr.listen(.{ .reuse_port = true }); + defer server.deinit(); + + print("Server Listening on {s}:{}\n", .{ host, port_number }); + + const response = "HTTP/1.1 200 OK\r\n" ++ "Content-Length: 11\r\n" ++ "Content-Type: text/plain\r\n" ++ "Connection: close\r\n" ++ "\r\n" ++ "hello world"; + + while (server.accept()) |client| { + defer client.stream.close(); + + print("Connection received from {}\n", .{client.address}); + + _ = try client.stream.write(response); + } else |err| { + print("Error accepting connection: {}\n", .{err}); + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 249d14b..0000000 --- a/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::io::Error; -use tiny_http::{Server, Response}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::process::exit; - - -fn main() -> Result<(), Error> { - // Get host from environment variable - let host = std::env::var("HOST").unwrap_or("127.0.0.1".to_string()); - let port = std::env::var("PORT").unwrap_or("8000".to_string()); - let addr = format!("{}:{}", host, port); - let server = Server::http(addr.clone()).unwrap(); - println!("Server is running on http://{}", addr); - - // Create a flag to signal when the server should stop - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - - // Set up signal handler - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - println!("Received Ctrl+C, shutting down..."); - exit(0); - }).expect("Error setting Ctrl-C handler"); - - // Handle each request - for request in server.incoming_requests() { - if !running.load(Ordering::SeqCst) { - break; - } - // Create a response with "Hello, World!" body - let response = Response::from_string("Hello, World!"); - // Send the response - request.respond(response).unwrap(); - } - - println!("Server has shut down."); - Ok(()) -} \ No newline at end of file