From 6564f1c52dc02deba0fc930b68bf5ba45be9a477 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 2 Feb 2024 15:20:02 -0500 Subject: [PATCH 01/22] chore: zig fmt fixes in Zig 0.12 --- examples/zig-kv/build.zig | 11 +++++--- examples/zig-kv/src/worker-kv.zig | 10 ++++---- kits/zig/worker/src/worker.zig | 42 ++++++++++++++++++------------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/examples/zig-kv/build.zig b/examples/zig-kv/build.zig index 8f5b624a..756d0a13 100644 --- a/examples/zig-kv/build.zig +++ b/examples/zig-kv/build.zig @@ -1,13 +1,16 @@ const std = @import("std"); -const examples = [1][]const u8{ "worker-kv" }; +const examples = [1][]const u8{"worker-kv"}; pub fn build(b: *std.Build) !void { - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" }); + const target = b.resolveTargetQuery(.{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }); const optimize = b.standardOptimizeOption(.{}); const worker_module = b.createModule(.{ - .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, + .root_source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, }); inline for (examples) |example| { @@ -19,7 +22,7 @@ pub fn build(b: *std.Build) !void { }); exe.wasi_exec_model = .reactor; - exe.addModule("worker", worker_module); + exe.root_module.addImport("worker", worker_module); b.installArtifact(exe); } diff --git a/examples/zig-kv/src/worker-kv.zig b/examples/zig-kv/src/worker-kv.zig index 3f1bcd78..f39812ae 100644 --- a/examples/zig-kv/src/worker-kv.zig +++ b/examples/zig-kv/src/worker-kv.zig @@ -8,15 +8,15 @@ fn requestFn(resp: *worker.Response, r: *worker.Request) void { var cache = r.context.cache; var counter: i32 = 0; - var v = cache.getOrPut("counter") catch undefined; + const v = cache.getOrPut("counter") catch undefined; if (!v.found_existing) { v.value_ptr.* = "0"; } else { - var counterValue = v.value_ptr.*; - var num = std.fmt.parseInt(i32, counterValue, 10) catch undefined; + const counterValue = v.value_ptr.*; + const num = std.fmt.parseInt(i32, counterValue, 10) catch undefined; counter = num + 1; - var num_s = std.fmt.allocPrint(allocator, "{d}", .{ counter }) catch undefined; + const num_s = std.fmt.allocPrint(allocator, "{d}", .{counter}) catch undefined; _ = cache.put("counter", num_s) catch undefined; } @@ -35,7 +35,7 @@ fn requestFn(resp: *worker.Response, r: *worker.Request) void { \\ ; - var body = std.fmt.allocPrint(allocator, s, .{ counter }) catch undefined; // add useragent + const body = std.fmt.allocPrint(allocator, s, .{counter}) catch undefined; // add useragent _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); _ = &resp.writeAll(body); diff --git a/kits/zig/worker/src/worker.zig b/kits/zig/worker/src/worker.zig index 0bfbfa1c..56899bcd 100644 --- a/kits/zig/worker/src/worker.zig +++ b/kits/zig/worker/src/worker.zig @@ -49,19 +49,19 @@ pub const Input = struct { }; pub const Output = struct { - data: []const u8, - headers: std.StringArrayHashMap([]const u8), - status: u16, - base64: bool, + data: []const u8, + headers: std.StringArrayHashMap([]const u8), + status: u16, + base64: bool, - httpHeader: http.Headers, + httpHeader: http.Headers, const Self = @This(); pub fn init() Self { return .{ .data = "", - .headers = std.StringArrayHashMap([]const u8).init(allocator), + .headers = std.StringArrayHashMap([]const u8).init(allocator), .status = 0, .base64 = false, .httpHeader = http.Headers.init(allocator), @@ -80,7 +80,7 @@ pub const Output = struct { self.status = statusCode; } - pub fn write(self: *Self, response: Response) !u32 { + pub fn write(self: *Self, response: Response) !u32 { self.base64 = response.base64; if (response.base64) { self.data = base64Encode(response.body); @@ -124,7 +124,9 @@ pub const Output = struct { const result = slice_stream.getWritten(); const stdout = std.io.getStdOut().writer(); - try stdout.print("{s}", .{ result }); + try stdout.print("{s}", .{result}); + + std.debug.print("{s}\n", .{result}); return self.data.len; } @@ -133,7 +135,7 @@ pub const Output = struct { fn base64Encode(data: []const u8) []const u8 { // This initializing Base64Encoder throws weird error if not wrapped in function (maybe Zig bug?) var enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); - var data_len = enc.calcSize(data.len); + const data_len = enc.calcSize(data.len); var buf: [16384]u8 = undefined; return enc.encode(buf[0..data_len], data); } @@ -143,7 +145,7 @@ fn getHeadersJsonObject(s: std.StringArrayHashMap([]const u8)) !std.json.Value { var i = s.iterator(); while (i.next()) |kv| { - try value.object.put(kv.key_ptr.*, std.json.Value{ .string = kv.value_ptr.*}); + try value.object.put(kv.key_ptr.*, std.json.Value{ .string = kv.value_ptr.* }); } return value; @@ -154,7 +156,7 @@ fn getCacheJsonObject(s: std.StringHashMap([]const u8)) !std.json.Value { var i = s.iterator(); while (i.next()) |entry| { - try value.object.put(entry.key_ptr.*, std.json.Value{ .string = entry.value_ptr.*}); + try value.object.put(entry.key_ptr.*, std.json.Value{ .string = entry.value_ptr.* }); } return value; @@ -165,7 +167,8 @@ pub fn readInput() !Input { var buf = std.io.bufferedReader(in.reader()); var r = buf.reader(); - var msg = try r.readAllAlloc(allocator, std.math.maxInt(u32)); + const msg = try r.readAllAlloc(allocator, std.math.maxInt(u32)); + std.debug.print("in {s}\n", .{msg}); return getInput(msg); } @@ -224,12 +227,12 @@ pub fn getWriterRequest() !RequestAndOutput { return std.os.exit(1); }; - var req = createRequest(&in) catch |err| { + const req = createRequest(&in) catch |err| { std.debug.print("error creating request : {!}\n", .{err}); return std.os.exit(1); }; - var output = Output.init(); + const output = Output.init(); return RequestAndOutput{ .request = req, @@ -250,17 +253,22 @@ pub const Context = struct { }; pub fn ServeFunc(requestFn: *const fn (*Response, *Request) void) void { - var r = try getWriterRequest(); + const r = try getWriterRequest(); var request = r.request; var output = r.output; - var response = Response{ .body = "", .base64 = false, .headers = http.Headers.init(allocator), .request = request, }; + var response = Response{ + .body = "", + .base64 = false, + .headers = http.Headers.init(allocator), + .request = request, + }; requestFn(&response, &request); output.httpHeader = response.headers; _ = output.write(response) catch |err| { - std.debug.print("error writing data: {!} \n", .{ err }); + std.debug.print("error writing data: {!} \n", .{err}); }; } From 97ef4ea510ea94726376445201cfab8b1de99d36 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 2 Feb 2024 16:19:54 -0500 Subject: [PATCH 02/22] feat: add Zig wws module (Fixes #218) This commit introduces `kits/zig/wws`, with the intention of replacing `kits/zig/worker`. The design of the new SDK improves upon the previous one by exposing utilities for parsing wws requests and serializing wws responses, to give the user more control over memory while still keeping the SDK easy to use. The name was changed to make it more ergonomic within the Zig ecosystem (the developer will pull in a `wws` dependency to interface with wws, rather than a `worker` dependency). --- examples/zig-module/build.zig | 34 +++++ examples/zig-module/build.zig.zon | 65 ++++++++ examples/zig-module/src/main.zig | 67 +++++++++ kits/zig/wws/build.zig | 44 ++++++ kits/zig/wws/build.zig.zon | 62 ++++++++ kits/zig/wws/src/wws.zig | 242 ++++++++++++++++++++++++++++++ 6 files changed, 514 insertions(+) create mode 100644 examples/zig-module/build.zig create mode 100644 examples/zig-module/build.zig.zon create mode 100644 examples/zig-module/src/main.zig create mode 100644 kits/zig/wws/build.zig create mode 100644 kits/zig/wws/build.zig.zon create mode 100644 kits/zig/wws/src/wws.zig diff --git a/examples/zig-module/build.zig b/examples/zig-module/build.zig new file mode 100644 index 00000000..6d1e8639 --- /dev/null +++ b/examples/zig-module/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const wws = @import("wws"); + +pub fn build(b: *std.Build) !void { + const target = wws.getTarget(b); + const optimize = b.standardOptimizeOption(.{}); + + const wws_dep = b.dependency("wws", .{}); + + const exe = b.addExecutable(.{ + .name = "example", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + exe.wasi_exec_model = .reactor; + exe.root_module.addImport("wws", wws_dep.module("wws")); + + b.installArtifact(exe); + + const config = + \\name = "example" + \\version = "1" + \\[data] + \\[data.kv] + \\namespace = "example" + ; + const wf = b.addWriteFiles(); + const config_path = wf.add("example.toml", config); + + const install_config = b.addInstallBinFile(config_path, "example.toml"); + + b.getInstallStep().dependOn(&install_config.step); +} diff --git a/examples/zig-module/build.zig.zon b/examples/zig-module/build.zig.zon new file mode 100644 index 00000000..278a6bf8 --- /dev/null +++ b/examples/zig-module/build.zig.zon @@ -0,0 +1,65 @@ +.{ + .name = "zig-build", + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + //}, + .wws = .{ + .path = "../../kits/zig/wws", + }, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + // This makes *all* files, recursively, included in this package. It is generally + // better to explicitly list the files and directories instead, to insure that + // fetching from tarballs, file system paths, and version control all result + // in the same contents hash. + "", + // For example... + //"build.zig", + //"build.zig.zon", + //"src", + //"LICENSE", + //"README.md", + }, +} diff --git a/examples/zig-module/src/main.zig b/examples/zig-module/src/main.zig new file mode 100644 index 00000000..8646cafc --- /dev/null +++ b/examples/zig-module/src/main.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const wws = @import("wws"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + switch (gpa.deinit()) { + .ok => {}, + .leak => {}, + } + } + const allocator = gpa.allocator(); + + // Parse request from stdin + var event = try wws.parseStream(allocator, .{}); + defer event.destroy(); + const wws_request = event.request; + + // Prepare response + var body = std.ArrayList(u8).init(allocator); + defer body.deinit(); + + try body.writer().print("{any} {s}\n", .{ wws_request.method, wws_request.url }); + + { + var it = wws_request.storage.iterator(); + while (it.next()) |entry| { + try body.writer().print("kv.{s}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); + } + } + + var headers = wws.Headers.init(allocator); + defer headers.deinit(); + try headers.append("Content-Type", "text/plain"); + + var storage = std.StringHashMap([]const u8).init(allocator); + defer storage.deinit(); + defer { + var it = storage.iterator(); + while (it.next()) |entry| { + allocator.free(entry.value_ptr.*); + } + } + + var counter: usize = if (wws_request.storage.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; + + // Increment counter, save the result to storage + counter += 1; + + { + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + try buf.writer().print("{d}", .{counter}); + try storage.put("counter", try buf.toOwnedSlice()); + } + + const response = try wws.formatResponse(allocator, .{ + .data = body.items, + .status = 200, + .headers = &headers, + .storage = &storage, + }); + defer allocator.free(response); + + const stdout = std.io.getStdOut(); + try stdout.writer().print("{s}", .{response}); +} diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig new file mode 100644 index 00000000..4630e664 --- /dev/null +++ b/kits/zig/wws/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + _ = b.standardTargetOptions(.{}); + _ = b.standardOptimizeOption(.{}); + + const module = b.addModule("wws", .{ + .root_source_file = .{ .path = "src/wws.zig" }, + .target = getTarget(b), + }); + + _ = module; +} + +pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget { + return b.resolveTargetQuery(.{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }); +} + +pub const WwsLibOptions = struct { + name: []const u8, + root_source_file: std.Build.LazyPath, + optimize: std.builtin.OptimizeMode, + imports: []const std.Build.Module.Import = &.{}, +}; + +pub fn addExecutable(b: *std.Build, options: WwsLibOptions) *std.Build.Step.Compile { + const exe = b.addExecutable(.{ + .name = options.name, + .root_source_file = options.root_source_file, + .target = getTarget(b), + .optimize = options.optimize, + }); + + exe.wasi_exec_model = .reactor; + + for (options.imports) |import| { + exe.root_module.addImport(import.name, import.module); + } + + return exe; +} diff --git a/kits/zig/wws/build.zig.zon b/kits/zig/wws/build.zig.zon new file mode 100644 index 00000000..12078d0f --- /dev/null +++ b/kits/zig/wws/build.zig.zon @@ -0,0 +1,62 @@ +.{ + .name = "wws", + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + // This makes *all* files, recursively, included in this package. It is generally + // better to explicitly list the files and directories instead, to insure that + // fetching from tarballs, file system paths, and version control all result + // in the same contents hash. + "", + // For example... + //"build.zig", + //"build.zig.zon", + //"src", + //"LICENSE", + //"README.md", + }, +} diff --git a/kits/zig/wws/src/wws.zig b/kits/zig/wws/src/wws.zig new file mode 100644 index 00000000..e944daa1 --- /dev/null +++ b/kits/zig/wws/src/wws.zig @@ -0,0 +1,242 @@ +const std = @import("std"); + +pub const Method = std.http.Method; +pub const Headers = std.http.Headers; + +pub const Request = struct { + url: []const u8, + body: []const u8, + method: Method, + headers: *Headers, + storage: *std.StringHashMap([]const u8), + params: *std.StringHashMap([]const u8), +}; + +const InputStreamType = enum { + stdin, +}; + +const InputStream = union(InputStreamType) { + stdin: void, +}; + +const ParseStreamError = error{ OutOfMemory, UnknownError, MalformedRequestObject }; + +const ParseStreamOptions = struct { + input_stream: InputStream = .stdin, +}; + +const ParseStreamResult = struct { + allocator: std.mem.Allocator, + request: *Request, + + pub fn destroy(self: *ParseStreamResult) void { + { + var it = self.request.params.iterator(); + while (it.next()) |*entry| { + self.allocator.free(entry.key_ptr.*); + self.allocator.free(entry.value_ptr.*); + } + self.request.params.deinit(); + self.allocator.destroy(self.request.params); + } + + { + var it = self.request.storage.iterator(); + while (it.next()) |*entry| { + self.allocator.free(entry.key_ptr.*); + self.allocator.free(entry.value_ptr.*); + } + self.request.storage.deinit(); + self.allocator.destroy(self.request.storage); + } + + self.request.headers.deinit(); + self.allocator.destroy(self.request.headers); + + self.allocator.free(self.request.url); + self.allocator.free(self.request.body); + + self.allocator.destroy(self.request); + } +}; + +/// Caller owns the memory +pub fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!ParseStreamResult { + const stream = switch (options.input_stream) { + .stdin => std.io.getStdIn().reader(), + }; + + var input = std.ArrayList(u8).init(allocator); + defer input.deinit(); + + stream.readAllArrayList(&input, std.math.maxInt(usize)) catch return ParseStreamError.UnknownError; + + var json = std.json.parseFromSlice(std.json.Value, allocator, input.items, .{}) catch return ParseStreamError.UnknownError; + defer json.deinit(); + + const r = try allocator.create(Request); + + switch (json.value) { + .object => |root| { + switch (root.get("url") orelse return ParseStreamError.MalformedRequestObject) { + .string => |s| { + r.*.url = try allocator.dupe(u8, s); + }, + else => return ParseStreamError.MalformedRequestObject, + } + + switch (root.get("method") orelse return ParseStreamError.MalformedRequestObject) { + .string => |s| { + r.*.method = @enumFromInt(Method.parse(s)); + }, + else => return ParseStreamError.MalformedRequestObject, + } + + switch (root.get("headers") orelse return ParseStreamError.MalformedRequestObject) { + .object => |o| { + const headers = try allocator.create(Headers); + headers.* = Headers.init(allocator); + errdefer headers.deinit(); + + var it = o.iterator(); + while (it.next()) |kv| { + switch (kv.value_ptr.*) { + .string => |v| { + try headers.append(kv.key_ptr.*, v); + }, + else => return ParseStreamError.MalformedRequestObject, + } + } + + r.*.headers = headers; + }, + else => return ParseStreamError.MalformedRequestObject, + } + + switch (root.get("body") orelse return ParseStreamError.MalformedRequestObject) { + .string => |s| { + r.*.body = try allocator.dupe(u8, s); + }, + else => return ParseStreamError.MalformedRequestObject, + } + + switch (root.get("kv") orelse return ParseStreamError.MalformedRequestObject) { + .object => |o| { + const storage = try allocator.create(std.StringHashMap([]const u8)); + storage.* = std.StringHashMap([]const u8).init(allocator); + errdefer storage.deinit(); + + var it = o.iterator(); + while (it.next()) |kv| { + switch (kv.value_ptr.*) { + .string => |v| { + const owned_key = try allocator.dupe(u8, kv.key_ptr.*); + errdefer allocator.free(owned_key); + + const owned_value = try allocator.dupe(u8, v); + errdefer allocator.free(owned_value); + + try storage.put(owned_key, owned_value); + }, + else => return ParseStreamError.MalformedRequestObject, + } + } + + r.*.storage = storage; + }, + else => return ParseStreamError.MalformedRequestObject, + } + + switch (root.get("params") orelse return ParseStreamError.MalformedRequestObject) { + .object => |o| { + const params = try allocator.create(std.StringHashMap([]const u8)); + params.* = std.StringHashMap([]const u8).init(allocator); + errdefer params.deinit(); + + var it = o.iterator(); + while (it.next()) |kv| { + switch (kv.value_ptr.*) { + .string => |v| { + const owned_key = try allocator.dupe(u8, kv.key_ptr.*); + errdefer allocator.free(owned_key); + + const owned_value = try allocator.dupe(u8, v); + errdefer allocator.free(owned_value); + + try params.put(owned_key, owned_value); + }, + else => return ParseStreamError.MalformedRequestObject, + } + } + + r.*.params = params; + }, + else => return ParseStreamError.MalformedRequestObject, + } + }, + else => return ParseStreamError.MalformedRequestObject, + } + + return .{ + .allocator = allocator, + .request = r, + }; +} + +const FormatResponseOptions = struct { + data: []const u8, + status: usize, + headers: *Headers, + storage: *std.StringHashMap([]const u8), +}; + +const FormatResponseError = error{ + UnknownError, + OutOfMemory, +}; + +pub fn formatResponse(allocator: std.mem.Allocator, options: FormatResponseOptions) FormatResponseError![]const u8 { + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + + var w = std.json.writeStream(buf.writer(), .{ .whitespace = .minified }); + { + try w.beginObject(); + + try w.objectField("data"); + try w.write(std.json.Value{ .string = options.data }); + + try w.objectField("status"); + try w.write(options.status); + + { + var o = std.json.ObjectMap.init(allocator); + defer o.deinit(); + + for (options.headers.list.items) |entry| { + try o.put(entry.name, .{ .string = entry.value }); + } + + try w.objectField("headers"); + try w.write(std.json.Value{ .object = o }); + } + + { + var o = std.json.ObjectMap.init(allocator); + defer o.deinit(); + + var it = options.storage.iterator(); + while (it.next()) |entry| { + try o.put(entry.key_ptr.*, .{ .string = entry.value_ptr.* }); + } + + try w.objectField("kv"); + try w.write(std.json.Value{ .object = o }); + } + + try w.endObject(); + } + + return buf.toOwnedSlice() catch FormatResponseError.UnknownError; +} From 539eef09f86418fb5b6eedadddc81566da4d24fb Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 2 Feb 2024 20:01:28 -0500 Subject: [PATCH 03/22] chore: simplify zig wws request parsing --- examples/zig-module/src/main.zig | 19 +++- kits/zig/wws/src/wws.zig | 185 +++++-------------------------- 2 files changed, 39 insertions(+), 165 deletions(-) diff --git a/examples/zig-module/src/main.zig b/examples/zig-module/src/main.zig index 8646cafc..7a68b392 100644 --- a/examples/zig-module/src/main.zig +++ b/examples/zig-module/src/main.zig @@ -11,10 +11,17 @@ pub fn main() !void { } const allocator = gpa.allocator(); - // Parse request from stdin - var event = try wws.parseStream(allocator, .{}); - defer event.destroy(); - const wws_request = event.request; + const stdin = std.io.getStdIn(); + var input = std.ArrayList(u8).init(allocator); + defer input.deinit(); + try stdin.reader().readAllArrayList(&input, std.math.maxInt(usize)); + + std.debug.print("{s}\n", .{input.items}); + + var result = try wws.parseStream(allocator, .{ .input_stream = .{ .bytes = input.items } }); + defer result.deinit(); + const wws_request = result.value; + std.debug.print("{s} {any}\n", .{ wws_request.url, wws_request.method }); // Prepare response var body = std.ArrayList(u8).init(allocator); @@ -23,7 +30,7 @@ pub fn main() !void { try body.writer().print("{any} {s}\n", .{ wws_request.method, wws_request.url }); { - var it = wws_request.storage.iterator(); + var it = wws_request.kv.map.iterator(); while (it.next()) |entry| { try body.writer().print("kv.{s}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); } @@ -42,7 +49,7 @@ pub fn main() !void { } } - var counter: usize = if (wws_request.storage.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; + var counter: usize = if (wws_request.kv.map.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; // Increment counter, save the result to storage counter += 1; diff --git a/kits/zig/wws/src/wws.zig b/kits/zig/wws/src/wws.zig index e944daa1..c803ce88 100644 --- a/kits/zig/wws/src/wws.zig +++ b/kits/zig/wws/src/wws.zig @@ -3,185 +3,52 @@ const std = @import("std"); pub const Method = std.http.Method; pub const Headers = std.http.Headers; -pub const Request = struct { - url: []const u8, - body: []const u8, - method: Method, - headers: *Headers, - storage: *std.StringHashMap([]const u8), - params: *std.StringHashMap([]const u8), -}; - const InputStreamType = enum { stdin, + bytes, }; const InputStream = union(InputStreamType) { stdin: void, + bytes: []const u8, }; -const ParseStreamError = error{ OutOfMemory, UnknownError, MalformedRequestObject }; +const ParseStreamError = error{Unknown}; const ParseStreamOptions = struct { input_stream: InputStream = .stdin, }; -const ParseStreamResult = struct { - allocator: std.mem.Allocator, - request: *Request, - - pub fn destroy(self: *ParseStreamResult) void { - { - var it = self.request.params.iterator(); - while (it.next()) |*entry| { - self.allocator.free(entry.key_ptr.*); - self.allocator.free(entry.value_ptr.*); - } - self.request.params.deinit(); - self.allocator.destroy(self.request.params); - } - - { - var it = self.request.storage.iterator(); - while (it.next()) |*entry| { - self.allocator.free(entry.key_ptr.*); - self.allocator.free(entry.value_ptr.*); - } - self.request.storage.deinit(); - self.allocator.destroy(self.request.storage); - } - - self.request.headers.deinit(); - self.allocator.destroy(self.request.headers); - - self.allocator.free(self.request.url); - self.allocator.free(self.request.body); - - self.allocator.destroy(self.request); - } +const Request = struct { + url: []const u8, + method: Method, + body: []const u8, + headers: std.json.ArrayHashMap([]const u8), + kv: std.json.ArrayHashMap([]const u8), + params: std.json.ArrayHashMap([]const u8), }; /// Caller owns the memory -pub fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!ParseStreamResult { - const stream = switch (options.input_stream) { - .stdin => std.io.getStdIn().reader(), - }; - +pub fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!std.json.Parsed(Request) { var input = std.ArrayList(u8).init(allocator); defer input.deinit(); - stream.readAllArrayList(&input, std.math.maxInt(usize)) catch return ParseStreamError.UnknownError; - - var json = std.json.parseFromSlice(std.json.Value, allocator, input.items, .{}) catch return ParseStreamError.UnknownError; - defer json.deinit(); - - const r = try allocator.create(Request); - - switch (json.value) { - .object => |root| { - switch (root.get("url") orelse return ParseStreamError.MalformedRequestObject) { - .string => |s| { - r.*.url = try allocator.dupe(u8, s); - }, - else => return ParseStreamError.MalformedRequestObject, - } - - switch (root.get("method") orelse return ParseStreamError.MalformedRequestObject) { - .string => |s| { - r.*.method = @enumFromInt(Method.parse(s)); - }, - else => return ParseStreamError.MalformedRequestObject, - } - - switch (root.get("headers") orelse return ParseStreamError.MalformedRequestObject) { - .object => |o| { - const headers = try allocator.create(Headers); - headers.* = Headers.init(allocator); - errdefer headers.deinit(); - - var it = o.iterator(); - while (it.next()) |kv| { - switch (kv.value_ptr.*) { - .string => |v| { - try headers.append(kv.key_ptr.*, v); - }, - else => return ParseStreamError.MalformedRequestObject, - } - } - - r.*.headers = headers; - }, - else => return ParseStreamError.MalformedRequestObject, - } - - switch (root.get("body") orelse return ParseStreamError.MalformedRequestObject) { - .string => |s| { - r.*.body = try allocator.dupe(u8, s); - }, - else => return ParseStreamError.MalformedRequestObject, - } - - switch (root.get("kv") orelse return ParseStreamError.MalformedRequestObject) { - .object => |o| { - const storage = try allocator.create(std.StringHashMap([]const u8)); - storage.* = std.StringHashMap([]const u8).init(allocator); - errdefer storage.deinit(); - - var it = o.iterator(); - while (it.next()) |kv| { - switch (kv.value_ptr.*) { - .string => |v| { - const owned_key = try allocator.dupe(u8, kv.key_ptr.*); - errdefer allocator.free(owned_key); - - const owned_value = try allocator.dupe(u8, v); - errdefer allocator.free(owned_value); - - try storage.put(owned_key, owned_value); - }, - else => return ParseStreamError.MalformedRequestObject, - } - } - - r.*.storage = storage; - }, - else => return ParseStreamError.MalformedRequestObject, - } - - switch (root.get("params") orelse return ParseStreamError.MalformedRequestObject) { - .object => |o| { - const params = try allocator.create(std.StringHashMap([]const u8)); - params.* = std.StringHashMap([]const u8).init(allocator); - errdefer params.deinit(); - - var it = o.iterator(); - while (it.next()) |kv| { - switch (kv.value_ptr.*) { - .string => |v| { - const owned_key = try allocator.dupe(u8, kv.key_ptr.*); - errdefer allocator.free(owned_key); - - const owned_value = try allocator.dupe(u8, v); - errdefer allocator.free(owned_value); - - try params.put(owned_key, owned_value); - }, - else => return ParseStreamError.MalformedRequestObject, - } - } - - r.*.params = params; - }, - else => return ParseStreamError.MalformedRequestObject, - } + switch (options.input_stream) { + .stdin => { + const stdin = std.io.getStdIn().reader(); + stdin.readAllArrayList(&input, std.math.maxInt(usize)) catch return ParseStreamError.Unknown; + }, + .bytes => |s| { + input.appendSlice(s) catch return ParseStreamError.Unknown; }, - else => return ParseStreamError.MalformedRequestObject, } - return .{ - .allocator = allocator, - .request = r, - }; + return std.json.parseFromSlice( + Request, + allocator, + input.items, + .{ .allocate = .alloc_always }, + ) catch ParseStreamError.Unknown; } const FormatResponseOptions = struct { @@ -192,7 +59,7 @@ const FormatResponseOptions = struct { }; const FormatResponseError = error{ - UnknownError, + Unknown, OutOfMemory, }; @@ -238,5 +105,5 @@ pub fn formatResponse(allocator: std.mem.Allocator, options: FormatResponseOptio try w.endObject(); } - return buf.toOwnedSlice() catch FormatResponseError.UnknownError; + return buf.toOwnedSlice() catch FormatResponseError.Unknown; } From 4956f9717925328ef40c4cf0ae7164ea9f992c98 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 3 Feb 2024 13:20:16 -0500 Subject: [PATCH 04/22] chore: simplify Zig SDK reading request/writing response --- examples/zig-module/src/main.zig | 84 ++++++++++++++---------------- kits/zig/wws/src/wws.zig | 87 ++++++++------------------------ 2 files changed, 57 insertions(+), 114 deletions(-) diff --git a/examples/zig-module/src/main.zig b/examples/zig-module/src/main.zig index 7a68b392..a1c0d837 100644 --- a/examples/zig-module/src/main.zig +++ b/examples/zig-module/src/main.zig @@ -1,74 +1,64 @@ const std = @import("std"); const wws = @import("wws"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer { - switch (gpa.deinit()) { - .ok => {}, - .leak => {}, - } - } - const allocator = gpa.allocator(); +/// Receives an arena allocator such that all allocations made while handling +/// the request can be easily freed by the caller +fn handle(arena: *std.heap.ArenaAllocator, request: *const wws.Request) !wws.Response { + const allocator = arena.allocator(); - const stdin = std.io.getStdIn(); - var input = std.ArrayList(u8).init(allocator); - defer input.deinit(); - try stdin.reader().readAllArrayList(&input, std.math.maxInt(usize)); - - std.debug.print("{s}\n", .{input.items}); - - var result = try wws.parseStream(allocator, .{ .input_stream = .{ .bytes = input.items } }); - defer result.deinit(); - const wws_request = result.value; - std.debug.print("{s} {any}\n", .{ wws_request.url, wws_request.method }); - - // Prepare response + // Prepare response body var body = std.ArrayList(u8).init(allocator); defer body.deinit(); - try body.writer().print("{any} {s}\n", .{ wws_request.method, wws_request.url }); + try body.writer().print("{any} {s}\n", .{ request.method, request.url }); { - var it = wws_request.kv.map.iterator(); + var it = request.kv.map.iterator(); while (it.next()) |entry| { try body.writer().print("kv.{s}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); } } - var headers = wws.Headers.init(allocator); - defer headers.deinit(); - try headers.append("Content-Type", "text/plain"); - - var storage = std.StringHashMap([]const u8).init(allocator); - defer storage.deinit(); - defer { - var it = storage.iterator(); - while (it.next()) |entry| { - allocator.free(entry.value_ptr.*); - } - } - - var counter: usize = if (wws_request.kv.map.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; + var counter: usize = if (request.kv.map.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; // Increment counter, save the result to storage counter += 1; + // Prepare response + var response = wws.Response{ + .data = try body.toOwnedSlice(), + }; + + try response.headers.map.put(allocator, "Content-Type", "text/plain"); { var buf = std.ArrayList(u8).init(allocator); defer buf.deinit(); + try buf.writer().print("{d}", .{counter}); - try storage.put("counter", try buf.toOwnedSlice()); + try response.kv.map.put(allocator, "counter", try buf.toOwnedSlice()); + } + + return response; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + switch (gpa.deinit()) { + .ok => {}, + .leak => {}, + } } + const allocator = gpa.allocator(); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + const parse_result = try wws.parseStream(allocator, .{}); + defer parse_result.deinit(); - const response = try wws.formatResponse(allocator, .{ - .data = body.items, - .status = 200, - .headers = &headers, - .storage = &storage, - }); - defer allocator.free(response); + const response = try handle(&arena, &parse_result.value); const stdout = std.io.getStdOut(); - try stdout.writer().print("{s}", .{response}); + try wws.writeResponse(response, stdout.writer()); } diff --git a/kits/zig/wws/src/wws.zig b/kits/zig/wws/src/wws.zig index c803ce88..963a7c98 100644 --- a/kits/zig/wws/src/wws.zig +++ b/kits/zig/wws/src/wws.zig @@ -1,7 +1,22 @@ const std = @import("std"); pub const Method = std.http.Method; -pub const Headers = std.http.Headers; + +pub const Request = struct { + url: []const u8, + method: Method, + body: []const u8, + headers: std.json.ArrayHashMap([]const u8), + kv: std.json.ArrayHashMap([]const u8), + params: std.json.ArrayHashMap([]const u8), +}; + +pub const Response = struct { + headers: std.json.ArrayHashMap([]const u8) = .{}, + data: []const u8 = "", + status: usize = 200, + kv: std.json.ArrayHashMap([]const u8) = .{}, +}; const InputStreamType = enum { stdin, @@ -19,17 +34,8 @@ const ParseStreamOptions = struct { input_stream: InputStream = .stdin, }; -const Request = struct { - url: []const u8, - method: Method, - body: []const u8, - headers: std.json.ArrayHashMap([]const u8), - kv: std.json.ArrayHashMap([]const u8), - params: std.json.ArrayHashMap([]const u8), -}; - -/// Caller owns the memory -pub fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!std.json.Parsed(Request) { +/// Caller must call deinit on the returned result to free the associated memory +pub inline fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!std.json.Parsed(Request) { var input = std.ArrayList(u8).init(allocator); defer input.deinit(); @@ -51,59 +57,6 @@ pub fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) Pa ) catch ParseStreamError.Unknown; } -const FormatResponseOptions = struct { - data: []const u8, - status: usize, - headers: *Headers, - storage: *std.StringHashMap([]const u8), -}; - -const FormatResponseError = error{ - Unknown, - OutOfMemory, -}; - -pub fn formatResponse(allocator: std.mem.Allocator, options: FormatResponseOptions) FormatResponseError![]const u8 { - var buf = std.ArrayList(u8).init(allocator); - defer buf.deinit(); - - var w = std.json.writeStream(buf.writer(), .{ .whitespace = .minified }); - { - try w.beginObject(); - - try w.objectField("data"); - try w.write(std.json.Value{ .string = options.data }); - - try w.objectField("status"); - try w.write(options.status); - - { - var o = std.json.ObjectMap.init(allocator); - defer o.deinit(); - - for (options.headers.list.items) |entry| { - try o.put(entry.name, .{ .string = entry.value }); - } - - try w.objectField("headers"); - try w.write(std.json.Value{ .object = o }); - } - - { - var o = std.json.ObjectMap.init(allocator); - defer o.deinit(); - - var it = options.storage.iterator(); - while (it.next()) |entry| { - try o.put(entry.key_ptr.*, .{ .string = entry.value_ptr.* }); - } - - try w.objectField("kv"); - try w.write(std.json.Value{ .object = o }); - } - - try w.endObject(); - } - - return buf.toOwnedSlice() catch FormatResponseError.Unknown; +pub inline fn writeResponse(response: Response, writer: anytype) !void { + try std.json.stringify(response, .{}, writer); } From 7323f28a0201e429c50348b2d168c53c68526dfa Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 3 Feb 2024 17:08:52 -0500 Subject: [PATCH 05/22] chore: move examples/zig-module to examples/zig-examples --- examples/zig-examples/build.zig | 93 ++++++++++++++ .../build.zig.zon | 0 examples/zig-examples/src/echo.zig | 27 ++++ .../{zig-module => zig-examples}/src/main.zig | 0 examples/zig-module/build.zig | 34 ----- kits/zig/wws/README.md | 119 ++++++++++++++++++ kits/zig/wws/src/wws.zig | 2 + 7 files changed, 241 insertions(+), 34 deletions(-) create mode 100644 examples/zig-examples/build.zig rename examples/{zig-module => zig-examples}/build.zig.zon (100%) create mode 100644 examples/zig-examples/src/echo.zig rename examples/{zig-module => zig-examples}/src/main.zig (100%) delete mode 100644 examples/zig-module/build.zig create mode 100644 kits/zig/wws/README.md diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig new file mode 100644 index 00000000..00b47052 --- /dev/null +++ b/examples/zig-examples/build.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const wws = @import("wws"); + +const WwsKv = struct { + namespace: []const u8, +}; + +const WwsFeatures = struct { + kv: ?WwsKv = null, +}; + +const ModuleOptions = struct { + name: []const u8, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + features: WwsFeatures = .{}, + wws: *std.Build.Dependency, + root_source_file: std.Build.LazyPath, +}; + +fn addModule(b: *std.Build, options: ModuleOptions) !void { + const exe = b.addExecutable(.{ + .name = options.name, + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + }); + exe.wasi_exec_model = .reactor; + exe.root_module.addImport("wws", options.wws.module("wws")); + + b.installArtifact(exe); + + var buf = std.ArrayList(u8).init(b.allocator); + defer buf.deinit(); + + try buf.writer().print( + \\name = "{s}" + \\version = "1" + , + .{ + options.name, + }, + ); + + if (options.features.kv) |kv| { + + try buf.writer().print( + \\[data] + \\[data.kv] + \\namespace = "{s}" + , + .{ + kv.namespace, + }, + ); + } + + const wf = b.addWriteFiles(); + const config_name = b.fmt("{s}.toml", .{ options.name}); + const config_path = wf.add(config_name, try buf.toOwnedSlice(),); + + const install_config = b.addInstallBinFile(config_path, config_name); + + b.getInstallStep().dependOn(&install_config.step); +} + +pub fn build(b: *std.Build) !void { + const target = wws.getTarget(b); + const optimize = b.standardOptimizeOption(.{}); + + const wws_dep = b.dependency("wws", .{}); + + try addModule(b, .{ + .name = "example", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + .features = .{ + .kv = .{ + .namespace = "example", + }, + }, + }); + + try addModule(b, .{ + .name = "echo", + .root_source_file = .{ .path = "src/echo.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + }); +} diff --git a/examples/zig-module/build.zig.zon b/examples/zig-examples/build.zig.zon similarity index 100% rename from examples/zig-module/build.zig.zon rename to examples/zig-examples/build.zig.zon diff --git a/examples/zig-examples/src/echo.zig b/examples/zig-examples/src/echo.zig new file mode 100644 index 00000000..65c10db0 --- /dev/null +++ b/examples/zig-examples/src/echo.zig @@ -0,0 +1,27 @@ +const std=@import("std"); +const wws=@import("wws"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + switch (gpa.deinit()) { + .ok => {}, + .leak => {}, + } + } + const allocator = gpa.allocator(); + + // Read request from stdin + const parse_result = try wws.parseStream(allocator, .{}); + defer parse_result.deinit(); + + const request = parse_result.value; + + // Simple echo response + const response = wws.Response{ + .data = request.url, + }; + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} \ No newline at end of file diff --git a/examples/zig-module/src/main.zig b/examples/zig-examples/src/main.zig similarity index 100% rename from examples/zig-module/src/main.zig rename to examples/zig-examples/src/main.zig diff --git a/examples/zig-module/build.zig b/examples/zig-module/build.zig deleted file mode 100644 index 6d1e8639..00000000 --- a/examples/zig-module/build.zig +++ /dev/null @@ -1,34 +0,0 @@ -const std = @import("std"); -const wws = @import("wws"); - -pub fn build(b: *std.Build) !void { - const target = wws.getTarget(b); - const optimize = b.standardOptimizeOption(.{}); - - const wws_dep = b.dependency("wws", .{}); - - const exe = b.addExecutable(.{ - .name = "example", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - exe.wasi_exec_model = .reactor; - exe.root_module.addImport("wws", wws_dep.module("wws")); - - b.installArtifact(exe); - - const config = - \\name = "example" - \\version = "1" - \\[data] - \\[data.kv] - \\namespace = "example" - ; - const wf = b.addWriteFiles(); - const config_path = wf.add("example.toml", config); - - const install_config = b.addInstallBinFile(config_path, "example.toml"); - - b.getInstallStep().dependOn(&install_config.step); -} diff --git a/kits/zig/wws/README.md b/kits/zig/wws/README.md new file mode 100644 index 00000000..365dba5b --- /dev/null +++ b/kits/zig/wws/README.md @@ -0,0 +1,119 @@ +# 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. + +> *Note: Last tested with Zig `0.12.0-dev.2208+4debd4338`* + +## Usage + +Add wws as a dependency to your Zig project: + +```shell-session +$ zig fetch --save +``` + +Include it in your build.zig: + +```zig +const std = @import("std"); +const wws = @import("wws"); + +pub fn build(b: *std.Build) !void { + const target = wws.getTarget(b); + const optimize = b.standardOptimizeOption(.{}); + + const wws_dep = b.dependency("wws", .{}); + + const exe = b.addExecutable(.{ + .name = "example", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + exe.wasi_exec_model = .reactor; + exe.root_module.addImport("wws", wws_dep.module("wws")); + + b.installArtifact(exe); + + const config = + \\name = "example" + \\version = "1" + \\[data] + \\[data.kv] + \\namespace = "example" + ; + const wf = b.addWriteFiles(); + const config_path = wf.add("example.toml", config); + + const install_config = b.addInstallBinFile(config_path, "example.toml"); + + b.getInstallStep().dependOn(&install_config.step); +} +``` + +Read request from stdin and write response to stdout: + +```zig +const std=@import("std"); +const wws=@import("wws"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + switch (gpa.deinit()) { + .ok => {}, + .leak => { + // Handle memory leaks however you want (e.g. error logging). + // Note that if your main function returns an error, + // wws will not return the response you've crafted. + }, + } + } + const allocator = gpa.allocator(); + + // Read request from stdin + const parse_result = try wws.parseStream(allocator, .{}); + defer parse_result.deinit(); + + const request = parse_result.value; + + // Simple echo response + const response = wws.Response{ + .data = request.url, + }; + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} +``` + +## Build + +To build all examples in ./examples/zig-examples + +```shell-session +$ zig build all +``` + +To build a specific example, for example kv: + +```shell-session +$ zig build kv +``` + +## Run + +This assumes you have `wws` available in your PATH: + +```shell-session +$ zig build run +``` + +## Testing + +At the Zig SDK path, execute: + +```shell-session +$ zig build test +``` + diff --git a/kits/zig/wws/src/wws.zig b/kits/zig/wws/src/wws.zig index 963a7c98..5ade8ce3 100644 --- a/kits/zig/wws/src/wws.zig +++ b/kits/zig/wws/src/wws.zig @@ -14,6 +14,8 @@ pub const Request = struct { pub const Response = struct { headers: std.json.ArrayHashMap([]const u8) = .{}, data: []const u8 = "", + // TODO Use std.http.Status when Response has its own + // json serialization helper functions status: usize = 200, kv: std.json.ArrayHashMap([]const u8) = .{}, }; From 6fc6a1cf209180c056531029f8e17d2841b81f17 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Mon, 5 Feb 2024 19:19:51 -0500 Subject: [PATCH 06/22] feat: add Var to Zig SDK --- kits/zig/wws/build.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig index 4630e664..06885316 100644 --- a/kits/zig/wws/build.zig +++ b/kits/zig/wws/build.zig @@ -1,5 +1,7 @@ const std = @import("std"); +pub const Var = struct { []const u8, []const u8 }; + pub fn build(b: *std.Build) void { _ = b.standardTargetOptions(.{}); _ = b.standardOptimizeOption(.{}); From b6d6e3fa8089364bacdcbd7c6bf100b417fb4e8a Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 9 Feb 2024 10:24:24 -0500 Subject: [PATCH 07/22] chore: Expose `addWorker` from Zig wws SDK --- examples/zig-examples/build.zig | 116 ++++++++++---------------------- kits/zig/wws/build.zig | 114 ++++++++++++++++++++++++++++--- 2 files changed, 141 insertions(+), 89 deletions(-) diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index 00b47052..aafed547 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -1,93 +1,49 @@ const std = @import("std"); const wws = @import("wws"); -const WwsKv = struct { - namespace: []const u8, -}; - -const WwsFeatures = struct { - kv: ?WwsKv = null, -}; - -const ModuleOptions = struct { - name: []const u8, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, - features: WwsFeatures = .{}, - wws: *std.Build.Dependency, - root_source_file: std.Build.LazyPath, -}; - -fn addModule(b: *std.Build, options: ModuleOptions) !void { - const exe = b.addExecutable(.{ - .name = options.name, - .root_source_file = options.root_source_file, - .target = options.target, - .optimize = options.optimize, - }); - exe.wasi_exec_model = .reactor; - exe.root_module.addImport("wws", options.wws.module("wws")); - - b.installArtifact(exe); - - var buf = std.ArrayList(u8).init(b.allocator); - defer buf.deinit(); - - try buf.writer().print( - \\name = "{s}" - \\version = "1" - , - .{ - options.name, - }, - ); - - if (options.features.kv) |kv| { - - try buf.writer().print( - \\[data] - \\[data.kv] - \\namespace = "{s}" - , - .{ - kv.namespace, - }, - ); - } - - const wf = b.addWriteFiles(); - const config_name = b.fmt("{s}.toml", .{ options.name}); - const config_path = wf.add(config_name, try buf.toOwnedSlice(),); - - const install_config = b.addInstallBinFile(config_path, config_name); - - b.getInstallStep().dependOn(&install_config.step); -} - pub fn build(b: *std.Build) !void { const target = wws.getTarget(b); const optimize = b.standardOptimizeOption(.{}); const wws_dep = b.dependency("wws", .{}); - try addModule(b, .{ - .name = "example", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - .wws = wws_dep, - .features = .{ - .kv = .{ - .namespace = "example", + const wf = b.addWriteFiles(); + + { + const worker = try wws.addWorker(b, .{ + .name = "example", + .path = "example", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + .features = .{ + .kv = .{ + .namespace = "example", + }, }, - }, - }); + }); + + try worker.addToWriteFiles(b, wf); + } - try addModule(b, .{ - .name = "echo", - .root_source_file = .{ .path = "src/echo.zig" }, - .target = target, - .optimize = optimize, - .wws = wws_dep, + { + const worker = try wws.addWorker(b, .{ + .name = "echo", + .path = "echo", + .root_source_file = .{ .path = "src/echo.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + }); + try worker.addToWriteFiles(b, wf); + } + + const install = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "root", }); + + b.getInstallStep().dependOn(&install.step); } diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig index 06885316..cc96c009 100644 --- a/kits/zig/wws/build.zig +++ b/kits/zig/wws/build.zig @@ -1,12 +1,12 @@ const std = @import("std"); -pub const Var = struct { []const u8, []const u8 }; +const module_name = "wws"; pub fn build(b: *std.Build) void { _ = b.standardTargetOptions(.{}); _ = b.standardOptimizeOption(.{}); - const module = b.addModule("wws", .{ + const module = b.addModule(module_name, .{ .root_source_file = .{ .path = "src/wws.zig" }, .target = getTarget(b), }); @@ -14,6 +14,7 @@ pub fn build(b: *std.Build) void { _ = module; } +// Returns the wasm32-wasi target pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget { return b.resolveTargetQuery(.{ .cpu_arch = .wasm32, @@ -28,19 +29,114 @@ pub const WwsLibOptions = struct { imports: []const std.Build.Module.Import = &.{}, }; -pub fn addExecutable(b: *std.Build, options: WwsLibOptions) *std.Build.Step.Compile { +pub const EnvVar = struct { + name: []const u8, + value: []const u8, +}; + +pub const WwsKv = struct { + namespace: []const u8, +}; + +pub const WwsFeatures = struct { + vars: ?[]const EnvVar = null, + kv: ?WwsKv = null, +}; + +pub const WwsWorker = struct { + exe: *std.Build.Step.Compile, + options: ModuleOptions, + + pub fn addToWriteFiles(self: WwsWorker, b: *std.Build, wf: *std.Build.Step.WriteFile) !void { + _ = wf.addCopyFile(self.exe.getEmittedBin(), b.fmt("{s}.wasm", .{self.options.path})); + + const config = try self.formatConfig(b.allocator); + defer b.allocator.free(config); + _ = wf.add(b.fmt("{s}.toml", .{self.options.path}), config); + } + + // Very naively write the worker config to a string. Environment variable values + // and KV namespace name are automatically quoted. + pub fn formatConfig(self: WwsWorker, allocator: std.mem.Allocator) ![]u8 { + var buf = std.ArrayList(u8).init(allocator); + errdefer buf.deinit(); + + try buf.writer().print( + \\name = "{s}" + \\version = "1" + \\ + , + .{ + self.options.name, + }, + ); + + if (self.options.features.vars) |vars| { + try buf.writer().print( + \\[vars] + \\ + , + .{}, + ); + for (vars) |v| { + try buf.writer().print( + \\{s} = "{s}" + \\ + , + .{ v.name, v.value }, + ); + } + } + + if (self.options.features.kv) |kv| { + try buf.writer().print( + \\[data] + \\[data.kv] + \\namespace = "{s}" + \\ + , + .{ + kv.namespace, + }, + ); + } + + return try buf.toOwnedSlice(); + } +}; + +pub const ModuleOptions = struct { + // The name of the module, written to the worker config + name: []const u8, + root_source_file: std.Build.LazyPath, + // The relative path of the module and its worker config. + // E.g. `.{ .path = "api/[...params]" }` will write to + // `api/[...params].wasm` and `api/[...params].toml`. + path: []const u8, + // The worker features/config including KV namespace, + // environment variables, and more. These are written + // to the worker config. + features: WwsFeatures = .{}, + // You must provide the wws dependency to ensure the + // wws module is linked to your project. + wws: *std.Build.Dependency, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +}; + +pub fn addWorker(b: *std.Build, options: ModuleOptions) !WwsWorker { const exe = b.addExecutable(.{ .name = options.name, .root_source_file = options.root_source_file, - .target = getTarget(b), + .target = options.target, .optimize = options.optimize, }); exe.wasi_exec_model = .reactor; + exe.root_module.addImport("wws", options.wws.module(module_name)); - for (options.imports) |import| { - exe.root_module.addImport(import.name, import.module); - } - - return exe; + return .{ + .exe = exe, + .options = options, + }; } From 3add0b925e9809de9e40d6080e6894451224abef Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 9 Feb 2024 10:36:40 -0500 Subject: [PATCH 08/22] chore: Refactor zig-examples build --- examples/zig-examples/README.md | 17 ++++++++++++ examples/zig-examples/build.zig | 49 +++++++++++++++++++-------------- kits/zig/wws/build.zig | 17 ++++-------- 3 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 examples/zig-examples/README.md diff --git a/examples/zig-examples/README.md b/examples/zig-examples/README.md new file mode 100644 index 00000000..d9e991b1 --- /dev/null +++ b/examples/zig-examples/README.md @@ -0,0 +1,17 @@ +# zig-examples + +This example Zig project demonstrates how to use the WWS Zig SDK to compile WWS workers from Zig source code. + +# Building + +```sh +zig build +``` + +# Running + +This step assumes that you have `wws` installed on your system. + +```sh +wws zig-out/root +``` diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index aafed547..c61b5b52 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -1,6 +1,29 @@ const std = @import("std"); const wws = @import("wws"); +const Example = struct { + name: []const u8, + root_source_file: std.Build.LazyPath, + path: ?[]const u8 = null, + features: ?wws.Features = null, +}; + +const examples = &[_]Example{ + .{ + .name = "example", + .root_source_file = .{ .path = "src/main.zig" }, + .features = .{ + .kv = .{ + .namespace = "example", + }, + }, + }, + .{ + .name = "echo", + .root_source_file = .{ .path = "src/echo.zig" }, + }, +}; + pub fn build(b: *std.Build) !void { const target = wws.getTarget(b); const optimize = b.standardOptimizeOption(.{}); @@ -9,36 +32,20 @@ pub fn build(b: *std.Build) !void { const wf = b.addWriteFiles(); - { + inline for (examples) |e| { const worker = try wws.addWorker(b, .{ - .name = "example", - .path = "example", - .root_source_file = .{ .path = "src/main.zig" }, + .name = e.name, + .path = e.path orelse e.name, + .root_source_file = e.root_source_file, .target = target, .optimize = optimize, .wws = wws_dep, - .features = .{ - .kv = .{ - .namespace = "example", - }, - }, + .features = e.features orelse .{}, }); try worker.addToWriteFiles(b, wf); } - { - const worker = try wws.addWorker(b, .{ - .name = "echo", - .path = "echo", - .root_source_file = .{ .path = "src/echo.zig" }, - .target = target, - .optimize = optimize, - .wws = wws_dep, - }); - try worker.addToWriteFiles(b, wf); - } - const install = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), .install_dir = .prefix, diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig index cc96c009..5dc0b6f7 100644 --- a/kits/zig/wws/build.zig +++ b/kits/zig/wws/build.zig @@ -22,13 +22,6 @@ pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget { }); } -pub const WwsLibOptions = struct { - name: []const u8, - root_source_file: std.Build.LazyPath, - optimize: std.builtin.OptimizeMode, - imports: []const std.Build.Module.Import = &.{}, -}; - pub const EnvVar = struct { name: []const u8, value: []const u8, @@ -38,14 +31,14 @@ pub const WwsKv = struct { namespace: []const u8, }; -pub const WwsFeatures = struct { +pub const Features = struct { vars: ?[]const EnvVar = null, kv: ?WwsKv = null, }; pub const WwsWorker = struct { exe: *std.Build.Step.Compile, - options: ModuleOptions, + options: WorkerOptions, pub fn addToWriteFiles(self: WwsWorker, b: *std.Build, wf: *std.Build.Step.WriteFile) !void { _ = wf.addCopyFile(self.exe.getEmittedBin(), b.fmt("{s}.wasm", .{self.options.path})); @@ -105,7 +98,7 @@ pub const WwsWorker = struct { } }; -pub const ModuleOptions = struct { +pub const WorkerOptions = struct { // The name of the module, written to the worker config name: []const u8, root_source_file: std.Build.LazyPath, @@ -116,7 +109,7 @@ pub const ModuleOptions = struct { // The worker features/config including KV namespace, // environment variables, and more. These are written // to the worker config. - features: WwsFeatures = .{}, + features: Features = .{}, // You must provide the wws dependency to ensure the // wws module is linked to your project. wws: *std.Build.Dependency, @@ -124,7 +117,7 @@ pub const ModuleOptions = struct { optimize: std.builtin.OptimizeMode, }; -pub fn addWorker(b: *std.Build, options: ModuleOptions) !WwsWorker { +pub fn addWorker(b: *std.Build, options: WorkerOptions) !WwsWorker { const exe = b.addExecutable(.{ .name = options.name, .root_source_file = options.root_source_file, From 2878f623b9aade44e627a16918fa006308878a6e Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 17 Feb 2024 12:04:25 -0500 Subject: [PATCH 09/22] chore: move Zig examples into `zig-examples` --- examples/zig-basic/README.md | 32 ----------------- examples/zig-basic/build.zig | 26 -------------- examples/zig-envs/README.md | 33 ----------------- examples/zig-envs/build.zig | 26 -------------- examples/zig-envs/zig-out/bin/envs.toml | 5 --- examples/zig-examples/README.md | 28 ++++++++++++--- examples/zig-examples/build.zig | 35 +++++++++++++++++++ .../bin => zig-examples/src}/_images/zig.svg | 0 .../{zig-basic => zig-examples}/src/basic.zig | 0 examples/zig-examples/src/echo.zig | 13 +++---- .../{zig-envs => zig-examples}/src/envs.zig | 0 .../{zig-mount => zig-examples}/src/mount.zig | 0 .../src/params.zig} | 0 .../src/worker-kv.zig | 0 examples/zig-kv/README.md | 33 ----------------- examples/zig-kv/build.zig | 29 --------------- examples/zig-kv/zig-out/bin/worker-kv.toml | 6 ---- examples/zig-mount/build.zig | 26 -------------- examples/zig-mount/zig-out/bin/mount.toml | 6 ---- examples/zig-params/README.md | 33 ----------------- examples/zig-params/build.zig | 26 -------------- 21 files changed, 63 insertions(+), 294 deletions(-) delete mode 100644 examples/zig-basic/README.md delete mode 100644 examples/zig-basic/build.zig delete mode 100644 examples/zig-envs/README.md delete mode 100644 examples/zig-envs/build.zig delete mode 100644 examples/zig-envs/zig-out/bin/envs.toml rename examples/{zig-mount/zig-out/bin => zig-examples/src}/_images/zig.svg (100%) rename examples/{zig-basic => zig-examples}/src/basic.zig (100%) rename examples/{zig-envs => zig-examples}/src/envs.zig (100%) rename examples/{zig-mount => zig-examples}/src/mount.zig (100%) rename examples/{zig-params/src/worker-params.zig => zig-examples/src/params.zig} (100%) rename examples/{zig-kv => zig-examples}/src/worker-kv.zig (100%) delete mode 100644 examples/zig-kv/README.md delete mode 100644 examples/zig-kv/build.zig delete mode 100644 examples/zig-kv/zig-out/bin/worker-kv.toml delete mode 100644 examples/zig-mount/build.zig delete mode 100644 examples/zig-mount/zig-out/bin/mount.toml delete mode 100644 examples/zig-params/README.md delete mode 100644 examples/zig-params/build.zig diff --git a/examples/zig-basic/README.md b/examples/zig-basic/README.md deleted file mode 100644 index c6f1305e..00000000 --- a/examples/zig-basic/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Basic example - -Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. - -## Prerequisites - -* Wasm Workers Server (wws): - - ```shell-session - curl -fsSL https://workers.wasmlabs.dev/install | bash - ``` - -* [Zig](https://ziglang.org/download/) `0.11.0` - -## Build - -All specific build confiugrations are in `build.zig` file. - -```shell-session -zig build -``` - -## Run - -```shell-session -wws ./zig-out/bin/ -``` - -## Resources - -* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig) -* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/) diff --git a/examples/zig-basic/build.zig b/examples/zig-basic/build.zig deleted file mode 100644 index a59f70d6..00000000 --- a/examples/zig-basic/build.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -const examples = [1][]const u8{ "basic" }; - -pub fn build(b: *std.Build) !void { - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" }); - const optimize = b.standardOptimizeOption(.{}); - - const worker_module = b.createModule(.{ - .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, - }); - - inline for (examples) |example| { - const exe = b.addExecutable(.{ - .name = example, - .root_source_file = .{ .path = "src/" ++ example ++ ".zig" }, - .target = target, - .optimize = optimize, - }); - - exe.wasi_exec_model = .reactor; - exe.addModule("worker", worker_module); - - b.installArtifact(exe); - } -} diff --git a/examples/zig-envs/README.md b/examples/zig-envs/README.md deleted file mode 100644 index cdbca6a9..00000000 --- a/examples/zig-envs/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Zig environment variables example - -Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. - -## Prerequisites - -* Wasm Workers Server (wws): - - ```shell-session - curl -fsSL https://workers.wasmlabs.dev/install | bash - ``` - -* [Zig](https://ziglang.org/download/) `0.11.0` - -## Build - -All specific build configurations are in `build.zig` file. - -```shell-session -zig build -``` - -## Run - -```shell-session -wws ./zig-out/bin/ -``` - -## Resources - -* [Environment variables](https://workers.wasmlabs.dev/docs/features/environment-variables) -* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig) -* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/) diff --git a/examples/zig-envs/build.zig b/examples/zig-envs/build.zig deleted file mode 100644 index 67d8478a..00000000 --- a/examples/zig-envs/build.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -const examples = [1][]const u8{ "envs" }; - -pub fn build(b: *std.Build) !void { - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" }); - const optimize = b.standardOptimizeOption(.{}); - - const worker_module = b.createModule(.{ - .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, - }); - - inline for (examples) |example| { - const exe = b.addExecutable(.{ - .name = example, - .root_source_file = .{ .path = "src/" ++ example ++ ".zig" }, - .target = target, - .optimize = optimize, - }); - - exe.wasi_exec_model = .reactor; - exe.addModule("worker", worker_module); - - b.installArtifact(exe); - } -} diff --git a/examples/zig-envs/zig-out/bin/envs.toml b/examples/zig-envs/zig-out/bin/envs.toml deleted file mode 100644 index 4c90dfc5..00000000 --- a/examples/zig-envs/zig-out/bin/envs.toml +++ /dev/null @@ -1,5 +0,0 @@ -name = "envs" -version = "1" - -[vars] -MESSAGE = "Hello! This message comes from an environment variable" \ No newline at end of file diff --git a/examples/zig-examples/README.md b/examples/zig-examples/README.md index d9e991b1..05ed6b5e 100644 --- a/examples/zig-examples/README.md +++ b/examples/zig-examples/README.md @@ -1,17 +1,37 @@ # zig-examples -This example Zig project demonstrates how to use the WWS Zig SDK to compile WWS workers from Zig source code. +This example Zig project demonstrates how to use the WWS Zig SDK to compile WWS workers from Zig source code. -# Building +## Prerequisites + +* Wasm Workers Server (wws): + + ```shell-session + curl -fsSL https://workers.wasmlabs.dev/install | bash + ``` + +* [Zig](https://ziglang.org/download/) `0.12.0` + +# Build + +All specific build confiugrations are in `build.zig` file. ```sh zig build ``` -# Running +# Run This step assumes that you have `wws` installed on your system. ```sh -wws zig-out/root +wws ./zig-out/root ``` + +## Resources + +* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig) +* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/) +* [Environment variables](https://workers.wasmlabs.dev/docs/features/environment-variables) +* [Key / Value store](https://workers.wasmlabs.dev/docs/features/key-value) +* [Dynamic routes](https://workers.wasmlabs.dev/docs/features/dynamic-routes) diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index c61b5b52..409a009b 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -22,6 +22,41 @@ const examples = &[_]Example{ .name = "echo", .root_source_file = .{ .path = "src/echo.zig" }, }, + .{ + .name = "basic", + .root_source_file = .{ .path = "src/basic.zig" }, + }, + .{ + .name = "envs", + .root_source_file = .{ .path = "src/envs.zig" }, + .features = .{ + .vars = &.{ + .{ .name = "MESSAGE", .value = "Hello! This message comes from an environment variable" }, + }, + }, + }, + .{ + .name = "workerkv", + .root_source_file = .{ .path = "src/worker-kv.zig" }, + .features = .{ .kv = .{ .namespace = "workerkv" } }, + }, + .{ + .name = "mount", + .root_source_file = .{ .path = "src/mount.zig" }, + .features = .{ + .folders = &.{ + .{ + .from = "./_images", + .to = "/src/images", + }, + }, + }, + }, + .{ + .name = "params", + .root_source_file = .{ .path = "src/params.zig" }, + .path = "[...params]", + }, }; pub fn build(b: *std.Build) !void { diff --git a/examples/zig-mount/zig-out/bin/_images/zig.svg b/examples/zig-examples/src/_images/zig.svg similarity index 100% rename from examples/zig-mount/zig-out/bin/_images/zig.svg rename to examples/zig-examples/src/_images/zig.svg diff --git a/examples/zig-basic/src/basic.zig b/examples/zig-examples/src/basic.zig similarity index 100% rename from examples/zig-basic/src/basic.zig rename to examples/zig-examples/src/basic.zig diff --git a/examples/zig-examples/src/echo.zig b/examples/zig-examples/src/echo.zig index 65c10db0..6147ee0b 100644 --- a/examples/zig-examples/src/echo.zig +++ b/examples/zig-examples/src/echo.zig @@ -1,14 +1,9 @@ -const std=@import("std"); -const wws=@import("wws"); +const std = @import("std"); +const wws = @import("wws"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer { - switch (gpa.deinit()) { - .ok => {}, - .leak => {}, - } - } + defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Read request from stdin @@ -24,4 +19,4 @@ pub fn main() !void { const stdout = std.io.getStdOut(); try wws.writeResponse(response, stdout.writer()); -} \ No newline at end of file +} diff --git a/examples/zig-envs/src/envs.zig b/examples/zig-examples/src/envs.zig similarity index 100% rename from examples/zig-envs/src/envs.zig rename to examples/zig-examples/src/envs.zig diff --git a/examples/zig-mount/src/mount.zig b/examples/zig-examples/src/mount.zig similarity index 100% rename from examples/zig-mount/src/mount.zig rename to examples/zig-examples/src/mount.zig diff --git a/examples/zig-params/src/worker-params.zig b/examples/zig-examples/src/params.zig similarity index 100% rename from examples/zig-params/src/worker-params.zig rename to examples/zig-examples/src/params.zig diff --git a/examples/zig-kv/src/worker-kv.zig b/examples/zig-examples/src/worker-kv.zig similarity index 100% rename from examples/zig-kv/src/worker-kv.zig rename to examples/zig-examples/src/worker-kv.zig diff --git a/examples/zig-kv/README.md b/examples/zig-kv/README.md deleted file mode 100644 index 2230a277..00000000 --- a/examples/zig-kv/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Key/Value example - -Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. - -## Prerequisites - -* Wasm Workers Server (wws): - - ```shell-session - curl -fsSL https://workers.wasmlabs.dev/install | bash - ``` - -* [Zig](https://ziglang.org/download/) `0.11.0` - -## Build - -All specific build confiugrations are in `build.zig` file. - -```shell-session -zig build -``` - -## Run - -```shell-session -wws ./zig-out/bin/ -``` - -## Resources - -* [Key / Value store](https://workers.wasmlabs.dev/docs/features/key-value) -* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig) -* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/) diff --git a/examples/zig-kv/build.zig b/examples/zig-kv/build.zig deleted file mode 100644 index 756d0a13..00000000 --- a/examples/zig-kv/build.zig +++ /dev/null @@ -1,29 +0,0 @@ -const std = @import("std"); - -const examples = [1][]const u8{"worker-kv"}; - -pub fn build(b: *std.Build) !void { - const target = b.resolveTargetQuery(.{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - }); - const optimize = b.standardOptimizeOption(.{}); - - const worker_module = b.createModule(.{ - .root_source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, - }); - - inline for (examples) |example| { - const exe = b.addExecutable(.{ - .name = example, - .root_source_file = .{ .path = "src/" ++ example ++ ".zig" }, - .target = target, - .optimize = optimize, - }); - - exe.wasi_exec_model = .reactor; - exe.root_module.addImport("worker", worker_module); - - b.installArtifact(exe); - } -} diff --git a/examples/zig-kv/zig-out/bin/worker-kv.toml b/examples/zig-kv/zig-out/bin/worker-kv.toml deleted file mode 100644 index d1987525..00000000 --- a/examples/zig-kv/zig-out/bin/worker-kv.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "workerkv" -version = "1" - -[data] -[data.kv] -namespace = "workerkv" \ No newline at end of file diff --git a/examples/zig-mount/build.zig b/examples/zig-mount/build.zig deleted file mode 100644 index 351f3858..00000000 --- a/examples/zig-mount/build.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -const examples = [1][]const u8{ "mount" }; - -pub fn build(b: *std.Build) !void { - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" }); - const optimize = b.standardOptimizeOption(.{}); - - const worker_module = b.createModule(.{ - .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, - }); - - inline for (examples) |example| { - const exe = b.addExecutable(.{ - .name = example, - .root_source_file = .{ .path = "src/" ++ example ++ ".zig" }, - .target = target, - .optimize = optimize, - }); - - exe.wasi_exec_model = .reactor; - exe.addModule("worker", worker_module); - - b.installArtifact(exe); - } -} diff --git a/examples/zig-mount/zig-out/bin/mount.toml b/examples/zig-mount/zig-out/bin/mount.toml deleted file mode 100644 index 02a5493c..00000000 --- a/examples/zig-mount/zig-out/bin/mount.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "envs" -version = "1" - -[[folders]] -from = "./_images" -to = "/src/images" \ No newline at end of file diff --git a/examples/zig-params/README.md b/examples/zig-params/README.md deleted file mode 100644 index 220ee921..00000000 --- a/examples/zig-params/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Params example - -Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. - -## Prerequisites - -* Wasm Workers Server (wws): - - ```shell-session - curl -fsSL https://workers.wasmlabs.dev/install | bash - ``` - -* [Zig](https://ziglang.org/download/) `0.11.0` - -## Build - -All specific build confiugrations are in `build.zig` file. - -```shell-session -zig build -``` - -## Run - -```shell-session -wws ./zig-out/bin/ -``` - -## Resources - -* [Dynamic routes](https://workers.wasmlabs.dev/docs/features/dynamic-routes) -* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig) -* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/) diff --git a/examples/zig-params/build.zig b/examples/zig-params/build.zig deleted file mode 100644 index 657dc469..00000000 --- a/examples/zig-params/build.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -const examples = [1][]const u8{ "worker-params" }; - -pub fn build(b: *std.Build) !void { - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" }); - const optimize = b.standardOptimizeOption(.{}); - - const worker_module = b.createModule(.{ - .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" }, - }); - - inline for (examples) |example| { - const exe = b.addExecutable(.{ - .name = example, - .root_source_file = .{ .path = "src/" ++ example ++ ".zig" }, - .target = target, - .optimize = optimize, - }); - - exe.wasi_exec_model = .reactor; - exe.addModule("worker", worker_module); - - b.installArtifact(exe); - } -} From db1089d9d23b8d05abf6cd7e079c0bf3b64eaf79 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 17 Feb 2024 12:04:58 -0500 Subject: [PATCH 10/22] feat: Update Zig kit for mount config options --- kits/zig/wws/build.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig index 5dc0b6f7..92deec51 100644 --- a/kits/zig/wws/build.zig +++ b/kits/zig/wws/build.zig @@ -31,9 +31,15 @@ pub const WwsKv = struct { namespace: []const u8, }; +pub const Mount = struct { + from: []const u8, + to: []const u8, +}; + pub const Features = struct { vars: ?[]const EnvVar = null, kv: ?WwsKv = null, + folders: ?[]const Mount = null, }; pub const WwsWorker = struct { @@ -94,6 +100,28 @@ pub const WwsWorker = struct { ); } + if (self.options.features.folders) |folders| { + try buf.writer().print( + \\[[folders]] + \\ + , + .{}, + ); + + for (folders) |f| { + try buf.writer().print( + \\from = "{s}" + \\to = "{s}" + \\ + , + .{ + f.from, + f.to, + }, + ); + } + } + return try buf.toOwnedSlice(); } }; From 0ecc56007893b6f149b0f5d04874f6def548bb58 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 17 Feb 2024 12:58:59 -0500 Subject: [PATCH 11/22] chore: Zig examples use the Zig wws kit --- examples/zig-examples/build.zig | 2 +- examples/zig-examples/src/basic.zig | 111 +++++++++++++----------- examples/zig-examples/src/envs.zig | 36 +++++--- examples/zig-examples/src/main.zig | 64 -------------- examples/zig-examples/src/mount.zig | 40 +++++---- examples/zig-examples/src/params.zig | 45 ++++++---- examples/zig-examples/src/worker-kv.zig | 90 ++++++++++--------- 7 files changed, 181 insertions(+), 207 deletions(-) delete mode 100644 examples/zig-examples/src/main.zig diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index 409a009b..1d7c675d 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -55,7 +55,7 @@ const examples = &[_]Example{ .{ .name = "params", .root_source_file = .{ .path = "src/params.zig" }, - .path = "[...params]", + .path = "params/[id]", }, }; diff --git a/examples/zig-examples/src/basic.zig b/examples/zig-examples/src/basic.zig index 77268184..f3c19af3 100644 --- a/examples/zig-examples/src/basic.zig +++ b/examples/zig-examples/src/basic.zig @@ -1,57 +1,64 @@ const std = @import("std"); -const worker = @import("worker"); - -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); - -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - - var payload: []const u8 = ""; - var reqBody = r.body; - - if (reqBody.len == 0) { - payload = "-"; - } else { - payload = reqBody; - } - - const s = - \\ - \\ - \\Wasm Workers Server - \\ - \\ - \\ - \\ - \\ - \\ - \\
- \\

Hello from Wasm Workers Server 👋

- \\
Replying to {s}
-        \\Method: {s}
-        \\User Agent: {s}
-        \\Payload: {s}
- \\

- \\This page was generated by a Zig⚡️ file running in WebAssembly. - \\

- \\
- \\ - ; - - var body = std.fmt.allocPrint(allocator, s, .{ r.url.path, r.method, "-", payload }) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); +const wws = @import("wws"); + +const tmpl = + \\ + \\ + \\Wasm Workers Server + \\ + \\ + \\ + \\ + \\ + \\ + \\
+ \\

Hello from Wasm Workers Server 👋

+ \\
Replying to {s}
+    \\Method: {s}
+    \\User Agent: {s}
+    \\Payload: {s}
+ \\

+ \\This page was generated by a Zig⚡️ file running in WebAssembly. + \\

+ \\
+ \\ +; + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const payload = if (request.body.len == 0) "-" else request.body; + + const body = try std.fmt.allocPrint(arena, tmpl, .{ request.url, request.method, "-", payload }); + + var response = wws.Response{ + .data = body, + }; + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } diff --git a/examples/zig-examples/src/envs.zig b/examples/zig-examples/src/envs.zig index 6be4131e..3905373b 100644 --- a/examples/zig-examples/src/envs.zig +++ b/examples/zig-examples/src/envs.zig @@ -1,25 +1,33 @@ const std = @import("std"); -const worker = @import("worker"); +const wws = @import("wws"); -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); +const tmpl = + \\The environment variable value is: {s} +; -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - _ = r; +fn handle(arena: std.mem.Allocator) !wws.Response { + const envvar = std.process.getEnvVarOwned(arena, "MESSAGE") catch ""; - const envvar = std.process.getEnvVarOwned(allocator, "MESSAGE") catch ""; - defer allocator.free(envvar); + const body = try std.fmt.allocPrint(arena, tmpl, .{envvar}); - const s = - \\The environment variable value is: {s} - ; + var response = wws.Response{ + .data = body, + }; - var body = std.fmt.allocPrint(allocator, s, .{ envvar }) catch undefined; // add useragent + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const response = try handle(arena.allocator()); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } diff --git a/examples/zig-examples/src/main.zig b/examples/zig-examples/src/main.zig deleted file mode 100644 index a1c0d837..00000000 --- a/examples/zig-examples/src/main.zig +++ /dev/null @@ -1,64 +0,0 @@ -const std = @import("std"); -const wws = @import("wws"); - -/// Receives an arena allocator such that all allocations made while handling -/// the request can be easily freed by the caller -fn handle(arena: *std.heap.ArenaAllocator, request: *const wws.Request) !wws.Response { - const allocator = arena.allocator(); - - // Prepare response body - var body = std.ArrayList(u8).init(allocator); - defer body.deinit(); - - try body.writer().print("{any} {s}\n", .{ request.method, request.url }); - - { - var it = request.kv.map.iterator(); - while (it.next()) |entry| { - try body.writer().print("kv.{s}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); - } - } - - var counter: usize = if (request.kv.map.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0; - - // Increment counter, save the result to storage - counter += 1; - - // Prepare response - var response = wws.Response{ - .data = try body.toOwnedSlice(), - }; - - try response.headers.map.put(allocator, "Content-Type", "text/plain"); - { - var buf = std.ArrayList(u8).init(allocator); - defer buf.deinit(); - - try buf.writer().print("{d}", .{counter}); - try response.kv.map.put(allocator, "counter", try buf.toOwnedSlice()); - } - - return response; -} - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer { - switch (gpa.deinit()) { - .ok => {}, - .leak => {}, - } - } - const allocator = gpa.allocator(); - - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const parse_result = try wws.parseStream(allocator, .{}); - defer parse_result.deinit(); - - const response = try handle(&arena, &parse_result.value); - - const stdout = std.io.getStdOut(); - try wws.writeResponse(response, stdout.writer()); -} diff --git a/examples/zig-examples/src/mount.zig b/examples/zig-examples/src/mount.zig index 72a64962..e86b37e1 100644 --- a/examples/zig-examples/src/mount.zig +++ b/examples/zig-examples/src/mount.zig @@ -1,26 +1,34 @@ const std = @import("std"); -const worker = @import("worker"); +const wws = @import("wws"); -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); - -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - _ = r; - - const file = std.fs.Dir.openFileWasi( - std.fs.cwd(), "zig.svg", .{ - .mode = std.fs.File.OpenMode.read_only, - .lock = std.fs.File.Lock.none, - }) catch unreachable; +fn handle(arena: std.mem.Allocator) !wws.Response { + const file = try std.fs.Dir.openFileWasi(std.fs.cwd(), "zig.svg", .{ + .mode = std.fs.File.OpenMode.read_only, + .lock = std.fs.File.Lock.none, + }); defer file.close(); const mb = (1 << 10) << 10; - const file_contents = file.readToEndAlloc(allocator, mb) catch ""; + const file_contents = try file.readToEndAlloc(arena, mb); + + var response = wws.Response{ + .data = file_contents, + }; + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(file_contents); + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const response = try handle(arena.allocator()); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } diff --git a/examples/zig-examples/src/params.zig b/examples/zig-examples/src/params.zig index a571d28b..c6c1289e 100644 --- a/examples/zig-examples/src/params.zig +++ b/examples/zig-examples/src/params.zig @@ -1,30 +1,37 @@ const std = @import("std"); -const worker = @import("worker"); +const wws = @import("wws"); -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); +const tmpl = + \\Hey! The parameter is: {s} +; -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - var params = r.context.params; +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const id = request.params.map.get("id") orelse "the value is not available"; - var id: []const u8 = "the value is not available"; + const body = try std.fmt.allocPrint(arena, tmpl, .{id}); - var v = params.get("id"); + var response = wws.Response{ + .data = body, + }; - if (v) |val| { - id = val; - } + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - const s = - \\Hey! The parameter is: {s} - ; - - var body = std.fmt.allocPrint(allocator, s, .{ id }) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } diff --git a/examples/zig-examples/src/worker-kv.zig b/examples/zig-examples/src/worker-kv.zig index f39812ae..8dadba6f 100644 --- a/examples/zig-examples/src/worker-kv.zig +++ b/examples/zig-examples/src/worker-kv.zig @@ -1,46 +1,54 @@ const std = @import("std"); -const worker = @import("worker"); - -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); - -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - var cache = r.context.cache; - var counter: i32 = 0; - - const v = cache.getOrPut("counter") catch undefined; - - if (!v.found_existing) { - v.value_ptr.* = "0"; - } else { - const counterValue = v.value_ptr.*; - const num = std.fmt.parseInt(i32, counterValue, 10) catch undefined; - counter = num + 1; - const num_s = std.fmt.allocPrint(allocator, "{d}", .{counter}) catch undefined; - _ = cache.put("counter", num_s) catch undefined; - } - - const s = - \\ - \\ - \\ - \\Wasm Workers Server - KV example - \\ - \\ - \\ - \\ - \\

Key / Value store in Zig

- \\

Counter: {d}

- \\

This page was generated by a Zig⚡️ file running in WebAssembly.

- \\ - ; - - const body = std.fmt.allocPrint(allocator, s, .{counter}) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); +const wws = @import("wws"); + +const tmpl = + \\ + \\ + \\ + \\Wasm Workers Server - KV example + \\ + \\ + \\ + \\ + \\

Key / Value store in Zig

+ \\

Counter: {d}

+ \\

This page was generated by a Zig⚡️ file running in WebAssembly.

+ \\ +; + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const value = request.kv.map.get("counter") orelse "0"; + var counter = std.fmt.parseInt(i32, value, 10) catch 0; + + counter += 1; + + const body = try std.fmt.allocPrint(arena, tmpl, .{counter}); + + var response = wws.Response{ + .data = body, + }; + + const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter}); + try response.kv.map.put(arena, "counter", num_s); + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } From 62eb781155d02cba96bb62a73b2efbd5df423a6f Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 17 Feb 2024 13:20:51 -0500 Subject: [PATCH 12/22] chore: Update build.zig to include image for folder mount example --- examples/zig-examples/build.zig | 16 +++------------- examples/zig-examples/src/echo.zig | 22 ---------------------- 2 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 examples/zig-examples/src/echo.zig diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index 1d7c675d..c1381e98 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -9,19 +9,6 @@ const Example = struct { }; const examples = &[_]Example{ - .{ - .name = "example", - .root_source_file = .{ .path = "src/main.zig" }, - .features = .{ - .kv = .{ - .namespace = "example", - }, - }, - }, - .{ - .name = "echo", - .root_source_file = .{ .path = "src/echo.zig" }, - }, .{ .name = "basic", .root_source_file = .{ .path = "src/basic.zig" }, @@ -81,6 +68,9 @@ pub fn build(b: *std.Build) !void { try worker.addToWriteFiles(b, wf); } + // Add folder for mount example + _ = wf.addCopyFile(.{ .path = "src/_images/zig.svg" }, "_images/zig.svg"); + const install = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), .install_dir = .prefix, diff --git a/examples/zig-examples/src/echo.zig b/examples/zig-examples/src/echo.zig deleted file mode 100644 index 6147ee0b..00000000 --- a/examples/zig-examples/src/echo.zig +++ /dev/null @@ -1,22 +0,0 @@ -const std = @import("std"); -const wws = @import("wws"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - // Read request from stdin - const parse_result = try wws.parseStream(allocator, .{}); - defer parse_result.deinit(); - - const request = parse_result.value; - - // Simple echo response - const response = wws.Response{ - .data = request.url, - }; - - const stdout = std.io.getStdOut(); - try wws.writeResponse(response, stdout.writer()); -} From ae44dad19ec79050c7e5b1c7ed23a3b80a2b2fb9 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sat, 17 Feb 2024 16:53:37 -0500 Subject: [PATCH 13/22] feat: Add Zig dynamic routing example using `zig-router` --- examples/zig-examples/build.zig | 22 +++++--- examples/zig-examples/build.zig.zon | 4 ++ examples/zig-examples/src/router.zig | 75 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 examples/zig-examples/src/router.zig diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index c1381e98..48a471b4 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -1,13 +1,6 @@ const std = @import("std"); const wws = @import("wws"); -const Example = struct { - name: []const u8, - root_source_file: std.Build.LazyPath, - path: ?[]const u8 = null, - features: ?wws.Features = null, -}; - const examples = &[_]Example{ .{ .name = "basic", @@ -44,6 +37,18 @@ const examples = &[_]Example{ .root_source_file = .{ .path = "src/params.zig" }, .path = "params/[id]", }, + .{ + .name = "router", + .root_source_file = .{ .path = "src/router.zig" }, + .path = "router/[...path]", + }, +}; + +const Example = struct { + name: []const u8, + root_source_file: std.Build.LazyPath, + path: ?[]const u8 = null, + features: ?wws.Features = null, }; pub fn build(b: *std.Build) !void { @@ -51,6 +56,7 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const wws_dep = b.dependency("wws", .{}); + const zig_router_dep = b.dependency("zig-router", .{}); const wf = b.addWriteFiles(); @@ -65,6 +71,8 @@ pub fn build(b: *std.Build) !void { .features = e.features orelse .{}, }); + worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); + try worker.addToWriteFiles(b, wf); } diff --git a/examples/zig-examples/build.zig.zon b/examples/zig-examples/build.zig.zon index 278a6bf8..ad5a9490 100644 --- a/examples/zig-examples/build.zig.zon +++ b/examples/zig-examples/build.zig.zon @@ -41,6 +41,10 @@ .wws = .{ .path = "../../kits/zig/wws", }, + .@"zig-router" = .{ + .url = "https://github.com/Cloudef/zig-router/archive/6ff2b5b5885082a26514f1604e5cbdb763f8c445.tar.gz", + .hash = "122085db606152dbae69a63a83a61005b063199d7551d600b979140dbacc9a4778d9", + }, }, // Specifies the set of files and directories that are included in this package. diff --git a/examples/zig-examples/src/router.zig b/examples/zig-examples/src/router.zig new file mode 100644 index 00000000..18d251e9 --- /dev/null +++ b/examples/zig-examples/src/router.zig @@ -0,0 +1,75 @@ +const std = @import("std"); +const wws = @import("wws"); +const zig_router = @import("zig-router"); + +fn getHello(arena: std.mem.Allocator) !wws.Response { + var response = wws.Response{ + .data = "Hello, world!", + }; + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + return response; +} + +const GetBlogPostParams = struct { + id: []const u8, +}; +fn getBlogPost(arena: std.mem.Allocator, params: GetBlogPostParams) !wws.Response { + var body = std.ArrayList(u8).init(arena); + + try body.writer().print("Blog article contents for id {s}", .{params.id}); + + var response = wws.Response{ + .data = try body.toOwnedSlice(), + }; + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + return response; +} + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const router = zig_router.Router( + .{}, + .{ + zig_router.Route(.GET, "/router/hello", getHello), + zig_router.Route(.GET, "/router/post/:id", getBlogPost), + }, + ); + + const uri = try std.Uri.parse(request.url); + + const response = router.match( + arena, + .{ + .method = request.method, + .path = uri.path, + .query = uri.query orelse "", + }, + .{arena}, + ) catch |err| switch (err) { + error.not_found => wws.Response{ .status = 404, .data = "not found" }, + error.bad_request => wws.Response{ .status = 400, .data = "bad request" }, + else => wws.Response{ .status = 500, .data = "internal server error" }, + }; + + return response; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + + std.debug.print("response {s}\n", .{response.data}); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} From 3bd939be02c35afc83c436fab467f7c29f966026 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sun, 18 Feb 2024 15:15:20 -0500 Subject: [PATCH 14/22] feat: Add Zig no-alloc-kv example that doesn't make heap allocations --- examples/zig-examples/build.zig | 4 ++ examples/zig-examples/src/no-alloc-kv.zig | 55 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 examples/zig-examples/src/no-alloc-kv.zig diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index 48a471b4..d293a39b 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -20,6 +20,10 @@ const examples = &[_]Example{ .root_source_file = .{ .path = "src/worker-kv.zig" }, .features = .{ .kv = .{ .namespace = "workerkv" } }, }, + .{ + .name = "no-alloc-kv", + .root_source_file = .{ .path = "src/no-alloc-kv.zig" }, + }, .{ .name = "mount", .root_source_file = .{ .path = "src/mount.zig" }, diff --git a/examples/zig-examples/src/no-alloc-kv.zig b/examples/zig-examples/src/no-alloc-kv.zig new file mode 100644 index 00000000..2f6719f1 --- /dev/null +++ b/examples/zig-examples/src/no-alloc-kv.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const wws = @import("wws"); + +const tmpl = + \\ + \\ + \\ + \\Wasm Workers Server - KV example + \\ + \\ + \\ + \\ + \\

Key / Value store in Zig

+ \\

Counter: {d}

+ \\

This page was generated by a Zig⚡️ file running in WebAssembly.

+ \\ +; +var tmpl_buf: [tmpl.len + 8]u8 = undefined; + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const value = request.kv.map.get("counter") orelse "0"; + var counter = std.fmt.parseInt(i32, value, 10) catch 0; + + counter += 1; + + const body = try std.fmt.bufPrint(&tmpl_buf, tmpl, .{counter}); + + var response = wws.Response{ + .data = body, + }; + + const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter}); + try response.kv.map.put(arena, "counter", num_s); + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + + return response; +} + +pub fn main() !void { + var buf: [4096 * 2]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&buf); + + const parse_result = try wws.parseStream(fba.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(fba.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(fba.allocator(), request); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} From 3c9b5606d4b428e57742fa56d6ca4500123ccbca Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sun, 18 Feb 2024 16:28:10 -0500 Subject: [PATCH 15/22] feat: Add Zig example that only makes dynamic allocations if necessary --- examples/zig-examples/build.zig | 4 ++ examples/zig-examples/src/mixed-alloc-kv.zig | 68 ++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 examples/zig-examples/src/mixed-alloc-kv.zig diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index d293a39b..aa1891df 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -24,6 +24,10 @@ const examples = &[_]Example{ .name = "no-alloc-kv", .root_source_file = .{ .path = "src/no-alloc-kv.zig" }, }, + .{ + .name = "mixed-alloc-kv", + .root_source_file = .{ .path = "src/mixed-alloc-kv.zig" }, + }, .{ .name = "mount", .root_source_file = .{ .path = "src/mount.zig" }, diff --git a/examples/zig-examples/src/mixed-alloc-kv.zig b/examples/zig-examples/src/mixed-alloc-kv.zig new file mode 100644 index 00000000..9c009938 --- /dev/null +++ b/examples/zig-examples/src/mixed-alloc-kv.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const wws = @import("wws"); + +const tmpl = + \\ + \\ + \\ + \\Wasm Workers Server - KV example + \\ + \\ + \\ + \\ + \\

Key / Value store in Zig

+ \\

Counter: {d}

+ \\

This page was generated by a Zig⚡️ file running in WebAssembly.

+ \\ +; +var tmpl_buf: [tmpl.len + 8]u8 = undefined; + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const value = request.kv.map.get("counter") orelse "0"; + var counter = std.fmt.parseInt(i32, value, 10) catch 0; + + counter += 1; + + const body = try std.fmt.bufPrint(&tmpl_buf, tmpl, .{counter}); + + var response = wws.Response{ + .data = body, + }; + + const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter}); + try response.kv.map.put(arena, "counter", num_s); + + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); + + return response; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + var sfa = std.heap.stackFallback(4096 * 2, arena.allocator()); + + const allocator = sfa.get(); + + const parse_result = wws.parseStream(allocator, .{}) catch { + std.debug.print("Failed to read request\n", .{}); + return error.ReadRequestFailed; + }; + defer parse_result.deinit(); + + const request = parse_result.value; + const response = handle(allocator, request) catch { + std.debug.print("Failed to handle request\n", .{}); + return error.HandleRequestFailed; + }; + + const stdout = std.io.getStdOut(); + wws.writeResponse(response, stdout.writer()) catch { + std.debug.print("Failed to write response\n", .{}); + return error.WriteResponseFailed; + }; +} From 8bdc4e75a5a0ba408b6f2720a4a38e68f7e6b16e Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sun, 18 Feb 2024 17:10:06 -0500 Subject: [PATCH 16/22] chore: Update Zig examples for latest Zig 0.12.0-dev.2809+f3bd17772 The dependency `zig-router` doesn't yet support the latest Zig. Once that library is updated, we can uncomment this example. `std.fs.Dir.openFileWasi` has been removed, so we can just use `std.fs.Dir.openFile`. --- examples/zig-examples/build.zig | 14 +++++++------- examples/zig-examples/build.zig.zon | 8 ++++---- examples/zig-examples/src/mount.zig | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index aa1891df..61ac3cd8 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -45,11 +45,11 @@ const examples = &[_]Example{ .root_source_file = .{ .path = "src/params.zig" }, .path = "params/[id]", }, - .{ - .name = "router", - .root_source_file = .{ .path = "src/router.zig" }, - .path = "router/[...path]", - }, + //.{ + // .name = "router", + // .root_source_file = .{ .path = "src/router.zig" }, + // .path = "router/[...path]", + //}, }; const Example = struct { @@ -64,7 +64,7 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const wws_dep = b.dependency("wws", .{}); - const zig_router_dep = b.dependency("zig-router", .{}); + //const zig_router_dep = b.dependency("zig-router", .{}); const wf = b.addWriteFiles(); @@ -79,7 +79,7 @@ pub fn build(b: *std.Build) !void { .features = e.features orelse .{}, }); - worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); + //worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); try worker.addToWriteFiles(b, wf); } diff --git a/examples/zig-examples/build.zig.zon b/examples/zig-examples/build.zig.zon index ad5a9490..0ed799cc 100644 --- a/examples/zig-examples/build.zig.zon +++ b/examples/zig-examples/build.zig.zon @@ -41,10 +41,10 @@ .wws = .{ .path = "../../kits/zig/wws", }, - .@"zig-router" = .{ - .url = "https://github.com/Cloudef/zig-router/archive/6ff2b5b5885082a26514f1604e5cbdb763f8c445.tar.gz", - .hash = "122085db606152dbae69a63a83a61005b063199d7551d600b979140dbacc9a4778d9", - }, + //.@"zig-router" = .{ + // .url = "https://github.com/Cloudef/zig-router/archive/dc438755c5a6d5fec2efb9e413e8c03a3f637585.tar.gz", + // .hash = "1220a53ebca3e6d9958341c83767ef9763f947ef68e60362764d12a972227a1339bc", + //}, }, // Specifies the set of files and directories that are included in this package. diff --git a/examples/zig-examples/src/mount.zig b/examples/zig-examples/src/mount.zig index e86b37e1..a374cec9 100644 --- a/examples/zig-examples/src/mount.zig +++ b/examples/zig-examples/src/mount.zig @@ -2,7 +2,7 @@ const std = @import("std"); const wws = @import("wws"); fn handle(arena: std.mem.Allocator) !wws.Response { - const file = try std.fs.Dir.openFileWasi(std.fs.cwd(), "zig.svg", .{ + const file = try std.fs.Dir.openFile(std.fs.cwd(), "zig.svg", .{ .mode = std.fs.File.OpenMode.read_only, .lock = std.fs.File.Lock.none, }); From 47673f8fc79bb4ba5a053cbd3dd28843195e8ff8 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Tue, 20 Feb 2024 10:22:43 -0500 Subject: [PATCH 17/22] update: bump zig-router and uncomment some Zig examples --- examples/zig-examples/build.zig | 14 +++++++------- examples/zig-examples/build.zig.zon | 8 ++++---- examples/zig-examples/src/router.zig | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index 61ac3cd8..aa1891df 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -45,11 +45,11 @@ const examples = &[_]Example{ .root_source_file = .{ .path = "src/params.zig" }, .path = "params/[id]", }, - //.{ - // .name = "router", - // .root_source_file = .{ .path = "src/router.zig" }, - // .path = "router/[...path]", - //}, + .{ + .name = "router", + .root_source_file = .{ .path = "src/router.zig" }, + .path = "router/[...path]", + }, }; const Example = struct { @@ -64,7 +64,7 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const wws_dep = b.dependency("wws", .{}); - //const zig_router_dep = b.dependency("zig-router", .{}); + const zig_router_dep = b.dependency("zig-router", .{}); const wf = b.addWriteFiles(); @@ -79,7 +79,7 @@ pub fn build(b: *std.Build) !void { .features = e.features orelse .{}, }); - //worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); + worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); try worker.addToWriteFiles(b, wf); } diff --git a/examples/zig-examples/build.zig.zon b/examples/zig-examples/build.zig.zon index 0ed799cc..1945c227 100644 --- a/examples/zig-examples/build.zig.zon +++ b/examples/zig-examples/build.zig.zon @@ -41,10 +41,10 @@ .wws = .{ .path = "../../kits/zig/wws", }, - //.@"zig-router" = .{ - // .url = "https://github.com/Cloudef/zig-router/archive/dc438755c5a6d5fec2efb9e413e8c03a3f637585.tar.gz", - // .hash = "1220a53ebca3e6d9958341c83767ef9763f947ef68e60362764d12a972227a1339bc", - //}, + .@"zig-router" = .{ + .url = "https://github.com/Cloudef/zig-router/archive/e8df16c9281969aef3cbb3c788f2c8c2b4f9ec30.tar.gz", + .hash = "1220fabbdaa72ff4801b62cc577e2e63849e960fbc22b67d560080726c53fb7c28cc", + }, }, // Specifies the set of files and directories that are included in this package. diff --git a/examples/zig-examples/src/router.zig b/examples/zig-examples/src/router.zig index 18d251e9..72c252be 100644 --- a/examples/zig-examples/src/router.zig +++ b/examples/zig-examples/src/router.zig @@ -31,8 +31,8 @@ fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { const router = zig_router.Router( .{}, .{ - zig_router.Route(.GET, "/router/hello", getHello), - zig_router.Route(.GET, "/router/post/:id", getBlogPost), + zig_router.Route(.GET, "/router/hello", getHello, .{}), + zig_router.Route(.GET, "/router/post/:id", getBlogPost, .{}), }, ); From 488c9bbf15aaf0a7ea32242eec8dba9b2271dd15 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 23 Feb 2024 10:54:47 -0500 Subject: [PATCH 18/22] fix: Add missing features to Zig examples --- examples/zig-examples/build.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig index aa1891df..fa0b53ce 100644 --- a/examples/zig-examples/build.zig +++ b/examples/zig-examples/build.zig @@ -23,10 +23,12 @@ const examples = &[_]Example{ .{ .name = "no-alloc-kv", .root_source_file = .{ .path = "src/no-alloc-kv.zig" }, + .features = .{ .kv = .{ .namespace = "workerkv" } }, }, .{ .name = "mixed-alloc-kv", .root_source_file = .{ .path = "src/mixed-alloc-kv.zig" }, + .features = .{ .kv = .{ .namespace = "workerkv" } }, }, .{ .name = "mount", From 16bbaaf21c853206b8796897d515a7fec13a576a Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sun, 25 Feb 2024 09:18:24 -0500 Subject: [PATCH 19/22] chore: Simplify Zig examples --- examples/zig-examples/src/mixed-alloc-kv.zig | 4 +--- examples/zig-examples/src/no-alloc-kv.zig | 6 +----- kits/zig/worker/src/worker.zig | 3 --- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/examples/zig-examples/src/mixed-alloc-kv.zig b/examples/zig-examples/src/mixed-alloc-kv.zig index 9c009938..b8cff5c6 100644 --- a/examples/zig-examples/src/mixed-alloc-kv.zig +++ b/examples/zig-examples/src/mixed-alloc-kv.zig @@ -15,7 +15,6 @@ const tmpl = \\

This page was generated by a Zig⚡️ file running in WebAssembly.

\\ ; -var tmpl_buf: [tmpl.len + 8]u8 = undefined; fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { const value = request.kv.map.get("counter") orelse "0"; @@ -23,7 +22,7 @@ fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { counter += 1; - const body = try std.fmt.bufPrint(&tmpl_buf, tmpl, .{counter}); + const body = try std.fmt.allocPrint(arena, tmpl, .{counter}); var response = wws.Response{ .data = body, @@ -45,7 +44,6 @@ pub fn main() !void { defer arena.deinit(); var sfa = std.heap.stackFallback(4096 * 2, arena.allocator()); - const allocator = sfa.get(); const parse_result = wws.parseStream(allocator, .{}) catch { diff --git a/examples/zig-examples/src/no-alloc-kv.zig b/examples/zig-examples/src/no-alloc-kv.zig index 2f6719f1..368e2568 100644 --- a/examples/zig-examples/src/no-alloc-kv.zig +++ b/examples/zig-examples/src/no-alloc-kv.zig @@ -15,7 +15,6 @@ const tmpl = \\

This page was generated by a Zig⚡️ file running in WebAssembly.

\\ ; -var tmpl_buf: [tmpl.len + 8]u8 = undefined; fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { const value = request.kv.map.get("counter") orelse "0"; @@ -23,7 +22,7 @@ fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { counter += 1; - const body = try std.fmt.bufPrint(&tmpl_buf, tmpl, .{counter}); + const body = try std.fmt.allocPrint(arena, tmpl, .{counter}); var response = wws.Response{ .data = body, @@ -44,9 +43,6 @@ pub fn main() !void { const parse_result = try wws.parseStream(fba.allocator(), .{}); defer parse_result.deinit(); - var arena = std.heap.ArenaAllocator.init(fba.allocator()); - defer arena.deinit(); - const request = parse_result.value; const response = try handle(fba.allocator(), request); diff --git a/kits/zig/worker/src/worker.zig b/kits/zig/worker/src/worker.zig index 56899bcd..9b2494cd 100644 --- a/kits/zig/worker/src/worker.zig +++ b/kits/zig/worker/src/worker.zig @@ -126,8 +126,6 @@ pub const Output = struct { const stdout = std.io.getStdOut().writer(); try stdout.print("{s}", .{result}); - std.debug.print("{s}\n", .{result}); - return self.data.len; } }; @@ -168,7 +166,6 @@ pub fn readInput() !Input { var r = buf.reader(); const msg = try r.readAllAlloc(allocator, std.math.maxInt(u32)); - std.debug.print("in {s}\n", .{msg}); return getInput(msg); } From 972c6f7f6413a8f0f7ddf4d6f41b87f56879aecf Mon Sep 17 00:00:00 2001 From: sea-grass Date: Sun, 25 Feb 2024 18:31:25 -0500 Subject: [PATCH 20/22] chore: Update Zig docs --- docs/docs/get-started/quickstart.md | 1 + docs/docs/languages/zig.md | 602 ++++++++++++++++------------ docs/docusaurus.config.js | 2 +- 3 files changed, 351 insertions(+), 254 deletions(-) diff --git a/docs/docs/get-started/quickstart.md b/docs/docs/get-started/quickstart.md index 82bda31a..6aba1ef2 100644 --- a/docs/docs/get-started/quickstart.md +++ b/docs/docs/get-started/quickstart.md @@ -71,5 +71,6 @@ Now you got the taste of Wasm Workers, it's time to create your first worker: * [Create your first Python worker](../languages/python.md) * [Create your first Ruby worker](../languages/ruby.md) * [Create your first Go worker](../languages/go.md) +* [Create your first Zig worker](../languages/zig.md) And if you are curious, here you have a guide about [how it works](./how-it-works.md). diff --git a/docs/docs/languages/zig.md b/docs/docs/languages/zig.md index fc81389a..4a4fa4e3 100644 --- a/docs/docs/languages/zig.md +++ b/docs/docs/languages/zig.md @@ -4,369 +4,465 @@ sidebar_position: 6 # Zig -Zig workers are tested with Zig version `0.11.0`. Then, they are loaded by Wasm Workers Server and start processing requests. +Zig workers are tested with Zig version `0.12.0`. Then, they are loaded by Wasm Workers Server and start processing requests. ## Your first Zig worker -The recommended way to implement workers is by using the `worker.ServeFunc` function. +The recommended way to implement workers is by using the `wws.zig` SDK. In this example, the worker will get a request and print all the related information. 1. Create a new Zig project: ```shell-session - zig init-exe + zig init ``` 2. Add Wasm Workers Server Zig dependency - At this point in time Zigs Package manager is not yet available. We will therefore clone the repository to make the library locally available. + At this point in time, the Zig SDK is located at a subpath of the wasm-workers-server repo. Zig's package manager works with archives and does not support referencing a package as a subpath of an archive. We will therefore add wasm-workers-server as a submodule of our project. ```shell-session mkdir lib - wget -O ./lib/worker.zig https://raw.githubusercontent.com/vmware-labs/wasm-workers-server/main/kits/zig/worker/src/worker.zig + git submodule add https://github.com/vmware-labs/wasm-workers-server.git lib/wws ``` -3. Edit the `src/main.zig` to match the following contents: +3. Add the wws dependency to `build.zig.zon` - ```c title="worker.zig" - const std = @import("std"); - const worker = @import("worker"); - - fn requestFn(resp: *worker.Response, r: *worker.Request) void { - _ = r; + We can now modify the `build.zig.zon` file to include the Zig SDK as a dependency: - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll("hello from zig"); + ```zig title="build.zig.zon" + .{ + // ... + .dependencies = .{ + // Add this to dependencies + .wws = .{ + .path = "lib/wws/kits/zig/wws", + }, + }, + // ... } + ``` - pub fn main() !void { - worker.ServeFunc(requestFn); +4. Edit the `build.zig` + + The WWS Zig SDK exports a few build-time helpers to compile your executable to WASM and generate your worker config. Let's update the `build.zig` to include the `wws` dependency and build our worker and its associated config. + + ```zig title="build.zig" + const std = @import("std"); + const wws = @import("wws"); + + pub fn build(b: *std.Build) !void { + // getTarget is a helper to get the proper wasm target + const target = wws.getTarget(b); + + const optimize = b.standardOptimizeOption(.{}); + + // This references the wws dependency we added to build.zig.zon + const wws_dep = b.dependency("wws", .{}); + + // addWorker lets us specify similar options to std.Build.addExecutable + const worker = try wws.addWorker(b, .{ + .name = "example", + // path lets us specify the file path for our worker and its config. + // This will affect routing. + .path = "hello/[name]", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + // We need to pass the wws dependency to addWorker so it can make sure + // that the wws module is provided to your application. + .wws = wws_dep, + // features lets you specify which WWS features your worker will have + // access to. + .features = .{ .kv = .{ .namespace = "example" } }, + }); + + // We use addWriteFiles to provide an output destination for our worker + // and its config. + const wf = b.addWriteFiles(); + + // addToWriteFiles will add the compiled wasm and worker config to a subpath + // determined by the path provided to addWorker above. + try worker.addToWriteFiles(b, wf); + + { + // Copy the wf directory contents into the zig-out directory. + const install = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + // The contents of wf will be accessible in zig-out/root. + .install_subdir = "root", + }); + + b.getInstallStep().dependOn(&install.step); + } } ``` -4. Additionally, you can now go further add all the information from the received `worker.Request`: +5. Edit `src/main.zig` to match the following contents: - ```c title="worker.zig" + ```zig title="src/main.zig" const std = @import("std"); - const worker = @import("worker"); - - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = arena.allocator(); + const wws = @import("wws"); + + const tmpl = + \\ + \\ + \\Wasm Workers Server + \\ + \\ + \\ + \\ + \\ + \\ + \\
+ \\

Hello {[name]s} from Wasm Workers Server 👋

+ \\
Replying to {[url]s}
+        \\Method: {[method]s}
+        \\User Agent: {[user_agent]s}
+        \\Payload: {[payload]s}
+ \\

+ \\This page was generated by a Zig⚡️ file running in WebAssembly. + \\

+ \\
+ \\
+ \\
+ \\ + \\
+ \\
+ \\ + ; - fn requestFn(resp: *worker.Response, r: *worker.Request) void { - std.debug.print("Hello from function\n", .{ }); + fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const payload = if (request.body.len == 0) "-" else request.body; + const user_agent = request.headers.map.get("user-agent") orelse "-"; + const name = request.params.map.get("name") orelse "world"; - // // TODO: prepare to read request body and send it back - std.debug.print("+++ doing payload \n", .{ }); + const body = try std.fmt.allocPrint(arena, tmpl, .{ + .url = request.url, + .method = request.method, + .user_agent = user_agent, + .payload = payload, + .name = name, + }); - var payload: []const u8 = ""; - var reqBody = r.data; + var response = wws.Response{ + .data = body, + }; - if (reqBody.len == 0) { - payload = "-"; - } else { - payload = reqBody; - } + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - const s = - \\ - \\ - \\Wasm Workers Server - \\ - \\ - \\ - \\ - \\ - \\ - \\
- \\

Hello from Wasm Workers Server 👋

- \\
Replying to {s}
-            \\Method: {s}
-            \\User Agent: {s}
-            \\Payload: {s}
- \\

- \\This page was generated by a Zig⚡️ file running in WebAssembly. - \\

- \\
- \\ - ; - - var body = std.fmt.allocPrint(allocator, s, .{ r.url.path, r.method, "-", payload }) catch undefined; - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } ``` -5. Compile the project +6. Compile the project ```shell-session - zig build-exe src/main.zig \ - --name worker \ - -mexec-model=reactor \ - -target wasm32-wasi \ - --mod worker::lib/worker.zig \ - --deps worker + $ zig build + $ tree zig-out + zig-out + └── root + └── hello + ├── [name].toml + └── [name].wasm ``` - You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/). - -6. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. +7. Run your worker with `wws`. If you haven't downloaded the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. ```shell-session - wws . - - ⚙️ Loading routes from: . - 🗺 Detected routes: - - http://127.0.0.1:8080/worker - => worker.wasm (name: default) + $ wws zig-out/root/ + ⚙️ Preparing the project from: zig-out/root/ + ⚙️ Loading routes from: zig-out/root/ + ⏳ Loading workers from 1 routes... + ✅ Workers loaded in 35.659083ms. + - http://127.0.0.1:8080/hello/[name] + => zig-out/root/hello/[name].wasm 🚀 Start serving requests at http://127.0.0.1:8080 ``` -7. Finally, open in your browser. +8. Finally, open in your browser. ## Add a Key / Value store Wasm Workers allows you to add a Key / Value store to your workers. Read more information about this feature in the [Key / Value store](../features/key-value.md) section. -To add a KV store to your worker, follow these steps: - -1. Create a new Zig project: - - ```shell-session - zig init-exe - ``` - -2. Add Wasm Workers Server Zig dependency - - At this point in time Zigs Package manager is not yet available. We will therefore clone the repository to make the library locally available. - - ```shell-session - mkdir lib - wget -O ./lib/worker.zig https://raw.githubusercontent.com/vmware-labs/wasm-workers-server/main/kits/zig/worker/worker.zig ./lib - ``` - -1. Edit `src/main.zig` file with the following contents: +We can add a KV store to our Zig worker by specifying it inside `features` when we call `wws.addWorker`: + +```zig title="build.zig" +pub fn build(b: *std.Build) !void { + // ... + + const worker = try wws.addWorker(b, .{ + .name = "workerkv", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + .features = .{ + // Define your KV namespace here + .kv = .{ .namespace = "workerkv" }, + }, + }); + + // ... +} +``` - ```c title="main.zig" - const std = @import("std"); - const worker = @import("worker"); +We can see the resulting TOML configuration file when we call `build.zig`: - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = arena.allocator(); +```shell-session +$ zig build +$ tree zig-out/root +zig-out/root/ +├── workerkv.toml +├── workerkv.wasm +$ cat zig-out/root/workerkv.toml +name = "workerkv" +version = "1" +[data] +[data.kv] +namespace = "workerkv" +``` - fn requestFn(resp: *worker.Response, r: *worker.Request) void { - var cache = r.context.cache; - var counter: i32 = 0; +Then, we can read from/write to the KV store with the following example: - var v = cache.getOrPut("counter") catch undefined; +```zig title="src/main.zig" +const std = @import("std"); +const wws = @import("wws"); - if (!v.found_existing) { - v.value_ptr.* = "0"; - } else { - var counterValue = v.value_ptr.*; - var num = std.fmt.parseInt(i32, counterValue, 10) catch undefined; - counter = num + 1; - var num_s = std.fmt.allocPrint(allocator, "{d}", .{ counter }) catch undefined; - _ = cache.put("counter", num_s) catch undefined; - } +const tmpl = + \\ + \\ + \\ + \\Wasm Workers Server - KV example + \\ + \\ + \\ + \\ + \\

Key / Value store in Zig

+ \\

Counter: {d}

+ \\

This page was generated by a Zig⚡️ file running in WebAssembly.

+ \\ +; - const s = - \\ - \\ - \\ - \\Wasm Workers Server - KV example - \\ - \\ - \\ - \\ - \\

Key / Value store in Zig

- \\

Counter: {d}

- \\

This page was generated by a Zig⚡️ file running in WebAssembly.

- \\ - ; - - var body = std.fmt.allocPrint(allocator, s, .{ counter }) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); - } +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const value = request.kv.map.get("counter") orelse "0"; + var counter = std.fmt.parseInt(i32, value, 10) catch 0; - pub fn main() !void { - worker.ServeFunc(requestFn); - } - ``` + counter += 1; -5. Compile the project + const body = try std.fmt.allocPrint(arena, tmpl, .{counter}); - ```shell-session - zig build-exe src/main.zig \ - --name worker-kv \ - -mexec-model=reactor \ - -target wasm32-wasi \ - --mod worker::lib/worker.zig \ - --deps worker - ``` + var response = wws.Response{ + .data = body, + }; - You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/). + const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter}); + try response.kv.map.put(arena, "counter", num_s); -1. Create a `worker-kv.toml` file with the following content. Note the name of the TOML file must match the name of the worker. In this case we have `worker-kv.wasm` and `worker-kv.toml` in the same folder: + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - ```toml title="worker-kv.toml" - name = "workerkv" - version = "1" + return response; +} - [data] - [data.kv] - namespace = "workerkv" - ``` +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); -1. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. + const parse_result = try wws.parseStream(gpa.allocator(), .{}); + defer parse_result.deinit(); - ```shell-session - wws . + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); - ⚙️ Loading routes from: . - 🗺 Detected routes: - - http://127.0.0.1:8080/worker-kv - => worker-kv.wasm (name: default) - 🚀 Start serving requests at http://127.0.0.1:8080 - ``` - -1. Finally, open in your browser. + const request = parse_result.value; + const response = try handle(arena.allocator(), request); + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} +``` ## Dynamic routes -You can define [dynamic routes by adding route parameters to your worker files](../features/dynamic-routes.md) (like `[id].wasm`). To read them in Zig, follow these steps: +You can define [dynamic routes by adding route parameters to your worker files](../features/dynamic-routes.md) (like `[id].wasm`). Using the Zig SDK, you can configure this in your `build.zig` when you declare your worker: -1. Use the `worker.ParamsKey` context value to read in the passed in parameters: - - ```c title="main.zig" - const std = @import("std"); - const worker = @import("worker"); +```zig title="build.zig" +pub fn build(b: *std.Build) !void { + // ... - fn requestFn(resp: *worker.Response, r: *worker.Request) void { - var params = r.context.params; + const worker = try wws.addWorker(b, .{ + .name = "params", + // We specify the file path (and therefore the HTTP route) of our worker + .path = "params/[id]", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + }); - ... - } - ``` - -2. Then, you can read the values as follows: - - ```c title="main.zig" - const std = @import("std"); - const worker = @import("worker"); - - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = arena.allocator(); + // ... +} +``` - fn requestFn(resp: *worker.Response, r: *worker.Request) void { - var params = r.context.params; +If we run `zig build`, we can see the resulting file tree: - var id: []const u8 = "the value is not available"; +```shell-session +$ zig build +$ tree zig-out/root +zig-out/root/ +├── params/[id].toml +├── params/[id].wasm +``` - var v = params.get("id"); +Then, you can read the params from the request with the following example: - if (v) |val| { - id = val; - } +```zig title="src/main.zig" +const std = @import("std"); +const wws = @import("wws"); - const s = - \\Hey! The parameter is: {s} - ; +const tmpl = + \\Hey! The parameter is: {s} +; - var body = std.fmt.allocPrint(allocator, s, .{ id }) catch undefined; // add useragent +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const id = request.params.map.get("id") orelse "the value is not available"; - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); - } + const body = try std.fmt.allocPrint(arena, tmpl, .{id}); - pub fn main() !void { - worker.ServeFunc(requestFn); - } - ``` + var response = wws.Response{ + .data = body, + }; -3. Compile the project + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - ```shell-session - zig build-exe src/main.zig \ - --name "[id]" \ - -mexec-model=reactor \ - -target wasm32-wasi \ - --mod worker::lib/worker.zig \ - --deps worker - ``` + return response; +} - You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/). +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); -4. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. + const parse_result = try wws.parseStream(allocator, .{}); + defer parse_result.deinit(); + const request = parse_result.value; - ```shell-session - wws . + var arena_impl = std.heap.ArenaAllocator.init(allocator); + defer arena_impl.deinit(); + const arena = arena_impl.allocator(); - ⚙️ Loading routes from: . - 🗺 Detected routes: - - http://127.0.0.1:8080/[id] - => worker-kv.wasm (name: default) - 🚀 Start serving requests at http://127.0.0.1:8080 - ``` + const response = try handle(arena, request); -5. Finally, open in your browser. + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} +``` ## Read environment variables -Environment variables are configured [via the related TOML configuration file](../features/environment-variables.md). These variables are accessible via `std.process.getEnvMap` or `std.process.getEnvVarOwned` in your worker. To read them, just use the same name you configured in your TOML file: +Environment variables are configured [via the related TOML configuration file](../features/environment-variables.md). While using the Zig SDK, this configuration file is generated for us when we call `wws.addWorker` in our `build.zig`: + +```zig title="build.zig" +pub fn build(b: *std.Build) !void { + // ... + + const worker = try wws.addWorker(b, .{ + .name = "envs", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + .wws = wws_dep, + .features = .{ + // Define your environment variables here + .vars = &.{ + .{ + .name = "MESSAGE", + .value = "Hello 👋! This message comes from an environment variable", + }, + }, + }, + }); + + // ... +} +``` -```toml title="envs.toml" +```shell-session +$ zig build +$ tree zig-out/root +zig-out/root/ +├── envs.toml +├── envs.wasm +$ cat zig-out/root/envs.toml name = "envs" version = "1" - [vars] MESSAGE = "Hello 👋! This message comes from an environment variable" ``` -Now, you can read the `MESSAGE` variable using either the `std.process.getEnvMap` or the `std.process.getEnvVarOwned` functions: +These variables are accessible via `std.process.getEnvMap` or `std.process.getEnvVarOwned` in your worker. To read them, just use the same name you configured in `build.zig`: -```c title="envs.zig" +```zig title="envs.zig" const std = @import("std"); -const worker = @import("worker"); +const wws = @import("wws"); -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const allocator = arena.allocator(); +const tmpl = + \\The environment variable value is: {s} +; -fn requestFn(resp: *worker.Response, r: *worker.Request) void { - _ = r; +fn handle(arena: std.mem.Allocator) !wws.Response { + const envvar = std.process.getEnvVarOwned(arena, "MESSAGE") catch ""; - const envvar = std.process.getEnvVarOwned(allocator, "MESSAGE") catch ""; - defer allocator.free(envvar); + const body = try std.fmt.allocPrint(arena, tmpl, .{envvar}); - const s = - \\The environment variable value is: {s} - ; + var response = wws.Response{ + .data = body, + }; - var body = std.fmt.allocPrint(allocator, s, .{ envvar }) catch undefined; // add useragent + try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server"); - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); + return response; } pub fn main() !void { - worker.ServeFunc(requestFn); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const response = try handle(arena.allocator()); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); } ``` @@ -378,7 +474,7 @@ Find other examples in the [`/examples` directory](https://github.com/vmware-lab ## Contributors -The Zig kit was originally authored by Christoph Voigt ([@voigt](https://github.com/voigt)). +The Zig kit was originally authored for Zig 0.11 by Christoph Voigt ([@voigt](https://github.com/voigt)). It was then updated for Zig 0.12 by Christopher Grass([@sea-grass](https://github.com/sea-grass)). ## Feature compatibility diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index d15be73e..2b3d1670 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -114,7 +114,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, - additionalLanguages: ['rust', 'toml', 'ruby', 'python'], + additionalLanguages: ['rust', 'toml', 'ruby', 'python', 'zig'], }, }), }; From 7d1d2f3b415e6da44853c005068c9af97476c219 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Wed, 20 Mar 2024 14:33:05 -0400 Subject: [PATCH 21/22] fix: Zig example on `0.12.0-dev.3381+7057bffc1` --- examples/zig-examples/src/basic.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zig-examples/src/basic.zig b/examples/zig-examples/src/basic.zig index f3c19af3..67990934 100644 --- a/examples/zig-examples/src/basic.zig +++ b/examples/zig-examples/src/basic.zig @@ -22,7 +22,7 @@ const tmpl = \\
\\

Hello from Wasm Workers Server 👋

\\
Replying to {s}
-    \\Method: {s}
+    \\Method: {d}
     \\User Agent: {s}
     \\Payload: {s}
\\

From 1d4ea4208d0ceb355ffe0fb51a14c96a7eaf6370 Mon Sep 17 00:00:00 2001 From: sea-grass Date: Fri, 22 Mar 2024 09:53:13 -0400 Subject: [PATCH 22/22] chore: remove Zig worker SDK in favour of Zig wws SDK --- kits/zig/worker/README.md | 49 ------ kits/zig/worker/src/worker.zig | 271 --------------------------------- 2 files changed, 320 deletions(-) delete mode 100644 kits/zig/worker/README.md delete mode 100644 kits/zig/worker/src/worker.zig diff --git a/kits/zig/worker/README.md b/kits/zig/worker/README.md deleted file mode 100644 index 403fd470..00000000 --- a/kits/zig/worker/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# 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. - -> *Note: this assumes Zig `0.11.0`* - -## Build - -To build all examples in ./examples - -```shell-session -$ zig build -Dtarget="wasm32-wasi" -``` - -To build a specific example: - -```shell-session -$ zig build-exe examples/.zig -target wasm32-wasi -``` - -## Testing - -At `./kits/zig/worker` execute: - -```shell-session -$ zig build -Dtarget="wasm32-wasi" -$ wws ./zig-out/bin/ -``` - -## sockaddr issue - -Using `http.Server.Response` was unsuccessful and lead to following error: - -``` -$ worker git:(144_-_add_support_for_zig) ✗ zig build -Dtarget="wasm32-wasi" -zig build-exe main Debug wasm32-wasi: error: the following command failed with 1 compilation errors: -/Users/c.voigt/.asdf/installs/zig/0.11.0/zig build-exe /Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/examples/main.zig --cache-dir /Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/zig-cache --global-cache-dir /Users/c.voigt/.cache/zig --name main -target wasm32-wasi -mcpu generic --mod worker::/Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/src/worker.zig --deps worker --listen=- -Build Summary: 6/9 steps succeeded; 1 failed (disable with --summary none) -install transitive failure -└─ install main transitive failure - └─ zig build-exe main Debug wasm32-wasi 1 errors -/Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/os.zig:182:28: error: root struct of file 'os.wasi' has no member named 'sockaddr' -pub const sockaddr = system.sockaddr; - ~~~~~~^~~~~~~~~ -referenced by: - Address: /Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/net.zig:18:12 - Address: /Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/net.zig:17:28 - remaining reference traces hidden; use '-freference-trace' to see all reference traces -``` \ No newline at end of file diff --git a/kits/zig/worker/src/worker.zig b/kits/zig/worker/src/worker.zig deleted file mode 100644 index 9b2494cd..00000000 --- a/kits/zig/worker/src/worker.zig +++ /dev/null @@ -1,271 +0,0 @@ -const std = @import("std"); -const io = std.io; -const http = std.http; - -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 Request = struct { - url: std.Uri, - method: []const u8, // TODO: change to http.Method enum - headers: http.Headers, - body: []const u8, - context: Context, -}; - -pub const Response = struct { - body: []const u8, - base64: bool, - headers: http.Headers, - request: Request, - - pub fn writeAll(res: *Response, data: []const u8) !u32 { - res.body = data; - return res.body.len; - } - - pub fn writeAllBase64(res: *Response, data: []const u8) !u32 { - res.body = base64Encode(data); - res.base64 = true; - return res.body.len; - } -}; - -// Note: as Zig does not support multiple return types, we use this struct -// to wrap both the request and the output to keep code a bit more clean -const RequestAndOutput = struct { - request: Request, - output: Output, -}; - -pub const Input = struct { - url: []const u8, - method: []const u8, - headers: std.StringArrayHashMap([]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 setStatus(self: *Self, statusCode: u16) void { - self.status = statusCode; - } - - pub fn write(self: *Self, response: Response) !u32 { - self.base64 = response.base64; - if (response.base64) { - self.data = base64Encode(response.body); - } else { - self.data = response.body; - } - - if (self.status == 0) { - self.setStatus(200); - } - - for (self.httpHeader.list.items) |item| { - try self.headers.put(item.name, item.value); - } - - // prepare writer for json - var out_buf: [4096]u8 = undefined; - var slice_stream = std.io.fixedBufferStream(&out_buf); - const out = slice_stream.writer(); - var w = std.json.writeStream(out, .{ .whitespace = .minified }); - - 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 w.write(try getHeadersJsonObject(self.headers)); - - try w.objectField("kv"); - try w.write(try getCacheJsonObject(cache)); - - try w.endObject(); - const result = slice_stream.getWritten(); - - const stdout = std.io.getStdOut().writer(); - try stdout.print("{s}", .{result}); - - return self.data.len; - } -}; - -fn base64Encode(data: []const u8) []const u8 { - // This initializing Base64Encoder throws weird error if not wrapped in function (maybe Zig bug?) - var enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); - const data_len = enc.calcSize(data.len); - var buf: [16384]u8 = undefined; - return enc.encode(buf[0..data_len], data); -} - -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()) |entry| { - try value.object.put(entry.key_ptr.*, std.json.Value{ .string = entry.value_ptr.* }); - } - - return value; -} - -pub fn readInput() !Input { - const in = std.io.getStdIn(); - var buf = std.io.bufferedReader(in.reader()); - var r = buf.reader(); - - const msg = try r.readAllAlloc(allocator, std.math.maxInt(u32)); - return getInput(msg); -} - -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.StringArrayHashMap([]const u8).init(allocator), - }; - - var headers_map = parsed.value.object.get("headers").?.object; - var headersIterator = headers_map.iterator(); - while (headersIterator.next()) |entry| { - try input.headers.put(entry.key_ptr.*, entry.value_ptr.*.string); - } - - var kv = parsed.value.object.get("kv").?.object; - var kvIterator = kv.iterator(); - while (kvIterator.next()) |entry| { - try cache.put(entry.key_ptr.*, entry.value_ptr.*.string); - } - - var p = parsed.value.object.get("params").?.object; - var paramsIterator = p.iterator(); - while (paramsIterator.next()) |entry| { - try params.put(entry.key_ptr.*, entry.value_ptr.*.string); - } - - return input; -} - -pub fn createRequest(in: *Input) !Request { - var req = Request{ - .url = try std.Uri.parseWithoutScheme(in.url), - .method = in.method, - .headers = http.Headers.init(allocator), - .body = in.body, - .context = Context.init(), - }; - - var i = in.headers.iterator(); - while (i.next()) |kv| { - try req.headers.append(kv.key_ptr.*, kv.value_ptr.*); - } - - return req; -} - -pub fn getWriterRequest() !RequestAndOutput { - var in = readInput() catch |err| { - std.debug.print("error reading input: {!}\n", .{err}); - return std.os.exit(1); - }; - - const req = createRequest(&in) catch |err| { - std.debug.print("error creating request : {!}\n", .{err}); - return std.os.exit(1); - }; - - const output = Output.init(); - - return RequestAndOutput{ - .request = req, - .output = output, - }; -} - -pub const Context = struct { - cache: *std.StringHashMap([]const u8), - params: *std.StringHashMap([]const u8), - - pub fn init() Context { - return .{ - .cache = &cache, - .params = ¶ms, - }; - } -}; - -pub fn ServeFunc(requestFn: *const fn (*Response, *Request) void) void { - const r = try getWriterRequest(); - var request = r.request; - var output = r.output; - - var response = Response{ - .body = "", - .base64 = false, - .headers = http.Headers.init(allocator), - .request = request, - }; - - requestFn(&response, &request); - - output.httpHeader = response.headers; - - _ = output.write(response) catch |err| { - std.debug.print("error writing data: {!} \n", .{err}); - }; -}