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: 0 additions & 2 deletions frameworks/Zig/httpz/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ pub fn build(b: *std.Build) !void {
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");

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
Expand Down
4 changes: 0 additions & 4 deletions frameworks/Zig/httpz/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@
.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: 14 additions & 9 deletions frameworks/Zig/httpz/httpz.dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
FROM fedora:40

WORKDIR /httpz
FROM debian:12.9

ENV PG_USER=benchmarkdbuser
ENV PG_PASS=benchmarkdbpass
ENV PG_DB=hello_world
ENV PG_HOST=tfb-database
ENV PG_PORT=5432

WORKDIR /app

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
ARG ZIG_VER=0.13.0

RUN apt-get update && apt-get install -y curl xz-utils ca-certificates

RUN curl https://ziglang.org/download/${ZIG_VER}/zig-linux-$(uname -m)-${ZIG_VER}.tar.xz -o zig-linux.tar.xz && \
tar xf zig-linux.tar.xz && \
mv zig-linux-$(uname -m)-${ZIG_VER}/ /opt/zig

RUN /opt/zig/zig build -Doptimize=ReleaseFast

EXPOSE 3000
RUN ls

CMD ["sh", "run.sh"]
CMD ["zig-out/bin/httpz"]
3 changes: 0 additions & 3 deletions frameworks/Zig/httpz/run.sh

This file was deleted.

100 changes: 32 additions & 68 deletions frameworks/Zig/httpz/src/endpoints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ const std = @import("std");
const httpz = @import("httpz");
const pg = @import("pg");
const datetimez = @import("datetimez");
const mustache = @import("mustache");

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 var date_str: []u8 = "";

pub const Global = struct {
pool: *pg.Pool,
rand: *std.rand.Random,
mutex: std.Thread.Mutex = .{},
};

const World = struct {
Expand Down Expand Up @@ -40,9 +36,7 @@ pub fn json(_: *Global, _: *httpz.Request, res: *httpz.Response) !void {
pub fn db(global: *Global, _: *httpz.Request, res: *httpz.Response) !void {
try setHeaders(res.arena, res);

global.mutex.lock();
const random_number = 1 + (global.rand.uintAtMostBiased(u32, 9999));
global.mutex.unlock();

const world = getWorld(global.pool, random_number) catch |err| {
std.debug.print("Error querying database: {}\n", .{err});
Expand Down Expand Up @@ -76,31 +70,37 @@ fn getWorld(pool: *pg.Pool, random_number: u32) !World {
fn setHeaders(allocator: std.mem.Allocator, res: *httpz.Response) !void {
res.header("Server", "Httpz");

const now = datetimez.datetime.Date.now();
const time = datetimez.datetime.Time.now();
//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 });
//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);
res.header("Date", try allocator.dupe(u8, date_str));
}

fn getFortunesHtml(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const u8 {
const fortunes = try getFortunes(allocator, pool);

const raw = try mustache.allocRenderText(allocator, template, .{ .fortunes = fortunes });
var sb = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);

// std.debug.print("mustache output {s}\n", .{raw});
const writer = sb.writer(allocator);
try sb.appendSlice(allocator, "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>");

const html = try deescapeHtml(allocator, raw);
for (fortunes) |ft| {
try writer.print("<tr><td>{d}</td><td>{s}</td></tr>", .{
ft.id,
try escape_html(allocator, ft.message),
});
}

// std.debug.print("html output {s}\n", .{html});
try sb.appendSlice(allocator, "</table></body></html>");

return html;
return sb.toOwnedSlice(allocator);
}

fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune {
Expand All @@ -110,18 +110,18 @@ fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune {
var rows = try conn.query("SELECT id, message FROM Fortune", .{});
defer rows.deinit();

var fortunes = std.ArrayList(Fortune).init(allocator);
defer fortunes.deinit();
var fortunes = try std.ArrayListUnmanaged(Fortune).initCapacity(allocator, 0);
defer fortunes.deinit(allocator);

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);
try fortunes.append(allocator, current_fortune);
}

const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." };
try fortunes.append(zero_fortune);
try fortunes.append(allocator, zero_fortune);

const fortunes_slice = try fortunes.toOwnedSlice();
const fortunes_slice = try fortunes.toOwnedSlice(allocator);
std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage);

return fortunes_slice;
Expand All @@ -131,53 +131,17 @@ 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: std.mem.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;
fn escape_html(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
var output = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);
defer output.deinit(allocator);

for (input) |char| {
switch (char) {
'<' => try output.appendSlice(allocator, "&lt;"),
'>' => try output.appendSlice(allocator, "&gt;"),
else => try output.append(allocator, char),
}
}

return output.toOwnedSlice();
return output.toOwnedSlice(allocator);
}
41 changes: 19 additions & 22 deletions frameworks/Zig/httpz/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ pub fn main() !void {
var pg_pool = try pool.initPool(allocator);
defer pg_pool.deinit();

const date_thread = try std.Thread.spawn(.{}, struct {
fn update() !void {
const ally = std.heap.page_allocator;
while (true) {
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";
endpoints.date_str = try std.fmt.allocPrint(ally, TB_DATE_FMT, .{ now.weekdayName()[0..3], now.day, now.monthName()[0..3], now.year, time.hour, time.minute, time.second });
std.time.sleep(std.time.ns_per_ms * 980);
}
}
}.update, .{});

date_thread.detach();

var prng = std.rand.DefaultPrng.init(@as(u64, @bitCast(std.time.milliTimestamp())));

var rand = prng.random();
Expand All @@ -29,10 +47,7 @@ pub fn main() !void {
.rand = &rand,
};

const args = try std.process.argsAlloc(allocator);

const port: u16 = if (args.len > 1) try std.fmt.parseInt(u16, args[1], 0) else 3000;

const port: u16 = 3000;
const workers = @as(u16, @intCast(16 * cpu_count));

server = try httpz.ServerApp(*endpoints.Global).init(allocator, .{
Expand All @@ -55,10 +70,6 @@ pub fn main() !void {
// static buffers. For example, if response headers don't fit in in
// $response.header_buffer_size, a buffer will be pulled from here.
// This is per-worker.
.large_buffer_count = 16,

// The size of each large buffer.
.large_buffer_size = 65536,

// Size of bytes retained for the connection arena between use. This will
// result in up to `count * min_conn * retain_allocated_bytes` of memory usage.
Expand All @@ -77,22 +88,8 @@ pub fn main() !void {
// This applies back pressure to the above workers and ensures that, under load
// pending requests get precedence over processing new requests.
.backlog = 2048,

// Size of the static buffer to give each thread. Memory usage will be
// `count * buffer_size`. If you're making heavy use of either `req.arena` or
// `res.arena`, this is likely the single easiest way to gain performance.
.buffer_size = 8192,
},
.request = .{
// Maximum request body size that we'll process. We can allocate up
// to this much memory per request for the body. Internally, we might
// keep this memory around for a number of requests as an optimization.
.max_body_size = 1_048_576,

// This memory is allocated upfront. The request header _must_ fit into
// this space, else the request will be rejected.
.buffer_size = 4_096,

// Maximum number of headers to accept.
// Additional headers will be silently ignored.
.max_header_count = 32,
Expand Down
Loading