Skip to content

Commit

Permalink
feat: add support for zig (vmware-labs#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
voigt committed Aug 14, 2023
1 parent 42dfc27 commit 28f5073
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 0 deletions.
2 changes: 2 additions & 0 deletions kits/zig/worker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zig-cache/
zig-out/
11 changes: 11 additions & 0 deletions kits/zig/worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Zig kit

This folder contains the Zig kit or SDK for Wasm Workers Server. Currently, it uses the regular STDIN / STDOUT approach to receive the request and provide the response.

## build

To build example in ./examples

```
$ zig build -Dtarget="wasm32-wasi"
```
84 changes: 84 additions & 0 deletions kits/zig/worker/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const std = @import("std");

const package_name = "worker";
const package_path = "src/worker.zig";

const examples = [2][]const u8{ "main", "basic" };

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{ });
const optimize = b.standardOptimizeOption(.{});

const worker_module = b.createModule(.{
.source_file = .{ .path = package_path },
});

inline for (examples) |example| {
const exe = b.addExecutable(.{
.name = example,
.root_source_file = .{ .path = "examples/" ++ example ++ ".zig" },
.target = target,
.optimize = optimize,
});

exe.addModule("worker", worker_module);

b.installArtifact(exe);
}

// const exe = b.addExecutable(.{
// .name = "worker",
// // 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 = .{ .path = "examples/main.zig" },
// .target = target,
// .optimize = optimize,
// });

// exe.addModule("worker", worker_module);
// // exe.install();

// // 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);

// // Creates a step for unit testing. This only builds the test executable
// // but does not run it.
// const unit_tests = b.addTest(.{
// .root_source_file = .{ .path = "src/main.zig" },
// .target = target,
// .optimize = optimize,
// });

// const run_unit_tests = b.addRunArtifact(unit_tests);

// // Similar to creating the run step earlier, this exposes a `test` step to
// // the `zig build --help` menu, providing a way for the user to request
// // running the unit tests.
// const test_step = b.step("test", "Run unit tests");
// test_step.dependOn(&run_unit_tests.step);
}
7 changes: 7 additions & 0 deletions kits/zig/worker/examples/basic.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const std = @import("std");
const w = @import("worker");

pub fn main() !void {
var word = w.GetWord();
std.debug.print("Hello from {s}!\n", .{ word });
}
7 changes: 7 additions & 0 deletions kits/zig/worker/examples/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const std = @import("std");
const w = @import("worker");

pub fn main() !void {
var word = w.GetWord();
std.debug.print("Hello from {s}!\n", .{ word });
}
279 changes: 279 additions & 0 deletions kits/zig/worker/src/worker.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
const std = @import("std");
const io = std.io;
const http = std.http;

pub fn GetWord() []const u8 {
return "lib";
}

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();

pub var cache = std.StringHashMap([]const u8).init(allocator);
pub var params = std.StringHashMap([]const u8).init(allocator);

pub const Input = struct {
url: []const u8,
method: []const u8,
headers: std.StringHashMap([]const u8),
body: []const u8,
};

pub const Output = struct {
data: []const u8,
headers: std.StringArrayHashMap([]const u8),
status: u16,
base64: bool,

httpHeader: http.Headers,

const Self = @This();

pub fn init() Self {
return .{
.data = "",
.headers = std.StringArrayHashMap([]const u8).init(allocator),
.status = 0,
.base64 = false,
.httpHeader = http.Headers.init(allocator),
};
}

pub fn header(self: *Self) http.Headers {
if (self.httpHeader == undefined) {
self.httpHeader = http.Headers.init(allocator);
}

return self.httpHeader;
}

pub fn writeHeader(self: *Self, statusCode: u16) void {
self.status = statusCode;
}

pub fn write(self: *Self, data: []const u8) !u32 {
// if (isValidUtf8(data)) {
if (isValidUtf8(data)) {
self.data = data;
} else {
self.base64 = true;
// is this correct?
const enc = std.base64.Base64Encoder.init(std.base64.url_safe_alphabet_chars, '=');
var dest: []u8 = undefined;
self.data = std.base64.Base64Encoder.encode(&enc, dest, data);
}

if (self.status == 0) {
self.status = 200;
}

// self.headers.init(allocator); // can we init thissomewhere else?
for (self.httpHeader.list.items) |item| {
std.debug.print("Fields: {!}\n", .{item});
// try self.headers.put(item.name, item.value);
}

// prepare writer for json
var out_buf: [1024]u8 = undefined;
var slice_stream = std.io.fixedBufferStream(&out_buf);
const out = slice_stream.writer();
var w = std.json.writeStream(out, .{ .whitespace = .indent_2 });

slice_stream.reset();
try w.beginObject();

try w.objectField("data");
try w.write(self.data);

try w.objectField("status");
try w.write(self.status);

try w.objectField("base64");
try w.write(self.base64);

try w.objectField("headers");
try self.headers.put("hello", "world");
try w.write(try getHeadersJsonObject(self.headers));

try cache.put("hello", "world");
try w.objectField("kv");
try w.write(try getCacheJsonObject(cache));

try w.endObject();
const result = slice_stream.getWritten();

// https://zig.news/kristoff/where-is-print-in-zig-57e9
// std.debug.print("{s}", .{out});
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}", .{result});

return self.data.len;
}
};

fn getHeadersJsonObject(s: std.StringArrayHashMap([]const u8)) !std.json.Value {
var value = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };

var i = s.iterator();
while (i.next()) |kv| {
try value.object.put(kv.key_ptr.*, std.json.Value{ .string = kv.value_ptr.*});
}

return value;
}

fn getCacheJsonObject(s: std.StringHashMap([]const u8)) !std.json.Value {
var value = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };

var i = s.iterator();
while (i.next()) |kv| {
try value.object.put(kv.key_ptr.*, std.json.Value{ .string = kv.value_ptr.*});
}

return value;
}

pub fn readInput() !Input {
// https://www.openmymind.net/Reading-A-Json-Config-In-Zig/
const in = std.io.getStdIn();
var buf = std.io.bufferedReader(in.reader());
var r = buf.reader();

var msg_buf: [4096]u8 = undefined;

// delimiter "\n" might not be adequate?
if (r.readUntilDelimiterOrEof(&msg_buf, '\n')) |msg| {
if (msg) | m | {
std.debug.print("json: {s}\n", .{m});
return getInput(m);
}
} else |err| {
std.debug.print("error parsing json: {!}\n", .{err});
}

return Input{};
}

fn getInput(s: []const u8) !Input {
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, s, .{});
defer parsed.deinit();

var input = Input{
.url = parsed.value.object.get("url").?.string,
.method = parsed.value.object.get("method").?.string,
.body = parsed.value.object.get("body").?.string,
.headers = std.StringHashMap([]const u8).init(allocator),
};

var headers_map = parsed.value.object.get("headers").?.object;

// can we maybe use an iterator here?
for (headers_map.keys()) |key| {
var v = try headers_map.getOrPut(key);
if (v.found_existing) {
var value = v.value_ptr.*.string;
std.debug.print("headers key: {s}, value: {s}\n", .{key, value});
try input.headers.put(key, value);
}
}

return input;
}



pub fn createRequest(in: *Input) !http.Client.Request {

// Create an HTTP client.
var client = http.Client{ .allocator = allocator };
// Release all associated resources with the client.
defer client.deinit();
// Parse the URI.

var req = client.Request{
.uri = in.url,
.method = in.method,
.headers = in.headers,
};

// req = req.WithContext(context.WithValue(req.Context(), CacheKey, cache));
// req = req.WithContext(context.WithValue(req.Context(), ParamsKey, params));

return req;
}

pub fn getWriterRequest() !@This() {
var in = readInput(allocator) catch |err| {
std.debug.print("error reading input: {s}\n", .{err});
return std.os.exit(1);
};

var req = createRequest(&in, allocator) catch |err| {
std.debug.print("error creating request : {s}\n", .{err});
return std.os.exit(1);
};

var w = Output{
.headers = std.StringHashMap([]const u8).init(allocator),
};

return .{
.req = req,
.w = w,
};
}

// works
// pub fn ServeFunc(f: anytype) void {
// f();
// }


// works as function budy, must be comptime-known
// pub fn ServeFunc(comptime cool: fn () void) void {
// cool();
// }

// works as function pointer
pub fn ServeFunc(cool: *const fn (*http.Server.Response) void) void {
var r = try getWriterRequest(allocator);
cool(r.req);
}


// This is from ChatGPT - I have no clue whether this works, nor whats going on here :D
fn isValidUtf8(data: []const u8) bool {
var i: usize = 0;
while (i < data.len) {
const byte: u8 = data[i];
if (byte < 0x80) {
// ASCII character
i += 1;
} else if (byte < 0xC2) {
// Invalid continuation byte
return false;
} else if (byte < 0xE0) {
// 2-byte sequence
if ((i + 1 >= data.len) || ((data[i + 1] & 0xC0) != 0x80)) {
return false;
}
i += 2;
} else if (byte < 0xF0) {
// 3-byte sequence
if ((i + 2 >= data.len) || ((data[i + 1] & 0xC0) != 0x80) || ((data[i + 2] & 0xC0) != 0x80)) {
return false;
}
i += 3;
} else if (byte < 0xF5) {
// 4-byte sequence
if ((i + 3 >= data.len) || ((data[i + 1] & 0xC0) != 0x80) || ((data[i + 2] & 0xC0) != 0x80) || ((data[i + 3] & 0xC0) != 0x80)) {
return false;
}
i += 4;
} else {
// Invalid UTF-8 byte
return false;
}
}
return true;
}

0 comments on commit 28f5073

Please sign in to comment.