Skip to content

Commit 540de89

Browse files
authored
Added Zig Httpz framework (#9298)
* Added Zig httpz framework * Zig httpz fortunes
1 parent 7c7ccee commit 540de89

File tree

10 files changed

+526
-0
lines changed

10 files changed

+526
-0
lines changed

frameworks/Zig/httpz/.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: 'zig-out/**/*',

frameworks/Zig/httpz/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
# [Httpz](https://github.com/karlseguin/http.zig) - An HTTP/1.1 server for Zig
3+
4+
## Description
5+
6+
Native Zig framework and zig http replacement
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"framework": "httpz",
3+
"tests": [{
4+
"default": {
5+
"json_url": "/json",
6+
"plaintext_url": "/plaintext",
7+
"db_url": "/db",
8+
"fortune_url": "/fortunes",
9+
"port": 3000,
10+
"approach": "Realistic",
11+
"classification": "Fullstack",
12+
"database": "Postgres",
13+
"framework": "httpz",
14+
"language": "Zig",
15+
"flavor": "None",
16+
"orm": "raw",
17+
"platform": "None",
18+
"webserver": "None",
19+
"os": "Linux",
20+
"database_os": "Linux",
21+
"display_name": "Httpz (Zig)",
22+
"notes": "",
23+
"versus": ""
24+
}
25+
}]
26+
}

frameworks/Zig/httpz/build.zig

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const std = @import("std");
2+
const ModuleMap = std.StringArrayHashMap(*std.Build.Module);
3+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
4+
const allocator = gpa.allocator();
5+
6+
// Although this function looks imperative, note that its job is to
7+
// declaratively construct a build graph that will be executed by an external
8+
// runner.
9+
pub fn build(b: *std.Build) !void {
10+
// Standard target options allows the person running `zig build` to choose
11+
// what target to build for. Here we do not override the defaults, which
12+
// means any target is allowed, and the default is native. Other options
13+
// for restricting supported target set are available.
14+
const target = b.standardTargetOptions(.{});
15+
16+
// Standard optimization options allow the person running `zig build` to select
17+
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do nots
18+
// set a preferred release mode, allowing the user to decide how to optimize.
19+
const optimize = b.standardOptimizeOption(.{});
20+
21+
const dep_opts = .{ .target = target, .optimize = optimize };
22+
23+
const exe = b.addExecutable(.{
24+
.name = "httpz",
25+
// In this case the main source file is merely a path, however, in more
26+
// complicated build scripts, this could be a generated file.
27+
.root_source_file = b.path("src/main.zig"),
28+
.target = target,
29+
.optimize = optimize,
30+
});
31+
32+
var modules = ModuleMap.init(allocator);
33+
defer modules.deinit();
34+
35+
const httpz_module = b.dependency("httpz", dep_opts).module("httpz");
36+
const pg_module = b.dependency("pg", dep_opts).module("pg");
37+
const datetimez_module = b.dependency("datetimez", dep_opts).module("zig-datetime");
38+
const mustache_module = b.dependency("mustache", dep_opts).module("mustache");
39+
40+
try modules.put("httpz", httpz_module);
41+
try modules.put("pg", pg_module);
42+
try modules.put("datetimez", datetimez_module);
43+
try modules.put("mustache", mustache_module);
44+
45+
// // Expose this as a module that others can import
46+
exe.root_module.addImport("httpz", httpz_module);
47+
exe.root_module.addImport("pg", pg_module);
48+
exe.root_module.addImport("datetimez", datetimez_module);
49+
exe.root_module.addImport("mustache", mustache_module);
50+
51+
// This declares intent for the executable to be installed into the
52+
// standard location when the user invokes the "install" step (the default
53+
// step when running `zig build`).
54+
b.installArtifact(exe);
55+
56+
// This *creates* a Run step in the build graph, to be executed when another
57+
// step is evaluated that depends on it. The next line below will establish
58+
// such a dependency.
59+
const run_cmd = b.addRunArtifact(exe);
60+
61+
// By making the run step depend on the install step, it will be run from the
62+
// installation directory rather than directly from within the cache directory.
63+
// This is not necessary, however, if the application depends on other installed
64+
// files, this ensures they will be present and in the expected location.
65+
run_cmd.step.dependOn(b.getInstallStep());
66+
67+
// This allows the user to pass arguments to the application in the build
68+
// command itself, like this: `zig build run -- arg1 arg2 etc`
69+
if (b.args) |args| {
70+
run_cmd.addArgs(args);
71+
}
72+
73+
// This creates a build step. It will be visible in the `zig build --help` menu,
74+
// and can be selected like this: `zig build run`
75+
// This will evaluate the `run` step rather than the default, which is "install".
76+
const run_step = b.step("run", "Run the app");
77+
run_step.dependOn(&run_cmd.step);
78+
}

frameworks/Zig/httpz/build.zig.zon

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.{ .name = "Zap testing", .version = "0.1.1", .paths = .{
2+
"build.zig",
3+
"build.zig.zon",
4+
"src",
5+
}, .dependencies = .{
6+
.pg = .{ .url = "https://github.com/karlseguin/pg.zig/archive/239a4468163a49d8c0d03285632eabe96003e9e2.tar.gz", .hash = "1220a1d7e51e2fa45e547c76a9e099c09d06e14b0b9bfc6baa89367f56f1ded399a0" },
7+
.httpz = .{
8+
.url = "git+https://github.com/karlseguin/http.zig?ref=zig-0.13#7d2ddae87af9b110783085c0ea6b03985faa4584",
9+
.hash = "12208c1f2c5f730c4c03aabeb0632ade7e21914af03e6510311b449458198d0835d6",
10+
},
11+
.datetimez = .{
12+
.url = "git+https://github.com/frmdstryr/zig-datetime#70aebf28fb3e137cd84123a9349d157a74708721",
13+
.hash = "122077215ce36e125a490e59ec1748ffd4f6ba00d4d14f7308978e5360711d72d77f",
14+
},
15+
.mustache = .{
16+
.url = "git+https://github.com/batiati/mustache-zig#ae5ecc1522da983dc39bb0d8b27f5d1b1d7956e3",
17+
.hash = "1220ac9e3316ce71ad9cd66c7f215462bf5c187828b50bb3d386549bf6af004e3bb0",
18+
},
19+
} }

frameworks/Zig/httpz/httpz.dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM fedora:40
2+
3+
WORKDIR /httpz
4+
5+
ENV PG_USER=benchmarkdbuser
6+
ENV PG_PASS=benchmarkdbpass
7+
ENV PG_DB=hello_world
8+
ENV PG_HOST=tfb-database
9+
ENV PG_PORT=5432
10+
11+
COPY src src
12+
COPY build.zig.zon build.zig.zon
13+
COPY build.zig build.zig
14+
COPY run.sh run.sh
15+
16+
RUN dnf install -y zig
17+
RUN zig version
18+
RUN zig build -Doptimize=ReleaseFast
19+
RUN cp /httpz/zig-out/bin/httpz /usr/local/bin
20+
21+
EXPOSE 3000
22+
23+
CMD ["sh", "run.sh"]

frameworks/Zig/httpz/run.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
echo "Waiting for Httpz framework to start..."
2+
3+
httpz
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
const std = @import("std");
2+
const httpz = @import("httpz");
3+
const pg = @import("pg");
4+
const datetimez = @import("datetimez");
5+
const mustache = @import("mustache");
6+
7+
const Allocator = std.mem.Allocator;
8+
const Thread = std.Thread;
9+
const Mutex = Thread.Mutex;
10+
const template = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>{{#fortunes}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/fortunes}}</table></body></html>";
11+
12+
pub const Global = struct {
13+
pool: *pg.Pool,
14+
prng: *std.rand.DefaultPrng,
15+
allocator: Allocator,
16+
mutex: std.Thread.Mutex = .{},
17+
};
18+
19+
const Message = struct {
20+
message: []const u8,
21+
};
22+
23+
const World = struct {
24+
id: i32,
25+
randomNumber: i32,
26+
};
27+
28+
const Fortune = struct {
29+
id: i32,
30+
message: []const u8,
31+
};
32+
33+
pub fn plaintext(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
34+
try setHeaders(global.allocator, res);
35+
36+
res.content_type = .TEXT;
37+
res.body = "Hello, World!";
38+
}
39+
40+
pub fn json(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
41+
try setHeaders(global.allocator, res);
42+
43+
const message = Message{ .message = "Hello, World!" };
44+
45+
try res.json(message, .{});
46+
}
47+
48+
pub fn db(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
49+
try setHeaders(global.allocator, res);
50+
51+
global.mutex.lock();
52+
const random_number = 1 + (global.prng.random().uintAtMost(u32, 9999));
53+
global.mutex.unlock();
54+
55+
const world = getWorld(global.pool, random_number) catch |err| {
56+
std.debug.print("Error querying database: {}\n", .{err});
57+
return;
58+
};
59+
60+
try res.json(world, .{});
61+
}
62+
63+
pub fn fortune(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
64+
try setHeaders(global.allocator, res);
65+
66+
const fortunes_html = try getFortunesHtml(global.allocator, global.pool);
67+
68+
res.header("content-type", "text/html; charset=utf-8");
69+
res.body = fortunes_html;
70+
}
71+
72+
fn getWorld(pool: *pg.Pool, random_number: u32) !World{
73+
var conn = try pool.acquire();
74+
defer conn.release();
75+
76+
const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number});
77+
78+
var row = row_result.?;
79+
defer row.deinit() catch {};
80+
81+
return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) };
82+
}
83+
84+
fn setHeaders(allocator: Allocator, res: *httpz.Response) !void {
85+
res.header("Server", "Httpz");
86+
87+
const now = datetimez.datetime.Date.now();
88+
const time = datetimez.datetime.Time.now();
89+
90+
// Wed, 17 Apr 2013 12:00:00 GMT
91+
// Return date in ISO format YYYY-MM-DD
92+
const TB_DATE_FMT = "{s:0>3}, {d:0>2} {s:0>3} {d:0>4} {d:0>2}:{d:0>2}:{d:0>2} GMT";
93+
const now_str = try std.fmt.allocPrint(allocator, TB_DATE_FMT, .{ now.weekdayName()[0..3], now.day, now.monthName()[0..3], now.year, time.hour, time.minute, time.second });
94+
95+
//defer allocator.free(now_str);
96+
97+
res.header("Date", now_str);
98+
}
99+
100+
fn getFortunesHtml(allocator: Allocator, pool: *pg.Pool) ![]const u8 {
101+
const fortunes = try getFortunes(allocator, pool);
102+
103+
const raw = try mustache.allocRenderText(allocator, template,.{ .fortunes = fortunes });
104+
105+
// std.debug.print("mustache output {s}\n", .{raw});
106+
107+
const html = try deescapeHtml(allocator, raw);
108+
109+
// std.debug.print("html output {s}\n", .{html});
110+
111+
return html;
112+
}
113+
114+
fn getFortunes(allocator: Allocator, pool: *pg.Pool) ![]const Fortune {
115+
var conn = try pool.acquire();
116+
defer conn.release();
117+
118+
var rows = try conn.query("SELECT id, message FROM Fortune", .{});
119+
defer rows.deinit();
120+
121+
var fortunes = std.ArrayList(Fortune).init(allocator);
122+
defer fortunes.deinit();
123+
124+
while (try rows.next()) |row| {
125+
const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) };
126+
try fortunes.append(current_fortune);
127+
}
128+
129+
const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." };
130+
try fortunes.append(zero_fortune);
131+
132+
const fortunes_slice = try fortunes.toOwnedSlice();
133+
std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage);
134+
135+
return fortunes_slice;
136+
}
137+
138+
fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool {
139+
return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt);
140+
}
141+
142+
fn deescapeHtml(allocator: Allocator, input: []const u8) ![]const u8 {
143+
var output = std.ArrayList(u8).init(allocator);
144+
defer output.deinit();
145+
146+
var i: usize = 0;
147+
while (i < input.len) {
148+
if (std.mem.startsWith(u8, input[i..], "&#32;")) {
149+
try output.append(' ');
150+
i += 5;
151+
} else if (std.mem.startsWith(u8, input[i..], "&#34;")) {
152+
try output.append('"');
153+
i += 5;
154+
} else if (std.mem.startsWith(u8, input[i..], "&#38;")) {
155+
try output.append('&');
156+
i += 5;
157+
} else if (std.mem.startsWith(u8, input[i..], "&#39;")) {
158+
try output.append('\'');
159+
i += 5;
160+
} else if (std.mem.startsWith(u8, input[i..], "&#40;")) {
161+
try output.append('(');
162+
i += 5;
163+
} else if (std.mem.startsWith(u8, input[i..], "&#41;")) {
164+
try output.append(')');
165+
i += 5;
166+
} else if (std.mem.startsWith(u8, input[i..], "&#43;")) {
167+
try output.append('+');
168+
i += 5;
169+
} else if (std.mem.startsWith(u8, input[i..], "&#44;")) {
170+
try output.append(',');
171+
i += 5;
172+
} else if (std.mem.startsWith(u8, input[i..], "&#46;")) {
173+
try output.append('.');
174+
i += 5;
175+
} else if (std.mem.startsWith(u8, input[i..], "&#47;")) {
176+
try output.append('/');
177+
i += 5;
178+
} else if (std.mem.startsWith(u8, input[i..], "&#58;")) {
179+
try output.append(':');
180+
i += 5;
181+
} else if (std.mem.startsWith(u8, input[i..], "&#59;")) {
182+
try output.append(';');
183+
i += 5;
184+
} else {
185+
try output.append(input[i]);
186+
i += 1;
187+
}
188+
}
189+
190+
return output.toOwnedSlice();
191+
}
192+

0 commit comments

Comments
 (0)