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'], }, }), }; 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-basic/src/basic.zig b/examples/zig-basic/src/basic.zig deleted file mode 100644 index 77268184..00000000 --- a/examples/zig-basic/src/basic.zig +++ /dev/null @@ -1,57 +0,0 @@ -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); -} - -pub fn main() !void { - worker.ServeFunc(requestFn); -} 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/src/envs.zig b/examples/zig-envs/src/envs.zig deleted file mode 100644 index 6be4131e..00000000 --- a/examples/zig-envs/src/envs.zig +++ /dev/null @@ -1,25 +0,0 @@ -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 { - _ = r; - - const envvar = std.process.getEnvVarOwned(allocator, "MESSAGE") catch ""; - defer allocator.free(envvar); - - const s = - \\The environment variable value is: {s} - ; - - var body = std.fmt.allocPrint(allocator, s, .{ envvar }) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); -} - -pub fn main() !void { - worker.ServeFunc(requestFn); -} 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-params/README.md b/examples/zig-examples/README.md similarity index 52% rename from examples/zig-params/README.md rename to examples/zig-examples/README.md index 220ee921..05ed6b5e 100644 --- a/examples/zig-params/README.md +++ b/examples/zig-examples/README.md @@ -1,6 +1,6 @@ -# Params example +# zig-examples -Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. +This example Zig project demonstrates how to use the WWS Zig SDK to compile WWS workers from Zig source code. ## Prerequisites @@ -10,24 +10,28 @@ Compile a Zig worker to WebAssembly and run it in Wasm Workers Server. curl -fsSL https://workers.wasmlabs.dev/install | bash ``` -* [Zig](https://ziglang.org/download/) `0.11.0` +* [Zig](https://ziglang.org/download/) `0.12.0` -## Build +# Build All specific build confiugrations are in `build.zig` file. -```shell-session +```sh zig build ``` -## Run +# Run -```shell-session -wws ./zig-out/bin/ +This step assumes that you have `wws` installed on your system. + +```sh +wws ./zig-out/root ``` ## 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/) +* [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 new file mode 100644 index 00000000..fa0b53ce --- /dev/null +++ b/examples/zig-examples/build.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const wws = @import("wws"); + +const examples = &[_]Example{ + .{ + .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 = "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", + .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/[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 { + const target = wws.getTarget(b); + const optimize = b.standardOptimizeOption(.{}); + + const wws_dep = b.dependency("wws", .{}); + const zig_router_dep = b.dependency("zig-router", .{}); + + const wf = b.addWriteFiles(); + + inline for (examples) |e| { + const worker = try wws.addWorker(b, .{ + .name = e.name, + .path = e.path orelse e.name, + .root_source_file = e.root_source_file, + .target = target, + .optimize = optimize, + .wws = wws_dep, + .features = e.features orelse .{}, + }); + + worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router")); + + 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, + .install_subdir = "root", + }); + + b.getInstallStep().dependOn(&install.step); +} diff --git a/examples/zig-examples/build.zig.zon b/examples/zig-examples/build.zig.zon new file mode 100644 index 00000000..1945c227 --- /dev/null +++ b/examples/zig-examples/build.zig.zon @@ -0,0 +1,69 @@ +.{ + .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", + }, + .@"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. + // 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-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-examples/src/basic.zig b/examples/zig-examples/src/basic.zig new file mode 100644 index 00000000..67990934 --- /dev/null +++ b/examples/zig-examples/src/basic.zig @@ -0,0 +1,64 @@ +const std = @import("std"); +const wws = @import("wws"); + +const tmpl = + \\ + \\ + \\Wasm Workers Server + \\ + \\ + \\ + \\ + \\ + \\ + \\
+ \\

Hello from Wasm Workers Server 👋

+ \\
Replying to {s}
+    \\Method: {d}
+    \\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 { + 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 new file mode 100644 index 00000000..3905373b --- /dev/null +++ b/examples/zig-examples/src/envs.zig @@ -0,0 +1,33 @@ +const std = @import("std"); +const wws = @import("wws"); + +const tmpl = + \\The environment variable value is: {s} +; + +fn handle(arena: std.mem.Allocator) !wws.Response { + const envvar = std.process.getEnvVarOwned(arena, "MESSAGE") catch ""; + + const body = try std.fmt.allocPrint(arena, tmpl, .{envvar}); + + var response = wws.Response{ + .data = body, + }; + + 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(); + + const response = try handle(arena.allocator()); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} 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..b8cff5c6 --- /dev/null +++ b/examples/zig-examples/src/mixed-alloc-kv.zig @@ -0,0 +1,66 @@ +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.

+ \\ +; + +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 { + 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; + }; +} diff --git a/examples/zig-examples/src/mount.zig b/examples/zig-examples/src/mount.zig new file mode 100644 index 00000000..a374cec9 --- /dev/null +++ b/examples/zig-examples/src/mount.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const wws = @import("wws"); + +fn handle(arena: std.mem.Allocator) !wws.Response { + 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, + }); + defer file.close(); + + const mb = (1 << 10) << 10; + 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"); + + 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(); + + const response = try handle(arena.allocator()); + + const stdout = std.io.getStdOut(); + try wws.writeResponse(response, stdout.writer()); +} 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..368e2568 --- /dev/null +++ b/examples/zig-examples/src/no-alloc-kv.zig @@ -0,0 +1,51 @@ +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.

+ \\ +; + +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 { + 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(); + + const request = parse_result.value; + const response = try handle(fba.allocator(), request); + + 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 new file mode 100644 index 00000000..c6c1289e --- /dev/null +++ b/examples/zig-examples/src/params.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const wws = @import("wws"); + +const tmpl = + \\Hey! The parameter is: {s} +; + +fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response { + const id = request.params.map.get("id") orelse "the value is not available"; + + const body = try std.fmt.allocPrint(arena, tmpl, .{id}); + + var response = wws.Response{ + .data = body, + }; + + 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(); + + 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/router.zig b/examples/zig-examples/src/router.zig new file mode 100644 index 00000000..72c252be --- /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()); +} diff --git a/examples/zig-examples/src/worker-kv.zig b/examples/zig-examples/src/worker-kv.zig new file mode 100644 index 00000000..8dadba6f --- /dev/null +++ b/examples/zig-examples/src/worker-kv.zig @@ -0,0 +1,54 @@ +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.

+ \\ +; + +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 { + 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-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 8f5b624a..00000000 --- a/examples/zig-kv/build.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -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 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-kv/src/worker-kv.zig b/examples/zig-kv/src/worker-kv.zig deleted file mode 100644 index 3f1bcd78..00000000 --- a/examples/zig-kv/src/worker-kv.zig +++ /dev/null @@ -1,46 +0,0 @@ -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; - - var 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; - counter = num + 1; - var 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.

- \\ - ; - - var body = std.fmt.allocPrint(allocator, s, .{ counter }) catch undefined; // add useragent - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(body); -} - -pub fn main() !void { - worker.ServeFunc(requestFn); -} 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/src/mount.zig b/examples/zig-mount/src/mount.zig deleted file mode 100644 index 72a64962..00000000 --- a/examples/zig-mount/src/mount.zig +++ /dev/null @@ -1,26 +0,0 @@ -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 { - _ = 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; - defer file.close(); - - const mb = (1 << 10) << 10; - const file_contents = file.readToEndAlloc(allocator, mb) catch ""; - - _ = &resp.headers.append("x-generated-by", "wasm-workers-server"); - _ = &resp.writeAll(file_contents); -} - -pub fn main() !void { - worker.ServeFunc(requestFn); -} 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/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); - } -} diff --git a/examples/zig-params/src/worker-params.zig b/examples/zig-params/src/worker-params.zig deleted file mode 100644 index a571d28b..00000000 --- a/examples/zig-params/src/worker-params.zig +++ /dev/null @@ -1,30 +0,0 @@ -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; - - var id: []const u8 = "the value is not available"; - - var v = params.get("id"); - - if (v) |val| { - id = val; - } - - 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); -} - -pub fn main() !void { - worker.ServeFunc(requestFn); -} 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 0bfbfa1c..00000000 --- a/kits/zig/worker/src/worker.zig +++ /dev/null @@ -1,266 +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, '='); - var 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(); - - var 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); - }; - - var req = createRequest(&in) catch |err| { - std.debug.print("error creating request : {!}\n", .{err}); - return std.os.exit(1); - }; - - var 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 { - var 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 }); - }; -} 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/build.zig b/kits/zig/wws/build.zig new file mode 100644 index 00000000..92deec51 --- /dev/null +++ b/kits/zig/wws/build.zig @@ -0,0 +1,163 @@ +const std = @import("std"); + +const module_name = "wws"; + +pub fn build(b: *std.Build) void { + _ = b.standardTargetOptions(.{}); + _ = b.standardOptimizeOption(.{}); + + const module = b.addModule(module_name, .{ + .root_source_file = .{ .path = "src/wws.zig" }, + .target = getTarget(b), + }); + + _ = module; +} + +// Returns the wasm32-wasi target +pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget { + return b.resolveTargetQuery(.{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }); +} + +pub const EnvVar = struct { + name: []const u8, + value: []const u8, +}; + +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 { + exe: *std.Build.Step.Compile, + 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})); + + 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, + }, + ); + } + + 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(); + } +}; + +pub const WorkerOptions = 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: Features = .{}, + // 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: WorkerOptions) !WwsWorker { + 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(module_name)); + + return .{ + .exe = exe, + .options = options, + }; +} 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..5ade8ce3 --- /dev/null +++ b/kits/zig/wws/src/wws.zig @@ -0,0 +1,64 @@ +const std = @import("std"); + +pub const Method = std.http.Method; + +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 = "", + // TODO Use std.http.Status when Response has its own + // json serialization helper functions + status: usize = 200, + kv: std.json.ArrayHashMap([]const u8) = .{}, +}; + +const InputStreamType = enum { + stdin, + bytes, +}; + +const InputStream = union(InputStreamType) { + stdin: void, + bytes: []const u8, +}; + +const ParseStreamError = error{Unknown}; + +const ParseStreamOptions = struct { + input_stream: InputStream = .stdin, +}; + +/// 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(); + + 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; + }, + } + + return std.json.parseFromSlice( + Request, + allocator, + input.items, + .{ .allocate = .alloc_always }, + ) catch ParseStreamError.Unknown; +} + +pub inline fn writeResponse(response: Response, writer: anytype) !void { + try std.json.stringify(response, .{}, writer); +}