Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frameworks/Zig/httpz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zig-cache/**/*',
zig-out: 'zig-out/**/*',
25 changes: 25 additions & 0 deletions frameworks/Zig/httpz/README.md
Original file line number Diff line number Diff line change
@@ -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

26 changes: 26 additions & 0 deletions frameworks/Zig/httpz/benchmark_config.json
Original file line number Diff line number Diff line change
@@ -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": ""
}
}]
}
78 changes: 78 additions & 0 deletions frameworks/Zig/httpz/build.zig
Original file line number Diff line number Diff line change
@@ -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);
}
19 changes: 19 additions & 0 deletions frameworks/Zig/httpz/build.zig.zon
Original file line number Diff line number Diff line change
@@ -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",
},
} }
23 changes: 23 additions & 0 deletions frameworks/Zig/httpz/httpz.dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
3 changes: 3 additions & 0 deletions frameworks/Zig/httpz/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
echo "Waiting for Httpz framework to start..."

httpz
192 changes: 192 additions & 0 deletions frameworks/Zig/httpz/src/endpoints.zig
Original file line number Diff line number Diff line change
@@ -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 = "<!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>";

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..], "&#32;")) {
try output.append(' ');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#34;")) {
try output.append('"');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#38;")) {
try output.append('&');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#39;")) {
try output.append('\'');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#40;")) {
try output.append('(');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#41;")) {
try output.append(')');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#43;")) {
try output.append('+');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#44;")) {
try output.append(',');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#46;")) {
try output.append('.');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#47;")) {
try output.append('/');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#58;")) {
try output.append(':');
i += 5;
} else if (std.mem.startsWith(u8, input[i..], "&#59;")) {
try output.append(';');
i += 5;
} else {
try output.append(input[i]);
i += 1;
}
}

return output.toOwnedSlice();
}

Loading
Loading