diff --git a/.github/workflows/build-zig-11.yml b/.github/workflows/build-current-zig.yml similarity index 94% rename from .github/workflows/build-zig-11.yml rename to .github/workflows/build-current-zig.yml index 877441b7..de3c986c 100644 --- a/.github/workflows/build-zig-11.yml +++ b/.github/workflows/build-current-zig.yml @@ -1,4 +1,4 @@ -name: Works with Zig 0.11.0 +name: Works with Zig 0.12.0 on: push: branches: @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - uses: goto-bus-stop/setup-zig@v2 with: - version: 0.11.0 + version: 0.12.0 - name: Check zig version run: zig version - name: Build all examples diff --git a/.github/workflows/mastercheck-localhost.yml b/.github/workflows/mastercheck-localhost.yml deleted file mode 100644 index e5975949..00000000 --- a/.github/workflows/mastercheck-localhost.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Works with Zig master (localhost patch) -on: - # push: - # branches: - # - master - # pull_request: - # branches: - # - master - # schedule: - # - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - ci: - strategy: - matrix: - # platform: [ubuntu-latest, windows-latest, macos-latest] - platform: [ubuntu-latest] - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v3 - - uses: goto-bus-stop/setup-zig@v2 - with: - version: master - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: wget - uses: wei/wget@v1 - with: - args: https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.8.tar.gz - - name: Check zig version - run: zig version - - name: hack build.zig.zon - run: | - mv build.zig.zon build.zig.zon.moved && mv build.zig.zon.localhost build.zig.zon && python -m http.server & - sleep 3 - zig build - - name: Build all examples - run: zig build all - - name: Run authentication tests - run: zig build test-authentication - - name: Run http parameter tests - run: zig build test-httpparams - - name: Run sendfile tests - run: zig build test-sendfile - diff --git a/.github/workflows/mastercheck.yml b/.github/workflows/mastercheck.yml index 7ab68704..8b54daca 100644 --- a/.github/workflows/mastercheck.yml +++ b/.github/workflows/mastercheck.yml @@ -1,21 +1,20 @@ name: Works with Zig master on: - # push: - # branches: - # - master - # pull_request: - # branches: - # - master - # schedule: - # - cron: "0 0 * * *" - workflow_dispatch: + push: + branches: + - zig-0.13.0 + pull_request: + branches: + - zig-0.13.0 + schedule: + - cron: "0 0 * * *" + workflow_dispatch: jobs: ci: strategy: matrix: - # platform: [ubuntu-latest, windows-latest, macos-latest] - platform: [ubuntu-latest] + platform: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v3 @@ -26,5 +25,45 @@ jobs: run: zig version - name: Build all examples run: zig build all - - name: Run all tests - run: zig build test + # Run tests separately so we can see more clearly which one fails + - name: Run mustache tests + run: zig build test-mustache + - name: Run httpparams tests + run: zig build test-httpparams + - name: Run sendfile tests + run: zig build test-sendfile + - name: Run authentication tests + run: zig build test-authentication + - name: Report end of tests + run: echo "tests finished" + + update-readme: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + + steps: + - uses: actions/checkout@v3 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: master + + - name: Build announceybot + run: zig build announceybot + + - name: Run script to update README + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ steps.tag.outputs.version }} + run: | + zig-out/bin/announceybot update-readme "zig-0.13.0" + + - name: Commit and push if it has changed + run: | + git diff + git checkout zig-0.13.0 + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -am "Update README" + git push origin zig-0.13.0 diff --git a/.gitignore b/.gitignore index 6b2dd8e4..6d2d37c1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ wrk/csharp/obj/ wrk/csharp/out/ scratch **/.mypy_cache/* -docs/ .DS_Store .vs/ **/*.perflog diff --git a/README.md b/README.md index 4ca730cb..4d21efb8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - - # ⚡zap⚡ - blazingly fast backends in zig ![](https://github.com/zigzap/zap/actions/workflows/build-zig-11.yml/badge.svg) ![](https://github.com/zigzap/zap/actions/workflows/mastercheck.yml/badge.svg) [![Discord](https://img.shields.io/discord/1107835896356675706?label=chat&logo=discord&style=plastic)](https://discord.gg/jQAAN6Ubyj) @@ -26,18 +24,18 @@ that it proved to be: Exactly the goals I set out to achieve! - ## Most FAQ: -### Zap uses the latest stable zig release (0.11.0) for a reason. So you don't have to keep up with frequent breaking changes. It's an "LTS feature". If you want to use zig master, use the `zig-0.12.0` branch but be aware that I don't provide `build.zig.zon` snippets or tagged releases for it for the time being. If you know what you are doing, that shouldn't stop you from using it with zig master though. +### Zap uses the latest stable zig release (0.1@.0) for a reason. So you don't have to keep up with frequent breaking changes. It's an "LTS feature". If you want to use zig master, use the `zig-master` branch (coming soon) but be aware that I don't provide `build.zig.zon` snippets or tagged releases for it for the time being. If you know what you are doing, that shouldn't stop you from using it with zig master though. - Q: **Where is the API documentation?** - - A: Docs are a work in progress. You can check them out [here](https://zigzap.org/zap). The docs are based on the `zig-0.12.0` branch but apply to the current release (zig 0.11.0), too. + - A: Docs are a work in progress. You can check them out [here](https://zigzap.org/zap). + - A: Run `zig build run-docserver` to serve them locally. - Q: **Zap doesn't build with Zig master?** - - A: See the 0.12.0 branch. An example of how to use it is - [here](https://github.com/zigzap/hello-0.12.0). Please note that the 0.12.0 - branch is not the official master branch of ZAP. Yet. Until zig 0.12.0 is - released. + - A: See the zig-master branch (soon). An example of how to use it is + [here](https://github.com/zigzap/hello-0.13.0). Please note that the + zig-master branch is not the official master branch of ZAP. Yet. Until zig + 0.13.0 is released. - Q: **Does ZAP work on Windows?** - A: No. This is due to the underlying facil.io C library. Future versions of facil.io might support Windows but there is no timeline yet. Your best options @@ -57,6 +55,10 @@ I recommend checking out **Endpoint-based examples for more realistic use cases**. Most of the examples are super stripped down to only include what's necessary to show a feature. +**NOTE: To see API docs, run `zig build run-docserver`.** To specify a custom +port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989 +--docs=path/to/docs`. + - **Super easy build process**: Zap's `build.zig` now uses the new Zig package manager for its C-dependencies, no git submodules anymore. - _tested on Linux and macOS (arm, M1)_ @@ -222,7 +224,7 @@ code leaks memory. ## Getting started -Make sure you have **the latest zig release (0.11.0)** installed. Fetch it from +Make sure you have zig 0.12.0 installed. Fetch it from [here](https://ziglang.org/download). ```shell @@ -236,7 +238,7 @@ $ # open http://localhost:3000 in your browser ## Using ⚡zap⚡ in your own projects -Make sure you have **the latest zig release (0.11.0)** installed. Fetch it from +Make sure you have **the latest zig release (0.12.0)** installed. Fetch it from [here](https://ziglang.org/download). If you don't have an existing zig project, create one like this: @@ -250,7 +252,7 @@ $ git init ## (optional) `nix develop` to get a development shell providing zig and all dependencies to build and run the GO, python, and rust examples for the `wrk` performance tests. For the mere building of zap projects, -`nix develop .#build` will only fetch zig 0.11.0. +`nix develop .#build` will only fetch zig 0.11.0. TODO: upgrade to zig 0.12. With an existing Zig project, adding Zap to it is easy: @@ -266,12 +268,15 @@ To add zap to `build.zig.zon`: .version = "0.0.1", .dependencies = .{ - // zap v0.6.0 + // zap v0.7.0 .zap = .{ - .url = "https://github.com/zigzap/zap/archive/refs/tags/v0.6.0.tar.gz", - .hash = "1220a5a1e6b18fa384d8a98e5d5a25720ddadbcfed01da2e4ca55c7cfb3dc1caa62a", - } - } + .url = "https://github.com/zigzap/zap/archive/refs/tags/v0.7.0.tar.gz", + .hash = "12203126ff24e8018655eb7444c91f0d527d1213af16fcf2a578281abc994d01cc46", + }, + }, + .paths = .{ + "", + }, } ``` @@ -285,8 +290,8 @@ Then, in your `build.zig`'s `build` function, add the following before .optimize = optimize, .openssl = false, // set to true to enable TLS support }); - exe.addModule("zap", zap.module("zap")); - exe.linkLibrary(zap.artifact("facil.io")); + + exe.root_module.addImport("zap", zap.module("zap")); ``` From then on, you can use the Zap package in your project. Check out the @@ -405,27 +410,3 @@ pub fn main() !void { } ``` - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build.zig b/build.zig index 7279ee49..98b0a0d0 100644 --- a/build.zig +++ b/build.zig @@ -1,11 +1,11 @@ const std = @import("std"); const build_facilio = @import("facil.io/build.zig").build_facilio; -pub fn build(b: *std.build.Builder) !void { +pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); - if (target.getOsTag() == .windows) { + if (target.result.os.tag == .windows) { std.log.err("\x1b[31mPlatform Not Supported\x1b[0m\nCurrently, Facil.io and Zap are not compatible with Windows. Consider using Linux or Windows Subsystem for Linux (WSL) instead.\nFor more information, please see:\n- https://github.com/zigzap/zap#most-faq\n- https://facil.io/#forking-contributing-and-all-that-jazz\n", .{}); - std.os.exit(1); + std.process.exit(1); } // Standard release options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. @@ -13,24 +13,39 @@ pub fn build(b: *std.build.Builder) !void { const use_openssl = b.option(bool, "openssl", "Use system-installed openssl for TLS support in zap") orelse blk: { // Alternatively, use an os env var to determine whether to build openssl support - if (std.os.getenv("ZAP_USE_OPENSSL")) |val| { + if (std.process.getEnvVarOwned(b.allocator, "ZAP_USE_OPENSSL")) |val| { + defer b.allocator.free(val); if (std.mem.eql(u8, val, "true")) break :blk true; - } + } else |_| {} break :blk false; }; - // create a module to be used internally. - var zap_module = b.createModule(.{ - .source_file = .{ .path = "src/zap.zig" }, - }); - - // register the module so it can be referenced using the package manager. - try b.modules.put(b.dupe("zap"), zap_module); - const facilio = try build_facilio("facil.io", b, target, optimize, use_openssl); + const zap_module = b.addModule("zap", .{ + .root_source_file = b.path("src/zap.zig"), + .target = target, + .optimize = optimize, + }); + zap_module.linkLibrary(facilio); + const all_step = b.step("all", "build all examples"); + // -- Docs + const docs_obj = b.addObject(.{ + .name = "zap", // name doesn't seem to matter + .root_source_file = b.path("src/zap.zig"), + .target = target, + .optimize = .Debug, + }); + const install_docs = b.addInstallDirectory(.{ + .install_dir = .prefix, + .install_subdir = "zap", // will also be the main namespace in the docs + .source_dir = docs_obj.getEmittedDocs(), + }); + b.step("docs", "Build docs").dependOn(&install_docs.step); + // -- + inline for ([_]struct { name: []const u8, src: []const u8, @@ -56,6 +71,7 @@ pub fn build(b: *std.build.Builder) !void { .{ .name = "middleware_with_endpoint", .src = "examples/middleware_with_endpoint/middleware_with_endpoint.zig" }, .{ .name = "senderror", .src = "examples/senderror/senderror.zig" }, .{ .name = "bindataformpost", .src = "examples/bindataformpost/bindataformpost.zig" }, + .{ .name = "accept", .src = "examples/accept/accept.zig" }, }) |excfg| { const ex_name = excfg.name; const ex_src = excfg.src; @@ -80,13 +96,12 @@ pub fn build(b: *std.build.Builder) !void { var example = b.addExecutable(.{ .name = ex_name, - .root_source_file = .{ .path = ex_src }, + .root_source_file = b.path(ex_src), .target = target, .optimize = optimize, }); - example.linkLibrary(facilio); - example.addModule("zap", zap_module); + example.root_module.addImport("zap", zap_module); // const example_run = example.run(); const example_run = b.addRunArtifact(example); @@ -120,12 +135,11 @@ pub fn build(b: *std.build.Builder) !void { // const auth_tests = b.addTest(.{ .name = "auth_tests", - .root_source_file = .{ .path = "src/tests/test_auth.zig" }, + .root_source_file = b.path("src/tests/test_auth.zig"), .target = target, .optimize = optimize, }); - auth_tests.linkLibrary(facilio); - auth_tests.addModule("zap", zap_module); + auth_tests.root_module.addImport("zap", zap_module); const run_auth_tests = b.addRunArtifact(auth_tests); const install_auth_tests = b.addInstallArtifact(auth_tests, .{}); @@ -133,12 +147,11 @@ pub fn build(b: *std.build.Builder) !void { // mustache tests const mustache_tests = b.addTest(.{ .name = "mustache_tests", - .root_source_file = .{ .path = "src/tests/test_mustache.zig" }, + .root_source_file = b.path("src/tests/test_mustache.zig"), .target = target, .optimize = optimize, }); - mustache_tests.linkLibrary(facilio); - mustache_tests.addModule("zap", zap_module); + mustache_tests.root_module.addImport("zap", zap_module); const run_mustache_tests = b.addRunArtifact(mustache_tests); const install_mustache_tests = b.addInstallArtifact(mustache_tests, .{}); @@ -146,13 +159,13 @@ pub fn build(b: *std.build.Builder) !void { // http paramters (qyery, body) tests const httpparams_tests = b.addTest(.{ .name = "http_params_tests", - .root_source_file = .{ .path = "src/tests/test_http_params.zig" }, + .root_source_file = b.path("src/tests/test_http_params.zig"), .target = target, .optimize = optimize, }); - httpparams_tests.linkLibrary(facilio); - httpparams_tests.addModule("zap", zap_module); + httpparams_tests.root_module.addImport("zap", zap_module); + const run_httpparams_tests = b.addRunArtifact(httpparams_tests); // TODO: for some reason, tests aren't run more than once unless // dependencies have changed. @@ -163,13 +176,12 @@ pub fn build(b: *std.build.Builder) !void { // http paramters (qyery, body) tests const sendfile_tests = b.addTest(.{ .name = "sendfile_tests", - .root_source_file = .{ .path = "src/tests/test_sendfile.zig" }, + .root_source_file = b.path("src/tests/test_sendfile.zig"), .target = target, .optimize = optimize, }); - sendfile_tests.linkLibrary(facilio); - sendfile_tests.addModule("zap", zap_module); + sendfile_tests.root_module.addImport("zap", zap_module); const run_sendfile_tests = b.addRunArtifact(sendfile_tests); const install_sendfile_tests = b.addInstallArtifact(sendfile_tests, .{}); @@ -202,9 +214,9 @@ pub fn build(b: *std.build.Builder) !void { // // pkghash // - var pkghash_exe = b.addExecutable(.{ + const pkghash_exe = b.addExecutable(.{ .name = "pkghash", - .root_source_file = .{ .path = "./tools/pkghash.zig" }, + .root_source_file = b.path("./tools/pkghash.zig"), .target = target, .optimize = optimize, }); @@ -213,12 +225,36 @@ pub fn build(b: *std.build.Builder) !void { pkghash_step.dependOn(&pkghash_build_step.step); all_step.dependOn(&pkghash_build_step.step); + // + // docserver + // + const docserver_exe = b.addExecutable(.{ + .name = "docserver", + .root_source_file = b.path("./tools/docserver.zig"), + .target = target, + .optimize = optimize, + }); + docserver_exe.root_module.addImport("zap", zap_module); + var docserver_step = b.step("docserver", "Build docserver"); + const docserver_build_step = b.addInstallArtifact(docserver_exe, .{}); + docserver_step.dependOn(&docserver_build_step.step); + docserver_step.dependOn(&install_docs.step); + + const docserver_run_step = b.step("run-docserver", "run the docserver"); + const docserver_run = b.addRunArtifact(docserver_exe); + docserver_run.addPrefixedDirectoryArg("--docs=", docs_obj.getEmittedDocs()); + + docserver_run_step.dependOn(&docserver_run.step); + docserver_run_step.dependOn(docserver_step); + + all_step.dependOn(&docserver_build_step.step); + // // announceybot // - var announceybot_exe = b.addExecutable(.{ + const announceybot_exe = b.addExecutable(.{ .name = "announceybot", - .root_source_file = .{ .path = "./tools/announceybot.zig" }, + .root_source_file = b.path("./tools/announceybot.zig"), .target = target, .optimize = optimize, }); diff --git a/build.zig.zon b/build.zig.zon index f7602046..950014ef 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1 +1,10 @@ -.{ .name = "zap", .version = "0.6.0" } +.{ + .name = "zap", + .version = "0.7.1", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "facil.io", + }, +} diff --git a/doc/release-template.md b/doc/release-template.md index e41db517..cd08a68b 100644 --- a/doc/release-template.md +++ b/doc/release-template.md @@ -22,7 +22,10 @@ Here is a complete `build.zig.zon` example: .zap = .{ .url = "https://github.com/zigzap/zap/archive/refs/tags/{tag}.tar.gz", .hash = "{hash}", - } + }, + .paths = .{ + "", + }, } } @@ -35,7 +38,8 @@ Then, in your `build.zig`'s `build` function, add the following before const zap = b.dependency("zap", .{ .target = target, .optimize = optimize, + .openssl = false, // set to true to enable TLS support }); - exe.addModule("zap", zap.module("zap")); + exe.root_module.addImport("zap", zap.module("zap")); exe.linkLibrary(zap.artifact("facil.io")); ``` diff --git a/examples/accept/accept.zig b/examples/accept/accept.zig new file mode 100644 index 00000000..477e9738 --- /dev/null +++ b/examples/accept/accept.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const zap = @import("zap"); + +var gpa = std.heap.GeneralPurposeAllocator(.{ + .thread_safe = true, +}){}; + +fn on_request_verbose(r: zap.Request) void { + // use a local buffer for the parsed accept headers + var accept_buffer: [1024]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&accept_buffer); + const accept_allocator = fba.allocator(); + + const content_type: zap.ContentType = content_type: { + var accept_list = r.parseAcceptHeaders(accept_allocator) catch break :content_type .HTML; + defer accept_list.deinit(); + + for (accept_list.items) |accept| { + break :content_type accept.toContentType() orelse continue; + } + break :content_type .HTML; + }; + + r.setContentType(content_type) catch return; + switch (content_type) { + .TEXT => { + r.sendBody("Hello from ZAP!!!") catch return; + }, + .HTML => { + r.sendBody("

Hello from ZAP!!!

") catch return; + }, + .XML => { + r.sendBody( + \\ + \\ + \\ + \\ Hello from ZAP!!! + \\ + \\ + ) catch return; + }, + .JSON => { + var buffer: [128]u8 = undefined; + const json = zap.stringifyBuf(&buffer, .{ .message = "Hello from ZAP!!!" }, .{}) orelse return; + r.sendJson(json) catch return; + }, + .XHTML => { + r.sendBody( + \\ + \\ + \\ + \\

Hello from ZAP!!!

+ \\ + \\ + ) catch return; + }, + } +} + +pub fn main() !void { + var listener = zap.HttpListener.init(.{ + .port = 3000, + .on_request = on_request_verbose, + .log = true, + .max_clients = 100000, + }); + try listener.listen(); + + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + + // start worker threads + zap.start(.{ + .threads = 2, + .workers = 2, + }); +} diff --git a/examples/bindataformpost/bindataformpost.zig b/examples/bindataformpost/bindataformpost.zig index 944b92e7..4c2c7ad1 100644 --- a/examples/bindataformpost/bindataformpost.zig +++ b/examples/bindataformpost/bindataformpost.zig @@ -5,7 +5,7 @@ const Handler = struct { var alloc: std.mem.Allocator = undefined; pub fn on_request(r: zap.Request) void { - // check for FORM parameters + // parse for FORM (body) parameters first r.parseBody() catch |err| { std.log.err("Parse Body error: {any}. Expected if body is empty", .{err}); }; @@ -13,10 +13,11 @@ const Handler = struct { if (r.body) |body| { std.log.info("Body length is {any}\n", .{body.len}); } - // check for query params (for ?terminate=true) + + // parse potential query params (for ?terminate=true) r.parseQuery(); - var param_count = r.getParamCount(); + const param_count = r.getParamCount(); std.log.info("param_count: {}", .{param_count}); // iterate over all params @@ -56,8 +57,11 @@ const Handler = struct { else => { // might be a string param, we don't care // let's just get it as string + // always_alloc param = false -> the string will be a slice from the request buffer + // --> no deinit necessary if (r.getParamStr(Handler.alloc, kv.key.str, false)) |maybe_str| { const value: []const u8 = if (maybe_str) |s| s.str else "(no value)"; + // above, we didn't defer s.deinit because the string is just a slice from the request buffer std.log.debug(" {s} = {s}", .{ kv.key.str, value }); } else |err| { std.log.err("Error: {any}\n", .{err}); @@ -70,7 +74,6 @@ const Handler = struct { // check if we received a terminate=true parameter if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| { if (maybe_str) |*s| { - defer s.deinit(); std.log.info("?terminate={s}\n", .{s.str}); if (std.mem.eql(u8, s.str, "true")) { zap.stop(); @@ -87,7 +90,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); Handler.alloc = allocator; diff --git a/examples/cookies/cookies.zig b/examples/cookies/cookies.zig index 10ee440e..087731fe 100644 --- a/examples/cookies/cookies.zig +++ b/examples/cookies/cookies.zig @@ -5,17 +5,19 @@ const zap = @import("zap"); fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { const uri = try std.Uri.parse(url); - var h = std.http.Headers{ .allocator = a }; - defer h.deinit(); - var http_client: std.http.Client = .{ .allocator = a }; defer http_client.deinit(); - var req = try http_client.request(.GET, uri, h, .{}); + var server_header_buffer: [2048]u8 = undefined; + var req = try http_client.open(.GET, uri, .{ + .server_header_buffer = &server_header_buffer, + .extra_headers = &.{ + .{ .name = "cookie", .value = "ZIG_ZAP=awesome" }, + }, + }); defer req.deinit(); - try req.headers.append("cookie", "ZIG_ZAP=awesome"); - try req.start(); + try req.send(); try req.wait(); } @@ -28,7 +30,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); const Handler = struct { var alloc: std.mem.Allocator = undefined; @@ -37,17 +39,19 @@ pub fn main() !void { std.debug.print("\n=====================================================\n", .{}); defer std.debug.print("=====================================================\n\n", .{}); - r.parseCookies(false); + r.parseCookies(false); // url_encoded = false - var cookie_count = r.getCookiesCount(); + const cookie_count = r.getCookiesCount(); std.log.info("cookie_count: {}", .{cookie_count}); - // iterate over all cookies as strings + // iterate over all cookies as strings (always_alloc=false) var strCookies = r.cookiesToOwnedStrList(alloc, false) catch unreachable; defer strCookies.deinit(); std.debug.print("\n", .{}); for (strCookies.items) |kv| { std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str }); + // we don't need to deinit kv.key and kv.value because we requested always_alloc=false + // so they are just slices into the request buffer } std.debug.print("\n", .{}); @@ -63,7 +67,7 @@ pub fn main() !void { std.debug.print("\n", .{}); if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| { if (maybe_str) |*s| { - defer s.deinit(); + defer s.deinit(); // unnecessary because always_alloc=false std.log.info("Cookie ZIG_ZAP = {s}", .{s.str}); } else { diff --git a/examples/endpoint/main.zig b/examples/endpoint/main.zig index 31bb487b..dcfffda2 100644 --- a/examples/endpoint/main.zig +++ b/examples/endpoint/main.zig @@ -3,7 +3,7 @@ const zap = @import("zap"); const UserWeb = @import("userweb.zig"); const StopEndpoint = @import("stopendpoint.zig"); -// this is just to demo that we can catch arbitrary slugs +// this is just to demo that we can catch arbitrary slugs as fallback fn on_request(r: zap.Request) void { if (r.path) |the_path| { std.debug.print("REQUESTED PATH: {s}\n", .{the_path}); @@ -16,7 +16,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); // we scope everything that can allocate within this block for leak detection { @@ -56,7 +56,7 @@ pub fn main() !void { // and run zap.start(.{ - .threads = 2000, + .threads = 2, // IMPORTANT! It is crucial to only have a single worker for this example to work! // Multiple workers would have multiple copies of the users hashmap. // diff --git a/examples/endpoint/users.zig b/examples/endpoint/users.zig index 75c114ed..2ab7a17b 100644 --- a/examples/endpoint/users.zig +++ b/examples/endpoint/users.zig @@ -41,12 +41,12 @@ pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize { user.lastnamelen = 0; if (first) |firstname| { - std.mem.copy(u8, user.firstnamebuf[0..], firstname); + @memcpy(user.firstnamebuf[0..firstname.len], firstname); user.firstnamelen = firstname.len; } if (last) |lastname| { - std.mem.copy(u8, user.lastnamebuf[0..], lastname); + @memcpy(user.lastnamebuf[0..lastname.len], lastname); user.lastnamelen = lastname.len; } @@ -101,11 +101,11 @@ pub fn update( pUser.firstnamelen = 0; pUser.lastnamelen = 0; if (first) |firstname| { - std.mem.copy(u8, pUser.firstnamebuf[0..], firstname); + @memcpy(pUser.firstnamebuf[0..firstname.len], firstname); pUser.firstnamelen = firstname.len; } if (last) |lastname| { - std.mem.copy(u8, pUser.lastnamebuf[0..], lastname); + @memcpy(pUser.lastnamebuf[0..lastname.len], lastname); pUser.lastnamelen = lastname.len; } } diff --git a/examples/endpoint/userweb.zig b/examples/endpoint/userweb.zig index ca11a6ab..6c6775f8 100644 --- a/examples/endpoint/userweb.zig +++ b/examples/endpoint/userweb.zig @@ -54,7 +54,8 @@ fn userIdFromPath(self: *Self, path: []const u8) ?usize { } fn getUser(e: *zap.Endpoint, r: zap.Request) void { - const self = @fieldParentPtr(Self, "ep", e); + const self: *Self = @fieldParentPtr("ep", e); + if (r.path) |path| { // /users if (path.len == e.settings.path.len) { @@ -81,9 +82,9 @@ fn listUsers(self: *Self, r: zap.Request) void { } fn postUser(e: *zap.Endpoint, r: zap.Request) void { - const self = @fieldParentPtr(Self, "ep", e); + const self: *Self = @fieldParentPtr("ep", e); if (r.body) |body| { - var maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; + const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; if (maybe_user) |u| { defer u.deinit(); if (self._users.addByName(u.value.first_name, u.value.last_name)) |id| { @@ -100,12 +101,12 @@ fn postUser(e: *zap.Endpoint, r: zap.Request) void { } fn putUser(e: *zap.Endpoint, r: zap.Request) void { - const self = @fieldParentPtr(Self, "ep", e); + const self: *Self = @fieldParentPtr("ep", e); if (r.path) |path| { if (self.userIdFromPath(path)) |id| { if (self._users.get(id)) |_| { if (r.body) |body| { - var maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; + const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; if (maybe_user) |u| { defer u.deinit(); var jsonbuf: [128]u8 = undefined; @@ -126,7 +127,7 @@ fn putUser(e: *zap.Endpoint, r: zap.Request) void { } fn deleteUser(e: *zap.Endpoint, r: zap.Request) void { - const self = @fieldParentPtr(Self, "ep", e); + const self: *Self = @fieldParentPtr("ep", e); if (r.path) |path| { if (self.userIdFromPath(path)) |id| { var jsonbuf: [128]u8 = undefined; diff --git a/examples/hello_json/hello_json.zig b/examples/hello_json/hello_json.zig index 4361e281..9f26381d 100644 --- a/examples/hello_json/hello_json.zig +++ b/examples/hello_json/hello_json.zig @@ -41,7 +41,7 @@ fn setupUserData(a: std.mem.Allocator) !void { } pub fn main() !void { - var a = std.heap.page_allocator; + const a = std.heap.page_allocator; try setupUserData(a); var listener = zap.HttpListener.init(.{ .port = 3000, @@ -63,6 +63,6 @@ pub fn main() !void { // start worker threads zap.start(.{ .threads = 2, - .workers = 2, + .workers = 1, // user map cannot be shared among multiple worker processes }); } diff --git a/examples/http_params/http_params.zig b/examples/http_params/http_params.zig index d6753f3d..e387e9b0 100644 --- a/examples/http_params/http_params.zig +++ b/examples/http_params/http_params.zig @@ -5,16 +5,16 @@ const zap = @import("zap"); fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { const uri = try std.Uri.parse(url); - var h = std.http.Headers{ .allocator = a }; - defer h.deinit(); - var http_client: std.http.Client = .{ .allocator = a }; defer http_client.deinit(); - var req = try http_client.request(.GET, uri, h, .{}); + var server_header_buffer: [2048]u8 = undefined; + var req = try http_client.open(.GET, uri, .{ + .server_header_buffer = &server_header_buffer, + }); defer req.deinit(); - try req.start(); + try req.send(); try req.wait(); } @@ -27,7 +27,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); const Handler = struct { var alloc: std.mem.Allocator = undefined; @@ -44,7 +44,7 @@ pub fn main() !void { // check for query parameters r.parseQuery(); - var param_count = r.getParamCount(); + const param_count = r.getParamCount(); std.log.info("param_count: {}", .{param_count}); // ================================================================ diff --git a/examples/https/https.zig b/examples/https/https.zig index 88c53a72..aeb7930e 100644 --- a/examples/https/https.zig +++ b/examples/https/https.zig @@ -30,7 +30,7 @@ fn help_and_exit(filename: []const u8, err: anyerror) void { , .{ filename, err }, ); - std.os.exit(1); + std.process.exit(1); } pub fn main() !void { const CERT_FILE = "mycert.pem"; diff --git a/examples/middleware/middleware.zig b/examples/middleware/middleware.zig index 5687f936..776a930d 100644 --- a/examples/middleware/middleware.zig +++ b/examples/middleware/middleware.zig @@ -72,7 +72,7 @@ const UserMiddleWare = struct { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); _ = self; // do our work: fill in the user field of the context @@ -115,7 +115,7 @@ const SessionMiddleWare = struct { // note that the first parameter is of type *Handler, not *Self !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); _ = self; context.session = Session{ @@ -151,7 +151,7 @@ const HtmlMiddleWare = struct { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); _ = self; std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context}); @@ -190,7 +190,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); SharedAllocator.init(allocator); // we create our HTML middleware component that handles the request diff --git a/examples/middleware_with_endpoint/middleware_with_endpoint.zig b/examples/middleware_with_endpoint/middleware_with_endpoint.zig index 9e4b6c8f..543600c7 100644 --- a/examples/middleware_with_endpoint/middleware_with_endpoint.zig +++ b/examples/middleware_with_endpoint/middleware_with_endpoint.zig @@ -60,7 +60,7 @@ const UserMiddleWare = struct { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); _ = self; // do our work: fill in the user field of the context @@ -105,7 +105,7 @@ const SessionMiddleWare = struct { // note that the first parameter is of type *Handler, not *Self !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); _ = self; context.session = Session{ @@ -155,7 +155,7 @@ const HtmlEndpoint = struct { } pub fn get(ep: *zap.Endpoint, r: zap.Request) void { - const self = @fieldParentPtr(Self, "ep", ep); + const self: *Self = @fieldParentPtr("ep", ep); _ = self; var buf: [1024]u8 = undefined; @@ -200,7 +200,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); SharedAllocator.init(allocator); // create the endpoint diff --git a/examples/senderror/senderror.zig b/examples/senderror/senderror.zig index 9a2fe73a..a9756ce1 100644 --- a/examples/senderror/senderror.zig +++ b/examples/senderror/senderror.zig @@ -7,7 +7,7 @@ fn MAKE_MEGA_ERROR() !void { fn MY_REQUEST_HANDLER(r: zap.Request) void { MAKE_MEGA_ERROR() catch |err| { - r.sendError(err, 505); + r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); }; } diff --git a/examples/simple_router/simple_router.zig b/examples/simple_router/simple_router.zig index 9d58b993..eb588392 100644 --- a/examples/simple_router/simple_router.zig +++ b/examples/simple_router/simple_router.zig @@ -73,7 +73,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); var simpleRouter = zap.Router.init(allocator, .{ .not_found = not_found, diff --git a/examples/websockets/websockets.zig b/examples/websockets/websockets.zig index fea0933e..e3472420 100644 --- a/examples/websockets/websockets.zig +++ b/examples/websockets/websockets.zig @@ -46,8 +46,8 @@ const ContextManager = struct { self.lock.lock(); defer self.lock.unlock(); - var ctx = try self.allocator.create(Context); - var userName = try std.fmt.allocPrint( + const ctx = try self.allocator.create(Context); + const userName = try std.fmt.allocPrint( self.allocator, "{s}{d}", .{ self.usernamePrefix, self.contexts.items.len }, @@ -202,7 +202,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; - var allocator = gpa.allocator(); + const allocator = gpa.allocator(); GlobalContextManager = ContextManager.init(allocator, "chatroom", "user-"); defer GlobalContextManager.deinit(); diff --git a/facil.io/build.zig b/facil.io/build.zig index 53183157..df6cd59c 100644 --- a/facil.io/build.zig +++ b/facil.io/build.zig @@ -2,11 +2,11 @@ const std = @import("std"); pub fn build_facilio( comptime subdir: []const u8, - b: *std.build.Builder, - target: std.zig.CrossTarget, + b: *std.Build, + target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, use_openssl: bool, -) !*std.build.CompileStep { +) !*std.Build.Step.Compile { const lib = b.addStaticLibrary(.{ .name = "facil.io", .target = target, @@ -15,7 +15,7 @@ pub fn build_facilio( // Generate flags var flags = std.ArrayList([]const u8).init(std.heap.page_allocator); - if (lib.optimize != .Debug) try flags.append("-Os"); + if (optimize != .Debug) try flags.append("-Os"); try flags.append("-Wno-return-type-c-linkage"); try flags.append("-fno-sanitize=undefined"); @@ -26,46 +26,52 @@ pub fn build_facilio( // try flags.append("-DFIO_HTTP_EXACT_LOGGING"); - if (target.getAbi() == .musl) + if (target.result.abi == .musl) try flags.append("-D_LARGEFILE64_SOURCE"); if (use_openssl) try flags.append("-DHAVE_OPENSSL -DFIO_TLS_FOUND"); // Include paths - lib.addIncludePath(.{ .path = subdir ++ "/." }); - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil" }); - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil/fiobj" }); - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil/cli" }); - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil/http" }); - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil/http/parsers" }); + lib.addIncludePath(b.path(subdir ++ "/.")); + lib.addIncludePath(b.path(subdir ++ "/lib/facil")); + lib.addIncludePath(b.path(subdir ++ "/lib/facil/fiobj")); + lib.addIncludePath(b.path(subdir ++ "/lib/facil/cli")); + lib.addIncludePath(b.path(subdir ++ "/lib/facil/http")); + lib.addIncludePath(b.path(subdir ++ "/lib/facil/http/parsers")); if (use_openssl) - lib.addIncludePath(.{ .path = subdir ++ "/lib/facil/tls" }); + lib.addIncludePath(b.path(subdir ++ "/lib/facil/tls")); // C source files - lib.addCSourceFiles(&.{ - subdir ++ "/lib/facil/fio.c", - subdir ++ "/lib/facil/fio_zig.c", - subdir ++ "/lib/facil/http/http.c", - subdir ++ "/lib/facil/http/http1.c", - subdir ++ "/lib/facil/http/websockets.c", - subdir ++ "/lib/facil/http/http_internal.c", - subdir ++ "/lib/facil/fiobj/fiobj_numbers.c", - subdir ++ "/lib/facil/fiobj/fio_siphash.c", - subdir ++ "/lib/facil/fiobj/fiobj_str.c", - subdir ++ "/lib/facil/fiobj/fiobj_ary.c", - subdir ++ "/lib/facil/fiobj/fiobj_data.c", - subdir ++ "/lib/facil/fiobj/fiobj_hash.c", - subdir ++ "/lib/facil/fiobj/fiobj_json.c", - subdir ++ "/lib/facil/fiobj/fiobject.c", - subdir ++ "/lib/facil/fiobj/fiobj_mustache.c", - subdir ++ "/lib/facil/cli/fio_cli.c", - }, flags.items); + lib.addCSourceFiles(.{ + .files = &.{ + subdir ++ "/lib/facil/fio.c", + subdir ++ "/lib/facil/fio_zig.c", + subdir ++ "/lib/facil/http/http.c", + subdir ++ "/lib/facil/http/http1.c", + subdir ++ "/lib/facil/http/websockets.c", + subdir ++ "/lib/facil/http/http_internal.c", + subdir ++ "/lib/facil/fiobj/fiobj_numbers.c", + subdir ++ "/lib/facil/fiobj/fio_siphash.c", + subdir ++ "/lib/facil/fiobj/fiobj_str.c", + subdir ++ "/lib/facil/fiobj/fiobj_ary.c", + subdir ++ "/lib/facil/fiobj/fiobj_data.c", + subdir ++ "/lib/facil/fiobj/fiobj_hash.c", + subdir ++ "/lib/facil/fiobj/fiobj_json.c", + subdir ++ "/lib/facil/fiobj/fiobject.c", + subdir ++ "/lib/facil/fiobj/fiobj_mustache.c", + subdir ++ "/lib/facil/cli/fio_cli.c", + }, + .flags = flags.items, + }); if (use_openssl) { - lib.addCSourceFiles(&.{ - subdir ++ "/lib/facil/tls/fio_tls_openssl.c", - subdir ++ "/lib/facil/tls/fio_tls_missing.c", - }, flags.items); + lib.addCSourceFiles(.{ + .files = &.{ + subdir ++ "/lib/facil/tls/fio_tls_openssl.c", + subdir ++ "/lib/facil/tls/fio_tls_missing.c", + }, + .flags = flags.items, + }); } // link against libc diff --git a/facil.io/build.zig.zon b/facil.io/build.zig.zon index 277ebf3f..3ed7ce9a 100644 --- a/facil.io/build.zig.zon +++ b/facil.io/build.zig.zon @@ -2,4 +2,3 @@ .name = "facil.io", .version = "0.0.12", } - diff --git a/flake.lock b/flake.lock index 5c8c63d0..ffba2365 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { @@ -37,11 +37,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -55,11 +55,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -92,11 +92,11 @@ }, "locked": { "dir": "contrib", - "lastModified": 1692702614, - "narHash": "sha256-FeY8hAB77tnUTDbK6WYA+DG3Nx5xQrbOC17Cfl3pTm4=", + "lastModified": 1704461694, + "narHash": "sha256-dQc9Bkh5uf0R4po3NWnCGx+3eqOZR7iSR4jmRvNNm+E=", "owner": "neovim", "repo": "neovim", - "rev": "014b87646fc3273a09d6b20ebb648a8eb24a0a98", + "rev": "c509f4907bf7405c9c2ae3f7eff76c5d552944cc", "type": "github" }, "original": { @@ -108,11 +108,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1702882221, - "narHash": "sha256-L/uOrBqkGsa45EvQk4DLq/aR6JeomW+7Mwe0mC/dVUM=", + "lastModified": 1704290814, + "narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "25fef6e30d8ad48f47a8411ccfe986d8baed8a15", + "rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421", "type": "github" }, "original": { @@ -124,16 +124,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1689088367, - "narHash": "sha256-Y2tl2TlKCWEHrOeM9ivjCLlRAKH3qoPUE/emhZECU14=", + "lastModified": 1702350026, + "narHash": "sha256-A+GNZFZdfl4JdDphYKBJ5Ef1HOiFsP18vQe9mqjmUis=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5c9ddb86679c400d6b7360797b8a22167c2053f8", + "rev": "9463103069725474698139ab10f17a9d125da859", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.05", + "ref": "nixos-23.05", "repo": "nixpkgs", "type": "github" } @@ -184,11 +184,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1692663634, - "narHash": "sha256-wioqr80UOA0tNXaJy4D0i9fFaLG2RoQi5e9Dpd4WojE=", + "lastModified": 1704888534, + "narHash": "sha256-douEXUiWCVL9NvWKYBc8ydq51qLLUwlBo6lJJoktkGw=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "d666e5137fe0c43353c555fb47748813084decab", + "rev": "c69295c92a98947295755a9ac2d49a8d447cc04d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 58583f4e..01f1b72a 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,7 @@ devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ # neovim-nightly-pkgs.neovim - zigpkgs."0.11.0" + zigpkgs."0.12.0" bat wrk python310 @@ -89,7 +89,8 @@ devShells.build = pkgs.mkShell { nativeBuildInputs = with pkgs; [ - zigpkgs."0.11.0" + zigpkgs."0.12.0" + pkgs.openssl ]; buildInputs = with pkgs; [ @@ -103,6 +104,28 @@ # once we set SHELL to point to the interactive bash, neovim will # launch the correct $SHELL in its :terminal export SHELL=${pkgs.bashInteractive}/bin/bash + export LD_LIBRARY_PATH=${pkgs.zlib.out}/lib:${pkgs.icu.out}/lib:${pkgs.openssl.out}/lib:$LD_LIBRARY_PATH + ''; + }; + + devShells.masta = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + zigpkgs.master + pkgs.openssl + ]; + + buildInputs = with pkgs; [ + # we need a version of bash capable of being interactive + # as opposed to a bash just used for building this flake + # in non-interactive mode + bashInteractive + ]; + + shellHook = '' + # once we set SHELL to point to the interactive bash, neovim will + # launch the correct $SHELL in its :terminal + export SHELL=${pkgs.bashInteractive}/bin/bash + export LD_LIBRARY_PATH=${pkgs.zlib.out}/lib:${pkgs.icu.out}/lib:${pkgs.openssl.out}/lib:$LD_LIBRARY_PATH ''; }; diff --git a/src/endpoint.zig b/src/endpoint.zig index c4ba86bf..07cecc95 100644 --- a/src/endpoint.zig +++ b/src/endpoint.zig @@ -112,7 +112,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// GET: here, the auth_endpoint will be passed in as endpoint. /// Authenticates GET requests using the Authenticator. pub fn get(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { @@ -132,7 +132,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// POST: here, the auth_endpoint will be passed in as endpoint. /// Authenticates POST requests using the Authenticator. pub fn post(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { @@ -152,7 +152,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// PUT: here, the auth_endpoint will be passed in as endpoint. /// Authenticates PUT requests using the Authenticator. pub fn put(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { @@ -172,7 +172,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// DELETE: here, the auth_endpoint will be passed in as endpoint. /// Authenticates DELETE requests using the Authenticator. pub fn delete(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { @@ -192,7 +192,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// PATCH: here, the auth_endpoint will be passed in as endpoint. /// Authenticates PATCH requests using the Authenticator. pub fn patch(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { @@ -212,7 +212,7 @@ pub fn Authenticating(comptime Authenticator: type) type { /// OPTIONS: here, the auth_endpoint will be passed in as endpoint. /// Authenticates OPTIONS requests using the Authenticator. pub fn options(e: *Endpoint, r: zap.Request) void { - const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); + const authEp: *Self = @fieldParentPtr("auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { diff --git a/src/fio.zig b/src/fio.zig index a71ff6ce..31e94964 100644 --- a/src/fio.zig +++ b/src/fio.zig @@ -118,10 +118,10 @@ pub const struct_fio_str_info_s = extern struct { pub const fio_str_info_s = struct_fio_str_info_s; pub extern fn http_send_body(h: [*c]http_s, data: ?*anyopaque, length: usize) c_int; pub fn fiobj_each1(arg_o: FIOBJ, arg_start_at: usize, arg_task: ?*const fn (FIOBJ, ?*anyopaque) callconv(.C) c_int, arg_arg: ?*anyopaque) callconv(.C) usize { - var o = arg_o; - var start_at = arg_start_at; - var task = arg_task; - var arg = arg_arg; + const o = arg_o; + const start_at = arg_start_at; + const task = arg_task; + const arg = arg_arg; if ((((o != 0) and ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 1))))) == @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 0)))))) and ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6))))) != @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6)))))) and (fiobj_type_vtable(o).*.each != null)) return fiobj_type_vtable(o).*.each.?(o, start_at, task, arg); return 0; } @@ -288,8 +288,8 @@ pub const fiobj_object_header_s = extern struct { ref: u32, }; pub fn fiobj_type_is(arg_o: FIOBJ, arg_type: fiobj_type_enum) callconv(.C) usize { - var o = arg_o; - var @"type" = arg_type; + const o = arg_o; + const @"type" = arg_type; while (true) { switch (@as(c_int, @bitCast(@as(c_uint, @"type")))) { @as(c_int, 1) => return @as(usize, @bitCast(@as(c_long, @intFromBool(((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 1))))) != 0) or (@as(c_int, @bitCast(@as(c_uint, @as([*c]fiobj_type_enum, @ptrFromInt(o))[@as(c_uint, @intCast(@as(c_int, 0)))]))) == FIOBJ_T_NUMBER))))), @@ -311,7 +311,7 @@ pub fn fiobj_type_is(arg_o: FIOBJ, arg_type: fiobj_type_enum) callconv(.C) usize return @as(usize, @bitCast(@as(c_long, @intFromBool((((o != 0) and ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 1))))) == @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 0)))))) and ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6))))) != @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6)))))) and (@as(c_int, @bitCast(@as(c_uint, @as([*c]fiobj_type_enum, @ptrCast(@alignCast(@as(?*anyopaque, @ptrFromInt(o & ~@as(usize, @bitCast(@as(c_long, @as(c_int, 7)))))))))[@as(c_uint, @intCast(@as(c_int, 0)))]))) == @as(c_int, @bitCast(@as(c_uint, @"type")))))))); } pub fn fiobj_type(arg_o: FIOBJ) callconv(.C) fiobj_type_enum { - var o = arg_o; + const o = arg_o; if (!(o != 0)) return @as(u8, @bitCast(@as(i8, @truncate(FIOBJ_T_NULL)))); if ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 1))))) != 0) return @as(u8, @bitCast(@as(i8, @truncate(FIOBJ_T_NUMBER)))); if ((o & @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6))))) == @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 6))))) return @as(u8, @bitCast(@as(u8, @truncate(o)))); @@ -326,7 +326,7 @@ pub extern const FIOBJECT_VTABLE_ARRAY: fiobj_object_vtable_s; pub extern const FIOBJECT_VTABLE_HASH: fiobj_object_vtable_s; pub extern const FIOBJECT_VTABLE_DATA: fiobj_object_vtable_s; pub fn fiobj_type_vtable(arg_o: FIOBJ) callconv(.C) [*c]const fiobj_object_vtable_s { - var o = arg_o; + const o = arg_o; while (true) { switch (@as(c_int, @bitCast(@as(c_uint, fiobj_type(o))))) { @as(c_int, 1) => return &FIOBJECT_VTABLE_NUMBER, @@ -361,7 +361,7 @@ pub fn fiobj_obj2float(o: FIOBJ) callconv(.C) f64 { pub extern fn fio_ltocstr(c_long) fio_str_info_s; pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { if (!(o != 0)) { - var ret: fio_str_info_s = fio_str_info_s{ + const ret: fio_str_info_s = fio_str_info_s{ .capa = @as(usize, @bitCast(@as(c_long, @as(c_int, 0)))), .len = @as(usize, @bitCast(@as(c_long, @as(c_int, 4)))), .data = @as([*c]u8, @ptrFromInt(@intFromPtr("null"))), @@ -374,7 +374,7 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { switch (@as(c_int, @bitCast(@as(c_uint, @as(u8, @bitCast(@as(u8, @truncate(o)))))))) { @as(c_int, 6) => { { - var ret: fio_str_info_s = fio_str_info_s{ + const ret: fio_str_info_s = fio_str_info_s{ .capa = @as(usize, @bitCast(@as(c_long, @as(c_int, 0)))), .len = @as(usize, @bitCast(@as(c_long, @as(c_int, 4)))), .data = @as([*c]u8, @ptrFromInt(@intFromPtr("null"))), @@ -384,7 +384,7 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { }, @as(c_int, 38) => { { - var ret: fio_str_info_s = fio_str_info_s{ + const ret: fio_str_info_s = fio_str_info_s{ .capa = @as(usize, @bitCast(@as(c_long, @as(c_int, 0)))), .len = @as(usize, @bitCast(@as(c_long, @as(c_int, 5)))), .data = @as([*c]u8, @ptrFromInt(@intFromPtr("false"))), @@ -394,7 +394,7 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { }, @as(c_int, 22) => { { - var ret: fio_str_info_s = fio_str_info_s{ + const ret: fio_str_info_s = fio_str_info_s{ .capa = @as(usize, @bitCast(@as(c_long, @as(c_int, 0)))), .len = @as(usize, @bitCast(@as(c_long, @as(c_int, 4)))), .data = @as([*c]u8, @ptrFromInt(@intFromPtr("true"))), @@ -556,8 +556,8 @@ pub extern fn http_date2rfc7231(target: [*c]u8, tmbuf: [*c]struct_tm) usize; pub extern fn http_date2rfc2109(target: [*c]u8, tmbuf: [*c]struct_tm) usize; pub extern fn http_date2rfc2822(target: [*c]u8, tmbuf: [*c]struct_tm) usize; pub fn http_date2str(arg_target: [*c]u8, arg_tmbuf: [*c]struct_tm) callconv(.C) usize { - var target = arg_target; - var tmbuf = arg_tmbuf; + const target = arg_target; + const tmbuf = arg_tmbuf; return http_date2rfc7231(target, tmbuf); } pub extern fn http_time2str(target: [*c]u8, t: time_t) usize; diff --git a/src/http.zig b/src/http.zig index 5241c3f6..6c2ef450 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1,29 +1,39 @@ const std = @import("std"); -/// HTTP Status codes according to `rfc7231` -/// https://tools.ietf.org/html/rfc7231#section-6 -/// (taken from https://github.com/Luukdegram/apple_pie/blob/master/src/response.zig) +/// HTTP Status codes according to `rfc9110` +/// https://datatracker.ietf.org/doc/html/rfc9110#name-status-codes +/// (modified from https://github.com/Luukdegram/apple_pie/blob/master/src/response.zig) pub const StatusCode = enum(u16) { - // Informational 1xx + // Information responses @"continue" = 100, - // Successful 2xx switching_protocols = 101, + processing = 102, // (WebDAV) + early_hints = 103, + + // Successful responses ok = 200, created = 201, accepted = 202, non_authoritative_information = 203, no_content = 204, reset_content = 205, - // redirections 3xx partial_content = 206, + multi_status = 207, // (WebDAV) + already_reported = 208, // (WebDAV) + im_used = 226, // (HTTP Delta encoding) + + // Redirection messages multiple_choices = 300, moved_permanently = 301, found = 302, see_other = 303, not_modified = 304, use_proxy = 305, + unused = 306, temporary_redirect = 307, - // client errors 4xx + permanent_redirect = 308, + + // Client error responses bad_request = 400, unauthorized = 401, payment_required = 402, @@ -37,23 +47,35 @@ pub const StatusCode = enum(u16) { gone = 410, length_required = 411, precondition_failed = 412, - request_entity_too_large = 413, - request_uri_too_long = 414, - unsupported_mediatype = 415, - requested_range_not_satisfiable = 416, + payload_too_large = 413, + uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, expectation_failed = 417, - /// teapot is an extension status code and not required for clients to support - teapot = 418, + im_a_teapot = 418, + misdirected_request = 421, + unprocessable_content = 422, // (WebDAV) + locked = 423, // (WebDAV) + failed_dependency = 424, // (WebDAV) + too_early = 425, upgrade_required = 426, - /// extra status code according to `https://tools.ietf.org/html/rfc6585#section-5` + precondition_required = 428, + too_many_requests = 429, request_header_fields_too_large = 431, - // server errors 5xx + unavailable_for_legal_reasons = 451, + + // Server error responses internal_server_error = 500, not_implemented = 501, bad_gateway = 502, service_unavailable = 503, gateway_timeout = 504, http_version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, // (WebDAV) + loop_detected = 508, // (WebDAV) + not_extended = 510, + network_authentication_required = 511, _, /// Returns the string value of a `StatusCode` diff --git a/src/http_auth.zig b/src/http_auth.zig index e8a7ad21..6cba1f68 100644 --- a/src/http_auth.zig +++ b/src/http_auth.zig @@ -122,7 +122,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { ); return .AuthFailed; } - var decoded = buffer[0..decoded_size]; + const decoded = buffer[0..decoded_size]; decoder.decode(decoded, encoded) catch |err| { zap.debug( "ERROR: UserPassAuth: unable to decode `{s}`: {any}\n", @@ -400,7 +400,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty lookup: *Lookup, args: UserPassSessionArgs, ) !Self { - var ret: Self = .{ + const ret: Self = .{ .allocator = allocator, .settings = .{ .usernameParam = try allocator.dupe(u8, args.usernameParam), diff --git a/src/middleware.zig b/src/middleware.zig index 8862c1ce..91b3da36 100644 --- a/src/middleware.zig +++ b/src/middleware.zig @@ -82,7 +82,7 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt /// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if /// the endpoint processed the request. pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool { - var self = @fieldParentPtr(Self, "handler", handler); + const self: *Self = @fieldParentPtr("handler", handler); r.setUserContext(context); self.endpoint.onRequest(r); diff --git a/src/request.zig b/src/request.zig index 75a3be7f..a2095310 100644 --- a/src/request.zig +++ b/src/request.zig @@ -6,6 +6,8 @@ const fio = @import("fio.zig"); const util = @import("util.zig"); const zap = @import("zap.zig"); +const ContentType = zap.ContentType; + pub const HttpError = error{ HttpSendBody, HttpSetContentType, @@ -16,15 +18,6 @@ pub const HttpError = error{ SendFile, }; -/// Http Content Type enum. -/// Needs some love. -pub const ContentType = enum { - TEXT, - HTML, - JSON, - // TODO: more content types -}; - /// Key value pair of strings from HTTP parameters pub const HttpParamStrKV = struct { key: util.FreeOrNot, @@ -111,7 +104,7 @@ pub const HttpParamBinaryFile = struct { filename: ?[]const u8 = null, /// format function for printing file upload data - pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void { + pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { const d = value.data orelse "\\0"; const m = value.mimetype orelse "null"; const f = value.filename orelse "null"; @@ -317,11 +310,15 @@ pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context { return null; } } - /// Tries to send an error stack trace. -pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void { +/// Use like this: +/// ```zig +/// const err = zap.HttpError; // this is to show that `err` is an Error +/// r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); +/// ``` +pub fn sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) void { // TODO: query accept headers - if (self._internal_sendError(err, errorcode_num)) { + if (self._internal_sendError(err, err_trace, errorcode_num)) { return; } else |_| { self.sendBody(@errorName(err)) catch return; @@ -329,7 +326,7 @@ pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void { } /// Used internally. Probably does not need to be public. -pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void { +pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) !void { // TODO: query accept headers // TODO: let's hope 20k is enough. Maybe just really allocate here self.h.*.status = errorcode_num; @@ -339,9 +336,12 @@ pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usiz var writer = string.writer(); try writer.print("ERROR: {any}\n\n", .{err}); - const debugInfo = try std.debug.getSelfDebugInfo(); - const ttyConfig: std.io.tty.Config = .no_color; - try std.debug.writeCurrentStackTrace(writer, debugInfo, ttyConfig, null); + if (err_trace) |trace| { + const debugInfo = try std.debug.getSelfDebugInfo(); + const ttyConfig: std.io.tty.Config = .no_color; + try std.debug.writeStackTrace(trace, writer, fba.allocator(), debugInfo, ttyConfig); + } + try self.sendBody(string.items); } @@ -439,6 +439,63 @@ pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 { return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname)); } +pub const HttpHeaderCommon = enum(usize) { + /// Represents the HTTP Header "Accept". + accept, + /// Represents the HTTP Header "Cache-Control". + cache_control, + /// Represents the HTTP Header "Connection". + connection, + /// Represents the HTTP Header "Content-Encoding". + content_encoding, + /// Represents the HTTP Header "Content-Length". + content_length, + /// Represents the HTTP Header "Content-Range". + content_range, + /// Represents the HTTP Header "Content-Type". + content_type, + /// Represents the HTTP Header "Cookie". + cookie, + /// Represents the HTTP Header "Date". + date, + /// Represents the HTTP Header "Etag". + etag, + /// Represents the HTTP Header "Host". + host, + /// Represents the HTTP Header "Last-Modified". + last_modified, + /// Represents the HTTP Header "Origin". + origin, + /// Represents the HTTP Header "Set-Cookie". + set_cookie, + /// Represents the HTTP Header "Upgrade". + upgrade, +}; + +/// Returns the header value of a given common header key. Returned memory +/// should not be freed. +pub fn getHeaderCommon(self: *const Self, which: HttpHeaderCommon) ?[]const u8 { + const field = switch (which) { + .accept => fio.HTTP_HEADER_ACCEPT, + .cache_control => fio.HTTP_HEADER_CACHE_CONTROL, + .connection => fio.HTTP_HEADER_CONNECTION, + .content_encoding => fio.HTTP_HEADER_CONTENT_ENCODING, + .content_length => fio.HTTP_HEADER_CONTENT_LENGTH, + .content_range => fio.HTTP_HEADER_CONTENT_RANGE, + .content_type => fio.HTTP_HEADER_CONTENT_TYPE, + .cookie => fio.HTTP_HEADER_COOKIE, + .date => fio.HTTP_HEADER_DATE, + .etag => fio.HTTP_HEADER_ETAG, + .host => fio.HTTP_HEADER_HOST, + .last_modified => fio.HTTP_HEADER_LAST_MODIFIED, + .origin => fio.HTTP_HEADER_ORIGIN, + .set_cookie => fio.HTTP_HEADER_SET_COOKIE, + .upgrade => fio.HTTP_HEADER_UPGRADE, + }; + const fiobj = zap.fio.fiobj_hash_get(self.h.*.headers, field); + return zap.fio2str(fiobj); +} + /// Set header. pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void { const hname: fio.fio_str_info_s = .{ @@ -520,6 +577,78 @@ pub fn parseCookies(self: *const Self, url_encoded: bool) void { fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0); } +pub const AcceptItem = struct { + raw: []const u8, + type: Fragment, + subtype: Fragment, + q: f64, + + const Fragment = union(enum) { + glob, + value: []const u8, + }; + + pub fn lessThan(_: void, lhs: AcceptItem, rhs: AcceptItem) bool { + return lhs.q < rhs.q; + } + + pub fn toContentType(item: AcceptItem) ?ContentType { + if (ContentType.string_map.get(item.raw)) |common| { + return common; + } + return null; + } +}; + +/// List holding access headers parsed by parseAcceptHeaders() +const AcceptHeaderList = std.ArrayList(AcceptItem); + +/// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest +pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !AcceptHeaderList { + const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader; + + const comma_count = std.mem.count(u8, accept_str, ","); + + var list = try AcceptHeaderList.initCapacity(allocator, comma_count + 1); + errdefer list.deinit(); + + var tok_iter = std.mem.tokenize(u8, accept_str, ", "); + while (tok_iter.next()) |tok| { + var split_iter = std.mem.split(u8, tok, ";"); + const mimetype_str = split_iter.next().?; + const q_factor = q_factor: { + const q_factor_str = split_iter.next() orelse break :q_factor 1; + var eq_iter = std.mem.split(u8, q_factor_str, "="); + const q = eq_iter.next().?; + if (q[0] != 'q') break :q_factor 1; + const value = eq_iter.next() orelse break :q_factor 1; + const parsed = std.fmt.parseFloat(f64, value) catch break :q_factor 1; + break :q_factor parsed; + }; + + var type_split_iter = std.mem.split(u8, mimetype_str, "/"); + + const mimetype_type_str = type_split_iter.next() orelse continue; + const mimetype_subtype_str = type_split_iter.next() orelse continue; + + const new_item: AcceptItem = .{ + .raw = mimetype_str, + .type = if (std.mem.eql(u8, "*", mimetype_type_str)) .glob else .{ .value = mimetype_type_str }, + .subtype = if (std.mem.eql(u8, "*", mimetype_subtype_str)) .glob else .{ .value = mimetype_subtype_str }, + .q = q_factor, + }; + for (list.items, 1..) |item, i| { + if (AcceptItem.lessThan({}, new_item, item)) { + list.insertAssumeCapacity(i, new_item); + break; + } + } else { + list.appendAssumeCapacity(new_item); + } + } + return list; +} + /// Set a response cookie pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { const c: fio.http_cookie_args_s = .{ diff --git a/src/tests/test_auth.zig b/src/tests/test_auth.zig index 5e5f2625..2ac4905d 100644 --- a/src/tests/test_auth.zig +++ b/src/tests/test_auth.zig @@ -1,12 +1,9 @@ const std = @import("std"); const zap = @import("zap"); -// const Authenticators = @import("http_auth.zig"); const Authenticators = zap.Auth; const Endpoint = zap.Endpoint; const fio = zap; -// const fio = @import("fio.zig"); const util = zap; -// const util = @import("util.zig"); test "BearerAuthSingle authenticate" { const a = std.testing.allocator; @@ -132,37 +129,34 @@ const ClientAuthReqHeaderFields = struct { }; fn makeRequest(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeaderFields) !void { - const uri = try std.Uri.parse(url); - - var h = std.http.Headers{ .allocator = a }; - defer h.deinit(); - - if (auth) |auth_fields| { - const authstring = try std.fmt.allocPrint(a, "{s}{s}", .{ auth_fields.auth.str(), auth_fields.token }); - defer a.free(authstring); - try h.append(auth_fields.auth.headerFieldStrHeader(), authstring); - } - var http_client: std.http.Client = .{ .allocator = a }; defer http_client.deinit(); - var req = try http_client.request(.GET, uri, h, .{}); - defer req.deinit(); - - try req.start(); - try req.wait(); - // req.deinit() panics! - // defer req.deinit(); - - // without this block, the tests sometimes get stuck which - // might have to do with connection pooling and connections being in - // a different state when all data has been read?!? - { - var buffer: [1024]u8 = undefined; - // we know we won't receive a lot - const len = try req.reader().readAll(&buffer); - std.debug.print("RESPONSE:\n{s}\n", .{buffer[0..len]}); - } + var auth_buf: [256]u8 = undefined; + const auth_string: []const u8 = blk: { + if (auth) |auth_fields| { + const authstring = try std.fmt.bufPrint(&auth_buf, "{s}{s}", .{ auth_fields.auth.str(), auth_fields.token }); + break :blk authstring; + } else { + break :blk ""; + } + }; + _ = try http_client.fetch(.{ + .location = .{ .url = url }, + .headers = .{ + .authorization = .omit, + }, + .extra_headers = blk: { + if (auth) |auth_fields| { + break :blk &.{.{ + .name = auth_fields.auth.headerFieldStrHeader(), + .value = auth_string, + }}; + } else { + break :blk &.{}; + } + }, + }); zap.stop(); } diff --git a/src/tests/test_http_params.zig b/src/tests/test_http_params.zig index 45ffc7de..5e8ee8e8 100644 --- a/src/tests/test_http_params.zig +++ b/src/tests/test_http_params.zig @@ -2,19 +2,13 @@ const std = @import("std"); const zap = @import("zap"); fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { - const uri = try std.Uri.parse(url); - - var h = std.http.Headers{ .allocator = a }; - defer h.deinit(); - var http_client: std.http.Client = .{ .allocator = a }; defer http_client.deinit(); - var req = try http_client.request(.GET, uri, h, .{}); - defer req.deinit(); + _ = try http_client.fetch(.{ + .location = .{ .url = url }, + }); - try req.start(); - try req.wait(); zap.stop(); } @@ -23,7 +17,7 @@ fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { } test "http parameters" { - var allocator = std.testing.allocator; + const allocator = std.testing.allocator; const Handler = struct { var alloc: std.mem.Allocator = undefined; diff --git a/src/tests/test_sendfile.zig b/src/tests/test_sendfile.zig index 51064cf3..8b16a8a4 100644 --- a/src/tests/test_sendfile.zig +++ b/src/tests/test_sendfile.zig @@ -7,20 +7,16 @@ var read_len: ?usize = null; const testfile = @embedFile("testfile.txt"); fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { - const uri = try std.Uri.parse(url); - - var h = std.http.Headers{ .allocator = a }; - defer h.deinit(); - var http_client: std.http.Client = .{ .allocator = a }; defer http_client.deinit(); - - var req = try http_client.request(.GET, uri, h, .{}); - defer req.deinit(); - - try req.start(); - try req.wait(); - read_len = try req.readAll(&buffer); + var response = std.ArrayList(u8).init(a); + defer response.deinit(); + _ = try http_client.fetch(.{ + .location = .{ .url = url }, + .response_storage = .{ .dynamic = &response }, + }); + read_len = response.items.len; + @memcpy(buffer[0..read_len.?], response.items); zap.stop(); } @@ -33,7 +29,7 @@ pub fn on_request(r: zap.Request) void { } test "send file" { - var allocator = std.testing.allocator; + const allocator = std.testing.allocator; // setup listener var listener = zap.HttpListener.init( diff --git a/src/websockets.zig b/src/websockets.zig index 0e462700..772a2893 100644 --- a/src/websockets.zig +++ b/src/websockets.zig @@ -50,7 +50,7 @@ pub fn Handler(comptime ContextType: type) type { /// This function will end the HTTP stage of the connection and attempt to "upgrade" to a WebSockets connection. pub fn upgrade(h: [*c]fio.http_s, settings: *WebSocketSettings) WebSocketError!void { - var fio_settings: fio.websocket_settings_s = .{ + const fio_settings: fio.websocket_settings_s = .{ .on_message = internal_on_message, .on_open = internal_on_open, .on_ready = internal_on_ready, @@ -64,8 +64,8 @@ pub fn Handler(comptime ContextType: type) type { } fn internal_on_message(handle: WsHandle, msg: fio.fio_str_info_s, is_text: u8) callconv(.C) void { - var user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); - var message = msg.data[0..msg.len]; + const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); + const message = msg.data[0..msg.len]; if (user_provided_settings) |settings| { if (settings.on_message) |on_message| { on_message(settings.context, handle, message, is_text == 1); @@ -74,7 +74,7 @@ pub fn Handler(comptime ContextType: type) type { } fn internal_on_open(handle: WsHandle) callconv(.C) void { - var user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); + const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); if (user_provided_settings) |settings| { if (settings.on_open) |on_open| { on_open(settings.context, handle); @@ -83,7 +83,7 @@ pub fn Handler(comptime ContextType: type) type { } fn internal_on_ready(handle: WsHandle) callconv(.C) void { - var user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); + const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); if (user_provided_settings) |settings| { if (settings.on_ready) |on_ready| { on_ready(settings.context, handle); @@ -92,7 +92,7 @@ pub fn Handler(comptime ContextType: type) type { } fn internal_on_shutdown(handle: WsHandle) callconv(.C) void { - var user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); + const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); if (user_provided_settings) |settings| { if (settings.on_shutdown) |on_shutdown| { on_shutdown(settings.context, handle); @@ -101,7 +101,7 @@ pub fn Handler(comptime ContextType: type) type { } fn internal_on_close(uuid: isize, udata: ?*anyopaque) callconv(.C) void { - var user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(udata))); + const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(udata))); if (user_provided_settings) |settings| { if (settings.on_close) |on_close| { on_close(settings.context, uuid); @@ -119,7 +119,7 @@ pub fn Handler(comptime ContextType: type) type { pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void { if (fio.websocket_write( handle, - fio.str2fio(message), + util.str2fio(message), if (is_text) 1 else 0, ) != 0) { return error.WriteError; @@ -192,7 +192,7 @@ pub fn Handler(comptime ContextType: type) type { /// we need it to look up the ziggified callbacks. pub inline fn subscribe(handle: WsHandle, args: *SubscribeArgs) WebSocketError!usize { if (handle == null) return error.SubscribeError; - var fio_args: fio.websocket_subscribe_s_zigcompat = .{ + const fio_args: fio.websocket_subscribe_s_zigcompat = .{ .ws = handle.?, .channel = util.str2fio(args.channel), .on_message = if (args.on_message) |_| internal_subscription_on_message else null, diff --git a/src/zap.zig b/src/zap.zig index 0414d68c..a766e709 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -43,7 +43,7 @@ pub const Tls = @import("tls.zig"); /// } /// /// fn get(e: *zap.Endpoint, r: zap.Request) void { -/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e); +/// const self: *StopEndpoint = @fieldParentPtr("ep", e); /// _ = self; /// _ = r; /// zap.stop(); @@ -118,10 +118,27 @@ pub fn enableDebugLog() void { /// start Zap with debug logging on pub fn startWithLogging(args: fio.fio_start_args) void { - debug = true; + _debug = true; fio.fio_start(args); } +/// Registers a new mimetype to be used for files ending with the given extension. +pub fn mimetypeRegister(file_extension: []const u8, mime_type_str: []const u8) void { + // NOTE: facil.io is expecting a non-const pointer to u8 values, but it does not + // not appear to actually modify the value. Here we do a const cast so + // that it is easy to pass static strings to http_mimetype_register without + // needing to allocate a buffer on the heap. + const extension = @constCast(file_extension); + const mimetype = fio.fiobj_str_new(mime_type_str.ptr, mime_type_str.len); + + fio.http_mimetype_register(extension.ptr, extension.len, mimetype); +} + +/// Clears the Mime-Type registry (it will be empty after this call). +pub fn mimetypeClear() void { + fio.http_mimetype_clear(); +} + pub const ListenError = error{ AlreadyListening, ListenError, @@ -142,8 +159,18 @@ pub const HttpError = error{ pub const ContentType = enum { TEXT, HTML, + XML, JSON, + XHTML, // TODO: more content types + + pub const string_map = std.ComptimeStringMap(ContentType, .{ + .{ "text/plain", .TEXT }, + .{ "text/html", .HTML }, + .{ "application/xml", .XML }, + .{ "application/json", .JSON }, + .{ "application/xhtml+xml", .XHTML }, + }); }; /// Used internally: facilio Http request callback function type diff --git a/tools/announceybot.exe b/tools/announceybot.exe index 7f224f49..805a1601 100755 Binary files a/tools/announceybot.exe and b/tools/announceybot.exe differ diff --git a/tools/announceybot.zig b/tools/announceybot.zig index 6dbef9df..3a88deb7 100644 --- a/tools/announceybot.zig +++ b/tools/announceybot.zig @@ -28,7 +28,8 @@ fn usage() void { \\ instructions ; std.debug.print("{s}", .{message}); - std.os.exit(1); + + std.process.exit(1); } var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; @@ -88,7 +89,7 @@ fn get_tag_annotation(allocator: std.mem.Allocator, tagname: []const u8) ![]cons tagname, }; - const result = try std.ChildProcess.exec(.{ + const result = try std.ChildProcess.run(.{ .allocator = allocator, .argv = &args, }); @@ -150,24 +151,26 @@ fn sendToDiscordPart(allocator: std.mem.Allocator, url: []const u8, message_json // url const uri = try std.Uri.parse(url); - // http headers - var h = std.http.Headers{ .allocator = allocator }; - defer h.deinit(); - try h.append("accept", "*/*"); - try h.append("Content-Type", "application/json"); - // client var http_client: std.http.Client = .{ .allocator = allocator }; defer http_client.deinit(); + var server_header_buffer: [2048]u8 = undefined; + // request - var req = try http_client.request(.POST, uri, h, .{}); + var req = try http_client.open(.POST, uri, .{ + .server_header_buffer = &server_header_buffer, + .extra_headers = &.{ + .{ .name = "accept", .value = "*/*" }, + .{ .name = "Content-Type", .value = "application/json" }, + }, + }); defer req.deinit(); req.transfer_encoding = .chunked; // connect, send request - try req.start(); + try req.send(); // send POST payload try req.writer().writeAll(message_json); @@ -182,7 +185,7 @@ fn sendToDiscordPart(allocator: std.mem.Allocator, url: []const u8, message_json fn sendToDiscord(allocator: std.mem.Allocator, url: []const u8, message: []const u8) !void { // json payload // max size: 100kB - var buf: []u8 = try allocator.alloc(u8, 100 * 1024); + const buf: []u8 = try allocator.alloc(u8, 100 * 1024); defer allocator.free(buf); var fba = std.heap.FixedBufferAllocator.init(buf); var string = std.ArrayList(u8).init(fba.allocator()); @@ -336,7 +339,7 @@ fn command_announce(allocator: std.mem.Allocator, tag: []const u8) !void { defer allocator.free(url); sendToDiscord(allocator, url, announcement) catch |err| { std.debug.print("HTTP ERROR: {any}\n", .{err}); - std.os.exit(1); + std.process.exit(1); }; } @@ -399,7 +402,7 @@ fn command_update_readme(allocator: std.mem.Allocator, tag: []const u8) !void { // we need to put the \n back in. // TODO: change this by using some "search" iterator that just // returns indices etc - var output_line = try std.fmt.allocPrint(allocator, "{s}\n", .{line}); + const output_line = try std.fmt.allocPrint(allocator, "{s}\n", .{line}); defer allocator.free(output_line); _ = try writer.write(output_line); } diff --git a/tools/announceybot/release-announcement-template.md b/tools/announceybot/release-announcement-template.md index 2275105e..92cbb0b8 100644 --- a/tools/announceybot/release-announcement-template.md +++ b/tools/announceybot/release-announcement-template.md @@ -18,8 +18,11 @@ Modify your `build.zig.zon` like this: .zap = .{ .url = "https://github.com/zigzap/zap/archive/refs/tags/{tag}.tar.gz", .hash = "{hash}", - } - } + }, + }, + .paths = .{ + "", + }, } ``` diff --git a/tools/announceybot/release-dep-update-template.md b/tools/announceybot/release-dep-update-template.md index b56ee3a5..5ada783a 100644 --- a/tools/announceybot/release-dep-update-template.md +++ b/tools/announceybot/release-dep-update-template.md @@ -6,9 +6,14 @@ .dependencies = .{ // zap {tag} .zap = .{ - .url = "https://github.com/zigzap/zap/archive/refs/tags/{tag}.tar.gz", + // when tagged: + // .url = "https://github.com/zigzap/zap/archive/refs/tags/{tag}.tar.gz", + .url = "https://github.com/zigzap/zap/archive/{tag}.tar.gz", .hash = "{hash}", - } - } + }, + }, + .paths = .{ + "", + }, } ``` diff --git a/tools/announceybot/release-note-template.md b/tools/announceybot/release-note-template.md index 9b47e863..57d4e0a3 100644 --- a/tools/announceybot/release-note-template.md +++ b/tools/announceybot/release-note-template.md @@ -28,8 +28,11 @@ Here is a complete `build.zig.zon` example: .zap = .{ .url = "https://github.com/zigzap/zap/archive/refs/tags/{tag}.tar.gz", .hash = "{hash}", - } - } + }, + }, + .paths = .{ + "", + }, } ``` @@ -41,7 +44,8 @@ Then, in your `build.zig`'s `build` function, add the following before const zap = b.dependency("zap", .{ .target = target, .optimize = optimize, + .openssl = false, // set to true to enable TLS support }); - exe.addModule("zap", zap.module("zap")); + exe.root_module.addImport("zap", zap.module("zap")); exe.linkLibrary(zap.artifact("facil.io")); ``` diff --git a/tools/docserver.zig b/tools/docserver.zig new file mode 100644 index 00000000..e6fc5ba4 --- /dev/null +++ b/tools/docserver.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const zap = @import("zap"); + +fn on_request(r: zap.Request) void { + r.setStatus(.not_found); + r.sendBody("

404 - File not found

") catch return; +} + +pub fn main() !void { + var args_it = std.process.args(); + var port: usize = 8080; + var docs_dir: []const u8 = "zig-out/zap"; + + while (args_it.next()) |arg| { + if (std.mem.startsWith(u8, arg, "--port=")) { + // try to parse port + if (std.fmt.parseUnsigned(usize, arg[7..], 0)) |the_port| { + port = the_port; + } else |_| { + std.debug.print("Invalid port number. Using default port {}\n", .{port}); + } + } + + if (std.mem.startsWith(u8, arg, "--docs=")) { + docs_dir = arg[7..]; + } + } + + zap.mimetypeRegister("wasm", "application/wasm"); + + var listener = zap.HttpListener.init(.{ + .port = port, + .on_request = on_request, + .public_folder = docs_dir, + .log = true, + }); + try listener.listen(); + + std.debug.print("\nServing docs from {s} at 0.0.0.0:{}\n", .{ docs_dir, port }); + std.debug.print("\nSee docs at http://localhost:{}\n\n", .{port}); + + // start worker threads + zap.start(.{ + .threads = 2, + .workers = 1, + }); +} diff --git a/tools/pkghash.zig b/tools/pkghash.zig index f3997fc6..805206aa 100644 --- a/tools/pkghash.zig +++ b/tools/pkghash.zig @@ -71,7 +71,7 @@ pub const usage_pkg = ; pub fn gitLatestTag(gpa: Allocator, pkg_dir: []const u8) ![]const u8 { - const result = try std.ChildProcess.exec(.{ + const result = try std.ChildProcess.run(.{ .allocator = gpa, .argv = &.{ "git", @@ -97,7 +97,7 @@ pub fn gitLatestTag(gpa: Allocator, pkg_dir: []const u8) ![]const u8 { } pub fn gitFileList(gpa: Allocator, pkg_dir: []const u8) ![]const u8 { - const result = try std.ChildProcess.exec(.{ + const result = try std.ChildProcess.run(.{ .allocator = gpa, .argv = &.{ "git", @@ -266,8 +266,8 @@ pub fn cmdPkg(gpa: Allocator, arena: Allocator, args: []const []const u8) !void // computePackageHash will close the directory after completion // std.debug.print("abspath: {s}\n", .{cwd_absolute_path}); - var cwd_copy = try fs.openIterableDirAbsolute(cwd_absolute_path, .{}); - errdefer cwd_copy.dir.close(); + var cwd_copy = try fs.openDirAbsolute(cwd_absolute_path, .{}); + errdefer cwd_copy.close(); var thread_pool: ThreadPool = undefined; try thread_pool.init(.{ .allocator = gpa }); @@ -281,7 +281,7 @@ pub fn cmdPkg(gpa: Allocator, arena: Allocator, args: []const []const u8) !void }; break :blk try computePackageHashExcludingDirectories( &thread_pool, - .{ .dir = cwd_copy.dir }, + cwd_copy, excluded_directories, ); }; @@ -355,7 +355,7 @@ fn isExecutable(file: fs.File) !bool { pub fn computePackageHashExcludingDirectories( thread_pool: *ThreadPool, - pkg_dir: fs.IterableDir, + pkg_dir: fs.Dir, excluded_directories: []const []const u8, ) ![Manifest.Hash.digest_length]u8 { const gpa = thread_pool.allocator; @@ -405,7 +405,7 @@ pub fn computePackageHashExcludingDirectories( .failure = undefined, // to be populated by the worker }; wait_group.start(); - try thread_pool.spawn(workerHashFile, .{ pkg_dir.dir, hashed_file, &wait_group }); + try thread_pool.spawn(workerHashFile, .{ pkg_dir, hashed_file, &wait_group }); try all_files.append(hashed_file); } diff --git a/wrk/zigstd/main.zig b/wrk/zigstd/main.zig index 30e0636d..45f9273e 100644 --- a/wrk/zigstd/main.zig +++ b/wrk/zigstd/main.zig @@ -1,43 +1,33 @@ const std = @import("std"); pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{ - .thread_safe = true, - }){}; - var allocator = gpa.allocator(); + // var gpa = std.heap.GeneralPurposeAllocator(.{ + // .thread_safe = true, + // }){}; + // const allocator = gpa.allocator(); - var server = std.http.Server.init(allocator, .{ + const address = try std.net.Address.parseIp("127.0.0.1", 3000); + var http_server = try address.listen(.{ .reuse_address = true, }); - defer server.deinit(); - const address = try std.net.Address.parseIp("127.0.0.1", 3000); - try server.listen(address); + var read_buffer: [2048]u8 = undefined; - const max_header_size = 8192; + // const max_header_size = 8192; while (true) { - var res = try server.accept(.{ - .allocator = allocator, - .header_strategy = .{ .dynamic = max_header_size }, - }); - // const start_time = std.time.nanoTimestamp(); - defer res.deinit(); - defer _ = res.reset(); - try res.wait(); + const connection = try http_server.accept(); + defer connection.stream.close(); + var server = std.http.Server.init(connection, &read_buffer); + var request = try server.receiveHead(); const server_body: []const u8 = "HI FROM ZIG STD!\n"; - res.transfer_encoding = .{ .content_length = server_body.len }; - try res.headers.append("content-type", "text/plain"); - try res.headers.append("connection", "close"); - try res.do(); - var buf: [128]u8 = undefined; - _ = try res.readAll(&buf); - _ = try res.writer().writeAll(server_body); - try res.finish(); - // const end_time = std.time.nanoTimestamp(); - // const diff = end_time - start_time; - // std.debug.print("{d}\n", .{diff}); + try request.respond(server_body, .{ + .extra_headers = &.{ + .{ .name = "content_type", .value = "text/plain" }, + .{ .name = "connection", .value = "close" }, + }, + }); } }