diff --git a/frameworks/Zig/httpz/.gitignore b/frameworks/Zig/httpz/.gitignore
new file mode 100644
index 00000000000..170dc0f1403
--- /dev/null
+++ b/frameworks/Zig/httpz/.gitignore
@@ -0,0 +1,2 @@
+zig-cache/**/*',
+zig-out: 'zig-out/**/*',
diff --git a/frameworks/Zig/httpz/README.md b/frameworks/Zig/httpz/README.md
new file mode 100644
index 00000000000..e83169efe17
--- /dev/null
+++ b/frameworks/Zig/httpz/README.md
@@ -0,0 +1,25 @@
+
+# [Httpz](https://github.com/karlseguin/http.zig) - An HTTP/1.1 server for Zig
+
+## Description
+
+Native Zig framework and zig http replacement
+
+## Test URLs
+
+### Test 1: JSON Encoding
+
+ http://localhost:3000/json
+
+### Test 2: Plaintext
+
+ http://localhost:3000/plaintext
+
+### Test 2: Single Row Query
+
+ http://localhost:3000/db
+
+### Test 4: Fortunes (Template rendering)
+
+ http://localhost:3000/fortunes
+
diff --git a/frameworks/Zig/httpz/benchmark_config.json b/frameworks/Zig/httpz/benchmark_config.json
new file mode 100644
index 00000000000..e36c9c17a1c
--- /dev/null
+++ b/frameworks/Zig/httpz/benchmark_config.json
@@ -0,0 +1,26 @@
+{
+ "framework": "httpz",
+ "tests": [{
+ "default": {
+ "json_url": "/json",
+ "plaintext_url": "/plaintext",
+ "db_url": "/db",
+ "fortune_url": "/fortunes",
+ "port": 3000,
+ "approach": "Realistic",
+ "classification": "Fullstack",
+ "database": "Postgres",
+ "framework": "httpz",
+ "language": "Zig",
+ "flavor": "None",
+ "orm": "raw",
+ "platform": "None",
+ "webserver": "None",
+ "os": "Linux",
+ "database_os": "Linux",
+ "display_name": "Httpz (Zig)",
+ "notes": "",
+ "versus": ""
+ }
+ }]
+}
diff --git a/frameworks/Zig/httpz/build.zig b/frameworks/Zig/httpz/build.zig
new file mode 100644
index 00000000000..5978de7c6aa
--- /dev/null
+++ b/frameworks/Zig/httpz/build.zig
@@ -0,0 +1,78 @@
+const std = @import("std");
+const ModuleMap = std.StringArrayHashMap(*std.Build.Module);
+var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+const allocator = gpa.allocator();
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) !void {
+ // Standard target options allows the person running `zig build` to choose
+ // what target to build for. Here we do not override the defaults, which
+ // means any target is allowed, and the default is native. Other options
+ // for restricting supported target set are available.
+ const target = b.standardTargetOptions(.{});
+
+ // Standard optimization options allow the person running `zig build` to select
+ // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do nots
+ // set a preferred release mode, allowing the user to decide how to optimize.
+ const optimize = b.standardOptimizeOption(.{});
+
+ const dep_opts = .{ .target = target, .optimize = optimize };
+
+ const exe = b.addExecutable(.{
+ .name = "httpz",
+ // In this case the main source file is merely a path, however, in more
+ // complicated build scripts, this could be a generated file.
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ var modules = ModuleMap.init(allocator);
+ defer modules.deinit();
+
+ const httpz_module = b.dependency("httpz", dep_opts).module("httpz");
+ const pg_module = b.dependency("pg", dep_opts).module("pg");
+ const datetimez_module = b.dependency("datetimez", dep_opts).module("zig-datetime");
+ const mustache_module = b.dependency("mustache", dep_opts).module("mustache");
+
+ try modules.put("httpz", httpz_module);
+ try modules.put("pg", pg_module);
+ try modules.put("datetimez", datetimez_module);
+ try modules.put("mustache", mustache_module);
+
+ // // Expose this as a module that others can import
+ exe.root_module.addImport("httpz", httpz_module);
+ exe.root_module.addImport("pg", pg_module);
+ exe.root_module.addImport("datetimez", datetimez_module);
+ exe.root_module.addImport("mustache", mustache_module);
+
+ // This declares intent for the executable to be installed into the
+ // standard location when the user invokes the "install" step (the default
+ // step when running `zig build`).
+ b.installArtifact(exe);
+
+ // This *creates* a Run step in the build graph, to be executed when another
+ // step is evaluated that depends on it. The next line below will establish
+ // such a dependency.
+ const run_cmd = b.addRunArtifact(exe);
+
+ // By making the run step depend on the install step, it will be run from the
+ // installation directory rather than directly from within the cache directory.
+ // This is not necessary, however, if the application depends on other installed
+ // files, this ensures they will be present and in the expected location.
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ // This allows the user to pass arguments to the application in the build
+ // command itself, like this: `zig build run -- arg1 arg2 etc`
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ // This creates a build step. It will be visible in the `zig build --help` menu,
+ // and can be selected like this: `zig build run`
+ // This will evaluate the `run` step rather than the default, which is "install".
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+}
diff --git a/frameworks/Zig/httpz/build.zig.zon b/frameworks/Zig/httpz/build.zig.zon
new file mode 100644
index 00000000000..58b494c2fe3
--- /dev/null
+++ b/frameworks/Zig/httpz/build.zig.zon
@@ -0,0 +1,19 @@
+.{ .name = "Zap testing", .version = "0.1.1", .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+}, .dependencies = .{
+ .pg = .{ .url = "https://github.com/karlseguin/pg.zig/archive/239a4468163a49d8c0d03285632eabe96003e9e2.tar.gz", .hash = "1220a1d7e51e2fa45e547c76a9e099c09d06e14b0b9bfc6baa89367f56f1ded399a0" },
+ .httpz = .{
+ .url = "git+https://github.com/karlseguin/http.zig?ref=zig-0.13#7d2ddae87af9b110783085c0ea6b03985faa4584",
+ .hash = "12208c1f2c5f730c4c03aabeb0632ade7e21914af03e6510311b449458198d0835d6",
+ },
+ .datetimez = .{
+ .url = "git+https://github.com/frmdstryr/zig-datetime#70aebf28fb3e137cd84123a9349d157a74708721",
+ .hash = "122077215ce36e125a490e59ec1748ffd4f6ba00d4d14f7308978e5360711d72d77f",
+ },
+ .mustache = .{
+ .url = "git+https://github.com/batiati/mustache-zig#ae5ecc1522da983dc39bb0d8b27f5d1b1d7956e3",
+ .hash = "1220ac9e3316ce71ad9cd66c7f215462bf5c187828b50bb3d386549bf6af004e3bb0",
+ },
+} }
diff --git a/frameworks/Zig/httpz/httpz.dockerfile b/frameworks/Zig/httpz/httpz.dockerfile
new file mode 100644
index 00000000000..5257b77ea18
--- /dev/null
+++ b/frameworks/Zig/httpz/httpz.dockerfile
@@ -0,0 +1,23 @@
+FROM fedora:40
+
+WORKDIR /httpz
+
+ENV PG_USER=benchmarkdbuser
+ENV PG_PASS=benchmarkdbpass
+ENV PG_DB=hello_world
+ENV PG_HOST=tfb-database
+ENV PG_PORT=5432
+
+COPY src src
+COPY build.zig.zon build.zig.zon
+COPY build.zig build.zig
+COPY run.sh run.sh
+
+RUN dnf install -y zig
+RUN zig version
+RUN zig build -Doptimize=ReleaseFast
+RUN cp /httpz/zig-out/bin/httpz /usr/local/bin
+
+EXPOSE 3000
+
+CMD ["sh", "run.sh"]
\ No newline at end of file
diff --git a/frameworks/Zig/httpz/run.sh b/frameworks/Zig/httpz/run.sh
new file mode 100644
index 00000000000..582c2ad0228
--- /dev/null
+++ b/frameworks/Zig/httpz/run.sh
@@ -0,0 +1,3 @@
+echo "Waiting for Httpz framework to start..."
+
+httpz
\ No newline at end of file
diff --git a/frameworks/Zig/httpz/src/endpoints.zig b/frameworks/Zig/httpz/src/endpoints.zig
new file mode 100644
index 00000000000..0ee22b274de
--- /dev/null
+++ b/frameworks/Zig/httpz/src/endpoints.zig
@@ -0,0 +1,192 @@
+const std = @import("std");
+const httpz = @import("httpz");
+const pg = @import("pg");
+const datetimez = @import("datetimez");
+const mustache = @import("mustache");
+
+const Allocator = std.mem.Allocator;
+const Thread = std.Thread;
+const Mutex = Thread.Mutex;
+const template = "
Fortunes| id | message |
{{#fortunes}}| {{id}} | {{message}} |
{{/fortunes}}
";
+
+pub const Global = struct {
+ pool: *pg.Pool,
+ prng: *std.rand.DefaultPrng,
+ allocator: Allocator,
+ mutex: std.Thread.Mutex = .{},
+};
+
+const Message = struct {
+ message: []const u8,
+};
+
+const World = struct {
+ id: i32,
+ randomNumber: i32,
+};
+
+const Fortune = struct {
+ id: i32,
+ message: []const u8,
+};
+
+pub fn plaintext(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
+ try setHeaders(global.allocator, res);
+
+ res.content_type = .TEXT;
+ res.body = "Hello, World!";
+}
+
+pub fn json(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
+ try setHeaders(global.allocator, res);
+
+ const message = Message{ .message = "Hello, World!" };
+
+ try res.json(message, .{});
+}
+
+pub fn db(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
+ try setHeaders(global.allocator, res);
+
+ global.mutex.lock();
+ const random_number = 1 + (global.prng.random().uintAtMost(u32, 9999));
+ global.mutex.unlock();
+
+ const world = getWorld(global.pool, random_number) catch |err| {
+ std.debug.print("Error querying database: {}\n", .{err});
+ return;
+ };
+
+ try res.json(world, .{});
+}
+
+pub fn fortune(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
+ try setHeaders(global.allocator, res);
+
+ const fortunes_html = try getFortunesHtml(global.allocator, global.pool);
+
+ res.header("content-type", "text/html; charset=utf-8");
+ res.body = fortunes_html;
+}
+
+fn getWorld(pool: *pg.Pool, random_number: u32) !World{
+ var conn = try pool.acquire();
+ defer conn.release();
+
+ const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number});
+
+ var row = row_result.?;
+ defer row.deinit() catch {};
+
+ return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) };
+}
+
+fn setHeaders(allocator: Allocator, res: *httpz.Response) !void {
+ res.header("Server", "Httpz");
+
+ const now = datetimez.datetime.Date.now();
+ const time = datetimez.datetime.Time.now();
+
+ // Wed, 17 Apr 2013 12:00:00 GMT
+ // Return date in ISO format YYYY-MM-DD
+ 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";
+ 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 });
+
+ //defer allocator.free(now_str);
+
+ res.header("Date", now_str);
+}
+
+fn getFortunesHtml(allocator: Allocator, pool: *pg.Pool) ![]const u8 {
+ const fortunes = try getFortunes(allocator, pool);
+
+ const raw = try mustache.allocRenderText(allocator, template,.{ .fortunes = fortunes });
+
+ // std.debug.print("mustache output {s}\n", .{raw});
+
+ const html = try deescapeHtml(allocator, raw);
+
+ // std.debug.print("html output {s}\n", .{html});
+
+ return html;
+}
+
+fn getFortunes(allocator: Allocator, pool: *pg.Pool) ![]const Fortune {
+ var conn = try pool.acquire();
+ defer conn.release();
+
+ var rows = try conn.query("SELECT id, message FROM Fortune", .{});
+ defer rows.deinit();
+
+ var fortunes = std.ArrayList(Fortune).init(allocator);
+ defer fortunes.deinit();
+
+ while (try rows.next()) |row| {
+ const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) };
+ try fortunes.append(current_fortune);
+ }
+
+ const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." };
+ try fortunes.append(zero_fortune);
+
+ const fortunes_slice = try fortunes.toOwnedSlice();
+ std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage);
+
+ return fortunes_slice;
+}
+
+fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool {
+ return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt);
+}
+
+fn deescapeHtml(allocator: Allocator, input: []const u8) ![]const u8 {
+ var output = std.ArrayList(u8).init(allocator);
+ defer output.deinit();
+
+ var i: usize = 0;
+ while (i < input.len) {
+ if (std.mem.startsWith(u8, input[i..], " ")) {
+ try output.append(' ');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], """)) {
+ try output.append('"');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], "&")) {
+ try output.append('&');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], "'")) {
+ try output.append('\'');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], "(")) {
+ try output.append('(');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], ")")) {
+ try output.append(')');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], "+")) {
+ try output.append('+');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], ",")) {
+ try output.append(',');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], ".")) {
+ try output.append('.');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], "/")) {
+ try output.append('/');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], ":")) {
+ try output.append(':');
+ i += 5;
+ } else if (std.mem.startsWith(u8, input[i..], ";")) {
+ try output.append(';');
+ i += 5;
+ } else {
+ try output.append(input[i]);
+ i += 1;
+ }
+ }
+
+ return output.toOwnedSlice();
+}
+
diff --git a/frameworks/Zig/httpz/src/main.zig b/frameworks/Zig/httpz/src/main.zig
new file mode 100644
index 00000000000..ae2c1a70ac4
--- /dev/null
+++ b/frameworks/Zig/httpz/src/main.zig
@@ -0,0 +1,71 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const httpz = @import("httpz");
+const pg = @import("pg");
+const datetimez = @import("datetimez");
+const pool = @import("pool.zig");
+
+const endpoints = @import("endpoints.zig");
+
+const RndGen = std.rand.DefaultPrng;
+const Allocator = std.mem.Allocator;
+const Pool = pg.Pool;
+
+var server: httpz.ServerCtx(*endpoints.Global,*endpoints.Global) = undefined;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{
+ .thread_safe = true,
+ }){};
+
+ const allocator = gpa.allocator();
+
+ var pg_pool = try pool.initPool(allocator);
+ defer pg_pool.deinit();
+
+ var prng = std.rand.DefaultPrng.init(@as(u64, @bitCast(std.time.milliTimestamp())));
+
+ var global = endpoints.Global{ .pool = pg_pool, .prng = &prng, .allocator = allocator };
+
+ server = try httpz.ServerApp(*endpoints.Global).init(allocator, .{
+ .port = 3000, .address = "0.0.0.0", }, &global);
+ defer server.deinit();
+
+ // now that our server is up, we register our intent to handle SIGINT
+ try std.posix.sigaction(std.posix.SIG.INT, &.{
+ .handler = .{.handler = shutdown},
+ .mask = std.posix.empty_sigset,
+ .flags = 0,
+ }, null);
+
+ var router = server.router();
+ router.get("/json", endpoints.json);
+ router.get("/plaintext", endpoints.plaintext);
+ router.get("/db", endpoints.db);
+ router.get("/fortunes", endpoints.fortune);
+
+ std.debug.print("Httpz listening at 0.0.0.0:{d}\n", .{3000});
+
+ try server.listen();
+}
+
+fn shutdown(_: c_int) callconv(.C) void {
+ // this will unblock the server.listen()
+ server.stop();
+}
+
+fn notFound(_: *httpz.Request, res: *httpz.Response) !void {
+ res.status = 404;
+
+ // you can set the body directly to a []u8, but note that the memory
+ // must be valid beyond your handler. Use the res.arena if you need to allocate
+ // memory for the body.
+ res.body = "Not Found";
+}
+
+// note that the error handler return `void` and not `!void`
+fn errorHandler(req: *httpz.Request, res: *httpz.Response, err: anyerror) void {
+ res.status = 500;
+ res.body = "Internal Server Error";
+ std.log.warn("httpz: unhandled exception for request: {s}\nErr: {}", .{req.url.raw, err});
+}
\ No newline at end of file
diff --git a/frameworks/Zig/httpz/src/pool.zig b/frameworks/Zig/httpz/src/pool.zig
new file mode 100644
index 00000000000..c41cb329540
--- /dev/null
+++ b/frameworks/Zig/httpz/src/pool.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const regex = @import("regex");
+const pg = @import("pg");
+
+const Allocator = std.mem.Allocator;
+const Pool = pg.Pool;
+const ArrayList = std.ArrayList;
+
+pub fn initPool(allocator: Allocator) !*pg.Pool {
+ const info = try parsePostgresConnStr(allocator);
+ //std.debug.print("Connection: {s}:{s}@{s}:{d}/{s}\n", .{ info.username, info.password, info.hostname, info.port, info.database });
+
+ const pg_pool = try Pool.init(allocator, .{
+ .size = 28,
+ .connect = .{
+ .port = info.port,
+ .host = info.hostname,
+ },
+ .auth = .{
+ .username = info.username,
+ .database = info.database,
+ .password = info.password,
+ },
+ .timeout = 10_000,
+ });
+
+ return pg_pool;
+}
+
+pub const ConnectionInfo = struct {
+ username: []const u8,
+ password: []const u8,
+ hostname: []const u8,
+ port: u16,
+ database: []const u8,
+};
+
+fn addressAsString(address: std.net.Address) ![]const u8 {
+ const bytes = @as(*const [4]u8, @ptrCast(&address.in.sa.addr));
+
+ var buffer: [256]u8 = undefined;
+ var source = std.io.StreamSource{ .buffer = std.io.fixedBufferStream(&buffer) };
+ var writer = source.writer();
+
+ //try writer.writeAll("Hello, World!");
+
+ try writer.print("{}.{}.{}.{}", .{
+ bytes[0],
+ bytes[1],
+ bytes[2],
+ bytes[3],
+ });
+
+ const output = source.buffer.getWritten();
+
+ return output;
+}
+
+fn parsePostgresConnStr(allocator: Allocator) !ConnectionInfo {
+ const pg_port = try getEnvVar(allocator, "PG_PORT", "5432");
+ // std.debug.print("tfb port {s}\n", .{pg_port});
+ var port = try std.fmt.parseInt(u16, pg_port, 0);
+
+ if (port == 0) {
+ port = 5432;
+ }
+
+ return ConnectionInfo{
+ .username = try getEnvVar(allocator, "PG_USER", "benchmarkdbuser"),
+ .password = try getEnvVar(allocator, "PG_PASS", "benchmarkdbpass"),
+ .hostname = try getEnvVar(allocator, "PG_HOST", "localhost"),
+ .port = port,
+ .database = try getEnvVar(allocator, "PG_DB", "hello_world"),
+ };
+}
+
+fn getEnvVar(allocator: Allocator, name: []const u8, default: []const u8) ![]const u8 {
+ const env_var = std.process.getEnvVarOwned(allocator, name) catch |err| switch (err) {
+ error.EnvironmentVariableNotFound => return default,
+ error.OutOfMemory => return err,
+ error.InvalidWtf8 => return err,
+ };
+
+ if (env_var.len == 0) return default;
+
+ return env_var;
+}