Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Commit 58a6e84

Browse files
authored
Zig Dusty framework (#10806)
* Adds zig dusty framework benchmark * Zig dusty dockerfile fixes * Zig dusty maintainers
1 parent 8df99c9 commit 58a6e84

9 files changed

Lines changed: 492 additions & 0 deletions

File tree

frameworks/Zig/dusty/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.zig-cache
2+
zig-out

frameworks/Zig/dusty/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
# [Dusty](https://github.com/lalinsky/dusty) - Zig HTTP client/server library
3+
4+
## Description
5+
6+
Zig HTTP client/server library built on top of zio (coroutine/async engine)
7+
8+
## Test URLs
9+
10+
### Test 1: JSON Encoding
11+
12+
http://localhost:3000/json
13+
14+
### Test 2: Plaintext
15+
16+
http://localhost:3000/plaintext
17+
18+
### Test 2: Single Row Query
19+
20+
http://localhost:3000/db
21+
22+
### Test 4: Fortunes (Template rendering)
23+
24+
http://localhost:3000/fortunes
25+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"framework": "dusty",
3+
"maintainers": ["dragosv"],
4+
"tests": [{
5+
"default": {
6+
"json_url": "/json",
7+
"plaintext_url": "/plaintext",
8+
"db_url": "/db",
9+
"fortune_url": "/fortunes",
10+
"port": 3000,
11+
"approach": "Realistic",
12+
"classification": "Fullstack",
13+
"database": "Postgres",
14+
"framework": "dusty",
15+
"language": "Zig",
16+
"flavor": "None",
17+
"orm": "raw",
18+
"platform": "None",
19+
"webserver": "None",
20+
"os": "Linux",
21+
"database_os": "Linux",
22+
"display_name": "Dusty (Zig)",
23+
"notes": "",
24+
"versus": ""
25+
}
26+
}]
27+
}

frameworks/Zig/dusty/build.zig

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) !void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
const dep_opts = .{ .target = target, .optimize = optimize };
8+
9+
const root_module = b.addModule("root_mod", .{
10+
.target = target,
11+
.optimize = optimize,
12+
.root_source_file = b.path("src/main.zig"),
13+
});
14+
15+
const dusty_dep = b.dependency("dusty", dep_opts);
16+
const dusty_module = dusty_dep.module("dusty");
17+
const zio_module = dusty_dep.builder.dependency("zio", dep_opts).module("zio");
18+
const pg_module = b.dependency("pg", dep_opts).module("pg");
19+
const datetimez_module = b.dependency("datetimez", dep_opts).module("datetime");
20+
21+
const exe = b.addExecutable(.{
22+
.name = "dusty",
23+
.root_module = root_module,
24+
});
25+
26+
exe.root_module.addImport("dusty", dusty_module);
27+
exe.root_module.addImport("zio", zio_module);
28+
exe.root_module.addImport("pg", pg_module);
29+
exe.root_module.addImport("datetimez", datetimez_module);
30+
31+
// This declares intent for the executable to be installed into the
32+
// standard location when the user invokes the "install" step (the default
33+
// step when running `zig build`).
34+
b.installArtifact(exe);
35+
36+
// This *creates* a Run step in the build graph, to be executed when another
37+
// step is evaluated that depends on it. The next line below will establish
38+
// such a dependency.
39+
const run_cmd = b.addRunArtifact(exe);
40+
41+
// By making the run step depend on the install step, it will be run from the
42+
// installation directory rather than directly from within the cache directory.
43+
// This is not necessary, however, if the application depends on other installed
44+
// files, this ensures they will be present and in the expected location.
45+
run_cmd.step.dependOn(b.getInstallStep());
46+
47+
// This allows the user to pass arguments to the application in the build
48+
// command itself, like this: `zig build run -- arg1 arg2 etc`
49+
if (b.args) |args| {
50+
run_cmd.addArgs(args);
51+
}
52+
53+
// This creates a build step. It will be visible in the `zig build --help` menu,
54+
// and can be selected like this: `zig build run`
55+
// This will evaluate the `run` step rather than the default, which is "install".
56+
const run_step = b.step("run", "Run the app");
57+
run_step.dependOn(&run_cmd.step);
58+
}

frameworks/Zig/dusty/build.zig.zon

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.{
2+
.name = .dusty_testing,
3+
.fingerprint = 0x402c0022f1133ce0,
4+
.version = "0.1.1",
5+
.paths = .{
6+
"build.zig",
7+
"build.zig.zon",
8+
"src",
9+
},
10+
.dependencies = .{
11+
.pg = .{
12+
.url = "git+https://github.com/karlseguin/pg.zig.git#f8d4892387fbad2abdf775783e101e50a7114335",
13+
.hash = "pg-0.0.0-Wp_7gag6BgD_QAZrPhNNEGpnUZR_LEkKT40Ura3p-4yX",
14+
},
15+
.dusty = .{
16+
.url = "git+https://github.com/lalinsky/dusty.git#8aaedf71a069758bd88437d117ccd89069dc9bf8",
17+
.hash = "dusty-0.0.0-Qdw7Rqh_CQDJNptlxOVIRgT4DxHnAKT9KohxfNhSH9bC",
18+
},
19+
.datetimez = .{
20+
.url = "git+https://github.com/frmdstryr/zig-datetime.git#3a39a21e6e34dcb0ade0ff828d0914d40ba535f3",
21+
.hash = "datetime-0.8.0-cJNXzP_YAQBxQ5hkNNP6ScnG5XsqciJmeP5RVV4xwCBA",
22+
},
23+
},
24+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
FROM debian:12.9 AS build
2+
3+
WORKDIR /app
4+
5+
COPY src src
6+
COPY build.zig.zon build.zig.zon
7+
COPY build.zig build.zig
8+
9+
ARG ZIG_VER=0.15.2
10+
11+
RUN apt-get update && apt-get install -y wget xz-utils ca-certificates git
12+
13+
RUN wget https://ziglang.org/download/${ZIG_VER}/zig-$(uname -m)-linux-${ZIG_VER}.tar.xz
14+
15+
RUN tar -xvf zig-$(uname -m)-linux-${ZIG_VER}.tar.xz
16+
17+
RUN mv zig-$(uname -m)-linux-${ZIG_VER} /usr/local/zig
18+
19+
ENV PATH="/usr/local/zig:$PATH"
20+
RUN zig build -Doptimize=ReleaseFast
21+
22+
23+
FROM debian:12-slim
24+
25+
ENV PG_USER=benchmarkdbuser
26+
ENV PG_PASS=benchmarkdbpass
27+
ENV PG_DB=hello_world
28+
ENV PG_HOST=tfb-database
29+
ENV PG_PORT=5432
30+
31+
RUN apt-get -qq update
32+
RUN apt-get -qy install ca-certificates
33+
34+
COPY --from=build /app/zig-out/bin/dusty /server
35+
36+
EXPOSE 3000
37+
38+
ENTRYPOINT ["/server"]
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
const std = @import("std");
2+
const dusty = @import("dusty");
3+
const pg = @import("pg");
4+
const datetimez = @import("datetimez");
5+
6+
pub var date_str: [29]u8 = undefined;
7+
8+
pub const Global = struct {
9+
pool: *pg.Pool,
10+
rand: *std.Random,
11+
};
12+
13+
const World = struct {
14+
id: i32,
15+
randomNumber: i32,
16+
};
17+
18+
const Fortune = struct {
19+
id: i32,
20+
message: []const u8,
21+
};
22+
23+
pub fn plaintext(_: *Global, _: *dusty.Request, res: *dusty.Response) !void {
24+
try setHeaders(res);
25+
26+
try res.header("Content-Type", "text/plain");
27+
res.body = "Hello, World!";
28+
}
29+
30+
pub fn json(_: *Global, _: *dusty.Request, res: *dusty.Response) !void {
31+
try setHeaders(res);
32+
33+
try res.json(.{ .message = "Hello, World!" }, .{});
34+
}
35+
36+
pub fn db(global: *Global, _: *dusty.Request, res: *dusty.Response) !void {
37+
try setHeaders(res);
38+
39+
const random_number = 1 + (global.rand.uintAtMostBiased(u32, 9999));
40+
41+
const world = getWorld(global.pool, random_number) catch |err| {
42+
std.debug.print("Error querying database: {}\n", .{err});
43+
return;
44+
};
45+
46+
try res.json(world, .{});
47+
}
48+
49+
pub fn fortune(global: *Global, _: *dusty.Request, res: *dusty.Response) !void {
50+
try setHeaders(res);
51+
52+
const fortunes_html = try getFortunesHtml(res.arena, global.pool);
53+
54+
try res.header("Content-Type", "text/html; charset=utf-8");
55+
res.body = fortunes_html;
56+
}
57+
58+
fn getWorld(pool: *pg.Pool, random_number: u32) !World {
59+
var conn = try pool.acquire();
60+
defer conn.release();
61+
62+
const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number});
63+
64+
var row = row_result.?;
65+
defer row.deinit() catch {};
66+
67+
return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) };
68+
}
69+
70+
fn setHeaders(res: *dusty.Response) !void {
71+
try res.header("Server", "Dusty");
72+
try res.header("Date", try res.arena.dupe(u8, &date_str));
73+
}
74+
75+
fn getFortunesHtml(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const u8 {
76+
const fortunes = try getFortunes(allocator, pool);
77+
78+
var sb = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);
79+
80+
const writer = sb.writer(allocator);
81+
try sb.appendSlice(allocator, "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>");
82+
83+
for (fortunes) |ft| {
84+
try writer.print("<tr><td>{d}</td><td>{s}</td></tr>", .{
85+
ft.id,
86+
try escape_html(allocator, ft.message),
87+
});
88+
}
89+
90+
try sb.appendSlice(allocator, "</table></body></html>");
91+
92+
return sb.toOwnedSlice(allocator);
93+
}
94+
95+
fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune {
96+
var conn = try pool.acquire();
97+
defer conn.release();
98+
99+
var rows = try conn.query("SELECT id, message FROM Fortune", .{});
100+
defer rows.deinit();
101+
102+
var fortunes = try std.ArrayListUnmanaged(Fortune).initCapacity(allocator, 0);
103+
defer fortunes.deinit(allocator);
104+
105+
while (try rows.next()) |row| {
106+
const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) };
107+
try fortunes.append(allocator, current_fortune);
108+
}
109+
110+
const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." };
111+
try fortunes.append(allocator, zero_fortune);
112+
113+
const fortunes_slice = try fortunes.toOwnedSlice(allocator);
114+
std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage);
115+
116+
return fortunes_slice;
117+
}
118+
119+
fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool {
120+
return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt);
121+
}
122+
123+
fn escape_html(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
124+
var output = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);
125+
defer output.deinit(allocator);
126+
127+
for (input) |char| {
128+
switch (char) {
129+
'<' => try output.appendSlice(allocator, "&lt;"),
130+
'>' => try output.appendSlice(allocator, "&gt;"),
131+
else => try output.append(allocator, char),
132+
}
133+
}
134+
135+
return output.toOwnedSlice(allocator);
136+
}

0 commit comments

Comments
 (0)