1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +00:00

Merge pull request #160 from zigzap/XXX

Breaking Niceties 😁 

- [x] Simplify zap.Endpoint
- [x] add error union to request fn return type
    - [x] add zap.Endpoint.ErrorStrategy 
- [x] maybe add `zap.App` 
    - [x] that's a bit like `zap.Midlleware` in the sense of supporting an arbitrary (app) context, 
    - [x] and provides an/a (per-thread) arena allocator per request for convenience 
    - [x] context-aware Endpoints
    - [x] context-aware Authenticating Endpoints
This commit is contained in:
Rene Schallner 2025-03-30 15:09:03 +02:00 committed by GitHub
commit 793423b7c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 2018 additions and 3858 deletions

View file

@ -1,4 +1,4 @@
name: Works with Zig 0.13.0 name: Works with Zig 0.14.0
on: on:
push: push:
branches: branches:

4
.gitignore vendored
View file

@ -16,4 +16,6 @@ scratch
.vs/ .vs/
**/*.perflog **/*.perflog
wrk/*.png wrk/*.png
.zig-cache/ mycert.pem
release.md
mykey.pem

229
README.md
View file

@ -16,25 +16,21 @@ web application framework](https://facil.io).
## **⚡ZAP⚡ IS FAST, ROBUST, AND STABLE** ## **⚡ZAP⚡ IS FAST, ROBUST, AND STABLE**
After having used ZAP in production for a year, I can confidently assert that it After having used ZAP in production for years, I can confidently assert that it
proved to be: proved to be:
- ⚡ **blazingly fast** - ⚡ **blazingly fast**
- 💪 **extremely robust** 💪 - 💪 **extremely robust** 💪
Exactly the goals I set out to achieve!
## FAQ: ## FAQ:
- Q: **What version of Zig does Zap support?** - Q: **What version of Zig does Zap support?**
- Zap uses the latest stable zig release (0.13.0), so you don't have to keep - Zap uses the latest stable zig release (0.14.0), so you don't have to keep
up with frequent breaking changes. It's an "LTS feature". up with frequent breaking changes. It's an "LTS feature".
- Q: **Can Zap build with Zig's master branch?** - Q: **Can Zap build with Zig's master branch?**
- See the `zig-master` branch. An example of how to use it is - See the `zig-master` branch. Please note that the zig-master branch is not
[here](https://github.com/zigzap/hello-master). Please note that the the official master branch of ZAP. Be aware that I don't provide tagged
zig-master branch is not the official master branch of ZAP. Be aware that releases for it. If you know what you are doing, that shouldn't stop you
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. from using it with zig master though.
- Q: **Where is the API documentation?** - Q: **Where is the API documentation?**
- Docs are a work in progress. You can check them out - Docs are a work in progress. You can check them out
@ -43,7 +39,7 @@ Exactly the goals I set out to achieve!
- Q: **Does ZAP work on Windows?** - Q: **Does ZAP work on Windows?**
- No. This is due to the underlying facil.io C library. Future versions - 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 of facil.io might support Windows but there is no timeline yet. Your best
options on Windows are WSL2 or a docker container. options on Windows are **WSL2 or a docker container**.
- Q: **Does ZAP support TLS / HTTPS?** - Q: **Does ZAP support TLS / HTTPS?**
- Yes, ZAP supports using the system's openssl. See the - Yes, ZAP supports using the system's openssl. See the
[https](./examples/https/https.zig) example and make sure to build with [https](./examples/https/https.zig) example and make sure to build with
@ -55,29 +51,43 @@ Exactly the goals I set out to achieve!
## Here's what works ## Here's what works
I recommend checking out **Endpoint-based examples for more realistic I recommend checking out **the new App-based** or the Endpoint-based
use cases**. Most of the examples are super stripped down to only include examples, as they reflect how I intended Zap to be used.
what's necessary to show a feature.
**NOTE: To see API docs, run `zig build run-docserver`.** To specify a custom Most of the examples are super stripped down to only include what's necessary to
show a feature.
**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 port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989
--docs=path/to/docs`. --docs=path/to/docs`.
- **Super easy build process**: Zap's `build.zig` now uses the new Zig package ### New App-Based Examples
manager for its C-dependencies, no git submodules anymore.
- _tested on Linux and macOS (arm, M1)_ - **[app_basic](examples/app/basic.zig)**: Shows how to use zap.App with a
- **[hello](examples/hello/hello.zig)**: welcomes you with some static HTML simple Endpoint.
- **[routes](examples/routes/routes.zig)**: a super easy example dispatching on - **[app_auth](examples/app/auth.zig)**: Shows how to use zap.App with an
the HTTP path. **NOTE**: The dispatch in the example is a super-basic Endpoint using an Authenticator.
DIY-style dispatch. See endpoint-based examples for more realistic use cases.
- **[serve](examples/serve/serve.zig)**: the traditional static web server with See the other examples for specific uses of Zap.
optional dynamic request handling
- **[sendfile](examples/sendfile/sendfile.zig)**: simple example of how to send Benefits of using `zap.App`:
a file, honoring compression headers, etc.
- **[bindataformpost](examples/bindataformpost/bindataformpost.zig)**: example - Provides a global, user-defined "Application Context" to all endpoints.
to receive binary files via form post. - Made to work with "Endpoints": an endpoint is a struct that covers a `/slug`
- **[hello_json](examples/hello_json/hello_json.zig)**: serves you json of the requested URL and provides a callback for each supported request method
dependent on HTTP path (get, put, delete, options, post, head, patch).
- Each request callback receives:
- a per-thread arena allocator you can use for throwaway allocations without
worrying about freeing them.
- the global "Application Context" of your app's choice
- Endpoint request callbacks are allowed to return errors:
- you can use `try`.
- the endpoint's ErrorStrategy defines if runtime errors should be reported to
the console, to the response (=browser for debugging), or if the error
should be returned.
### Legacy Endpoint-based examples
- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring a - **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring a
`/users` endpoint for performing PUT/DELETE/GET/POST operations and listing `/users` endpoint for performing PUT/DELETE/GET/POST operations and listing
users, together with a simple frontend to play with. **It also introduces a users, together with a simple frontend to play with. **It also introduces a
@ -87,26 +97,16 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989
`GeneralPurposeAllocator` to report memory leaks when ZAP is shut down. `GeneralPurposeAllocator` to report memory leaks when ZAP is shut down.
The [StopEndpoint](examples/endpoint/stopendpoint.zig) just stops ZAP when The [StopEndpoint](examples/endpoint/stopendpoint.zig) just stops ZAP when
receiving a request on the `/stop` route. receiving a request on the `/stop` route.
- **[mustache](examples/mustache/mustache.zig)**: a simple example using
[mustache](https://mustache.github.io/) templating.
- **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a - **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a
simple authenticated endpoint. Read more about authentication simple authenticated endpoint. Read more about authentication
[here](./doc/authentication.md). [here](./doc/authentication.md).
- **[http parameters](examples/http_params/http_params.zig)**: a simple example
sending itself query parameters of all supported types.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending itself a ### Legacy Middleware-Style examples
cookie and responding with a session cookie.
- **[websockets](examples/websockets/)**: a simple websockets chat for the
browser.
- **[Username/Password Session
Authentication](./examples/userpass_session_auth/)**: A convenience
authenticator that redirects un-authenticated requests to a login page and
sends cookies containing session tokens based on username/password pairs
received via POST request.
- **[MIDDLEWARE support](examples/middleware/middleware.zig)**: chain together - **[MIDDLEWARE support](examples/middleware/middleware.zig)**: chain together
request handlers in middleware style. Provide custom context structs, totally request handlers in middleware style. Provide custom context structs, totally
type-safe, using **[ZIG-CEPTION](doc/zig-ception.md)**. If you come from GO type-safe. If you come from GO this might appeal to you.
this might appeal to you.
- **[MIDDLEWARE with endpoint - **[MIDDLEWARE with endpoint
support](examples/middleware_with_endpoint/middleware_with_endpoint.zig)**: support](examples/middleware_with_endpoint/middleware_with_endpoint.zig)**:
Same as the example above, but this time we use an endpoint at the end of the Same as the example above, but this time we use an endpoint at the end of the
@ -126,6 +126,36 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989
struct in the callbacks via the `@fieldParentPtr()` trick that is used struct in the callbacks via the `@fieldParentPtr()` trick that is used
extensively in Zap's examples, like the [endpoint extensively in Zap's examples, like the [endpoint
example](examples/endpoint/endpoint.zig). example](examples/endpoint/endpoint.zig).
### Specific and Very Basic Examples
- **[hello](examples/hello/hello.zig)**: welcomes you with some static HTML
- **[routes](examples/routes/routes.zig)**: a super easy example dispatching on
the HTTP path. **NOTE**: The dispatch in the example is a super-basic
DIY-style dispatch. See endpoint-based examples for more realistic use cases.
- [**simple_router**](examples/simple_router/simple_router.zig): See how you
can use `zap.Router` to dispatch to handlers by HTTP path.
- **[serve](examples/serve/serve.zig)**: the traditional static web server with
optional dynamic request handling
- **[sendfile](examples/sendfile/sendfile.zig)**: simple example of how to send
a file, honoring compression headers, etc.
- **[bindataformpost](examples/bindataformpost/bindataformpost.zig)**: example
to receive binary files via form post.
- **[hello_json](examples/hello_json/hello_json.zig)**: serves you json
dependent on HTTP path
- **[mustache](examples/mustache/mustache.zig)**: a simple example using
[mustache](https://mustache.github.io/) templating.
- **[http parameters](examples/http_params/http_params.zig)**: a simple example
sending itself query parameters of all supported types.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending itself a
cookie and responding with a session cookie.
- **[websockets](examples/websockets/)**: a simple websockets chat for the
browser.
- **[Username/Password Session
Authentication](./examples/userpass_session_auth/)**: A convenience
authenticator that redirects un-authenticated requests to a login page and
sends cookies containing session tokens based on username/password pairs
received via POST request.
- [**Error Trace Responses**](./examples/senderror/senderror.zig): You can now - [**Error Trace Responses**](./examples/senderror/senderror.zig): You can now
call `r.sendError(err, status_code)` when you catch an error and a stack trace call `r.sendError(err, status_code)` when you catch an error and a stack trace
will be returned to the client / browser. will be returned to the client / browser.
@ -136,12 +166,6 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989
- run it like this: `ZAP_USE_OPENSSL=true zig build run-https` - run it like this: `ZAP_USE_OPENSSL=true zig build run-https`
OR like this: `zig build -Dopenssl=true run-https` OR like this: `zig build -Dopenssl=true run-https`
- it will tell you how to generate certificates - it will tell you how to generate certificates
- [**simple_router**](examples/simple_router/simple_router.zig): See how you
can use `zap.Router` to dispatch to handlers by HTTP path.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
to a point where I can use it as the JSON REST API backend for real research
projects, serving thousands of concurrent clients.
## ⚡blazingly fast⚡ ## ⚡blazingly fast⚡
@ -158,25 +182,7 @@ machine (x86_64-linux):
- Zig Zap was nearly 30% faster than GO - Zig Zap was nearly 30% faster than GO
- Zig Zap had over 50% more throughput than GO - Zig Zap had over 50% more throughput than GO
- **YMMV !!!**
**Update**: Thanks to @felipetrz, I got to test against more realistic Python
and Rust examples. Both python `sanic` and rust `axum` were easy enough to
integrate.
**Update**: I have automated the benchmarks. See
[blazingly-fast.md](./blazingly-fast.md) for more information. Also, thanks to
@alexpyattaev, the benchmarks are fairer now, pinning server and client to
specific CPU cores.
**Update**: I have consolidated the benchmarks to one good representative per
language. See more details in [blazingly-fast.md](./blazingly-fast.md). It
contains rust implementations that come pretty close to Zap's performance in the
simplistic testing scenario.
![](./wrk/samples/README_req_per_sec.png)
![](./wrk/samples/README_xfer_per_sec.png)
So, being somewhere in the ballpark of basic GO performance, zig zap seems to be So, being somewhere in the ballpark of basic GO performance, zig zap seems to be
... of reasonable performance 😎. ... of reasonable performance 😎.
@ -184,7 +190,38 @@ So, being somewhere in the ballpark of basic GO performance, zig zap seems to be
I can rest my case that developing ZAP was a good idea because it's faster than I can rest my case that developing ZAP was a good idea because it's faster than
both alternatives: a) staying with Python, and b) creating a GO + Zig hybrid. both alternatives: a) staying with Python, and b) creating a GO + Zig hybrid.
See more details in [blazingly-fast.md](blazingly-fast.md). ### On (now missing) Micro-Benchmakrs
I used to have some micro-benchmarks in this repo, showing that Zap beat all the
other things I tried, and eventually got tired of the meaningless discussions
they provoked, the endless issues and PRs that followed, wanting me to add and
maintain even more contestants, do more justice to beloved other frameworks,
etc.
Case in point, even for me the micro-benchmarks became meaningless. They were
just some rough indicator to me confirming that I didn't do anything terribly
wrong to facil.io, and that facil.io proved to be a reasonable choice, also from
a performance perspective.
However, none of the projects I use Zap for, ever even remotely resembled
anything close to a static HTTP response micro-benchmark.
For my more CPU-heavy than IO-heavy use-cases, a thread-based microframework
that's super robust is still my preferred choice, to this day.
Having said that, I would **still love** for other, pure-zig HTTP frameworks to
eventually make Zap obsolete. Now, in 2025, the list of candidates is looking
really promising.
### 📣 Shout-Outs
- [httpz](https://github.com/karlseguin/http.zig) : Pure Zig! Closer to Zap's
model. Performance = good!
- [jetzig](https://github.com/jetzig-framework/jetzig) : Comfortably develop
modern web applications quickly, using http.zig under the hood
- [zzz](https://github.com/tardy-org/zzz) : Super promising, super-fast,
especially for IO-heavy tasks, io_uring support - need I say more?
## 💪 Robust ## 💪 Robust
@ -217,9 +254,10 @@ local variables that require tens of megabytes of stack space.
### 🛡️ Memory-safe ### 🛡️ Memory-safe
See the [StopEndpoint](examples/endpoint/stopendpoint.zig) in the See the [StopEndpoint](examples/endpoint/stopendpoint.zig) in the
[endpoint](examples/endpoint) example. That example uses ZIG's awesome [endpoint](examples/endpoint) example. The `StopEndpoint` just stops ZAP when
`GeneralPurposeAllocator` to report memory leaks when ZAP is shut down. The receiving a request on the `/stop` route. That example uses ZIG's awesome
`StopEndpoint` just stops ZAP when receiving a request on the `/stop` route. `GeneralPurposeAllocator` in [main.zig](examples/endpoint/main.zig) to report
memory leaks when ZAP is shut down.
You can use the same strategy in your debug builds and tests to check if your You can use the same strategy in your debug builds and tests to check if your
code leaks memory. code leaks memory.
@ -228,7 +266,7 @@ code leaks memory.
## Getting started ## Getting started
Make sure you have **zig 0.13.0** installed. Fetch it from Make sure you have **zig 0.14.0** installed. Fetch it from
[here](https://ziglang.org/download). [here](https://ziglang.org/download).
```shell ```shell
@ -237,12 +275,11 @@ $ cd zap
$ zig build run-hello $ zig build run-hello
$ # open http://localhost:3000 in your browser $ # open http://localhost:3000 in your browser
``` ```
... and open [http://localhost:3000](http://localhost:3000) in your browser. ... and open [http://localhost:3000](http://localhost:3000) in your browser.
## Using ⚡zap⚡ in your own projects ## Using ⚡zap⚡ in your own projects
Make sure you have **the latest zig release (0.13.0)** installed. Fetch it from Make sure you have **the latest zig release (0.14.0)** installed. Fetch it from
[here](https://ziglang.org/download). [here](https://ziglang.org/download).
If you don't have an existing zig project, create one like this: If you don't have an existing zig project, create one like this:
@ -250,17 +287,11 @@ If you don't have an existing zig project, create one like this:
```shell ```shell
$ mkdir zaptest && cd zaptest $ mkdir zaptest && cd zaptest
$ zig init $ zig init
$ git init ## (optional)
``` ```
**Note**: Nix/NixOS users are lucky; you can use the existing `flake.nix` and run
`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. TODO: upgrade to latest zig.
With an existing Zig project, adding Zap to it is easy: With an existing Zig project, adding Zap to it is easy:
1. Add zap to your `build.zig.zon` 1. Zig fetch zap
2. Add zap to your `build.zig` 2. Add zap to your `build.zig`
In your zig project folder (where `build.zig` is located), run: In your zig project folder (where `build.zig` is located), run:
@ -284,26 +315,9 @@ Then, in your `build.zig`'s `build` function, add the following before
exe.root_module.addImport("zap", zap.module("zap")); exe.root_module.addImport("zap", zap.module("zap"));
``` ```
From then on, you can use the Zap package in your project. Check out the From then on, you can use the Zap package in your project via `const zap =
examples to see how to use Zap. @import("zap");`. Check out the examples to see how to use Zap.
## Updating your project to the latest version of zap
You can change the URL to Zap in your `build.zig.zon`
- easiest: use a tagged release
- or to one of the tagged versions, e.g. `0.0.9`
- or to the latest commit of `zap`
### Using a tagged release
Go to the [release page](https://github.com/zigzap/zap/releases). Every release
will state its version number and also provide instructions for changing
`build.zig.zon` and `build.zig`.
### Using other versions
See [here](./doc/other-versions.md).
## Contribute to ⚡zap⚡ - blazingly fast ## Contribute to ⚡zap⚡ - blazingly fast
@ -314,16 +328,7 @@ world a blazingly fast place by providing patches or pull requests, add
documentation or examples, or interesting issues and bug reports - you'll know documentation or examples, or interesting issues and bug reports - you'll know
what to do when you receive your calling 👼. what to do when you receive your calling 👼.
Check out [CONTRIBUTING.md](CONTRIBUTING.md) for more details. **We have our own [ZAP discord](https://discord.gg/jQAAN6Ubyj) server!!!**
See also [introducing.md](introducing.md) for more on the state and progress of
this project.
**We now have our own [ZAP discord](https://discord.gg/jQAAN6Ubyj) server!!!**
You can also reach me on [the zig showtime discord
server](https://discord.gg/CBzE3VMb) under the handle renerocksai
(renerocksai#1894).
## Support ⚡zap⚡ ## Support ⚡zap⚡

View file

@ -1,332 +0,0 @@
# ⚡blazingly fast⚡
Initially, I conducted a series of quick tests, using wrk with simple HTTP
servers written in GO and in zig zap. I made sure that all servers only output
17 bytes of HTTP body.
Just to get some sort of indication, I also included measurements for python
since I used to write my REST APIs in python before creating zig zap.
You can check out the scripts I used for the tests in [./wrk](wrk/).
## Why
I aimed to enhance the performance of my Python + Flask backends by replacing
them with a Zig version. To evaluate the success of this transition, I compared
the performance of a static HTTP server implemented in Python and its Zig
counterpart, which showed significant improvements.
To further assess the Zig server's performance, I compared it with a Go
implementation, to compare against a widely used industry-standard. I expected
similar performance levels but was pleasantly surprised when Zap outperformed Go
by approximately 30% on my test machine.
Intrigued by Rust's reputed performance capabilities, I also experimented with a
Rust version. The results of this experiment are discussed in the
[Flaws](#flaws) section below.
## What
So, what are the benchmarks testing?
- simple http servers that reply to GET requests with a constant, 17-bytes long response
- 4 cores are assigned to the subject under test (the respective server)
- 4 cores are assigned to `wrk`
- using 4 threads
- aiming at 400 concurrent connections
## How
I have fully automated the benchmarks and graph generation.
To generate the data:
```console
$ ./wrk/measure_all.sh
```
To generate the graphs:
```console
$ python wrk/graph.py
```
For dependencies, please see the [flake.nix](./flake.nix#L46).
## Flaws
The benchmarks have limitations, such as the lack of request latencies. The Rust
community has often criticized these benchmarks as biased. However, no such
criticisms have come from the Go or Python communities.
In response to the Rust community's concerns, we've added three Rust
implementations for comparison:
- A standard version from [the Rust book](https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html).
- An "axum" version to highlight Rust's speed.
- A refined version of the Rust book version.
Originally, the goal was to compare "batteries included" versions, which created
a disparity by comparing the optimized zap / facil.io code with basic bundled
functionalities. These tests were for personal interest and not meant to be
definitive benchmarks.
To address this bias, we've added the Rust-axum and Python-sanic benchmarks. For
more information, refer to the relevant discussions and pull requests.
## More benchmarks?
I often receive requests or PRs to include additional benchmarks, which a lot of
times I find to be either ego-driven or a cause for unnecessary disputes. People
tend to favor their preferred language or framework. Zig, Rust, C, and C++ are
all capable of efficiently creating fast web servers, with different frameworks
potentially excelling in certain benchmarks. My main concern was whether Zap,
given its current level of abstraction, could compete with standard web servers.
This question has been answered, and I see no need for further benchmarks.
So far, we have the following benchmark subjects (implementations) which you'll
find in the graphs below:
- **zig-zap** : ZAP implementation
- **go** : GO implementation
- **python** : Python implementation
- **python-sanic** : Python implementation with sanic framework
- **rust-bythebook** : Rust example from the Rust book (not representative)
- **rust-bythebook-improved** : Improved version of the by-the-book code (thx @alexpyattaev)
- **rust-clean** : A clean, straight-forward Rust implementation (thx @alexpyattaev)
- **rust-axum** : Rust implementation using the axum framework (realistic)
- **(csharp)** : CSharp implementation (thx @leo-costa)
- **cpp-beast** : A C++ implementation using boost::beast (thx @kassane)
## The computer makes the difference
After automating the performance benchmarks, I gathered data from three
different computers. It's interesting to see the variation in relative numbers.
### The test machine (graphs in the README)
![](./wrk/samples/req_per_sec_graph.png)
![](./wrk/samples/xfer_per_sec_graph.png)
```
➜ neofetch --stdout
rs@ryzen
--------
OS: NixOS 23.05.997.ddf4688dc7a (Stoat) x86_64
Host: Micro-Star International Co., Ltd. B550-A PRO (MS-7C56)
Kernel: 6.3.7
Uptime: 15 days, 11 hours, 13 mins
Packages: 2094 (nix-system), 1356 (nix-user), 7 (flatpak)
Shell: bash 5.2.15
Resolution: 3840x2160
DE: none+i3
WM: i3
Terminal: tmux
CPU: AMD Ryzen 5 5600X (12) @ 3.700GHz
GPU: AMD ATI Radeon RX 6700/6700 XT/6750 XT / 6800M/6850M XT
Memory: 4981MiB / 32028MiB
➜ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 5 5600X 6-Core Processor
CPU family: 25
Model: 33
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 0
Frequency boost: enabled
CPU(s) scaling MHz: 67%
CPU max MHz: 4650.2920
CPU min MHz: 2200.0000
BogoMIPS: 7399.43
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt
pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16
sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefet
ch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ib
pb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xget
bv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd arat npt lbrv svm_lock nrip
_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip pku ospk
e vaes vpclmulqdq rdpid overflow_recov succor smca fsrm
Virtualization features:
Virtualization: AMD-V
Caches (sum of all):
L1d: 192 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 3 MiB (6 instances)
L3: 32 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-11
Vulnerabilities:
Itlb multihit: Not affected
L1tf: Not affected
Mds: Not affected
Meltdown: Not affected
Mmio stale data: Not affected
Retbleed: Not affected
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Retpolines, IBPB conditional, IBRS_FW, STIBP always-on, RSB filling, PBRSB-eIBRS Not affected
Srbds: Not affected
Tsx async abort: Not affected
```
### Workstation at work
A beast. Many cores (which we don't use).
![](./wrk/samples/workstation_req_per_sec_graph.png)
![](./wrk/samples/workstation_xfer_per_sec_graph.png)
```
[rene@nixos:~]$ neofetch --stdout
rene@nixos
----------
OS: NixOS 23.05.2947.475d5ae2c4cb (Stoat) x86_64
Host: LENOVO 1038
Kernel: 6.1.46
Uptime: 26 mins
Packages: 5804 (nix-system), 566 (nix-user)
Shell: bash 5.2.15
Terminal: /dev/pts/2
CPU: Intel Xeon Gold 5218 (64) @ 3.900GHz
GPU: NVIDIA Quadro P620
GPU: NVIDIA Tesla M40
Memory: 1610MiB / 95247MiB
[rene@nixos:~]$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Vendor ID: GenuineIntel
Model name: Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz
CPU family: 6
Model: 85
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 2
Stepping: 7
CPU(s) scaling MHz: 57%
CPU max MHz: 3900,0000
CPU min MHz: 1000,0000
BogoMIPS: 4600,00
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs b
ts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_
deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cdp_l3 invpcid_single intel_ppin ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpri
ority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb intel_pt avx512cd avx512bw avx512vl xsaveopt xs
avec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req pku ospke avx512_vnni md_clear flush_l1d arch_capabi
lities
Virtualization features:
Virtualization: VT-x
Caches (sum of all):
L1d: 1 MiB (32 instances)
L1i: 1 MiB (32 instances)
L2: 32 MiB (32 instances)
L3: 44 MiB (2 instances)
NUMA:
NUMA node(s): 2
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
Vulnerabilities:
Gather data sampling: Mitigation; Microcode
Itlb multihit: KVM: Mitigation: VMX disabled
L1tf: Not affected
Mds: Not affected
Meltdown: Not affected
Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Retbleed: Mitigation; Enhanced IBRS
Spec rstack overflow: Not affected
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling, PBRSB-eIBRS SW sequence
Srbds: Not affected
Tsx async abort: Mitigation; TSX disabled
```
### Work Laptop
Very strange. It absolutely **LOVES** zap 🤣!
![](./wrk/samples/laptop_req_per_sec_graph.png)
![](./wrk/samples/laptop_xfer_per_sec_graph.png)
```
➜ neofetch --stdout
rs@nixos
--------
OS: NixOS 23.05.2918.4cdad15f34e6 (Stoat) x86_64
Host: LENOVO 20TKS0W700
Kernel: 6.1.45
Uptime: 1 day, 4 hours, 50 mins
Packages: 6259 (nix-system), 267 (nix-user), 9 (flatpak)
Shell: bash 5.2.15
Resolution: 3840x1600, 3840x2160
DE: none+i3
WM: i3
Terminal: tmux
CPU: Intel i9-10885H (16) @ 5.300GHz
GPU: NVIDIA GeForce GTX 1650 Ti Mobile
Memory: 4525MiB / 31805MiB
➜ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz
CPU family: 6
Model: 165
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 2
CPU(s) scaling MHz: 56%
CPU max MHz: 5300.0000
CPU min MHz: 800.0000
BogoMIPS: 4800.00
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust sgx bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp pku ospke sgx_lc md_clear flush_l1d arch_capabilities
Virtualization: VT-x
L1d cache: 256 KiB (8 instances)
L1i cache: 256 KiB (8 instances)
L2 cache: 2 MiB (8 instances)
L3 cache: 16 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
Vulnerability Gather data sampling: Mitigation; Microcode
Vulnerability Itlb multihit: KVM: Mitigation: VMX disabled
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Retbleed: Mitigation; Enhanced IBRS
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling, PBRSB-eIBRS SW sequence
Vulnerability Srbds: Mitigation; Microcode
Vulnerability Tsx async abort: Not affected
```

View file

@ -50,6 +50,8 @@ pub fn build(b: *std.Build) !void {
name: []const u8, name: []const u8,
src: []const u8, src: []const u8,
}{ }{
.{ .name = "app_basic", .src = "examples/app/basic.zig" },
.{ .name = "app_auth", .src = "examples/app/auth.zig" },
.{ .name = "hello", .src = "examples/hello/hello.zig" }, .{ .name = "hello", .src = "examples/hello/hello.zig" },
.{ .name = "https", .src = "examples/https/https.zig" }, .{ .name = "https", .src = "examples/https/https.zig" },
.{ .name = "hello2", .src = "examples/hello2/hello2.zig" }, .{ .name = "hello2", .src = "examples/hello2/hello2.zig" },
@ -58,8 +60,6 @@ pub fn build(b: *std.Build) !void {
.{ .name = "serve", .src = "examples/serve/serve.zig" }, .{ .name = "serve", .src = "examples/serve/serve.zig" },
.{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" }, .{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" },
.{ .name = "endpoint", .src = "examples/endpoint/main.zig" }, .{ .name = "endpoint", .src = "examples/endpoint/main.zig" },
.{ .name = "wrk", .src = "wrk/zig/main.zig" },
.{ .name = "wrk_zigstd", .src = "wrk/zigstd/main.zig" },
.{ .name = "mustache", .src = "examples/mustache/mustache.zig" }, .{ .name = "mustache", .src = "examples/mustache/mustache.zig" },
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" }, .{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" }, .{ .name = "http_params", .src = "examples/http_params/http_params.zig" },

View file

@ -1,35 +0,0 @@
#!/usr/bin/env bash
tag=$1
override=$2
if [ "$tag" == "--override" ] ; then
override=$tag
tag=""
fi
if [ "$tag" == "" ] ; then
tag=$(git rev-parse --abbrev-ref HEAD)
echo "Warning: no tag provided, using: >> $tag <<"
fi
git archive --format=tar.gz -o ${tag}.tar.gz --prefix=zap-$tag/ HEAD
git diff --quiet
if [ $? -ne 0 ] ; then
if [ "$override" == "--override" ] ; then
./zig-out/bin/pkghash -g --tag=$tag --template=doc/release-template.md
else
echo "WARNING: GIT WORKING TREE IS DIRTY!"
echo "If you want to get zig hash anyway, run:"
echo "./zig-out/bin/pkghash -g"
echo "or, with full-blown release-notes:"
echo "./zig-out/bin/pkghash -g --tag=$tag --template=doc/release-template.md"
echo ""
echo "To skip this message and do the pkghash thing anyway, supply the"
echo "--override parameter"
fi
else
./zig-out/bin/pkghash -g --tag=$tag --template=doc/release-template.md
fi

View file

@ -9,11 +9,13 @@ authentication, see the [UserPassSession](../src/http_auth.zig#L319) and its
For convenience, Authenticator types exist that can authenticate requests. For convenience, Authenticator types exist that can authenticate requests.
Zap also provides an `Endpoint.Authenticating` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig). Zap also provides an `Endpoint.Authenticating` endpoint-wrapper. Have a look at
the [example](../examples/endpoint_auth) and the
[tests](../src/tests/test_auth.zig).
The following describes the Authenticator types. All of them provide the The following describes the Authenticator types. All of them provide the
`authenticateRequest()` function, which takes a `zap.Request` and returns `authenticateRequest()` function, which takes a `zap.Request` and returns a bool
a bool value whether it could be authenticated or not. value whether it could be authenticated or not.
Further down, we show how to use the Authenticators, and also the Further down, we show how to use the Authenticators, and also the
`Endpoint.Authenticating`. `Endpoint.Authenticating`.
@ -187,7 +189,8 @@ fn on_request(r: zap.Request) void {
Here, we only show using one of the Authenticator types. See the tests for more Here, we only show using one of the Authenticator types. See the tests for more
examples. examples.
The `Endpoint.Authenticating` honors `.unauthorized` in the endpoint settings, where you can pass in a callback to deal with unauthorized requests. If you leave it to `null`, the endpoint will automatically reply with a `401 - Unauthorized` response. The `Endpoint.Authenticating` uses the `.unauthorized()` method of the endpoint
to deal with unauthorized requests. `unauthorized` must be implemented.
The example below should make clear how to wrap an endpoint into an The example below should make clear how to wrap an endpoint into an
`Endpoint.Authenticating`: `Endpoint.Authenticating`:
@ -205,18 +208,20 @@ const HTTP_RESPONSE: []const u8 =
\\ </body></html> \\ </body></html>
; ;
// authenticated requests go here pub const Endpoint = struct {
fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void { path: []const u8,
_ = e;
r.sendBody(HTTP_RESPONSE) catch return;
}
// just for fun, we also catch the unauthorized callback // authenticated requests go here
fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void { fn get(_: *Endpoint, r: zap.Request) void {
_ = e; r.sendBody(HTTP_RESPONSE) catch return;
r.setStatus(.unauthorized); }
r.sendBody("UNAUTHORIZED ACCESS") catch return;
} // just for fun, we also catch the unauthorized callback
fn unauthorized(_: *Endpoint, r: zap.Request) void {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return;
}
};
pub fn main() !void { pub fn main() !void {
// setup listener // setup listener
@ -233,11 +238,9 @@ pub fn main() !void {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = zap.Endpoint.init(.{ var ep : Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create authenticator // create authenticator
const Authenticator = zap.Auth.BearerSingle; const Authenticator = zap.Auth.BearerSingle;
@ -245,10 +248,10 @@ pub fn main() !void {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = zap.Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
std.debug.print( std.debug.print(

View file

@ -1,101 +0,0 @@
# Self-hosting release packages until Zig master is fixed
Recently, GitHub started hosting release archives on a dedicated host
codeload.github.com. This is when the problems started. Back then, zig's package
manager was not expecting to be re-directed to a different URL. On top of that,
GitHub changed the redirected-to URLs so they wouldn't end in `.tar.gz` anymore.
Above issues were fixed but after some progress on `zig.http` related standard
library stuff, a similar error started impacting the package manager: parsing
long TLS responses has the [issue
ziglang/zig#15990](https://github.com/ziglang/zig/issues/15590).
So, here we are. Since this topic has come up often enough now, it deserves its
own doc.
## The workaround: self-hosting on localhost
My workaround is: not using https! The easiest way to do this, is:
- create the tar archive yourself
- start a python http server on the command line
- replace the URL in the build.zig.zon with a http and localhost one.
For simple packages, this is relatively easy. But zap itself has a
`build.zig.zon` that references its `facilio` dependency. For that reason, ZAP's
build.zig.zon also needs to change: to only reference localhost packages.
The consequence of changing build.zig.zon is: zap's package hash changes! -->
Any build.zig.zon that wants to use ZAP needs to change, too.
This is why, for the time being, I am always creating two releases,
a `release-0.0.n` one and `release-0.0.n-localhost` one, for each release.
So, while the TLS bug persists, you have to use the `-localhost` releases. The
procedure is:
- fetch zap's dependency `facilio` from GitHub
- fetch zap's `localhost` release from GitHub
- _(use the localhost URL and package hash in your build.zig)_
- start a local http server
- run zig build
- stop the http server
Here is an example for the `release-0.0.20-localhost` release which is the
current release at the time of writing:
```console
$ # get dependency required by zap
$ wget https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.8.tar.gz
$ # get zap itself
$ wget https://github.com/zigzap/zap/archive/refs/tags/release-0.0.20-localhost.tar.gz
$ # start a http server on port 8000
$ python -m http.server
```
... and use the following in your build.zig.zon:
```zig
// zap release-0.0.20-localhost
.zap = .{
.url = "http://127.0.0.1/release-0.0.20-localhost.tar.gz",
.hash = "12204c663be7639e98af40ad738780014b18bcf35efbdb4c701aad51c7dec45abf4d",
}
```
After the first successful zig build, zig will have cached both dependencies,
the direct zap one and the transient facilio one, and you won't need to start an
HTTP server again until you want to update your dependencies.
## Building Release Packages yourself
- In your branch, replace `build.zig.zon` with `build.zig.zon.localhost`
- **make sure everything is committed and your branch is clean.** This is
essential for calculating the package hash.
- `zig build pkghash` if you haven't already.
- tag your release: `git tag MY_TAG`
- I recommend putting `localhost` in the tagname
- `./create-archive.sh MY_TAG`
The `create-archive.sh` script will spit out release notes that contain the
hashes, as well as a `MY_TAG.tar.gz`.
You can then host this via python HTTP server and proceed as if you had
downloaded it from github.
If all goes well, your dependend code should be able to use your freshly-built
zap release, depending on it via localhost URL in its `build.zig.zon`.
If not, fix bugs, rinse, and repeat.
You may want to push to your fork and create a GitHub 'localhost' release.
When you're happy with the release, you may consider replacing `build.zig.zon`
with the non-localhost version from the master branch. Commit it, make sure your
worktree is clean, and perform above steps again. This time, using a tag that
doesn't contain `localhost`. You can then push to your fork and create a release
for the future when zig's bug is fixed.

View file

@ -1,40 +0,0 @@
# Alternatives to released versions
## Using a tagged version
Go to [to the tags page](https://github.com/zigzap/zap/tags) to view all
available tagged versions of zap. From there, right click on the `tar.gz` link
to copy the URL to put into your `build.zig.zon`.
After changing the `.url` field, you will get an error like this at the next
attempt to `zig build`:
```
.../build.zig.zon:8:21: error: hash mismatch:
expected: 12205fd0b60720fb2a40d82118ee75c15cb5589bb9faf901c8a39a93551dd6253049,
found: 1220f4ea8be4a85716ae1362d34c077dca10f10d1baf9196fc890e658c56f78b7424
.hash = "12205fd0b60720fb2a40d82118ee75c15cb5589bb9faf901c8a39a93551dd6253049",
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
**Note:** If you don't get this error, clean your global zig cache: `rm -fr
~/.cache/zig`. This shouldn't happen with current zig master anymore.
With the new URL, the old hash in the `build.zig.zon` is no longer valid. You
need to take the hash value displayed after `found: ` in the error message as
the `.hash` value in `build.zig.zon`.
## Using an arbitrary (last) commit
Use the same workflow as above for tags, excpept for the URL, use this schema:
```zig
.url = "https://github.com/zigzap/zap/archive/[COMMIT-HASH].tar.gz",
```
Replace `[COMMIT-HASH]` with the full commit hash as provided, e.g. by `git
log`.

View file

@ -1,81 +0,0 @@
# ZIG-CEPTION!
In ZAP, we have great zig-ception moment in the [middleware
example](../examples/middleware/middleware.zig). But first we need to introduce
one key function of `zap.Middleware`: **combining structs at comptime!**
## Combining structs at runtime
Here is how it is used in user-code:
```zig
// create a combined context struct
const Context = struct {
user: ?UserMiddleWare.User = null,
session: ?SessionMiddleWare.Session = null,
};
```
Why do we create combined structs? Because all our Middleware handler functions
need to receive a per-request context. But each wants their own data: the User
middleware might want to access a User struct, the Session middleware might want
a Session struct, and so on. So, which struct should we use in the prototype of
the "on_request" callback function? We could just use an `anyopaque` pointer.
That would solve the generic function prototype problem. But then everyone
implementing such a handler would need to cast this pointer back into - what?
Into the same type that the caller of the handler used. It gets really messy
when we continue this train of thought.
So, in ZAP, I opted for one Context type for all request handlers. Since ZAP is
a library, it cannot know what your preferred Context struct is. What it should
consist of. Therefore, it lets you combine all the structs your and maybe your
3rd parties's middleware components require - at comptime! And derive the
callback function prototype from that. If you look at the [middleware
example](../examples/middleware/middleware.zig), you'll notice, it's really
smooth to use.
**NOTE:** In your contexts, please also use OPTIONALS. They are set null at
context creation time. And will aid you in not shooting yourself in the foot
when accessing context fields that haven't been initialized - which may happen
when the order of your chain of components isn't perfect yet. 😉
## The zig-ception moment
Have a look at an excerpt of the example:
```zig
// create a combined context struct
const Context = struct {
user: ?UserMiddleWare.User = null,
session: ?SessionMiddleWare.Session = null,
};
// we create a Handler type based on our Context
const Handler = zap.Middleware.Handler(Context);
//
// ZIG-CEPTION!!!
//
// Note how amazing zig is:
// - we create the "mixed" context based on the both middleware structs
// - we create the handler based on this context
// - we create the middleware structs based on the handler
// - which needs the context
// - which needs the middleware structs
// - ZIG-CEPTION!
// Example user middleware: puts user info into the context
const UserMiddleWare = struct {
handler: Handler,
// .. the UserMiddleWare depends on the handler
// which depends on the Context
// which depends on this UserMiddleWare struct
// ZIG-CEPTION!!!
```
## 🤯
The comments in the code say it all.
**Isn't ZIG AMAZING?**

View file

@ -5,7 +5,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
}){}; }){};
fn on_request_verbose(r: zap.Request) void { fn on_request_verbose(r: zap.Request) !void {
// use a local buffer for the parsed accept headers // use a local buffer for the parsed accept headers
var accept_buffer: [1024]u8 = undefined; var accept_buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&accept_buffer); var fba = std.heap.FixedBufferAllocator.init(&accept_buffer);
@ -21,38 +21,53 @@ fn on_request_verbose(r: zap.Request) void {
break :content_type .HTML; break :content_type .HTML;
}; };
r.setContentType(content_type) catch return; // just for fun: print ALL headers
var maybe_headers: ?zap.Request.HttpParamStrKVList = blk: {
const h = r.headersToOwnedList(gpa.allocator()) catch |err| {
std.debug.print("Error getting headers: {}\n", .{err});
break :blk null;
};
break :blk h;
};
if (maybe_headers) |*headers| {
defer headers.deinit();
for (headers.items) |header| {
std.debug.print("Header {s} = {s}\n", .{ header.key, header.value });
}
}
try r.setContentType(content_type);
switch (content_type) { switch (content_type) {
.TEXT => { .TEXT => {
r.sendBody("Hello from ZAP!!!") catch return; try r.sendBody("Hello from ZAP!!!");
}, },
.HTML => { .HTML => {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
}, },
.XML => { .XML => {
r.sendBody( try r.sendBody(
\\<?xml version="1.0" encoding="UTF-8"?> \\<?xml version="1.0" encoding="UTF-8"?>
\\<message> \\<message>
\\ <warning> \\ <warning>
\\ Hello from ZAP!!! \\ Hello from ZAP!!!
\\ </warning> \\ </warning>
\\</message> \\</message>
) catch return; );
}, },
.JSON => { .JSON => {
var buffer: [128]u8 = undefined; var buffer: [128]u8 = undefined;
const json = zap.stringifyBuf(&buffer, .{ .message = "Hello from ZAP!!!" }, .{}) orelse return; const json = try zap.util.stringifyBuf(&buffer, .{ .message = "Hello from ZAP!!!" }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}, },
.XHTML => { .XHTML => {
r.sendBody( try r.sendBody(
\\<?xml version="1.0" encoding="UTF-8"?> \\<?xml version="1.0" encoding="UTF-8"?>
\\<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"> \\<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
\\ <body> \\ <body>
\\ <h1>Hello from ZAP!!!</h1> \\ <h1>Hello from ZAP!!!</h1>
\\ </body> \\ </body>
\\</html> \\</html>
) catch return; );
}, },
} }
} }
@ -66,7 +81,18 @@ pub fn main() !void {
}); });
try listener.listen(); try listener.listen();
std.debug.print("Listening on 0.0.0.0:3000\n", .{}); std.debug.print(
\\ Listening on 0.0.0.0:3000
\\
\\ Test me with:
\\ curl --header "Accept: text/plain" localhost:3000
\\ curl --header "Accept: text/html" localhost:3000
\\ curl --header "Accept: application/xml" localhost:3000
\\ curl --header "Accept: application/json" localhost:3000
\\ curl --header "Accept: application/xhtml+xml" localhost:3000
\\
\\
, .{});
// start worker threads // start worker threads
zap.start(.{ zap.start(.{

119
examples/app/auth.zig Normal file
View file

@ -0,0 +1,119 @@
const std = @import("std");
const zap = @import("zap");
const Allocator = std.mem.Allocator;
// The "Application Context"
const MyContext = struct {
bearer_token: []const u8,
};
// We reply with this
const HTTP_RESPONSE_TEMPLATE: []const u8 =
\\ <html><body>
\\ {s} from ZAP on {s} (token {s} == {s} : {s})!!!
\\ </body></html>
\\
;
// Our simple endpoint that will be wrapped by the authenticator
const MyEndpoint = struct {
// the slug
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
fn get_bearer_token(r: zap.Request) []const u8 {
const auth_header = zap.Auth.extractAuthHeader(.Bearer, &r) orelse "Bearer (no token)";
return auth_header[zap.Auth.AuthScheme.Bearer.str().len..];
}
// authenticated GET requests go here
// we use the endpoint, the context, the arena, and try
pub fn get(ep: *MyEndpoint, arena: Allocator, context: *MyContext, r: zap.Request) !void {
const used_token = get_bearer_token(r);
const response = try std.fmt.allocPrint(
arena,
HTTP_RESPONSE_TEMPLATE,
.{ "Hello", ep.path, used_token, context.bearer_token, "OK" },
);
r.setStatus(.ok);
try r.sendBody(response);
}
// we also catch the unauthorized callback
// we use the endpoint, the context, the arena, and try
pub fn unauthorized(ep: *MyEndpoint, arena: Allocator, context: *MyContext, r: zap.Request) !void {
r.setStatus(.unauthorized);
const used_token = get_bearer_token(r);
const response = try std.fmt.allocPrint(
arena,
HTTP_RESPONSE_TEMPLATE,
.{ "UNAUTHORIZED", ep.path, used_token, context.bearer_token, "NOT OK" },
);
try r.sendBody(response);
}
// not implemented, don't care
pub fn post(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
};
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{
// just to be explicit
.thread_safe = true,
}) = .{};
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
const allocator = gpa.allocator();
// our global app context
var my_context: MyContext = .{ .bearer_token = "ABCDEFG" }; // ABCDEFG is our Bearer token
// our global app that holds the context
// App is the type
// app is the instance
const App = zap.App.Create(MyContext);
var app = try App.init(allocator, &my_context, .{});
defer app.deinit();
// create mini endpoint
var ep: MyEndpoint = .{
.path = "/test",
};
// create authenticator, use token from context
const Authenticator = zap.Auth.BearerSingle; // Simple Authenticator that uses a single bearer token
var authenticator = try Authenticator.init(allocator, my_context.bearer_token, null);
defer authenticator.deinit();
// create authenticating endpoint by combining endpoint and authenticator
const BearerAuthEndpoint = App.Endpoint.Authenticating(MyEndpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
// make the authenticating endpoint known to the app
try app.register(&auth_ep);
// listen
try app.listen(.{
.interface = "0.0.0.0",
.port = 3000,
});
std.debug.print(
\\ Run the following:
\\
\\ curl http://localhost:3000/test -i -H "Authorization: Bearer ABCDEFG" -v
\\ curl http://localhost:3000/test -i -H "Authorization: Bearer invalid" -v
\\
\\ and see what happens
\\
, .{});
// start worker threads
zap.start(.{
.threads = 2,
.workers = 1,
});
}

100
examples/app/basic.zig Normal file
View file

@ -0,0 +1,100 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const zap = @import("zap");
// The global Application Context
const MyContext = struct {
db_connection: []const u8,
pub fn init(connection: []const u8) MyContext {
return .{
.db_connection = connection,
};
}
};
// A very simple endpoint handling only GET requests
const SimpleEndpoint = struct {
// zap.App.Endpoint Interface part
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
// data specific for this endpoint
some_data: []const u8,
pub fn init(path: []const u8, data: []const u8) SimpleEndpoint {
return .{
.path = path,
.some_data = data,
};
}
// handle GET requests
pub fn get(e: *SimpleEndpoint, arena: Allocator, context: *MyContext, r: zap.Request) anyerror!void {
const thread_id = std.Thread.getCurrentId();
r.setStatus(.ok);
// look, we use the arena allocator here -> no need to free the response_text later!
// and we also just `try` it, not worrying about errors
const response_text = try std.fmt.allocPrint(
arena,
\\Hello!
\\context.db_connection: {s}
\\endpoint.data: {s}
\\arena: {}
\\thread_id: {}
\\
,
.{ context.db_connection, e.some_data, arena.ptr, thread_id },
);
try r.sendBody(response_text);
std.time.sleep(std.time.ns_per_ms * 300);
}
// empty stubs for all other request methods
pub fn post(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) anyerror!void {}
pub fn put(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) anyerror!void {}
pub fn delete(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) anyerror!void {}
pub fn patch(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) anyerror!void {}
pub fn options(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) anyerror!void {}
};
pub fn main() !void {
// setup allocations
var gpa: std.heap.GeneralPurposeAllocator(.{
// just to be explicit
.thread_safe = true,
}) = .{};
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
const allocator = gpa.allocator();
// create an app context
var my_context = MyContext.init("db connection established!");
// create an App instance
const App = zap.App.Create(MyContext);
var app = try App.init(allocator, &my_context, .{});
defer app.deinit();
// create the endpoint
var my_endpoint = SimpleEndpoint.init("/", "some endpoint specific data");
// register the endpoint with the app
try app.register(&my_endpoint);
// listen on the network
try app.listen(.{
.interface = "0.0.0.0",
.port = 3000,
});
std.debug.print("Listening on 0.0.0.0:3000\n", .{});
// start worker threads -- only 1 process!!!
zap.start(.{
.threads = 2,
.workers = 1,
});
}

View file

@ -4,7 +4,7 @@ const zap = @import("zap");
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
// parse for FORM (body) parameters first // parse for FORM (body) parameters first
r.parseBody() catch |err| { r.parseBody() catch |err| {
std.log.err("Parse Body error: {any}. Expected if body is empty", .{err}); std.log.err("Parse Body error: {any}. Expected if body is empty", .{err});
@ -24,12 +24,12 @@ const Handler = struct {
// //
// HERE WE HANDLE THE BINARY FILE // HERE WE HANDLE THE BINARY FILE
// //
const params = r.parametersToOwnedList(Handler.alloc, false) catch unreachable; const params = try r.parametersToOwnedList(Handler.alloc);
defer params.deinit(); defer params.deinit();
for (params.items) |kv| { for (params.items) |kv| {
if (kv.value) |v| { if (kv.value) |v| {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key.str, v }); std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key, v });
switch (v) { switch (v) {
// single-file upload // single-file upload
zap.Request.HttpParam.Hash_Binfile => |*file| { zap.Request.HttpParam.Hash_Binfile => |*file| {
@ -55,34 +55,22 @@ const Handler = struct {
files.*.deinit(); files.*.deinit();
}, },
else => { else => {
// might be a string param, we don't care // let's just get it as its raw slice
// let's just get it as string const value: []const u8 = r.getParamSlice(kv.key) orelse "(no value)";
// always_alloc param = false -> the string will be a slice from the request buffer std.log.debug(" {s} = {s}", .{ kv.key, value });
// --> 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});
}
}, },
} }
} }
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| { if (r.getParamSlice("terminate")) |str| {
if (maybe_str) |*s| { std.log.info("?terminate={s}\n", .{str});
std.log.info("?terminate={s}\n", .{s.str}); if (std.mem.eql(u8, str, "true")) {
if (std.mem.eql(u8, s.str, "true")) { zap.stop();
zap.stop();
}
} }
} else |err| {
std.log.err("cannot check for terminate param: {any}\n", .{err});
} }
r.sendJson("{ \"ok\": true }") catch unreachable; try r.sendJson("{ \"ok\": true }");
} }
}; };
@ -90,6 +78,7 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
}){}; }){};
defer _ = gpa.detectLeaks();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
Handler.alloc = allocator; Handler.alloc = allocator;

View file

@ -35,7 +35,7 @@ pub fn main() !void {
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
std.debug.print("\n=====================================================\n", .{}); std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{}); defer std.debug.print("=====================================================\n\n", .{});
@ -44,12 +44,12 @@ pub fn main() !void {
const cookie_count = r.getCookiesCount(); const cookie_count = r.getCookiesCount();
std.log.info("cookie_count: {}", .{cookie_count}); std.log.info("cookie_count: {}", .{cookie_count});
// iterate over all cookies as strings (always_alloc=false) // iterate over all cookies as strings
var strCookies = r.cookiesToOwnedStrList(alloc, false) catch unreachable; var strCookies = r.cookiesToOwnedStrList(alloc) catch unreachable;
defer strCookies.deinit(); defer strCookies.deinit();
std.debug.print("\n", .{}); std.debug.print("\n", .{});
for (strCookies.items) |kv| { for (strCookies.items) |kv| {
std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str }); std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key, kv.value });
// we don't need to deinit kv.key and kv.value because we requested always_alloc=false // 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 // so they are just slices into the request buffer
} }
@ -57,26 +57,22 @@ pub fn main() !void {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
// // iterate over all cookies // // iterate over all cookies
const cookies = r.cookiesToOwnedList(alloc, false) catch unreachable; const cookies = r.cookiesToOwnedList(alloc) catch unreachable;
defer cookies.deinit(); defer cookies.deinit();
for (cookies.items) |kv| { for (cookies.items) |kv| {
std.log.info("cookie `{s}` is {any}", .{ kv.key.str, kv.value }); std.log.info("cookie `{s}` is {any}", .{ kv.key, kv.value });
} }
// let's get cookie "ZIG_ZAP" by name // let's get cookie "ZIG_ZAP" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| { if (r.getCookieStr(alloc, "ZIG_ZAP")) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |s| {
defer s.deinit(); // unnecessary because always_alloc=false defer alloc.free(s);
std.log.info("Cookie ZIG_ZAP = {s}", .{s});
std.log.info("Cookie ZIG_ZAP = {s}", .{s.str});
} else { } else {
std.log.info("Cookie ZIG_ZAP not found!", .{}); std.log.info("Cookie ZIG_ZAP not found!", .{});
} }
} } else |err| {
// since we provided "false" for duplicating strings in the call
// to getCookieStr(), there won't be an allocation error
else |err| {
std.log.err("ERROR!\n", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err}); std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
} }

View file

@ -4,12 +4,12 @@ const UserWeb = @import("userweb.zig");
const StopEndpoint = @import("stopendpoint.zig"); const StopEndpoint = @import("stopendpoint.zig");
// this is just to demo that we can catch arbitrary slugs as fallback // this is just to demo that we can catch arbitrary slugs as fallback
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("REQUESTED PATH: {s}\n", .{the_path}); std.debug.print("REQUESTED PATH: {s}\n", .{the_path});
} }
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
pub fn main() !void { pub fn main() !void {
@ -41,8 +41,8 @@ pub fn main() !void {
var stopEp = StopEndpoint.init("/stop"); var stopEp = StopEndpoint.init("/stop");
// register endpoints with the listener // register endpoints with the listener
try listener.register(userWeb.endpoint()); try listener.register(&userWeb);
try listener.register(stopEp.endpoint()); try listener.register(&stopEp);
// fake some users // fake some users
var uid: usize = undefined; var uid: usize = undefined;

View file

@ -3,27 +3,25 @@ const zap = @import("zap");
/// A simple endpoint listening on the /stop route that shuts down zap /// A simple endpoint listening on the /stop route that shuts down zap
/// the main thread usually continues at the instructions after the call to zap.start(). /// the main thread usually continues at the instructions after the call to zap.start().
pub const Self = @This(); pub const StopEndpoint = @This();
ep: zap.Endpoint = undefined, path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn init( pub fn init(path: []const u8) StopEndpoint {
path: []const u8,
) Self {
return .{ return .{
.ep = zap.Endpoint.init(.{ .path = path,
.path = path,
.get = get,
}),
}; };
} }
pub fn endpoint(self: *Self) *zap.Endpoint { pub fn get(e: *StopEndpoint, r: zap.Request) anyerror!void {
return &self.ep;
}
fn get(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
_ = r; _ = r;
zap.stop(); zap.stop();
} }
pub fn post(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn put(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn delete(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn patch(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn options(_: *StopEndpoint, _: zap.Request) anyerror!void {}

View file

@ -5,7 +5,7 @@ users: std.AutoHashMap(usize, InternalUser) = undefined,
lock: std.Thread.Mutex = undefined, lock: std.Thread.Mutex = undefined,
count: usize = 0, count: usize = 0,
pub const Self = @This(); pub const Users = @This();
const InternalUser = struct { const InternalUser = struct {
id: usize = 0, id: usize = 0,
@ -21,7 +21,7 @@ pub const User = struct {
last_name: []const u8, last_name: []const u8,
}; };
pub fn init(a: std.mem.Allocator) Self { pub fn init(a: std.mem.Allocator) Users {
return .{ return .{
.alloc = a, .alloc = a,
.users = std.AutoHashMap(usize, InternalUser).init(a), .users = std.AutoHashMap(usize, InternalUser).init(a),
@ -29,13 +29,13 @@ pub fn init(a: std.mem.Allocator) Self {
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Users) void {
self.users.deinit(); self.users.deinit();
} }
// the request will be freed (and its mem reused by facilio) when it's // the request will be freed (and its mem reused by facilio) when it's
// completed, so we take copies of the names // completed, so we take copies of the names
pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize { pub fn addByName(self: *Users, first: ?[]const u8, last: ?[]const u8) !usize {
var user: InternalUser = undefined; var user: InternalUser = undefined;
user.firstnamelen = 0; user.firstnamelen = 0;
user.lastnamelen = 0; user.lastnamelen = 0;
@ -64,7 +64,7 @@ pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize {
} }
} }
pub fn delete(self: *Self, id: usize) bool { pub fn delete(self: *Users, id: usize) bool {
// We lock only on insertion, deletion, and listing // We lock only on insertion, deletion, and listing
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
@ -76,7 +76,7 @@ pub fn delete(self: *Self, id: usize) bool {
return ret; return ret;
} }
pub fn get(self: *Self, id: usize) ?User { pub fn get(self: *Users, id: usize) ?User {
// we don't care about locking here, as our usage-pattern is unlikely to // we don't care about locking here, as our usage-pattern is unlikely to
// get a user by id that is not known yet // get a user by id that is not known yet
if (self.users.getPtr(id)) |pUser| { if (self.users.getPtr(id)) |pUser| {
@ -90,7 +90,7 @@ pub fn get(self: *Self, id: usize) ?User {
} }
pub fn update( pub fn update(
self: *Self, self: *Users,
id: usize, id: usize,
first: ?[]const u8, first: ?[]const u8,
last: ?[]const u8, last: ?[]const u8,
@ -112,7 +112,7 @@ pub fn update(
return false; return false;
} }
pub fn toJSON(self: *Self) ![]const u8 { pub fn toJSON(self: *Users) ![]const u8 {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
@ -137,7 +137,7 @@ pub fn toJSON(self: *Self) ![]const u8 {
// //
// Note: the following code is kept in here because it taught us a lesson // Note: the following code is kept in here because it taught us a lesson
// //
pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void { pub fn listWithRaceCondition(self: *Users, out: *std.ArrayList(User)) !void {
// We lock only on insertion, deletion, and listing // We lock only on insertion, deletion, and listing
// //
// NOTE: race condition: // NOTE: race condition:
@ -169,18 +169,14 @@ pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void {
const JsonUserIteratorWithRaceCondition = struct { const JsonUserIteratorWithRaceCondition = struct {
it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined, it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined,
const This = @This();
// careful: pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) JsonUserIteratorWithRaceCondition {
// - Self refers to the file's struct
// - This refers to the JsonUserIterator struct
pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) This {
return .{ return .{
.it = internal_users.valueIterator(), .it = internal_users.valueIterator(),
}; };
} }
pub fn next(this: *This) ?User { pub fn next(this: *JsonUserIteratorWithRaceCondition) ?User {
if (this.it.next()) |pUser| { if (this.it.next()) |pUser| {
// we get a pointer to the internal user. so it should be safe to // we get a pointer to the internal user. so it should be safe to
// create slices from its first and last name buffers // create slices from its first and last name buffers

View file

@ -5,93 +5,79 @@ const User = Users.User;
// an Endpoint // an Endpoint
pub const Self = @This(); pub const UserWeb = @This();
alloc: std.mem.Allocator = undefined, alloc: std.mem.Allocator = undefined,
ep: zap.Endpoint = undefined,
_users: Users = undefined, _users: Users = undefined,
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn init( pub fn init(
a: std.mem.Allocator, a: std.mem.Allocator,
user_path: []const u8, user_path: []const u8,
) Self { ) UserWeb {
return .{ return .{
.alloc = a, .alloc = a,
._users = Users.init(a), ._users = Users.init(a),
.ep = zap.Endpoint.init(.{ .path = user_path,
.path = user_path,
.get = getUser,
.post = postUser,
.put = putUser,
.patch = putUser,
.delete = deleteUser,
.options = optionsUser,
}),
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *UserWeb) void {
self._users.deinit(); self._users.deinit();
} }
pub fn users(self: *Self) *Users { pub fn users(self: *UserWeb) *Users {
return &self._users; return &self._users;
} }
pub fn endpoint(self: *Self) *zap.Endpoint { fn userIdFromPath(self: *UserWeb, path: []const u8) ?usize {
return &self.ep; if (path.len >= self.path.len + 2) {
} if (path[self.path.len] != '/') {
fn userIdFromPath(self: *Self, path: []const u8) ?usize {
if (path.len >= self.ep.settings.path.len + 2) {
if (path[self.ep.settings.path.len] != '/') {
return null; return null;
} }
const idstr = path[self.ep.settings.path.len + 1 ..]; const idstr = path[self.path.len + 1 ..];
return std.fmt.parseUnsigned(usize, idstr, 10) catch null; return std.fmt.parseUnsigned(usize, idstr, 10) catch null;
} }
return null; return null;
} }
fn getUser(e: *zap.Endpoint, r: zap.Request) void { pub fn put(_: *UserWeb, _: zap.Request) anyerror!void {}
const self: *Self = @fieldParentPtr("ep", e); pub fn get(self: *UserWeb, r: zap.Request) anyerror!void {
if (r.path) |path| { if (r.path) |path| {
// /users // /users
if (path.len == e.settings.path.len) { if (path.len == self.path.len) {
return self.listUsers(r); return self.listUsers(r);
} }
var jsonbuf: [256]u8 = undefined; var jsonbuf: [256]u8 = undefined;
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
if (self._users.get(id)) |user| { if (self._users.get(id)) |user| {
if (zap.stringifyBuf(&jsonbuf, user, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, user, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} }
} }
} }
} }
fn listUsers(self: *Self, r: zap.Request) void { fn listUsers(self: *UserWeb, r: zap.Request) !void {
if (self._users.toJSON()) |json| { if (self._users.toJSON()) |json| {
defer self.alloc.free(json); defer self.alloc.free(json);
r.sendJson(json) catch return; try r.sendJson(json);
} else |err| { } else |err| {
std.debug.print("LIST error: {}\n", .{err}); return err;
} }
} }
fn postUser(e: *zap.Endpoint, r: zap.Request) void { pub fn post(self: *UserWeb, r: zap.Request) anyerror!void {
const self: *Self = @fieldParentPtr("ep", e);
if (r.body) |body| { if (r.body) |body| {
const 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| { if (maybe_user) |u| {
defer u.deinit(); defer u.deinit();
if (self._users.addByName(u.value.first_name, u.value.last_name)) |id| { if (self._users.addByName(u.value.first_name, u.value.last_name)) |id| {
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} else |err| { } else |err| {
std.debug.print("ADDING error: {}\n", .{err}); std.debug.print("ADDING error: {}\n", .{err});
return; return;
@ -100,8 +86,7 @@ fn postUser(e: *zap.Endpoint, r: zap.Request) void {
} }
} }
fn putUser(e: *zap.Endpoint, r: zap.Request) void { pub fn patch(self: *UserWeb, r: zap.Request) anyerror!void {
const self: *Self = @fieldParentPtr("ep", e);
if (r.path) |path| { if (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
if (self._users.get(id)) |_| { if (self._users.get(id)) |_| {
@ -111,13 +96,11 @@ fn putUser(e: *zap.Endpoint, r: zap.Request) void {
defer u.deinit(); defer u.deinit();
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (self._users.update(id, u.value.first_name, u.value.last_name)) { if (self._users.update(id, u.value.first_name, u.value.last_name)) {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} else { } else {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id }, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} }
} }
} }
@ -126,28 +109,24 @@ fn putUser(e: *zap.Endpoint, r: zap.Request) void {
} }
} }
fn deleteUser(e: *zap.Endpoint, r: zap.Request) void { pub fn delete(self: *UserWeb, r: zap.Request) anyerror!void {
const self: *Self = @fieldParentPtr("ep", e);
if (r.path) |path| { if (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (self._users.delete(id)) { if (self._users.delete(id)) {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} else { } else {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id }, .{})) |json| { const json = try zap.util.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id }, .{});
r.sendJson(json) catch return; try r.sendJson(json);
}
} }
} }
} }
} }
fn optionsUser(e: *zap.Endpoint, r: zap.Request) void { pub fn options(_: *UserWeb, r: zap.Request) anyerror!void {
_ = e; try r.setHeader("Access-Control-Allow-Origin", "*");
r.setHeader("Access-Control-Allow-Origin", "*") catch return; try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") catch return; r.setStatus(zap.http.StatusCode.no_content);
r.setStatus(zap.StatusCode.no_content);
r.markAsFinished(true); r.markAsFinished(true);
} }

View file

@ -10,18 +10,29 @@ const HTTP_RESPONSE: []const u8 =
\\ </body></html> \\ </body></html>
; ;
// authenticated requests go here const Endpoint = struct {
fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void { // the slug
_ = e; path: []const u8,
r.sendBody(HTTP_RESPONSE) catch return; error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
}
// just for fun, we also catch the unauthorized callback // authenticated requests go here
fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void { pub fn get(_: *Endpoint, r: zap.Request) !void {
_ = e; r.sendBody(HTTP_RESPONSE) catch return;
r.setStatus(.unauthorized); }
r.sendBody("UNAUTHORIZED ACCESS") catch return;
} // just for fun, we also catch the unauthorized callback
pub fn unauthorized(_: *Endpoint, r: zap.Request) !void {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return;
}
// not implemented, don't care
pub fn post(_: *Endpoint, _: zap.Request) !void {}
pub fn put(_: *Endpoint, _: zap.Request) !void {}
pub fn delete(_: *Endpoint, _: zap.Request) !void {}
pub fn patch(_: *Endpoint, _: zap.Request) !void {}
pub fn options(_: *Endpoint, _: zap.Request) !void {}
};
pub fn main() !void { pub fn main() !void {
// setup listener // setup listener
@ -38,11 +49,9 @@ pub fn main() !void {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = zap.Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create authenticator // create authenticator
const Authenticator = zap.Auth.BearerSingle; const Authenticator = zap.Auth.BearerSingle;
@ -50,10 +59,10 @@ pub fn main() !void {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = zap.Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
std.debug.print( std.debug.print(

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request_verbose(r: zap.Request) void { fn on_request_verbose(r: zap.Request) !void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("PATH: {s}\n", .{the_path}); std.debug.print("PATH: {s}\n", .{the_path});
} }
@ -9,11 +9,11 @@ fn on_request_verbose(r: zap.Request) void {
if (r.query) |the_query| { if (r.query) |the_query| {
std.debug.print("QUERY: {s}\n", .{the_query}); std.debug.print("QUERY: {s}\n", .{the_query});
} }
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
fn on_request_minimal(r: zap.Request) void { fn on_request_minimal(r: zap.Request) !void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
pub fn main() !void { pub fn main() !void {

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
const m = r.methodAsEnum(); const m = r.methodAsEnum();
const m_str = r.method orelse ""; const m_str = r.method orelse "";
const p = r.path orelse "/"; const p = r.path orelse "/";
@ -20,8 +20,8 @@ fn on_request(r: zap.Request) void {
std.debug.print(">> BODY: {s}\n", .{the_body}); std.debug.print(">> BODY: {s}\n", .{the_body});
} }
r.setContentTypeFromPath() catch return; try r.setContentTypeFromPath();
r.sendBody( try r.sendBody(
\\ <html><body> \\ <html><body>
\\ <h1>Hello from ZAP!!!</h1> \\ <h1>Hello from ZAP!!!</h1>
\\ <form action="/" method="post"> \\ <form action="/" method="post">
@ -32,7 +32,7 @@ fn on_request(r: zap.Request) void {
\\ <button>Send</button> \\ <button>Send</button>
\\ </form> \\ </form>
\\ </body></html> \\ </body></html>
) catch return; );
} }
pub fn main() !void { pub fn main() !void {
@ -43,7 +43,18 @@ pub fn main() !void {
}); });
try listener.listen(); try listener.listen();
std.debug.print("Listening on 0.0.0.0:3000\n", .{}); std.debug.print(
\\ Listening on 0.0.0.0:3000
\\
\\ Test me with:
\\ curl http://localhost:3000
\\ curl --header "special-header: test" localhost:3000
\\
\\ ... or open http://localhost:3000 in the browser
\\ and watch the log output here
\\
\\
, .{});
// start worker threads // start worker threads
zap.start(.{ zap.start(.{

View file

@ -6,7 +6,7 @@ const User = struct {
last_name: ?[]const u8 = null, last_name: ?[]const u8 = null,
}; };
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
if (r.methodAsEnum() != .GET) return; if (r.methodAsEnum() != .GET) return;
// /user/n // /user/n
@ -14,16 +14,16 @@ fn on_request(r: zap.Request) void {
if (the_path.len < 7 or !std.mem.startsWith(u8, the_path, "/user/")) if (the_path.len < 7 or !std.mem.startsWith(u8, the_path, "/user/"))
return; return;
const user_id: usize = @as(usize, @intCast(the_path[6] - 0x30)); const user_id: usize = @intCast(the_path[6] - 0x30);
std.debug.print("user_id: {d}\n", .{user_id});
std.debug.print("users: {d}\n", .{users.count()});
const user = users.get(user_id); const user = users.get(user_id);
std.debug.print("user: {?}\n", .{user});
var buf: [100]u8 = undefined; var buf: [256]u8 = undefined;
var json_to_send: []const u8 = undefined; var json_to_send: []const u8 = undefined;
if (zap.stringifyBuf(&buf, user, .{})) |json| { json_to_send = try zap.util.stringifyBuf(&buf, user, .{});
json_to_send = json;
} else {
json_to_send = "null";
}
std.debug.print("<< json: {s}\n", .{json_to_send}); std.debug.print("<< json: {s}\n", .{json_to_send});
r.setContentType(.JSON) catch return; r.setContentType(.JSON) catch return;
r.setContentTypeFromFilename("test.json") catch return; r.setContentTypeFromFilename("test.json") catch return;
@ -43,6 +43,7 @@ fn setupUserData(a: std.mem.Allocator) !void {
pub fn main() !void { pub fn main() !void {
const a = std.heap.page_allocator; const a = std.heap.page_allocator;
try setupUserData(a); try setupUserData(a);
defer users.deinit();
var listener = zap.HttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,

View file

@ -27,12 +27,14 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
}){}; }){};
defer _ = gpa.detectLeaks();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
std.debug.print("\n=====================================================\n", .{}); std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{}); defer std.debug.print("=====================================================\n\n", .{});
@ -69,42 +71,38 @@ pub fn main() !void {
// ================================================================ // ================================================================
// iterate over all params as strings // iterate over all params as strings
var strparams = r.parametersToOwnedStrList(alloc, false) catch unreachable; var strparams = try r.parametersToOwnedStrList(alloc);
defer strparams.deinit(); defer strparams.deinit();
std.debug.print("\n", .{}); std.debug.print("\n", .{});
for (strparams.items) |kv| { for (strparams.items) |kv| {
std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str }); std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key, kv.value });
} }
std.debug.print("\n", .{}); std.debug.print("\n", .{});
// iterate over all params // iterate over all params
const params = r.parametersToOwnedList(alloc, false) catch unreachable; const params = try r.parametersToOwnedList(alloc);
defer params.deinit(); defer params.deinit();
for (params.items) |kv| { for (params.items) |kv| {
std.log.info("Param `{s}` is {any}", .{ kv.key.str, kv.value }); std.log.info("Param `{s}` is {any}", .{ kv.key, kv.value });
} }
// let's get param "one" by name // let's get param "one" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getParamStr(alloc, "one", false)) |maybe_str| { if (r.getParamStr(alloc, "one")) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |s| {
defer s.deinit(); defer alloc.free(s);
std.log.info("Param one = {s}", .{s});
std.log.info("Param one = {s}", .{s.str});
} else { } else {
std.log.info("Param one not found!", .{}); std.log.info("Param one not found!", .{});
} }
} } else |err| {
// since we provided "false" for duplicating strings in the call
// to getParamStr(), there won't be an allocation error
else |err| {
std.log.err("cannot check for `one` param: {any}\n", .{err}); std.log.err("cannot check for `one` param: {any}\n", .{err});
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamSlice("terminate")) |maybe_str| { if (r.getParamSlice("terminate")) |s| {
if (std.mem.eql(u8, maybe_str, "true")) { if (std.mem.eql(u8, s, "true")) {
zap.stop(); zap.stop();
} }
} }

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request_verbose(r: zap.Request) void { fn on_request_verbose(r: zap.Request) !void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("PATH: {s}\n", .{the_path}); std.debug.print("PATH: {s}\n", .{the_path});
} }
@ -9,11 +9,11 @@ fn on_request_verbose(r: zap.Request) void {
if (r.query) |the_query| { if (r.query) |the_query| {
std.debug.print("QUERY: {s}\n", .{the_query}); std.debug.print("QUERY: {s}\n", .{the_query});
} }
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
fn on_request_minimal(r: zap.Request) void { fn on_request_minimal(r: zap.Request) !void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
fn help_and_exit(filename: []const u8, err: anyerror) void { fn help_and_exit(filename: []const u8, err: anyerror) void {

View file

@ -6,8 +6,6 @@ const SharedAllocator = struct {
// static // static
var allocator: std.mem.Allocator = undefined; var allocator: std.mem.Allocator = undefined;
const Self = @This();
// just a convenience function // just a convenience function
pub fn init(a: std.mem.Allocator) void { pub fn init(a: std.mem.Allocator) void {
allocator = a; allocator = a;
@ -43,8 +41,6 @@ const Handler = zap.Middleware.Handler(Context);
const UserMiddleWare = struct { const UserMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // Just some arbitrary struct we want in the per-request context
// note: it MUST have all default values!!! // note: it MUST have all default values!!!
// note: it MUST have all default values!!! // note: it MUST have all default values!!!
@ -57,22 +53,22 @@ const UserMiddleWare = struct {
email: []const u8 = undefined, email: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) UserMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // we need the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *Handler { pub fn getHandler(self: *UserMiddleWare) *Handler {
return &self.handler; return &self.handler;
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *UserMiddleWare !!!
pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *UserMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
// do our work: fill in the user field of the context // do our work: fill in the user field of the context
@ -92,8 +88,6 @@ const UserMiddleWare = struct {
const SessionMiddleWare = struct { const SessionMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // Just some arbitrary struct we want in the per-request context
// note: it MUST have all default values!!! // note: it MUST have all default values!!!
const Session = struct { const Session = struct {
@ -101,21 +95,21 @@ const SessionMiddleWare = struct {
token: []const u8 = undefined, token: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) SessionMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // we need the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *Handler { pub fn getHandler(self: *SessionMiddleWare) *Handler {
return &self.handler; return &self.handler;
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *SessionMiddleWare !!!
pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *SessionMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
context.session = Session{ context.session = Session{
@ -134,24 +128,22 @@ const SessionMiddleWare = struct {
const HtmlMiddleWare = struct { const HtmlMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This(); pub fn init(other: ?*Handler) HtmlMiddleWare {
pub fn init(other: ?*Handler) Self {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // we need the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *Handler { pub fn getHandler(self: *HtmlMiddleWare) *Handler {
return &self.handler; return &self.handler;
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *HtmlMiddleWare !!!
pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *HtmlMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context}); std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context});

View file

@ -6,8 +6,6 @@ const SharedAllocator = struct {
// static // static
var allocator: std.mem.Allocator = undefined; var allocator: std.mem.Allocator = undefined;
const Self = @This();
// just a convenience function // just a convenience function
pub fn init(a: std.mem.Allocator) void { pub fn init(a: std.mem.Allocator) void {
allocator = a; allocator = a;
@ -35,8 +33,6 @@ const Handler = zap.Middleware.Handler(Context);
const UserMiddleWare = struct { const UserMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // Just some arbitrary struct we want in the per-request context
// This is so that it can be constructed via .{} // This is so that it can be constructed via .{}
// as we can't expect the listener to know how to initialize our context structs // as we can't expect the listener to know how to initialize our context structs
@ -45,22 +41,22 @@ const UserMiddleWare = struct {
email: []const u8 = undefined, email: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) UserMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // we need the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *Handler { pub fn getHandler(self: *UserMiddleWare) *Handler {
return &self.handler; return &self.handler;
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *UserMiddleWare !!!
pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *UserMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
// do our work: fill in the user field of the context // do our work: fill in the user field of the context
@ -82,8 +78,6 @@ const UserMiddleWare = struct {
const SessionMiddleWare = struct { const SessionMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // Just some arbitrary struct we want in the per-request context
// note: it MUST have all default values!!! // note: it MUST have all default values!!!
const Session = struct { const Session = struct {
@ -91,21 +85,21 @@ const SessionMiddleWare = struct {
token: []const u8 = undefined, token: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) SessionMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // we need the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *Handler { pub fn getHandler(self: *SessionMiddleWare) *Handler {
return &self.handler; return &self.handler;
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *SessionMiddleWare !!!
pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *SessionMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
context.session = Session{ context.session = Session{
@ -136,26 +130,21 @@ const SessionMiddleWare = struct {
// `breakOnFinish` parameter. // `breakOnFinish` parameter.
// //
const HtmlEndpoint = struct { const HtmlEndpoint = struct {
ep: zap.Endpoint = undefined, path: []const u8 = "(undefined)",
const Self = @This();
pub fn init() Self { pub fn init() HtmlEndpoint {
return .{ return .{
.ep = zap.Endpoint.init(.{ .path = "/doesn't+matter",
.path = "/doesn't+matter",
.get = get,
}),
}; };
} }
pub fn endpoint(self: *Self) *zap.Endpoint { pub fn post(_: *HtmlEndpoint, _: zap.Request) !void {}
return &self.ep; pub fn put(_: *HtmlEndpoint, _: zap.Request) !void {}
} pub fn delete(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn get(ep: *zap.Endpoint, r: zap.Request) void { pub fn options(_: *HtmlEndpoint, _: zap.Request) !void {}
const self: *Self = @fieldParentPtr("ep", ep);
_ = self;
pub fn get(_: *HtmlEndpoint, r: zap.Request) !void {
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
var userFound: bool = false; var userFound: bool = false;
var sessionFound: bool = false; var sessionFound: bool = false;
@ -172,12 +161,12 @@ const HtmlEndpoint = struct {
sessionFound = true; sessionFound = true;
std.debug.assert(r.isFinished() == false); std.debug.assert(r.isFinished() == false);
const message = std.fmt.bufPrint(&buf, "User: {s} / {s}, Session: {s} / {s}", .{ const message = try std.fmt.bufPrint(&buf, "User: {s} / {s}, Session: {s} / {s}", .{
user.name, user.name,
user.email, user.email,
session.info, session.info,
session.token, session.token,
}) catch unreachable; });
r.setContentType(.TEXT) catch unreachable; r.setContentType(.TEXT) catch unreachable;
r.sendBody(message) catch unreachable; r.sendBody(message) catch unreachable;
std.debug.assert(r.isFinished() == true); std.debug.assert(r.isFinished() == true);
@ -205,8 +194,8 @@ pub fn main() !void {
var htmlEndpoint = HtmlEndpoint.init(); var htmlEndpoint = HtmlEndpoint.init();
// we wrap the endpoint with a middleware handler // we wrap the endpoint with a middleware handler
var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init( var htmlHandler = zap.Middleware.EndpointHandler(Handler, HtmlEndpoint, Context).init(
htmlEndpoint.endpoint(), // the endpoint &htmlEndpoint, // the endpoint
null, // no other handler (we are the last in the chain) null, // no other handler (we are the last in the chain)
.{}, // We can set custom EndpointHandlerOptions here .{}, // We can set custom EndpointHandlerOptions here
); );

View file

@ -2,7 +2,7 @@ const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Mustache = @import("zap").Mustache; const Mustache = @import("zap").Mustache;
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
const template = const template =
\\ {{=<< >>=}} \\ {{=<< >>=}}
\\ * Users: \\ * Users:

View file

@ -3,39 +3,39 @@ const zap = @import("zap");
// NOTE: this is a super simplified example, just using a hashmap to map // NOTE: this is a super simplified example, just using a hashmap to map
// from HTTP path to request function. // from HTTP path to request function.
fn dispatch_routes(r: zap.Request) void { fn dispatch_routes(r: zap.Request) !void {
// dispatch // dispatch
if (r.path) |the_path| { if (r.path) |the_path| {
if (routes.get(the_path)) |foo| { if (routes.get(the_path)) |foo| {
foo(r); try foo(r);
return; return;
} }
} }
// or default: present menu // or default: present menu
r.sendBody( try r.sendBody(
\\ <html> \\ <html>
\\ <body> \\ <body>
\\ <p><a href="/static">static</a></p> \\ <p><a href="/static">static</a></p>
\\ <p><a href="/dynamic">dynamic</a></p> \\ <p><a href="/dynamic">dynamic</a></p>
\\ </body> \\ </body>
\\ </html> \\ </html>
) catch return; );
} }
fn static_site(r: zap.Request) void { fn static_site(r: zap.Request) !void {
r.sendBody("<html><body><h1>Hello from STATIC ZAP!</h1></body></html>") catch return; try r.sendBody("<html><body><h1>Hello from STATIC ZAP!</h1></body></html>");
} }
var dynamic_counter: i32 = 0; var dynamic_counter: i32 = 0;
fn dynamic_site(r: zap.Request) void { fn dynamic_site(r: zap.Request) !void {
dynamic_counter += 1; dynamic_counter += 1;
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
const filled_buf = std.fmt.bufPrintZ( const filled_buf = try std.fmt.bufPrintZ(
&buf, &buf,
"<html><body><h1>Hello # {d} from DYNAMIC ZAP!!!</h1></body></html>", "<html><body><h1>Hello # {d} from DYNAMIC ZAP!!!</h1></body></html>",
.{dynamic_counter}, .{dynamic_counter},
) catch "ERROR"; );
r.sendBody(filled_buf) catch return; try r.sendBody(filled_buf);
} }
fn setup_routes(a: std.mem.Allocator) !void { fn setup_routes(a: std.mem.Allocator) !void {

View file

@ -5,7 +5,7 @@ fn MAKE_MEGA_ERROR() !void {
return error.MEGA_ERROR; return error.MEGA_ERROR;
} }
fn MY_REQUEST_HANDLER(r: zap.Request) void { fn MY_REQUEST_HANDLER(r: zap.Request) !void {
MAKE_MEGA_ERROR() catch |err| { MAKE_MEGA_ERROR() catch |err| {
r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505);
}; };

View file

@ -6,7 +6,7 @@ var read_len: ?usize = null;
const testfile = @embedFile("testfile.txt"); const testfile = @embedFile("testfile.txt");
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
// Sends a file if present in the filesystem orelse returns an error. // Sends a file if present in the filesystem orelse returns an error.
// //
// - efficiently sends a file using gzip compression // - efficiently sends a file using gzip compression

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
r.setStatus(.not_found); r.setStatus(.not_found);
r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return; r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return;
} }

View file

@ -2,7 +2,7 @@ const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
fn on_request_verbose(r: zap.Request) void { fn on_request_verbose(r: zap.Request) !void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("PATH: {s}\n", .{the_path}); std.debug.print("PATH: {s}\n", .{the_path});
} }
@ -14,13 +14,11 @@ fn on_request_verbose(r: zap.Request) void {
} }
pub const SomePackage = struct { pub const SomePackage = struct {
const Self = @This();
allocator: Allocator, allocator: Allocator,
a: i8, a: i8,
b: i8, b: i8,
pub fn init(allocator: Allocator, a: i8, b: i8) Self { pub fn init(allocator: Allocator, a: i8, b: i8) SomePackage {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.a = a, .a = a,
@ -28,7 +26,7 @@ pub const SomePackage = struct {
}; };
} }
pub fn getA(self: *Self, req: zap.Request) void { pub fn getA(self: *SomePackage, req: zap.Request) !void {
std.log.warn("get_a_requested", .{}); std.log.warn("get_a_requested", .{});
const string = std.fmt.allocPrint( const string = std.fmt.allocPrint(
@ -41,7 +39,7 @@ pub const SomePackage = struct {
req.sendBody(string) catch return; req.sendBody(string) catch return;
} }
pub fn getB(self: *Self, req: zap.Request) void { pub fn getB(self: *SomePackage, req: zap.Request) !void {
std.log.warn("get_b_requested", .{}); std.log.warn("get_b_requested", .{});
const string = std.fmt.allocPrint( const string = std.fmt.allocPrint(
@ -54,7 +52,7 @@ pub const SomePackage = struct {
req.sendBody(string) catch return; req.sendBody(string) catch return;
} }
pub fn incrementA(self: *Self, req: zap.Request) void { pub fn incrementA(self: *SomePackage, req: zap.Request) !void {
std.log.warn("increment_a_requested", .{}); std.log.warn("increment_a_requested", .{});
self.a += 1; self.a += 1;
@ -63,10 +61,10 @@ pub const SomePackage = struct {
} }
}; };
fn not_found(req: zap.Request) void { fn not_found(req: zap.Request) !void {
std.debug.print("not found handler", .{}); std.debug.print("not found handler", .{});
req.sendBody("Not found") catch return; try req.sendBody("Not found");
} }
pub fn main() !void { pub fn main() !void {
@ -98,8 +96,17 @@ pub fn main() !void {
}); });
try listener.listen(); try listener.listen();
std.debug.print("Listening on 0.0.0.0:3000\n", .{}); std.debug.print(
\\ Listening on 0.0.0.0:3000
\\
\\ Test me with:
\\ curl http://localhost:3000/
\\ curl http://localhost:3000/geta
\\ curl http://localhost:3000/getb
\\ curl http://localhost:3000/inca
\\
\\
, .{});
// start worker threads // start worker threads
zap.start(.{ zap.start(.{
.threads = 2, .threads = 2,

View file

@ -25,38 +25,38 @@ const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png");
var authenticator: Authenticator = undefined; var authenticator: Authenticator = undefined;
// the login page (embedded) // the login page (embedded)
fn on_login(r: zap.Request) void { fn on_login(r: zap.Request) !void {
r.sendBody(loginpage) catch return; try r.sendBody(loginpage);
} }
// the "normal page" // the "normal page"
fn on_normal_page(r: zap.Request) void { fn on_normal_page(r: zap.Request) !void {
zap.debug("on_normal_page()\n", .{}); zap.debug("on_normal_page()\n", .{});
r.sendBody( try r.sendBody(
\\ <html><body> \\ <html><body>
\\ <h1>Hello from ZAP!!!</h1> \\ <h1>Hello from ZAP!!!</h1>
\\ <p>You are logged in!!!</> \\ <p>You are logged in!!!</>
\\ <center><a href="/logout">logout</a></center> \\ <center><a href="/logout">logout</a></center>
\\ </body></html> \\ </body></html>
) catch return; );
} }
// the logged-out page // the logged-out page
fn on_logout(r: zap.Request) void { fn on_logout(r: zap.Request) !void {
zap.debug("on_logout()\n", .{}); zap.debug("on_logout()\n", .{});
authenticator.logout(&r); authenticator.logout(&r);
// note, the link below doesn't matter as the authenticator will send us // note, the link below doesn't matter as the authenticator will send us
// straight to the /login page // straight to the /login page
r.sendBody( try r.sendBody(
\\ <html><body> \\ <html><body>
\\ <p>You are logged out!!!</p> \\ <p>You are logged out!!!</p>
\\ <br> \\ <br>
\\ <p> <a href="/">Log back in</a></p> \\ <p> <a href="/">Log back in</a></p>
\\ </body></html> \\ </body></html>
) catch return; );
} }
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
switch (authenticator.authenticateRequest(&r)) { switch (authenticator.authenticateRequest(&r)) {
.Handled => { .Handled => {
// the authenticator handled the entire request for us. // the authenticator handled the entire request for us.
@ -72,6 +72,7 @@ fn on_request(r: zap.Request) void {
.AuthOK => { .AuthOK => {
// the authenticator says it is ok to proceed as usual // the authenticator says it is ok to proceed as usual
std.log.info("Auth OK", .{}); std.log.info("Auth OK", .{});
// dispatch to target path // dispatch to target path
if (r.path) |p| { if (r.path) |p| {
// used in the login page // used in the login page
@ -80,8 +81,8 @@ fn on_request(r: zap.Request) void {
// the authenticator. Hence, we name the img for the /login // the authenticator. Hence, we name the img for the /login
// page: /login/Ziggy....png // page: /login/Ziggy....png
if (std.mem.startsWith(u8, p, "/login/Ziggy_the_Ziguana.svg.png")) { if (std.mem.startsWith(u8, p, "/login/Ziggy_the_Ziguana.svg.png")) {
r.setContentTypeFromPath() catch unreachable; try r.setContentTypeFromPath();
r.sendBody(img) catch unreachable; try r.sendBody(img);
return; return;
} }
@ -124,6 +125,7 @@ pub fn main() !void {
// to detect leaks // to detect leaks
{ {
const allocator = gpa.allocator(); const allocator = gpa.allocator();
var listener = zap.HttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
@ -157,6 +159,8 @@ pub fn main() !void {
defer authenticator.deinit(); defer authenticator.deinit();
std.debug.print("Visit me on http://127.0.0.1:3000\n", .{}); std.debug.print("Visit me on http://127.0.0.1:3000\n", .{});
std.debug.print(" Username: zap", .{});
std.debug.print(" Password: awesome", .{});
// start worker threads // start worker threads
zap.start(.{ zap.start(.{

View file

@ -20,13 +20,11 @@ const ContextManager = struct {
lock: std.Thread.Mutex = .{}, lock: std.Thread.Mutex = .{},
contexts: ContextList = undefined, contexts: ContextList = undefined,
const Self = @This();
pub fn init( pub fn init(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
channelName: []const u8, channelName: []const u8,
usernamePrefix: []const u8, usernamePrefix: []const u8,
) Self { ) ContextManager {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.channel = channelName, .channel = channelName,
@ -35,14 +33,14 @@ const ContextManager = struct {
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *ContextManager) void {
for (self.contexts.items) |ctx| { for (self.contexts.items) |ctx| {
self.allocator.free(ctx.userName); self.allocator.free(ctx.userName);
} }
self.contexts.deinit(); self.contexts.deinit();
} }
pub fn newContext(self: *Self) !*Context { pub fn newContext(self: *ContextManager) !*Context {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
@ -163,13 +161,13 @@ fn handle_websocket_message(
// //
// HTTP stuff // HTTP stuff
// //
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
r.setHeader("Server", "zap.example") catch unreachable; r.setHeader("Server", "zap.example") catch unreachable;
r.sendBody( try r.sendBody(
\\ <html><body> \\ <html><body>
\\ <h1>This is a simple Websocket chatroom example</h1> \\ <h1>This is a simple Websocket chatroom example</h1>
\\ </body></html> \\ </body></html>
) catch return; );
} }
fn on_upgrade(r: zap.Request, target_protocol: []const u8) void { fn on_upgrade(r: zap.Request, target_protocol: []const u8) void {

12
flake.lock generated
View file

@ -70,11 +70,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1741037377, "lastModified": 1743259260,
"narHash": "sha256-SvtvVKHaUX4Owb+PasySwZsoc5VUeTf1px34BByiOxw=", "narHash": "sha256-ArWLUgRm1tKHiqlhnymyVqi5kLNCK5ghvm06mfCl4QY=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "02032da4af073d0f6110540c8677f16d4be0117f", "rev": "eb0e0f21f15c559d2ac7633dc81d079d1caf5f5f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -145,11 +145,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1741263138, "lastModified": 1743250246,
"narHash": "sha256-qlX8tgtZMTSOEeAM8AmC7K6mixgYOguhl/xLj5xQrXc=", "narHash": "sha256-gVFyxsxfqnEXSldeeURim7RRZGwPX4f/egLcSC7CXec=",
"owner": "mitchellh", "owner": "mitchellh",
"repo": "zig-overlay", "repo": "zig-overlay",
"rev": "627055069ee1409e8c9be7bcc533e8823fb87b18", "rev": "b0da956a6db25564d0ee461e669fb07a348d2528",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -3,7 +3,6 @@
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# nixpkgs.url = "github:nixos/nixpkgs/release-23.05";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
# required for latest zig # required for latest zig
@ -38,28 +37,10 @@
in rec { in rec {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
# TODO: re-enable this once it is fixed: zigpkgs."0.14.0" zigpkgs."0.14.0"
zigpkgs.master
bat bat
wrk wrk
python3
python3Packages.sanic
python3Packages.setuptools
python3Packages.matplotlib
poetry
poetry
pkgs.rustc
pkgs.cargo
pkgs.gcc
pkgs.rustfmt
pkgs.clippy
pkgs.go
pkgs.gotools
pkgs.gopls
pkgs.golint
pkgs.dotnet-sdk_8
pkgs.dotnet-runtime_8
pkgs.zlib pkgs.zlib
pkgs.icu pkgs.icu
pkgs.openssl pkgs.openssl
@ -85,9 +66,8 @@
devShells.build = pkgs.mkShell { devShells.build = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
# zigpkgs."0.14.0" zigpkgs."0.14.0"
zigpkgs.master zigpkgs.master
pkgs.openssl
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [

View file

@ -1,100 +0,0 @@
# Introducing ⚡zap⚡ - blazingly fast backends in zig
Zap is intended to become my [zig](https://ziglang.org) replacement for the kind of REST APIs I used to write in [python](https://python.org) with [Flask](https://flask.palletsprojects.com) and [mongodb](https://www.mongodb.com), etc. It can be considered to be a microframework for web applications.
What I need for that is a blazingly fast, robust HTTP server that I can use with zig. While facil.io supports TLS, I don't care about HTTPS support. In production, I use [nginx](https://www.nginx.com) as a reverse proxy anyway.
Zap wraps and patches [facil.io - the C web application framework](https://facil.io).
At the time of writing, ZAP is only a few days old and aims to be:
- **robust**
- **fast**
- **minimal**
**⚡ZAP⚡ IS SUPER ALPHA**
_Under the hood, everything is super robust and fast. My zig wrappers are fresh, juicy, and alpha._
Here's what works:
- **Super easy build process**: zap's `build.zig` fetches facilio's git sub-module, applies a patch to its logging for microsecond precision, and then builds and optionally runs everything.
- _tested on Linux and macOS (arm, M1)_
- **[hello](https://github.com/renerocksai/zap/blob/master/examples/hello/hello.zig)**: welcomes you with some static HTML
- **[routes](https://github.com/renerocksai/zap/blob/master/examples/routes/routes.zig)**: a super easy example dispatching on the HTTP path
- **[serve](https://github.com/renerocksai/zap/blob/master/examples/serve/serve.zig)**: the traditional static web server with optional dynamic request handling
- **[hello_json](https://github.com/renerocksai/zap/blob/master/examples/hello_json/hello_json.zig)**: serves you json dependent on HTTP path
- **[endpoint](https://github.com/renerocksai/zap/blob/master/examples/endpoint/)**: a simple JSON REST API example featuring a `/users` endpoint for PUTting/DELETE-ing/GET-ting/POST-ing and listing users, together with a static HTML and JavaScript frontend to play with.
If you want to take it for a quick spin:
```shell
$ git clone https://github.com/renerocksai/zap.git
$ cd zap
$ zig build run-hello
$ # open http://localhost:3000 in your browser
```
See [the README](https://github.com/renerocksai/zap) for how easy it is to get started, how to run the examples, and how to use zap in your own projects.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap to a point where I can use it as the JSON REST API backend for real research projects, serving thousands of concurrent clients. Now that the endpoint example works, ZAP has actually become pretty usable to me.
**Side-note:** It never ceases to amaze me how productive I can be in zig, eventhough I am still considering myself to be a newbie. Sometimes, it's almost like writing python but with all the nice speed and guarantees that zig gives you. Also, the C integration abilities of zig are just phenomenal! I am super excited about zig's future!
Now, on to the guiding principles of Zap.
## robust
A common recommendation for doing web stuff in zig is to write the actual HTTP server in Go, and use zig for the real work. While there is a selection of notable and cool HTTP server implementations written in zig out there, at the time of writing, most of them seem to a) depend on zig's async facilities which are unsupported until ca. April 2023 when async will return to the self-hosted compiler, and b) have not matured to a point where **I** feel safe using them in production. These are just my opionions and they could be totally wrong though.
However, when I conduct my next online research experiment with thousands of concurrent clients, I cannot afford to run into potential maturity-problems of the HTTP server. These projects typically feature a you-get-one-shot process with little room for errors or re-tries.
With zap, if something should go wrong, at least I'd be close enough to the source-code to, hopefully, be able to fix it in production. With that out of the way, I am super confident that facil.io is very mature compared to many of the alternatives. My `wrk` tests also look promising.
I intend to add app-specific performance tests, e.g. stress-testing the endpoint example, to make sure the zap endpoint framework is able to sustain a high load without running into performance or memory problems. That will be interesting.
## ⚡blazingly fast⚡
Claiming to be blazingly fast is the new black. At least, zap doesn't slow you down and if your server performs poorly, it's probably not exactly zap's fault. Zap relies on the [facil.io](https://facil.io) framework and so it can't really claim any performance fame for itself. In this initial implementation of zap, I didn't care about optimizations at all.
But, how fast is it? Being blazingly fast is relative. When compared with a simple GO HTTP server, a simple zig zap HTTP server performed really good on my machine:
- zig zap was nearly 30% faster than GO
- zig zap had over 50% more throughput than GO
I intentionally only tested static HTTP output, as that seemed to be the best common ground of all test subjects to me. The measurements were for just getting a ballpark baseline anyway.
**Update**: I was intrigued comparing to a basic rust HTTP server. Unfortunately, knowing nothing at all about rust, I couldn't find a simple, just-a-few-lines one like in Go and Python right away and hence tried to go for the one in the book [The Rust Programming Language](https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html). Wanting it to be of a somewhat fair comparison, I opted for the multi-threaded example. It didn't work out-of-the-book, but I got it to work (essentially, by commenting out all "print" statements) and changed it to not read files but outputting static text just like the other examples. **Maybe someone with rust experience** can have a look at my [wrk/rust/hello](wrk/rust/hello) code and tell me why it is surprisingly 'slow', as I expected it to be faster than or at least on-par with the basic Go example. I'll enable the GitHub discussions for this matter. My suspicion is bad performance of the mutexes.
![table](https://raw.githubusercontent.com/renerocksai/zap/master/wrk_table_summary.png)
![charts](https://raw.githubusercontent.com/renerocksai/zap/master/wrk_charts_summary.png)
So, being somewhere in the ballpark of basic GO performance, zig zap seems to be ... of reasonable performance 😎.
See more details in [blazingly-fast.md](https://github.com/renerocksai/zap/blob/master/blazingly-fast.md).
## minimal
Zap is minimal by necessity. I only (have time to) add what I need - for serving REST APIs and HTML. The primary use-case are frontends that I wrote that communicate with my APIs. Hence, the focus is more on getting stuff done rather than conforming to every standard there is. Even though facilio is able to support TLS, I don't care about that - at least for now. Also, if you present `404 - File not found` as human-readable HTML to the user, nobody forces you to also set the status code to 404, so it can be OK to spare those nanoseconds. Gotta go fast!
Facilio comes with Mustache parsing, TLS via third-party libs, websockets, redis support, concurrency stuff, Base64 support, logging facilities, pub/sub / cluster messages API, hash algorithm implementations, its own memory allocator, and so forth. It is really an amazing project!
On the lower level, you can use all of the above by working with `zap.C`. I'll zig-wrap what I need for my projects first, before adding more fancy stuff.
Also, there are nice and well-typed zig implementations for some of the above extra functionalities, and zap-wrapping them needs careful consideration. E.g. it might not be worth the extra effort to wrap facil.io's mustache support when there is a good zig alternative already. Performance / out-of-the-box integration might be arguments pro wrapping them in zap.
## wrapping up - zig is WYSIWYG code
I am super excited about both zig and zap's future. I am still impressed by how easy it is to integrate a C codebase into a zig project, then benefiting from and building on top of battle-tested high-performance C code. Additionally, with zig you get C-like performance with almost Python-like comfort. And you can be sure no exception is trying to get you when you least expect it. No hidden allocations, no hidden control-flows, how cool is that? **WYSIWYG code!**
Provided that the incorporated C code is well-written and -tested, WYSIWYG even holds mostly true for combined Zig and C projects.
You can truly build on the soulders of giants here. Mind you, it took me less than a week to arrive at the current state of zap where I am confident that I can already use it to write the one or other REST API with it and, after stress-testing, just move it into production - from merely researching Zig and C web frameworks a few days ago.
Oh, and have I mentioned Zig's built-in build system and testing framework? Those are both super amazing and super convenient. `zig build` is so much more useful than `make` (which I quite like to be honest). And `zig test` is just amazing, too. Zig's physical code layout: which file is located where and how can it be built, imported, tested - it all makes so much sense. Such a coherent, pleasant experience.
Looking forward, I am also tempted to try adding some log-and-replay facilities as a kind of backup for when things go wrong. I wouldn't be confident to attemt such things in C because I'd view them as being too much work; too much could go wrong. But with Zig, I am rather excited about the possibilities that open up and eager to try such things.
For great justice!

View file

@ -1,35 +0,0 @@
{
pkgs ? import <nixpkgs> {
overlays = [
(import (builtins.fetchTarball {
# url = https://github.com/nix-community/neovim-nightly-overlay/archive/master.tar.gz;
url = https://github.com/nix-community/neovim-nightly-overlay/archive/72ff8b1ca0331a8735c1eeaefb95c12dfe21d30a.tar.gz;
}))
];
}
} :
pkgs.mkShell {
nativeBuildInputs = [
pkgs.neovim-nightly
pkgs.bat
pkgs.wrk
pkgs.python3
pkgs.rustc
pkgs.cargo
pkgs.gcc
pkgs.rustfmt
pkgs.clippy
];
buildInputs = [
pkgs.go
pkgs.gotools
pkgs.gopls
# pkgs.go-outline
# pkgs.gocode
# pkgs.gopkgs
# pkgs.gocode-gomod
# pkgs.godef
pkgs.golint
];
}

386
src/App.zig Normal file
View file

@ -0,0 +1,386 @@
//! WIP: zap.App.
//!
//! - Per Request Arena(s) thread-local?
//! - Custom "State" Context, type-safe
//! - route handlers
//! - automatic error catching & logging, optional report to HTML
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Thread = std.Thread;
const RwLock = Thread.RwLock;
const zap = @import("zap.zig");
const Request = zap.Request;
const HttpListener = zap.HttpListener;
const ErrorStrategy = zap.Endpoint.ErrorStrategy;
pub const AppOpts = struct {
/// ErrorStrategy for (optional) request handler if no endpoint matches
default_error_strategy: ErrorStrategy = .log_to_console,
arena_retain_capacity: usize = 16 * 1024 * 1024,
};
/// creates an App with custom app context
pub fn Create(comptime Context: type) type {
return struct {
const App = @This();
// we make the following fields static so we can access them from a
// context-free, pure zap request handler
const InstanceData = struct {
context: *Context = undefined,
gpa: Allocator = undefined,
opts: AppOpts = undefined,
endpoints: std.ArrayListUnmanaged(*Endpoint.Interface) = .empty,
there_can_be_only_one: bool = false,
track_arenas: std.AutoHashMapUnmanaged(Thread.Id, ArenaAllocator) = .empty,
track_arena_lock: RwLock = .{},
/// the internal http listener
listener: HttpListener = undefined,
/// function pointer to handler for otherwise unhandled requests
/// Will automatically be set if your Context provides an unhandled
/// function of type `fn(*Context, Allocator, Request)`
///
unhandled: ?*const fn (*Context, Allocator, Request) anyerror!void = null,
};
var _static: InstanceData = .{};
/// Internal, static request handler callback. Will be set to the optional,
/// user-defined request callback that only gets called if no endpoints match
/// a request.
var on_request: ?*const fn (Allocator, *Context, Request) anyerror!void = null;
pub const Endpoint = struct {
pub const Interface = struct {
call: *const fn (*Interface, Request) anyerror!void = undefined,
path: []const u8,
destroy: *const fn (*Interface, Allocator) void = undefined,
};
pub fn Bind(ArbitraryEndpoint: type) type {
return struct {
endpoint: *ArbitraryEndpoint,
interface: Interface,
// tbh: unnecessary, since we have it in _static
app_context: *Context,
const Bound = @This();
pub fn unwrap(interface: *Interface) *Bound {
const self: *Bound = @alignCast(@fieldParentPtr("interface", interface));
return self;
}
pub fn destroy(interface: *Interface, allocator: Allocator) void {
const self: *Bound = @alignCast(@fieldParentPtr("interface", interface));
allocator.destroy(self);
}
pub fn onRequestInterface(interface: *Interface, r: Request) !void {
var self: *Bound = Bound.unwrap(interface);
var arena = try get_arena();
try self.onRequest(arena.allocator(), self.app_context, r);
_ = arena.reset(.{ .retain_with_limit = _static.opts.arena_retain_capacity });
}
pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void {
const ret = switch (r.methodAsEnum()) {
.GET => self.endpoint.*.get(arena, app_context, r),
.POST => self.endpoint.*.post(arena, app_context, r),
.PUT => self.endpoint.*.put(arena, app_context, r),
.DELETE => self.endpoint.*.delete(arena, app_context, r),
.PATCH => self.endpoint.*.patch(arena, app_context, r),
.OPTIONS => self.endpoint.*.options(arena, app_context, r),
else => error.UnsupportedHtmlRequestMethod,
};
if (ret) {
// handled without error
} else |err| {
switch (self.endpoint.*.error_strategy) {
.raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.debug(
"Error in {} {s} : {}",
.{ Bound, r.method orelse "(no method)", err },
),
}
}
}
};
}
pub fn init(ArbitraryEndpoint: type, endpoint: *ArbitraryEndpoint) Endpoint.Bind(ArbitraryEndpoint) {
checkEndpointType(ArbitraryEndpoint);
const BoundEp = Endpoint.Bind(ArbitraryEndpoint);
return .{
.endpoint = endpoint,
.interface = .{
.path = endpoint.path,
.call = BoundEp.onRequestInterface,
.destroy = BoundEp.destroy,
},
.app_context = _static.context,
};
}
pub fn checkEndpointType(T: type) void {
if (@hasField(T, "path")) {
if (@FieldType(T, "path") != []const u8) {
@compileError(@typeName(@FieldType(T, "path")) ++ " has wrong type, expected: []const u8");
}
} else {
@compileError(@typeName(T) ++ " has no path field");
}
if (@hasField(T, "error_strategy")) {
if (@FieldType(T, "error_strategy") != ErrorStrategy) {
@compileError(@typeName(@FieldType(T, "error_strategy")) ++ " has wrong type, expected: zap.Endpoint.ErrorStrategy");
}
} else {
@compileError(@typeName(T) ++ " has no error_strategy field");
}
const methods_to_check = [_][]const u8{
"get",
"post",
"put",
"delete",
"patch",
"options",
};
inline for (methods_to_check) |method| {
if (@hasDecl(T, method)) {
const Method = @TypeOf(@field(T, method));
const Expected = fn (_: *T, _: Allocator, _: *Context, _: Request) anyerror!void;
if (Method != Expected) {
@compileError(method ++ " method of " ++ @typeName(T) ++ " has wrong type:\n" ++ @typeName(Method) ++ "\nexpected:\n" ++ @typeName(Expected));
}
} else {
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
}
}
}
/// Wrap an endpoint with an Authenticator
pub fn Authenticating(EndpointType: type, Authenticator: type) type {
return struct {
authenticator: *Authenticator,
ep: *EndpointType,
path: []const u8,
error_strategy: ErrorStrategy,
const AuthenticatingEndpoint = @This();
/// Init the authenticating endpoint. Pass in a pointer to the endpoint
/// you want to wrap, and the Authenticator that takes care of authenticating
/// requests.
pub fn init(e: *EndpointType, authenticator: *Authenticator) AuthenticatingEndpoint {
return .{
.authenticator = authenticator,
.ep = e,
.path = e.path,
.error_strategy = e.error_strategy,
};
}
/// Authenticates GET requests using the Authenticator.
pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.get(arena, context, request),
.Handled => {},
};
}
/// Authenticates POST requests using the Authenticator.
pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.post(arena, context, request),
.Handled => {},
};
}
/// Authenticates PUT requests using the Authenticator.
pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
/// Authenticates DELETE requests using the Authenticator.
pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.delete(arena, context, request),
.Handled => {},
};
}
/// Authenticates PATCH requests using the Authenticator.
pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.patch(arena, context, request),
.Handled => {},
};
}
/// Authenticates OPTIONS requests using the Authenticator.
pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
};
}
};
pub const ListenerSettings = struct {
/// IP interface, e.g. 0.0.0.0
interface: [*c]const u8 = null,
/// IP port to listen on
port: usize,
public_folder: ?[]const u8 = null,
max_clients: ?isize = null,
max_body_size: ?usize = null,
timeout: ?u8 = null,
tls: ?zap.Tls = null,
};
pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !App {
if (_static.there_can_be_only_one) {
return error.OnlyOneAppAllowed;
}
_static.context = context_;
_static.gpa = gpa_;
_static.opts = opts_;
_static.there_can_be_only_one = true;
// set unhandled callback if provided by Context
if (@hasDecl(Context, "unhandled")) {
// try if we can use it
const Unhandled = @TypeOf(@field(Context, "unhandled"));
const Expected = fn (_: *Context, _: Allocator, _: Request) anyerror!void;
if (Unhandled != Expected) {
@compileError("`unhandled` method of " ++ @typeName(Context) ++ " has wrong type:\n" ++ @typeName(Unhandled) ++ "\nexpected:\n" ++ @typeName(Expected));
}
_static.unhandled = Context.unhandled;
}
return .{};
}
pub fn deinit(_: *App) void {
// we created endpoint wrappers but only tracked their interfaces
// hence, we need to destroy the wrappers through their interfaces
if (false) {
var it = _static.endpoints.iterator();
while (it.next()) |kv| {
const interface = kv.value_ptr;
interface.*.destroy(_static.gpa);
}
} else {
for (_static.endpoints.items) |interface| {
interface.destroy(interface, _static.gpa);
}
}
_static.endpoints.deinit(_static.gpa);
_static.track_arena_lock.lock();
defer _static.track_arena_lock.unlock();
var it = _static.track_arenas.valueIterator();
while (it.next()) |arena| {
// std.debug.print("deiniting arena: {*}\n", .{arena});
arena.deinit();
}
_static.track_arenas.deinit(_static.gpa);
}
pub fn get_arena() !*ArenaAllocator {
const thread_id = std.Thread.getCurrentId();
_static.track_arena_lock.lockShared();
if (_static.track_arenas.getPtr(thread_id)) |arena| {
_static.track_arena_lock.unlockShared();
return arena;
} else {
_static.track_arena_lock.unlockShared();
_static.track_arena_lock.lock();
defer _static.track_arena_lock.unlock();
const arena = ArenaAllocator.init(_static.gpa);
try _static.track_arenas.put(_static.gpa, thread_id, arena);
return _static.track_arenas.getPtr(thread_id).?;
}
}
/// Register an endpoint with this listener.
/// NOTE: endpoint paths are matched with startsWith
/// -> so use endpoints with distinctly starting names!!
/// If you try to register an endpoint whose path would shadow an
/// already registered one, you will receive an
/// EndpointPathShadowError.
pub fn register(_: *App, endpoint: anytype) !void {
for (_static.endpoints.items) |other| {
if (std.mem.startsWith(
u8,
other.path,
endpoint.path,
) or std.mem.startsWith(
u8,
endpoint.path,
other.path,
)) {
return zap.Endpoint.ListenerError.EndpointPathShadowError;
}
}
const EndpointType = @typeInfo(@TypeOf(endpoint)).pointer.child;
Endpoint.checkEndpointType(EndpointType);
const bound = try _static.gpa.create(Endpoint.Bind(EndpointType));
bound.* = Endpoint.init(EndpointType, endpoint);
try _static.endpoints.append(_static.gpa, &bound.interface);
}
pub fn listen(_: *App, l: ListenerSettings) !void {
_static.listener = HttpListener.init(.{
.interface = l.interface,
.port = l.port,
.public_folder = l.public_folder,
.max_clients = l.max_clients,
.max_body_size = l.max_body_size,
.timeout = l.timeout,
.tls = l.tls,
.on_request = onRequest,
});
try _static.listener.listen();
}
fn onRequest(r: Request) !void {
if (r.path) |p| {
for (_static.endpoints.items) |interface| {
if (std.mem.startsWith(u8, p, interface.path)) {
return try interface.call(interface, r);
}
}
}
if (on_request) |foo| {
var arena = try get_arena();
foo(arena.allocator(), _static.context, r) catch |err| {
switch (_static.opts.default_error_strategy) {
.raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.debug("Error in {} {s} : {}", .{ App, r.method orelse "(no method)", err }),
}
};
}
}
};
}

313
src/BoundFunction.zig Normal file
View file

@ -0,0 +1,313 @@
const std = @import("std");
/// Helper function that returns a function type with ArgType prepended to the
/// function's args.
/// Example:
/// Func = fn(usize) void
/// ArgType = *Instance
/// --------------------------
/// Result = fn(*Instance, usize) void
fn PrependFnArg(Func: type, ArgType: type) type {
const fn_info = @typeInfo(Func);
if (fn_info != .@"fn") @compileError("First argument must be a function type");
comptime var new_params: [fn_info.@"fn".params.len + 1]std.builtin.Type.Fn.Param = undefined;
new_params[0] = .{ .is_generic = false, .is_noalias = false, .type = ArgType };
for (fn_info.@"fn".params, 0..) |param, i| {
new_params[i + 1] = param;
}
return @Type(.{
.@"fn" = .{
.calling_convention = fn_info.@"fn".calling_convention,
.is_generic = fn_info.@"fn".is_generic,
.is_var_args = fn_info.@"fn".is_var_args,
.return_type = fn_info.@"fn".return_type,
.params = &new_params,
},
});
}
// External Generic Interface (CallbackInterface)
pub fn CallbackInterface(comptime Func: type) type {
const func_info = @typeInfo(Func);
if (func_info != .@"fn") @compileError("CallbackInterface expects a function type");
if (func_info.@"fn".is_generic) @compileError("CallbackInterface does not support generic functions");
if (func_info.@"fn".is_var_args) @compileError("CallbackInterface does not support var_args functions");
const ArgsTupleType = std.meta.ArgsTuple(Func);
const ReturnType = func_info.@"fn".return_type.?;
const FnPtrType = *const fn (ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType;
return struct {
ctx: ?*const anyopaque,
callFn: FnPtrType,
pub const Interface = @This();
pub fn call(self: Interface, args: ArgsTupleType) ReturnType {
if (self.ctx == null) @panic("Called uninitialized CallbackInterface");
if (ReturnType == void) {
self.callFn(self.ctx, args);
} else {
return self.callFn(self.ctx, args);
}
}
};
}
pub fn Bind(Instance: type, Func: type) type {
const func_info = @typeInfo(Func);
if (func_info != .@"fn") @compileError("Bind expects a function type as second parameter");
if (func_info.@"fn".is_generic) @compileError("Binding generic functions is not supported");
if (func_info.@"fn".is_var_args) @compileError("Binding var_args functions is not currently supported");
const ReturnType = func_info.@"fn".return_type.?;
const OriginalParams = func_info.@"fn".params; // Needed for comptime loops
const ArgsTupleType = std.meta.ArgsTuple(Func);
const InstanceMethod = PrependFnArg(Func, *Instance);
const InterfaceType = CallbackInterface(Func);
return struct {
instance: *Instance,
method: *const InstanceMethod,
pub const BoundFunction = @This();
// Trampoline function using runtime tuple construction
fn callDetached(ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType {
if (ctx == null) @panic("callDetached called with null context");
const self: *const BoundFunction = @ptrCast(@alignCast(ctx.?));
// 1. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
const CallArgsTupleType = comptime T: {
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
// Field 0: *Instance type
tuple_fields[0] = .{
.name = "0",
.type = @TypeOf(self.instance),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
// Fields 1..N: Original argument types (use ArgsTupleType fields)
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
tuple_fields[i + 1] = .{
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
.type = field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
}
break :T @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &tuple_fields,
.decls = &.{},
.is_tuple = true,
} });
};
// 2. Create and populate the tuple at runtime
var call_args_tuple: CallArgsTupleType = undefined;
@field(call_args_tuple, "0") = self.instance; // Set the instance pointer
// Copy original args from 'args' tuple to 'call_args_tuple'
comptime var i = 0;
inline while (i < OriginalParams.len) : (i += 1) {
const src_field_name = comptime std.fmt.comptimePrint("{}", .{i});
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
@field(call_args_tuple, dest_field_name) = @field(args, src_field_name);
}
// 3. Perform the call using the populated tuple
if (ReturnType == void) {
@call(.auto, self.method, call_args_tuple);
} else {
return @call(.auto, self.method, call_args_tuple);
}
}
pub fn interface(self: *const BoundFunction) InterfaceType {
return .{ .ctx = @ptrCast(self), .callFn = &callDetached };
}
// Direct call convenience method using runtime tuple construction
pub fn call(self: *const BoundFunction, args: anytype) ReturnType {
// 1. Verify 'args' is the correct ArgsTupleType or compatible tuple literal
// (This check could be more robust if needed)
if (@TypeOf(args) != ArgsTupleType) {
// Attempt reasonable check for tuple literal compatibility
if (@typeInfo(@TypeOf(args)) != .@"struct" or !@typeInfo(@TypeOf(args)).@"struct".is_tuple) {
@compileError(std.fmt.comptimePrint(
"Direct .call expects arguments as a tuple literal compatible with {}, found type {}",
.{ ArgsTupleType, @TypeOf(args) },
));
}
// Further check field count/types if necessary
if (std.meta.fields(@TypeOf(args)).len != OriginalParams.len) {
@compileError(std.fmt.comptimePrint(
"Direct .call tuple literal has wrong number of arguments (expected {}, got {}) for {}",
.{ OriginalParams.len, std.meta.fields(@TypeOf(args)).len, ArgsTupleType },
));
}
// Could add type checks per field here too
}
// 2. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
const CallArgsTupleType = comptime T: {
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
tuple_fields[0] = .{
.name = "0",
.type = @TypeOf(self.instance),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
tuple_fields[i + 1] = .{
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
.type = field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
}
break :T @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &tuple_fields,
.decls = &.{},
.is_tuple = true,
} });
};
// 3. Create and populate the tuple at runtime
var call_args_tuple: CallArgsTupleType = undefined;
@field(call_args_tuple, "0") = self.instance;
comptime var i = 0;
inline while (i < OriginalParams.len) : (i += 1) {
const field_name = comptime std.fmt.comptimePrint("{}", .{i});
// Check if field exists in args (useful for struct literals, less for tuples)
// For tuple literals, direct access should work if type check passed.
// if (@hasField(@TypeOf(args), field_name)) { ... }
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
@field(call_args_tuple, dest_field_name) = @field(args, field_name);
}
// 4. Perform the call using the populated tuple
if (ReturnType == void) {
@call(.auto, self.method, call_args_tuple);
} else {
return @call(.auto, self.method, call_args_tuple);
}
}
pub fn init(instance_: *Instance, method_: *const InstanceMethod) BoundFunction {
return .{ .instance = instance_, .method = method_ };
}
};
}
const testing = std.testing;
test "Bind Direct Call" {
const Person = struct {
name: []const u8,
_buf: [1024]u8 = undefined,
pub fn speak(self: *@This(), msg: []const u8) ![]const u8 {
return std.fmt.bufPrint(&self._buf, "{s}: {s}", .{ self.name, msg });
}
};
const FuncSig = fn ([]const u8) anyerror![]const u8;
var p = Person{ .name = "Alice" };
const bound = Bind(Person, FuncSig).init(&p, &Person.speak);
const res = try bound.call(.{"Hi"}); // Pass tuple literal
try testing.expectEqualStrings("Alice: Hi", res);
}
test "BindInterface Call (External)" {
const Person = struct {
name: []const u8,
_buf: [1024]u8 = undefined,
pub fn speak(self: *@This(), message: []const u8) ![]const u8 {
return std.fmt.bufPrint(&self._buf, "{s} says: >>{s}!<<\n", .{ self.name, message });
}
};
const CallBack = fn ([]const u8) anyerror![]const u8;
var alice: Person = .{ .name = "Alice" };
const BoundSpeak = Bind(Person, CallBack);
const bound_speak = BoundSpeak.init(&alice, &Person.speak);
var alice_interface = bound_speak.interface();
const greeting = try alice_interface.call(.{"Hello"}); // Pass tuple literal
try testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting);
}
test "BindInterface Polymorphism (External)" {
const Person = struct {
name: []const u8,
_buf: [1024]u8 = undefined,
pub fn speak(self: *@This(), message: []const u8) ![]const u8 {
return std.fmt.bufPrint(&self._buf, "{s} says: >>{s}!<<\n", .{ self.name, message });
}
};
const Dog = struct {
name: []const u8,
_buf: [1024]u8 = undefined,
pub fn bark(self: *@This(), message: []const u8) ![]const u8 {
return std.fmt.bufPrint(&self._buf, "{s} barks: >>{s}!<<\n", .{ self.name, message });
}
};
const CallBack = fn ([]const u8) anyerror![]const u8;
const CbInterface = CallbackInterface(CallBack);
var alice: Person = .{ .name = "Alice" };
const bound_alice = Bind(Person, CallBack).init(&alice, &Person.speak);
const alice_interface = bound_alice.interface();
var bob: Dog = .{ .name = "Bob" };
const bound_bob = Bind(Dog, CallBack).init(&bob, &Dog.bark);
const bob_interface = bound_bob.interface();
const interfaces = [_]CbInterface{ alice_interface, bob_interface };
var results: [2][]const u8 = undefined;
for (interfaces, 0..) |iface, i| {
results[i] = try iface.call(.{"Test"});
} // Pass tuple literal
try testing.expectEqualStrings("Alice says: >>Test!<<\n", results[0]);
try testing.expectEqualStrings("Bob barks: >>Test!<<\n", results[1]);
}
test "Void Return Type (External Interface)" {
var counter: u32 = 0;
const Counter = struct {
count: *u32,
pub fn increment(self: *@This(), amount: u32) void {
self.count.* += amount;
}
};
const Decrementer = struct {
count: *u32,
pub fn decrement(self: *@This(), amount: u32) void {
self.count.* -= amount;
}
};
const IncrementFn = fn (u32) void;
const IncInterface = CallbackInterface(IncrementFn);
var my_counter = Counter{ .count = &counter };
const bound_inc = Bind(Counter, IncrementFn).init(&my_counter, &Counter.increment);
bound_inc.call(.{5});
try testing.expectEqual(@as(u32, 5), counter);
var my_dec = Decrementer{ .count = &counter };
const bound_dec = Bind(Decrementer, IncrementFn).init(&my_dec, &Decrementer.decrement);
const iface1 = bound_inc.interface();
const iface2 = bound_dec.interface();
const void_ifaces = [_]IncInterface{ iface1, iface2 };
void_ifaces[0].call(.{3}); // counter = 5 + 3 = 8
try testing.expectEqual(@as(u32, 8), counter);
void_ifaces[1].call(.{2}); // counter = 8 - 2 = 6
try testing.expectEqual(@as(u32, 6), counter);
}

View file

@ -1,237 +1,254 @@
//! Endpoint and supporting types.
//!
//! An Endpoint can be any zig struct that defines all the callbacks lilsted
//! below.
//! Pass an instance of an Endpoint struct to zap.Endpoint.Listener.register()
//! function to register with the listener.
//!
//! **NOTE**: Endpoints must implement the following "interface":
//!
//! ```zig
//! /// The http request path / slug of the endpoint
//! path: []const u8,
//!
//! /// Handlers by request method:
//! pub fn get(_: *Self, _: zap.Request) void {}
//! pub fn post(_: *Self, _: zap.Request) void {}
//! pub fn put(_: *Self, _: zap.Request) void {}
//! pub fn delete(_: *Self, _: zap.Request) void {}
//! pub fn patch(_: *Self, _: zap.Request) void {}
//! pub fn options(_: *Self, _: zap.Request) void {}
//!
//! // optional, if auth stuff is used:
//! pub fn unauthorized(_: *Self, _: zap.Request) void {}
//! ```
//!
//! Example:
//! A simple endpoint listening on the /stop route that shuts down zap. The
//! main thread usually continues at the instructions after the call to
//! zap.start().
//!
//! ```zig
//! const StopEndpoint = struct {
//!
//! pub fn init( path: []const u8,) StopEndpoint {
//! return .{
//! .path = path,
//! };
//! }
//!
//! pub fn post(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn put(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn delete(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn patch(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn options(_: *StopEndpoint, _: zap.Request) void {}
//!
//! pub fn get(self: *StopEndpoint, r: zap.Request) void {
//! _ = self;
//! _ = r;
//! zap.stop();
//! }
//! };
//! ```
const std = @import("std"); const std = @import("std");
const zap = @import("zap.zig"); const zap = @import("zap.zig");
const auth = @import("http_auth.zig"); const auth = @import("http_auth.zig");
const Endpoint = @This(); /// Endpoint request error handling strategy
pub const ErrorStrategy = enum {
/// log errors to console
log_to_console,
/// log errors to console AND generate a HTML response
log_to_response,
/// raise errors -> TODO: clarify: where can they be caught? in App.run()
raise,
};
// zap types // zap types
const Request = zap.Request; const Request = zap.Request;
const ListenerSettings = zap.HttpListenerSettings; const ListenerSettings = zap.HttpListenerSettings;
const HttpListener = zap.HttpListener; const HttpListener = zap.HttpListener;
/// Type of the request function callbacks. pub fn checkEndpointType(T: type) void {
pub const RequestFn = *const fn (self: *Endpoint, r: Request) void; if (@hasField(T, "path")) {
if (@FieldType(T, "path") != []const u8) {
@compileError(@typeName(@FieldType(T, "path")) ++ " has wrong type, expected: []const u8");
}
} else {
@compileError(@typeName(T) ++ " has no path field");
}
/// Settings to initialize an Endpoint if (@hasField(T, "error_strategy")) {
pub const Settings = struct { if (@FieldType(T, "error_strategy") != ErrorStrategy) {
/// path / slug of the endpoint @compileError(@typeName(@FieldType(T, "error_strategy")) ++ " has wrong type, expected: zap.Endpoint.ErrorStrategy");
path: []const u8, }
/// callback to GET request handler } else {
get: ?RequestFn = null, @compileError(@typeName(T) ++ " has no error_strategy field");
/// callback to POST request handler }
post: ?RequestFn = null,
/// callback to PUT request handler
put: ?RequestFn = null,
/// callback to DELETE request handler
delete: ?RequestFn = null,
/// callback to PATCH request handler
patch: ?RequestFn = null,
/// callback to OPTIONS request handler
options: ?RequestFn = null,
/// Only applicable to Authenticating Endpoint: handler for unauthorized requests
unauthorized: ?RequestFn = null,
};
settings: Settings, const methods_to_check = [_][]const u8{
"get",
/// Initialize the endpoint. "post",
/// Set only the callbacks you need. Requests of HTTP methods without a "put",
/// provided callback will be ignored. "delete",
pub fn init(s: Settings) Endpoint { "patch",
return .{ "options",
.settings = .{
.path = s.path,
.get = s.get orelse &nop,
.post = s.post orelse &nop,
.put = s.put orelse &nop,
.delete = s.delete orelse &nop,
.patch = s.patch orelse &nop,
.options = s.options orelse &nop,
.unauthorized = s.unauthorized orelse &nop,
},
}; };
} inline for (methods_to_check) |method| {
if (@hasDecl(T, method)) {
// no operation. Dummy handler function for ignoring unset request types. if (@TypeOf(@field(T, method)) != fn (_: *T, _: Request) anyerror!void) {
fn nop(self: *Endpoint, r: Request) void { @compileError(method ++ " method of " ++ @typeName(T) ++ " has wrong type:\n" ++ @typeName(@TypeOf(T.get)) ++ "\nexpected:\n" ++ @typeName(fn (_: *T, _: Request) anyerror!void));
_ = self; }
_ = r; } else {
} @compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
}
/// The global request handler for this Endpoint, called by the listener.
pub fn onRequest(self: *Endpoint, r: zap.Request) void {
switch (r.methodAsEnum()) {
.GET => self.settings.get.?(self, r),
.POST => self.settings.post.?(self, r),
.PUT => self.settings.put.?(self, r),
.DELETE => self.settings.delete.?(self, r),
.PATCH => self.settings.patch.?(self, r),
.OPTIONS => self.settings.options.?(self, r),
else => return,
} }
} }
/// Wrap an endpoint with an Authenticator -> new Endpoint of type Endpoint pub const Binder = struct {
/// is available via the `endpoint()` function. pub const Interface = struct {
pub fn Authenticating(comptime Authenticator: type) type { call: *const fn (*Interface, zap.Request) anyerror!void = undefined,
path: []const u8,
destroy: *const fn (*Interface, std.mem.Allocator) void = undefined,
};
pub fn Bind(ArbitraryEndpoint: type) type {
return struct {
endpoint: *ArbitraryEndpoint,
interface: Interface,
const Bound = @This();
pub fn unwrap(interface: *Interface) *Bound {
const self: *Bound = @alignCast(@fieldParentPtr("interface", interface));
return self;
}
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Bound = @alignCast(@fieldParentPtr("interface", interface));
allocator.destroy(self);
}
pub fn onRequestInterface(interface: *Interface, r: zap.Request) !void {
var self: *Bound = Bound.unwrap(interface);
try self.onRequest(r);
}
pub fn onRequest(self: *Bound, r: zap.Request) !void {
const ret = switch (r.methodAsEnum()) {
.GET => self.endpoint.*.get(r),
.POST => self.endpoint.*.post(r),
.PUT => self.endpoint.*.put(r),
.DELETE => self.endpoint.*.delete(r),
.PATCH => self.endpoint.*.patch(r),
.OPTIONS => self.endpoint.*.options(r),
else => error.UnsupportedHtmlRequestMethod,
};
if (ret) {
// handled without error
} else |err| {
switch (self.endpoint.*.error_strategy) {
.raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.debug("Error in {} {s} : {}", .{ Bound, r.method orelse "(no method)", err }),
}
}
}
};
}
pub fn init(ArbitraryEndpoint: type, value: *ArbitraryEndpoint) Binder.Bind(ArbitraryEndpoint) {
checkEndpointType(ArbitraryEndpoint);
const BoundEp = Binder.Bind(ArbitraryEndpoint);
return .{
.endpoint = value,
.interface = .{
.path = value.path,
.call = BoundEp.onRequestInterface,
.destroy = BoundEp.destroy,
},
};
}
};
/// Wrap an endpoint with an Authenticator
pub fn Authenticating(EndpointType: type, Authenticator: type) type {
return struct { return struct {
authenticator: *Authenticator, authenticator: *Authenticator,
ep: *Endpoint, ep: *EndpointType,
auth_endpoint: Endpoint, path: []const u8,
const Self = @This(); error_strategy: ErrorStrategy,
const AuthenticatingEndpoint = @This();
/// Init the authenticating endpoint. Pass in a pointer to the endpoint /// Init the authenticating endpoint. Pass in a pointer to the endpoint
/// you want to wrap, and the Authenticator that takes care of authenticating /// you want to wrap, and the Authenticator that takes care of authenticating
/// requests. /// requests.
pub fn init(e: *Endpoint, authenticator: *Authenticator) Self { pub fn init(e: *EndpointType, authenticator: *Authenticator) AuthenticatingEndpoint {
return .{ return .{
.authenticator = authenticator, .authenticator = authenticator,
.ep = e, .ep = e,
.auth_endpoint = Endpoint.init(.{ .path = e.path,
.path = e.settings.path, .error_strategy = e.error_strategy,
// we override only the set ones. the other ones
// are set to null anyway -> will be nopped out
.get = if (e.settings.get != null) get else null,
.post = if (e.settings.post != null) post else null,
.put = if (e.settings.put != null) put else null,
.delete = if (e.settings.delete != null) delete else null,
.patch = if (e.settings.patch != null) patch else null,
.options = if (e.settings.options != null) options else null,
.unauthorized = e.settings.unauthorized,
}),
}; };
} }
/// Get the auth endpoint struct of type Endpoint so it can be stored in the listener.
/// When the listener calls the auth_endpoint, onRequest will have
/// access to all of this via fieldParentPtr
pub fn endpoint(self: *Self) *Endpoint {
return &self.auth_endpoint;
}
/// GET: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates GET requests using the Authenticator. /// Authenticates GET requests using the Authenticator.
pub fn get(e: *Endpoint, r: zap.Request) void { pub fn get(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.get(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.get.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
/// POST: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates POST requests using the Authenticator. /// Authenticates POST requests using the Authenticator.
pub fn post(e: *Endpoint, r: zap.Request) void { pub fn post(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.post(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.post.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
/// PUT: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates PUT requests using the Authenticator. /// Authenticates PUT requests using the Authenticator.
pub fn put(e: *Endpoint, r: zap.Request) void { pub fn put(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.put(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.put.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
/// DELETE: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates DELETE requests using the Authenticator. /// Authenticates DELETE requests using the Authenticator.
pub fn delete(e: *Endpoint, r: zap.Request) void { pub fn delete(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.delete(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.delete.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
/// PATCH: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates PATCH requests using the Authenticator. /// Authenticates PATCH requests using the Authenticator.
pub fn patch(e: *Endpoint, r: zap.Request) void { pub fn patch(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.patch(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.patch.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
/// OPTIONS: here, the auth_endpoint will be passed in as endpoint.
/// Authenticates OPTIONS requests using the Authenticator. /// Authenticates OPTIONS requests using the Authenticator.
pub fn options(e: *Endpoint, r: zap.Request) void { pub fn options(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
const authEp: *Self = @fieldParentPtr("auth_endpoint", e); try switch (self.authenticator.authenticateRequest(&r)) {
switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r),
.AuthFailed => { .AuthOK => self.ep.*.put(r),
if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.ep, r);
return;
} else {
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED") catch return;
return;
}
},
.AuthOK => authEp.ep.settings.put.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} };
} }
}; };
} }
pub const EndpointListenerError = error{ pub const ListenerError = error{
/// Since we use .startsWith to check for matching paths, you cannot use /// Since we use .startsWith to check for matching paths, you cannot use
/// endpoint paths that overlap at the beginning. --> When trying to register /// endpoint paths that overlap at the beginning. --> When trying to register
/// an endpoint whose path would shadow an already registered one, you will /// an endpoint whose path would shadow an already registered one, you will
@ -246,10 +263,8 @@ pub const Listener = struct {
listener: HttpListener, listener: HttpListener,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
const Self = @This(); /// Internal static interface struct of member endpoints
var endpoints: std.ArrayListUnmanaged(*Binder.Interface) = .empty;
/// Internal static struct of member endpoints
var endpoints: std.ArrayList(*Endpoint) = undefined;
/// Internal, static request handler callback. Will be set to the optional, /// Internal, static request handler callback. Will be set to the optional,
/// user-defined request callback that only gets called if no endpoints match /// user-defined request callback that only gets called if no endpoints match
@ -259,13 +274,15 @@ pub const Listener = struct {
/// Initialize a new endpoint listener. Note, if you pass an `on_request` /// Initialize a new endpoint listener. Note, if you pass an `on_request`
/// callback in the provided ListenerSettings, this request callback will be /// callback in the provided ListenerSettings, this request callback will be
/// called every time a request arrives that no endpoint matches. /// called every time a request arrives that no endpoint matches.
pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self { pub fn init(a: std.mem.Allocator, l: ListenerSettings) Listener {
endpoints = std.ArrayList(*Endpoint).init(a); // reset the global in case init is called multiple times, as is the
// case in the authentication tests
endpoints = .empty;
// take copy of listener settings before modifying the callback field // take copy of listener settings before modifying the callback field
var ls = l; var ls = l;
// override the settings with our internal, actul callback function // override the settings with our internal, actual callback function
// so that "we" will be called on request // so that "we" will be called on request
ls.on_request = Listener.onRequest; ls.on_request = Listener.onRequest;
@ -280,14 +297,16 @@ pub const Listener = struct {
/// De-init the listener and free its resources. /// De-init the listener and free its resources.
/// Registered endpoints will not be de-initialized automatically; just removed /// Registered endpoints will not be de-initialized automatically; just removed
/// from the internal map. /// from the internal map.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Listener) void {
_ = self; for (endpoints.items) |interface| {
endpoints.deinit(); interface.destroy(interface, self.allocator);
}
endpoints.deinit(self.allocator);
} }
/// Call this to start listening. After this, no more endpoints can be /// Call this to start listening. After this, no more endpoints can be
/// registered. /// registered.
pub fn listen(self: *Self) !void { pub fn listen(self: *Listener) !void {
try self.listener.listen(); try self.listener.listen();
} }
@ -295,36 +314,38 @@ pub const Listener = struct {
/// NOTE: endpoint paths are matched with startsWith -> so use endpoints with distinctly starting names!! /// NOTE: endpoint paths are matched with startsWith -> so use endpoints with distinctly starting names!!
/// If you try to register an endpoint whose path would shadow an already registered one, you will /// If you try to register an endpoint whose path would shadow an already registered one, you will
/// receive an EndpointPathShadowError. /// receive an EndpointPathShadowError.
pub fn register(self: *Self, e: *Endpoint) !void { pub fn register(self: *Listener, e: anytype) !void {
_ = self;
for (endpoints.items) |other| { for (endpoints.items) |other| {
if (std.mem.startsWith( if (std.mem.startsWith(
u8, u8,
other.settings.path, other.path,
e.settings.path, e.path,
) or std.mem.startsWith( ) or std.mem.startsWith(
u8, u8,
e.settings.path, e.path,
other.settings.path, other.path,
)) { )) {
return EndpointListenerError.EndpointPathShadowError; return ListenerError.EndpointPathShadowError;
} }
} }
try endpoints.append(e); const EndpointType = @typeInfo(@TypeOf(e)).pointer.child;
checkEndpointType(EndpointType);
const bound = try self.allocator.create(Binder.Bind(EndpointType));
bound.* = Binder.init(EndpointType, e);
try endpoints.append(self.allocator, &bound.interface);
} }
fn onRequest(r: Request) void { fn onRequest(r: Request) !void {
if (r.path) |p| { if (r.path) |p| {
for (endpoints.items) |e| { for (endpoints.items) |interface| {
if (std.mem.startsWith(u8, p, e.settings.path)) { if (std.mem.startsWith(u8, p, interface.path)) {
e.onRequest(r); return try interface.call(interface, r);
return;
} }
} }
} }
// if set, call the user-provided default callback // if set, call the user-provided default callback
if (on_request) |foo| { if (on_request) |foo| {
foo(r); try foo(r);
} }
} }
}; };

View file

@ -420,7 +420,9 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s {
// pub const http_cookie_args_s = opaque {}; // pub const http_cookie_args_s = opaque {};
pub extern fn http_set_header(h: [*c]http_s, name: FIOBJ, value: FIOBJ) c_int; pub extern fn http_set_header(h: [*c]http_s, name: FIOBJ, value: FIOBJ) c_int;
/// set header, copying the data
pub extern fn http_set_header2(h: [*c]http_s, name: fio_str_info_s, value: fio_str_info_s) c_int; pub extern fn http_set_header2(h: [*c]http_s, name: fio_str_info_s, value: fio_str_info_s) c_int;
/// set cookie, taking ownership of data
pub extern fn http_set_cookie(h: [*c]http_s, http_cookie_args_s) c_int; pub extern fn http_set_cookie(h: [*c]http_s, http_cookie_args_s) c_int;
pub extern fn http_sendfile(h: [*c]http_s, fd: c_int, length: usize, offset: usize) c_int; pub extern fn http_sendfile(h: [*c]http_s, fd: c_int, length: usize, offset: usize) c_int;
pub extern fn http_sendfile2(h: [*c]http_s, prefix: [*c]const u8, prefix_len: usize, encoded: [*c]const u8, encoded_len: usize) c_int; pub extern fn http_sendfile2(h: [*c]http_s, prefix: [*c]const u8, prefix_len: usize, encoded: [*c]const u8, encoded_len: usize) c_int;

View file

@ -61,8 +61,9 @@ pub const AuthResult = enum {
/// The authenticator handled the request that didn't pass authentication / /// The authenticator handled the request that didn't pass authentication /
/// authorization. /// authorization.
/// This is used to implement authenticators that redirect to a login /// This is used to implement authenticators that redirect to a login
/// page. An Authenticating endpoint will not do the default, which is trying /// page. An Authenticating endpoint will not do the default, which is
/// to call the `unauthorized` callback if one exists orelse ignore the request. /// trying to call the `unauthorized` callback. `unauthorized()` must be
/// implemented in the endpoint.
Handled, Handled,
}; };
@ -85,14 +86,14 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
realm: ?[]const u8, realm: ?[]const u8,
lookup: *Lookup, lookup: *Lookup,
const Self = @This(); const BasicAuth = @This();
/// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8` /// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8`
/// different implementations can /// different implementations can
/// - either decode, lookup and compare passwords /// - either decode, lookup and compare passwords
/// - or just check for existence of the base64-encoded user:pass combination /// - or just check for existence of the base64-encoded user:pass combination
/// if realm is provided (not null), a copy of it is taken -> call deinit() to clean up /// if realm is provided (not null), a copy of it is taken -> call deinit() to clean up
pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !BasicAuth {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.lookup = lookup, .lookup = lookup,
@ -101,7 +102,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
} }
/// Deinit the authenticator. /// Deinit the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BasicAuth) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
@ -109,7 +110,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
/// Use this to decode the auth_header into user:pass, lookup pass in lookup. /// Use this to decode the auth_header into user:pass, lookup pass in lookup.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticateUserPass(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticateUserPass(self: *BasicAuth, auth_header: []const u8) AuthResult {
zap.debug("AuthenticateUserPass\n", .{}); zap.debug("AuthenticateUserPass\n", .{});
const encoded = auth_header[AuthScheme.Basic.str().len..]; const encoded = auth_header[AuthScheme.Basic.str().len..];
const decoder = std.base64.standard.Decoder; const decoder = std.base64.standard.Decoder;
@ -172,14 +173,14 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
/// Use this to just look up if the base64-encoded auth_header exists in lookup. /// Use this to just look up if the base64-encoded auth_header exists in lookup.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticateToken68(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticateToken68(self: *BasicAuth, auth_header: []const u8) AuthResult {
const token = auth_header[AuthScheme.Basic.str().len..]; const token = auth_header[AuthScheme.Basic.str().len..];
return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed;
} }
/// dispatch based on kind (.UserPass / .Token689) and try to authenticate based on the header. /// dispatch based on kind (.UserPass / .Token689) and try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *BasicAuth, auth_header: []const u8) AuthResult {
zap.debug("AUTHENTICATE\n", .{}); zap.debug("AUTHENTICATE\n", .{});
switch (kind) { switch (kind) {
.UserPass => return self.authenticateUserPass(auth_header), .UserPass => return self.authenticateUserPass(auth_header),
@ -191,7 +192,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
/// ///
/// Tries to extract the authentication header and perform the authentication. /// Tries to extract the authentication header and perform the authentication.
/// If no authentication header is found, an authorization header is tried. /// If no authentication header is found, an authorization header is tried.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { pub fn authenticateRequest(self: *BasicAuth, r: *const zap.Request) AuthResult {
zap.debug("AUTHENTICATE REQUEST\n", .{}); zap.debug("AUTHENTICATE REQUEST\n", .{});
if (extractAuthHeader(.Basic, r)) |auth_header| { if (extractAuthHeader(.Basic, r)) |auth_header| {
zap.debug("Authentication Header found!\n", .{}); zap.debug("Authentication Header found!\n", .{});
@ -224,12 +225,10 @@ pub const BearerSingle = struct {
token: []const u8, token: []const u8,
realm: ?[]const u8, realm: ?[]const u8,
const Self = @This();
/// Creates a Single-Token Bearer Authenticator. /// Creates a Single-Token Bearer Authenticator.
/// Takes a copy of the token. /// Takes a copy of the token.
/// If realm is provided (not null), a copy is taken call deinit() to clean up. /// If realm is provided (not null), a copy is taken call deinit() to clean up.
pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !BearerSingle {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.token = try allocator.dupe(u8, token), .token = try allocator.dupe(u8, token),
@ -239,7 +238,7 @@ pub const BearerSingle = struct {
/// Try to authenticate based on the header. /// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *BearerSingle, auth_header: []const u8) AuthResult {
if (checkAuthHeader(.Bearer, auth_header) == false) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
} }
@ -250,7 +249,7 @@ pub const BearerSingle = struct {
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// Tries to extract the authentication header and perform the authentication. /// Tries to extract the authentication header and perform the authentication.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { pub fn authenticateRequest(self: *BearerSingle, r: *const zap.Request) AuthResult {
if (extractAuthHeader(.Bearer, r)) |auth_header| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(auth_header); return self.authenticate(auth_header);
} }
@ -258,7 +257,7 @@ pub const BearerSingle = struct {
} }
/// Deinits the authenticator. /// Deinits the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BearerSingle) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
@ -282,12 +281,12 @@ pub fn BearerMulti(comptime Lookup: type) type {
lookup: *Lookup, lookup: *Lookup,
realm: ?[]const u8, realm: ?[]const u8,
const Self = @This(); const BearerMultiAuth = @This();
/// Creates a Multi Token Bearer Authenticator. `lookup` must implement /// Creates a Multi Token Bearer Authenticator. `lookup` must implement
/// `.get([]const u8) -> []const u8` to look up tokens. /// `.get([]const u8) -> []const u8` to look up tokens.
/// If realm is provided (not null), a copy of it is taken -> call deinit() to clean up. /// If realm is provided (not null), a copy of it is taken -> call deinit() to clean up.
pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !BearerMultiAuth {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.lookup = lookup, .lookup = lookup,
@ -297,7 +296,7 @@ pub fn BearerMulti(comptime Lookup: type) type {
/// Deinit the authenticator. Only required if a realm was provided at /// Deinit the authenticator. Only required if a realm was provided at
/// init() time. /// init() time.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BearerMultiAuth) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
@ -305,7 +304,7 @@ pub fn BearerMulti(comptime Lookup: type) type {
/// Try to authenticate based on the header. /// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *BearerMultiAuth, auth_header: []const u8) AuthResult {
if (checkAuthHeader(.Bearer, auth_header) == false) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
} }
@ -316,7 +315,7 @@ pub fn BearerMulti(comptime Lookup: type) type {
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// Tries to extract the authentication header and perform the authentication. /// Tries to extract the authentication header and perform the authentication.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { pub fn authenticateRequest(self: *BearerMultiAuth, r: *const zap.Request) AuthResult {
if (extractAuthHeader(.Bearer, r)) |auth_header| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(auth_header); return self.authenticate(auth_header);
} }
@ -338,7 +337,7 @@ pub const UserPassSessionArgs = struct {
/// cookie max age in seconds; 0 -> session cookie /// cookie max age in seconds; 0 -> session cookie
cookieMaxAge: u8 = 0, cookieMaxAge: u8 = 0,
/// redirect status code, defaults to 302 found /// redirect status code, defaults to 302 found
redirectCode: zap.StatusCode = .found, redirectCode: zap.http.StatusCode = .found,
}; };
/// UserPassSession supports the following use case: /// UserPassSession supports the following use case:
@ -387,7 +386,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
passwordLookupLock: std.Thread.Mutex = .{}, passwordLookupLock: std.Thread.Mutex = .{},
tokenLookupLock: std.Thread.Mutex = .{}, tokenLookupLock: std.Thread.Mutex = .{},
const Self = @This(); const UserPassSessionAuth = @This();
const SessionTokenMap = std.StringHashMap(void); const SessionTokenMap = std.StringHashMap(void);
const Hash = std.crypto.hash.sha2.Sha256; const Hash = std.crypto.hash.sha2.Sha256;
@ -399,8 +398,8 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
lookup: *Lookup, lookup: *Lookup,
args: UserPassSessionArgs, args: UserPassSessionArgs,
) !Self { ) !UserPassSessionAuth {
const ret: Self = .{ const ret: UserPassSessionAuth = .{
.allocator = allocator, .allocator = allocator,
.settings = .{ .settings = .{
.usernameParam = try allocator.dupe(u8, args.usernameParam), .usernameParam = try allocator.dupe(u8, args.usernameParam),
@ -418,7 +417,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
/// De-init this authenticator. /// De-init this authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *UserPassSessionAuth) void {
self.allocator.free(self.settings.usernameParam); self.allocator.free(self.settings.usernameParam);
self.allocator.free(self.settings.passwordParam); self.allocator.free(self.settings.passwordParam);
self.allocator.free(self.settings.loginPage); self.allocator.free(self.settings.loginPage);
@ -433,7 +432,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
/// Check for session token cookie, remove the token from the valid tokens /// Check for session token cookie, remove the token from the valid tokens
pub fn logout(self: *Self, r: *const zap.Request) void { pub fn logout(self: *UserPassSessionAuth, r: *const zap.Request) void {
// we erase the list of valid tokens server-side (later) and set the // we erase the list of valid tokens server-side (later) and set the
// cookie to "invalid" on the client side. // cookie to "invalid" on the client side.
if (r.setCookie(.{ if (r.setCookie(.{
@ -449,15 +448,15 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
r.parseCookies(false); r.parseCookies(false);
// check for session cookie // check for session cookie
if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { if (r.getCookieStr(self.allocator, self.settings.cookieName)) |maybe_cookie| {
if (maybe_cookie) |cookie| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer self.allocator.free(cookie);
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();
if (self.sessionTokens.getKeyPtr(cookie.str)) |keyPtr| { if (self.sessionTokens.getKeyPtr(cookie)) |keyPtr| {
const keySlice = keyPtr.*; const keySlice = keyPtr.*;
// if cookie is a valid session, remove it! // if cookie is a valid session, remove it!
_ = self.sessionTokens.remove(cookie.str); _ = self.sessionTokens.remove(cookie);
// only now can we let go of the cookie str slice that // only now can we let go of the cookie str slice that
// was used as the key // was used as the key
self.allocator.free(keySlice); self.allocator.free(keySlice);
@ -468,7 +467,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
} }
fn _internal_authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { fn _internal_authenticateRequest(self: *UserPassSessionAuth, r: *const zap.Request) AuthResult {
// if we're requesting the login page, let the request through // if we're requesting the login page, let the request through
if (r.path) |p| { if (r.path) |p| {
if (std.mem.startsWith(u8, p, self.settings.loginPage)) { if (std.mem.startsWith(u8, p, self.settings.loginPage)) {
@ -485,18 +484,18 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
r.parseCookies(false); r.parseCookies(false);
// check for session cookie // check for session cookie
if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { if (r.getCookieStr(self.allocator, self.settings.cookieName)) |maybe_cookie| {
if (maybe_cookie) |cookie| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer self.allocator.free(cookie);
// locked or unlocked token lookup // locked or unlocked token lookup
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();
if (self.sessionTokens.contains(cookie.str)) { if (self.sessionTokens.contains(cookie)) {
// cookie is a valid session! // cookie is a valid session!
zap.debug("Auth: COOKIE IS OK!!!!: {s}\n", .{cookie.str}); zap.debug("Auth: COOKIE IS OK!!!!: {s}\n", .{cookie});
return .AuthOK; return .AuthOK;
} else { } else {
zap.debug("Auth: COOKIE IS BAD!!!!: {s}\n", .{cookie.str}); zap.debug("Auth: COOKIE IS BAD!!!!: {s}\n", .{cookie});
// this is not necessarily a bad thing. it could be a // this is not necessarily a bad thing. it could be a
// stale cookie from a previous session. So let's check // stale cookie from a previous session. So let's check
// if username and password are being sent and correct. // if username and password are being sent and correct.
@ -507,27 +506,26 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
// get params of username and password // get params of username and password
if (r.getParamStr(self.allocator, self.settings.usernameParam, false)) |maybe_username| { if (r.getParamStr(self.allocator, self.settings.usernameParam)) |maybe_username| {
if (maybe_username) |*username| { if (maybe_username) |username| {
defer username.deinit(); defer self.allocator.free(username);
if (r.getParamStr(self.allocator, self.settings.passwordParam, false)) |maybe_pw| { if (r.getParamStr(self.allocator, self.settings.passwordParam)) |maybe_pw| {
if (maybe_pw) |*pw| { if (maybe_pw) |pw| {
defer pw.deinit(); defer self.allocator.free(pw);
// now check // now check
const correct_pw_optional = brk: { const correct_pw_optional = brk: {
if (lockedPwLookups) { if (lockedPwLookups) {
self.passwordLookupLock.lock(); self.passwordLookupLock.lock();
defer self.passwordLookupLock.unlock(); defer self.passwordLookupLock.unlock();
break :brk self.lookup.*.get(username.str); break :brk self.lookup.*.get(username);
} else { } else {
break :brk self.lookup.*.get(username.str); break :brk self.lookup.*.get(username);
} }
}; };
if (correct_pw_optional) |correct_pw| { if (correct_pw_optional) |correct_pw| {
if (std.mem.eql(u8, pw.str, correct_pw)) { if (std.mem.eql(u8, pw, correct_pw)) {
// create session token // create session token
if (self.createAndStoreSessionToken(username.str, pw.str)) |token| { if (self.createAndStoreSessionToken(username, pw)) |token| {
defer self.allocator.free(token); defer self.allocator.free(token);
// now set the cookie header // now set the cookie header
if (r.setCookie(.{ if (r.setCookie(.{
@ -548,12 +546,12 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
} }
} else |err| { } else |err| {
zap.debug("getParamSt() for password failed in UserPassSession: {any}", .{err}); zap.debug("getParamStr() for password failed in UserPassSession: {any}", .{err});
return .AuthFailed; return .AuthFailed;
} }
} }
} else |err| { } else |err| {
zap.debug("getParamSt() for user failed in UserPassSession: {any}", .{err}); zap.debug("getParamStr() for user failed in UserPassSession: {any}", .{err});
return .AuthFailed; return .AuthFailed;
} }
return .AuthFailed; return .AuthFailed;
@ -562,7 +560,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// See above for how it works. /// See above for how it works.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { pub fn authenticateRequest(self: *UserPassSessionAuth, r: *const zap.Request) AuthResult {
switch (self._internal_authenticateRequest(r)) { switch (self._internal_authenticateRequest(r)) {
.AuthOK => { .AuthOK => {
// username and pass are ok -> created token, set header, caller can continue // username and pass are ok -> created token, set header, caller can continue
@ -582,11 +580,11 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
} }
fn redirect(self: *Self, r: *const zap.Request) !void { fn redirect(self: *UserPassSessionAuth, r: *const zap.Request) !void {
try r.redirectTo(self.settings.loginPage, self.settings.redirectCode); try r.redirectTo(self.settings.loginPage, self.settings.redirectCode);
} }
fn createSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 { fn createSessionToken(self: *UserPassSessionAuth, username: []const u8, password: []const u8) ![]const u8 {
var hasher = Hash.init(.{}); var hasher = Hash.init(.{});
hasher.update(username); hasher.update(username);
hasher.update(password); hasher.update(password);
@ -602,7 +600,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
return token_str; return token_str;
} }
fn createAndStoreSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 { fn createAndStoreSessionToken(self: *UserPassSessionAuth, username: []const u8, password: []const u8) ![]const u8 {
const token = try self.createSessionToken(username, password); const token = try self.createSessionToken(username, password);
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();

View file

@ -5,15 +5,15 @@ const std = @import("std");
debugOn: bool, debugOn: bool,
/// Access to facil.io's logging facilities /// Access to facil.io's logging facilities
const Self = @This(); const Log = @This();
pub fn init(comptime debug: bool) Self { pub fn init(comptime debug: bool) Log {
return .{ return .{
.debugOn = debug, .debugOn = debug,
}; };
} }
pub fn log(self: *const Self, comptime fmt: []const u8, args: anytype) void { pub fn log(self: *const Log, comptime fmt: []const u8, args: anytype) void {
if (self.debugOn) { if (self.debugOn) {
std.debug.print("[zap] - " ++ fmt, args); std.debug.print("[zap] - " ++ fmt, args);
} }

View file

@ -17,7 +17,7 @@ pub fn Handler(comptime ContextType: anytype) type {
// will be set // will be set
allocator: ?std.mem.Allocator = null, allocator: ?std.mem.Allocator = null,
pub const RequestFn = *const fn (*Self, zap.Request, *ContextType) bool; pub const RequestFn = *const fn (*Self, zap.Request, *ContextType) anyerror!bool;
const Self = @This(); const Self = @This();
pub fn init(on_request: RequestFn, other: ?*Self) Self { pub fn init(on_request: RequestFn, other: ?*Self) Self {
@ -30,7 +30,7 @@ pub fn Handler(comptime ContextType: anytype) type {
// example for handling a request request // example for handling a request request
// which you can use in your components, e.g.: // which you can use in your components, e.g.:
// return self.handler.handleOther(r, context); // return self.handler.handleOther(r, context);
pub fn handleOther(self: *Self, r: zap.Request, context: *ContextType) bool { pub fn handleOther(self: *Self, r: zap.Request, context: *ContextType) !bool {
// in structs embedding a handler, we'd @fieldParentPtr the first // in structs embedding a handler, we'd @fieldParentPtr the first
// param to get to the real self // param to get to the real self
@ -41,7 +41,7 @@ pub fn Handler(comptime ContextType: anytype) type {
var other_handler_finished = false; var other_handler_finished = false;
if (self.other_handler) |other_handler| { if (self.other_handler) |other_handler| {
if (other_handler.on_request) |on_request| { if (other_handler.on_request) |on_request| {
other_handler_finished = on_request(other_handler, r, context); other_handler_finished = try on_request(other_handler, r, context);
} }
} }
@ -63,10 +63,10 @@ pub const EndpointHandlerOptions = struct {
}; };
/// A convenience handler for artibrary zap.Endpoint /// A convenience handler for artibrary zap.Endpoint
pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anytype) type { pub fn EndpointHandler(comptime HandlerType: anytype, comptime EndpointType: anytype, comptime ContextType: anytype) type {
return struct { return struct {
handler: HandlerType, handler: HandlerType,
endpoint: *zap.Endpoint, endpoint: *EndpointType,
options: EndpointHandlerOptions, options: EndpointHandlerOptions,
const Self = @This(); const Self = @This();
@ -78,7 +78,7 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt
/// ///
/// If the `breakOnFinish` option is `true`, the handler will stop handing requests down the chain /// If the `breakOnFinish` option is `true`, the handler will stop handing requests down the chain
/// if the endpoint processed the request. /// if the endpoint processed the request.
pub fn init(endpoint: *zap.Endpoint, other: ?*HandlerType, options: EndpointHandlerOptions) Self { pub fn init(endpoint: *EndpointType, other: ?*HandlerType, options: EndpointHandlerOptions) Self {
return .{ return .{
.handler = HandlerType.init(onRequest, other), .handler = HandlerType.init(onRequest, other),
.endpoint = endpoint, .endpoint = endpoint,
@ -96,13 +96,21 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt
/// ///
/// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if /// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if
/// the endpoint processed the request. /// the endpoint processed the request.
pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool { pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) !bool {
const self: *Self = @fieldParentPtr("handler", handler); const self: *Self = @fieldParentPtr("handler", handler);
r.setUserContext(context); r.setUserContext(context);
if (!self.options.checkPath or if (!self.options.checkPath or
std.mem.startsWith(u8, r.path orelse "", self.endpoint.settings.path)) std.mem.startsWith(u8, r.path orelse "", self.endpoint.path))
{ {
self.endpoint.onRequest(r); switch (r.methodAsEnum()) {
.GET => try self.endpoint.*.get(r),
.POST => try self.endpoint.*.post(r),
.PUT => try self.endpoint.*.put(r),
.DELETE => try self.endpoint.*.delete(r),
.PATCH => try self.endpoint.*.patch(r),
.OPTIONS => try self.endpoint.*.options(r),
else => {},
}
} }
// if the request was handled by the endpoint, we may break the chain here // if the request was handled by the endpoint, we may break the chain here
@ -168,7 +176,7 @@ pub fn Listener(comptime ContextType: anytype) type {
/// Create your own listener if you want different behavior. /// Create your own listener if you want different behavior.
/// (Didn't want to make this a callback. Submit an issue if you really /// (Didn't want to make this a callback. Submit an issue if you really
/// think that's an issue). /// think that's an issue).
pub fn onRequest(r: zap.Request) void { pub fn onRequest(r: zap.Request) !void {
// we are the 1st handler in the chain, so we create a context // we are the 1st handler in the chain, so we create a context
var context: ContextType = .{}; var context: ContextType = .{};
@ -183,7 +191,7 @@ pub fn Listener(comptime ContextType: anytype) type {
initial_handler.allocator = allocator; initial_handler.allocator = allocator;
if (initial_handler.on_request) |on_request| { if (initial_handler.on_request) |on_request| {
// we don't care about the return value at the top level // we don't care about the return value at the top level
_ = on_request(initial_handler, r, &context); _ = try on_request(initial_handler, r, &context);
} }
} }
} }

View file

@ -2,7 +2,7 @@ const std = @import("std");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const Self = @This(); const Mustache = @This();
const struct_mustache_s = opaque {}; const struct_mustache_s = opaque {};
const mustache_s = struct_mustache_s; const mustache_s = struct_mustache_s;
@ -51,7 +51,7 @@ pub const Error = error{
/// Create a new `Mustache` instance; `deinit()` should be called to free /// Create a new `Mustache` instance; `deinit()` should be called to free
/// the object after usage. /// the object after usage.
pub fn init(load_args: LoadArgs) Error!Self { pub fn init(load_args: LoadArgs) Error!Mustache {
var err: mustache_error_en = undefined; var err: mustache_error_en = undefined;
const args: MustacheLoadArgsFio = .{ const args: MustacheLoadArgsFio = .{
@ -72,7 +72,7 @@ pub fn init(load_args: LoadArgs) Error!Self {
const ret = fiobj_mustache_new(args); const ret = fiobj_mustache_new(args);
switch (err) { switch (err) {
0 => return Self{ 0 => return Mustache{
.handle = ret.?, .handle = ret.?,
}, },
1 => return Error.MUSTACHE_ERR_TOO_DEEP, 1 => return Error.MUSTACHE_ERR_TOO_DEEP,
@ -93,18 +93,18 @@ pub fn init(load_args: LoadArgs) Error!Self {
/// Convenience function to create a new `Mustache` instance with in-memory data loaded; /// Convenience function to create a new `Mustache` instance with in-memory data loaded;
/// `deinit()` should be called to free the object after usage.. /// `deinit()` should be called to free the object after usage..
pub fn fromData(data: []const u8) Error!Self { pub fn fromData(data: []const u8) Error!Mustache {
return Self.init(.{ .data = data }); return Mustache.init(.{ .data = data });
} }
/// Convenience function to create a new `Mustache` instance with file-based data loaded; /// Convenience function to create a new `Mustache` instance with file-based data loaded;
/// `deinit()` should be called to free the object after usage.. /// `deinit()` should be called to free the object after usage..
pub fn fromFile(filename: []const u8) Error!Self { pub fn fromFile(filename: []const u8) Error!Mustache {
return Self.init(.{ .filename = filename }); return Mustache.init(.{ .filename = filename });
} }
/// Free the data backing a `Mustache` instance. /// Free the data backing a `Mustache` instance.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Mustache) void {
fiobj_mustache_free(self.handle); fiobj_mustache_free(self.handle);
} }
@ -137,7 +137,7 @@ pub const BuildResult = struct {
// TODO: The build may be slow because it needs to convert zig types to facil.io // TODO: The build may be slow because it needs to convert zig types to facil.io
// types. However, this needs to be investigated into. // types. However, this needs to be investigated into.
// See `fiobjectify` for more information. // See `fiobjectify` for more information.
pub fn build(self: *Self, data: anytype) BuildResult { pub fn build(self: *Mustache, data: anytype) BuildResult {
const T = @TypeOf(data); const T = @TypeOf(data);
if (@typeInfo(T) != .@"struct") { if (@typeInfo(T) != .@"struct") {
@compileError("No struct: '" ++ @typeName(T) ++ "'"); @compileError("No struct: '" ++ @typeName(T) ++ "'");

View file

@ -1,4 +1,6 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator;
const Log = @import("log.zig"); const Log = @import("log.zig");
const http = @import("http.zig"); const http = @import("http.zig");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
@ -20,21 +22,19 @@ pub const HttpError = error{
/// Key value pair of strings from HTTP parameters /// Key value pair of strings from HTTP parameters
pub const HttpParamStrKV = struct { pub const HttpParamStrKV = struct {
key: util.FreeOrNot, key: []const u8,
value: util.FreeOrNot, value: []const u8,
pub fn deinit(self: *@This()) void {
self.key.deinit();
self.value.deinit();
}
}; };
/// List of key value pairs of Http param strings. /// List of key value pairs of Http param strings.
pub const HttpParamStrKVList = struct { pub const HttpParamStrKVList = struct {
items: []HttpParamStrKV, items: []HttpParamStrKV,
allocator: std.mem.Allocator, allocator: Allocator,
pub fn deinit(self: *@This()) void {
for (self.items) |*item| { pub fn deinit(self: *HttpParamStrKVList) void {
item.deinit(); for (self.items) |item| {
self.allocator.free(item.key);
self.allocator.free(item.value);
} }
self.allocator.free(self.items); self.allocator.free(self.items);
} }
@ -43,10 +43,13 @@ pub const HttpParamStrKVList = struct {
/// List of key value pairs of Http params (might be of different types). /// List of key value pairs of Http params (might be of different types).
pub const HttpParamKVList = struct { pub const HttpParamKVList = struct {
items: []HttpParamKV, items: []HttpParamKV,
allocator: std.mem.Allocator, allocator: Allocator,
pub fn deinit(self: *const @This()) void { pub fn deinit(self: *const HttpParamKVList) void {
for (self.items) |*item| { for (self.items) |item| {
item.deinit(); self.allocator.free(item.key);
if (item.value) |v| {
v.free(self.allocator);
}
} }
self.allocator.free(self.items); self.allocator.free(self.items);
} }
@ -70,28 +73,31 @@ pub const HttpParam = union(HttpParamValueType) {
Int: isize, Int: isize,
Float: f64, Float: f64,
/// we don't do writable strings here /// we don't do writable strings here
String: util.FreeOrNot, String: []const u8,
/// value will always be null /// value will always be null
Unsupported: ?void, Unsupported: ?void,
/// we assume hashes are because of file transmissions /// we assume hashes are because of file transmissions
Hash_Binfile: HttpParamBinaryFile, Hash_Binfile: HttpParamBinaryFile,
/// value will always be null /// value will always be null
Array_Binfile: std.ArrayList(HttpParamBinaryFile), Array_Binfile: std.ArrayList(HttpParamBinaryFile),
pub fn free(self: HttpParam, alloc: Allocator) void {
switch (self) {
.String => |s| alloc.free(s),
.Array_Binfile => |a| {
a.deinit();
},
else => {
// nothing to free
},
}
}
}; };
/// Key value pair of one typed Http param /// Key value pair of one typed Http param
pub const HttpParamKV = struct { pub const HttpParamKV = struct {
key: util.FreeOrNot, key: []const u8,
value: ?HttpParam, value: ?HttpParam,
pub fn deinit(self: *@This()) void {
self.key.deinit();
if (self.value) |p| {
switch (p) {
.String => |*s| s.deinit(),
else => {},
}
}
}
}; };
/// Struct representing an uploaded file. /// Struct representing an uploaded file.
@ -104,7 +110,7 @@ pub const HttpParamBinaryFile = struct {
filename: ?[]const u8 = null, filename: ?[]const u8 = null,
/// format function for printing file upload data /// format function for printing file upload data
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { pub fn format(value: HttpParamBinaryFile, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
const d = value.data orelse "\\0"; const d = value.data orelse "\\0";
const m = value.mimetype orelse "null"; const m = value.mimetype orelse "null";
const f = value.filename orelse "null"; const f = value.filename orelse "null";
@ -112,7 +118,7 @@ pub const HttpParamBinaryFile = struct {
} }
}; };
fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam { fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
const key_name = fio.fiobj_str_new("name", 4); const key_name = fio.fiobj_str_new("name", 4);
const key_data = fio.fiobj_str_new("data", 4); const key_data = fio.fiobj_str_new("data", 4);
const key_type = fio.fiobj_str_new("type", 4); const key_type = fio.fiobj_str_new("type", 4);
@ -225,14 +231,15 @@ fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam {
} }
/// Parse FIO object into a typed Http param. Supports file uploads. /// Parse FIO object into a typed Http param. Supports file uploads.
pub fn Fiobj2HttpParam(a: std.mem.Allocator, o: fio.FIOBJ, dupe_string: bool) !?HttpParam { /// Allocator is only used for file uploads.
pub fn fiobj2HttpParam(a: Allocator, o: fio.FIOBJ) !?HttpParam {
return switch (fio.fiobj_type(o)) { return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_NULL => null, fio.FIOBJ_T_NULL => null,
fio.FIOBJ_T_TRUE => .{ .Bool = true }, fio.FIOBJ_T_TRUE => .{ .Bool = true },
fio.FIOBJ_T_FALSE => .{ .Bool = false }, fio.FIOBJ_T_FALSE => .{ .Bool = false },
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) }, fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) }, fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(a, o, dupe_string) }, fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAlloc(a, o) },
fio.FIOBJ_T_ARRAY => { fio.FIOBJ_T_ARRAY => {
return .{ .Unsupported = null }; return .{ .Unsupported = null };
}, },
@ -280,30 +287,30 @@ pub const UserContext = struct {
user_context: ?*anyopaque = null, user_context: ?*anyopaque = null,
}; };
const Self = @This(); const Request = @This();
/// mark the current request as finished. Important for middleware-style /// mark the current request as finished. Important for middleware-style
/// request handler chaining. Called when sending a body, redirecting, etc. /// request handler chaining. Called when sending a body, redirecting, etc.
pub fn markAsFinished(self: *const Self, finished: bool) void { pub fn markAsFinished(self: *const Request, finished: bool) void {
// we might be a copy // we might be a copy
self._is_finished.* = finished; self._is_finished.* = finished;
} }
/// tell whether request processing has finished. (e.g. response sent, /// tell whether request processing has finished. (e.g. response sent,
/// redirected, ...) /// redirected, ...)
pub fn isFinished(self: *const Self) bool { pub fn isFinished(self: *const Request) bool {
// we might be a copy // we might be a copy
return self._is_finished.*; return self._is_finished.*;
} }
/// if you absolutely must, you can set any context on the request here /// if you absolutely must, you can set any context on the request here
// (note, this line is linked to from the readme) -- TODO: sync // (note, this line is linked to from the readme) -- TODO: sync
pub fn setUserContext(self: *const Self, context: *anyopaque) void { pub fn setUserContext(self: *const Request, context: *anyopaque) void {
self._user_context.*.user_context = context; self._user_context.*.user_context = context;
} }
/// get the associated user context of the request. /// get the associated user context of the request.
pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context { pub fn getUserContext(self: *const Request, comptime Context: type) ?*Context {
if (self._user_context.*.user_context) |ptr| { if (self._user_context.*.user_context) |ptr| {
return @as(*Context, @ptrCast(@alignCast(ptr))); return @as(*Context, @ptrCast(@alignCast(ptr)));
} else { } else {
@ -316,7 +323,7 @@ pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context {
/// const err = zap.HttpError; // this is to show that `err` is an Error /// const err = zap.HttpError; // this is to show that `err` is an Error
/// r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); /// 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 { pub fn sendError(self: *const Request, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) void {
// TODO: query accept headers // TODO: query accept headers
if (self._internal_sendError(err, err_trace, errorcode_num)) { if (self._internal_sendError(err, err_trace, errorcode_num)) {
return; return;
@ -326,7 +333,7 @@ pub fn sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.Stack
} }
/// Used internally. Probably does not need to be public. /// Used internally. Probably does not need to be public.
pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) !void { pub fn _internal_sendError(self: *const Request, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) !void {
// TODO: query accept headers // TODO: query accept headers
// TODO: let's hope 20k is enough. Maybe just really allocate here // TODO: let's hope 20k is enough. Maybe just really allocate here
self.h.*.status = errorcode_num; self.h.*.status = errorcode_num;
@ -346,7 +353,7 @@ pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.bui
} }
/// Send body. /// Send body.
pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { pub fn sendBody(self: *const Request, body: []const u8) HttpError!void {
const ret = fio.http_send_body(self.h, @as( const ret = fio.http_send_body(self.h, @as(
*anyopaque, *anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)), @ptrFromInt(@intFromPtr(body.ptr)),
@ -357,7 +364,7 @@ pub fn sendBody(self: *const Self, body: []const u8) HttpError!void {
} }
/// Set content type and send json buffer. /// Set content type and send json buffer.
pub fn sendJson(self: *const Self, json: []const u8) HttpError!void { pub fn sendJson(self: *const Request, json: []const u8) HttpError!void {
if (self.setContentType(.JSON)) { if (self.setContentType(.JSON)) {
if (fio.http_send_body(self.h, @as( if (fio.http_send_body(self.h, @as(
*anyopaque, *anyopaque,
@ -368,7 +375,7 @@ pub fn sendJson(self: *const Self, json: []const u8) HttpError!void {
} }
/// Set content type. /// Set content type.
pub fn setContentType(self: *const Self, c: ContentType) HttpError!void { pub fn setContentType(self: *const Request, c: ContentType) HttpError!void {
const s = switch (c) { const s = switch (c) {
.TEXT => "text/plain", .TEXT => "text/plain",
.JSON => "application/json", .JSON => "application/json",
@ -379,7 +386,7 @@ pub fn setContentType(self: *const Self, c: ContentType) HttpError!void {
} }
/// redirect to path with status code 302 by default /// redirect to path with status code 302 by default
pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void { pub fn redirectTo(self: *const Request, path: []const u8, code: ?http.StatusCode) HttpError!void {
self.setStatus(if (code) |status| status else .found); self.setStatus(if (code) |status| status else .found);
try self.setHeader("Location", path); try self.setHeader("Location", path);
try self.sendBody("moved"); try self.sendBody("moved");
@ -388,7 +395,7 @@ pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) H
/// shows how to use the logger /// shows how to use the logger
pub fn setContentTypeWithLogger( pub fn setContentTypeWithLogger(
self: *const Self, self: *const Request,
c: ContentType, c: ContentType,
logger: *const Log, logger: *const Log,
) HttpError!void { ) HttpError!void {
@ -402,7 +409,7 @@ pub fn setContentTypeWithLogger(
} }
/// Tries to determine the content type by file extension of request path, and sets it. /// Tries to determine the content type by file extension of request path, and sets it.
pub fn setContentTypeFromPath(self: *const Self) !void { pub fn setContentTypeFromPath(self: *const Request) !void {
const t = fio.http_mimetype_find2(self.h.*.path); const t = fio.http_mimetype_find2(self.h.*.path);
if (fio.is_invalid(t) == 1) return error.HttpSetContentType; if (fio.is_invalid(t) == 1) return error.HttpSetContentType;
const ret = fio.fiobj_hash_set( const ret = fio.fiobj_hash_set(
@ -416,13 +423,14 @@ pub fn setContentTypeFromPath(self: *const Self) !void {
/// Tries to determine the content type by filename extension, and sets it. /// Tries to determine the content type by filename extension, and sets it.
/// If the extension cannot be determined, NoExtensionInFilename error is /// If the extension cannot be determined, NoExtensionInFilename error is
/// returned. /// returned.
pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void { pub fn setContentTypeFromFilename(self: *const Request, filename: []const u8) !void {
const ext = std.fs.path.extension(filename); const ext = std.fs.path.extension(filename);
if (ext.len > 1) { if (ext.len > 1) {
const e = ext[1..]; const e = ext[1..];
const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len); const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len);
// fio2str is OK since setHeader takes a copy
if (util.fio2str(obj)) |mime_str| { if (util.fio2str(obj)) |mime_str| {
try self.setHeader("content-type", mime_str); try self.setHeader("content-type", mime_str);
} }
@ -435,9 +443,11 @@ pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void
/// NOTE that header-names are lowerased automatically while parsing the request. /// NOTE that header-names are lowerased automatically while parsing the request.
/// so please only use lowercase keys! /// so please only use lowercase keys!
/// Returned mem is temp. Do not free it. /// Returned mem is temp. Do not free it.
pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 { pub fn getHeader(self: *const Request, name: []const u8) ?[]const u8 {
const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len); const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len);
defer fio.fiobj_free_wrapped(hname); defer fio.fiobj_free_wrapped(hname);
// fio2str is OK since headers are always strings -> slice is returned
// (and not a slice into some threadlocal "global")
return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname)); return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname));
} }
@ -476,7 +486,7 @@ pub const HttpHeaderCommon = enum(usize) {
/// Returns the header value of a given common header key. Returned memory /// Returns the header value of a given common header key. Returned memory
/// should not be freed. /// should not be freed.
pub fn getHeaderCommon(self: *const Self, which: HttpHeaderCommon) ?[]const u8 { pub fn getHeaderCommon(self: *const Request, which: HttpHeaderCommon) ?[]const u8 {
const field = switch (which) { const field = switch (which) {
.accept => fio.HTTP_HEADER_ACCEPT, .accept => fio.HTTP_HEADER_ACCEPT,
.cache_control => fio.HTTP_HEADER_CACHE_CONTROL, .cache_control => fio.HTTP_HEADER_CACHE_CONTROL,
@ -495,11 +505,13 @@ pub fn getHeaderCommon(self: *const Self, which: HttpHeaderCommon) ?[]const u8 {
.upgrade => fio.HTTP_HEADER_UPGRADE, .upgrade => fio.HTTP_HEADER_UPGRADE,
}; };
const fiobj = zap.fio.fiobj_hash_get(self.h.*.headers, field); const fiobj = zap.fio.fiobj_hash_get(self.h.*.headers, field);
return zap.fio2str(fiobj); // fio2str is OK since headers are always strings -> slice is returned
// (and not a slice into some threadlocal "global")
return zap.util.fio2str(fiobj);
} }
/// Set header. /// Set header.
pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void { pub fn setHeader(self: *const Request, name: []const u8, value: []const u8) HttpError!void {
const hname: fio.fio_str_info_s = .{ const hname: fio.fio_str_info_s = .{
.data = util.toCharPtr(name), .data = util.toCharPtr(name),
.len = name.len, .len = name.len,
@ -526,13 +538,26 @@ pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpErr
return error.HttpSetHeader; return error.HttpSetHeader;
} }
pub fn headersToOwnedList(self: *const Request, a: Allocator) !HttpParamStrKVList {
var headers = std.ArrayList(HttpParamStrKV).init(a);
var context: CallbackContext_StrKV = .{
.params = &headers,
.allocator = a,
};
const howmany = fio.fiobj_each1(self.h.*.headers, 0, CallbackContext_StrKV.callback, &context);
if (howmany != headers.items.len) {
return error.HttpIterHeaders;
}
return .{ .items = try headers.toOwnedSlice(), .allocator = a };
}
/// Set status by numeric value. /// Set status by numeric value.
pub fn setStatusNumeric(self: *const Self, status: usize) void { pub fn setStatusNumeric(self: *const Request, status: usize) void {
self.h.*.status = status; self.h.*.status = status;
} }
/// Set status by enum. /// Set status by enum.
pub fn setStatus(self: *const Self, status: http.StatusCode) void { pub fn setStatus(self: *const Request, status: http.StatusCode) void {
self.h.*.status = @as(usize, @intCast(@intFromEnum(status))); self.h.*.status = @as(usize, @intCast(@intFromEnum(status)));
} }
@ -547,7 +572,7 @@ pub fn setStatus(self: *const Self, status: http.StatusCode) void {
/// ///
/// Important: sets last-modified and cache-control headers with a max-age value of 1 hour! /// Important: sets last-modified and cache-control headers with a max-age value of 1 hour!
/// You can override that by setting those headers yourself, e.g.: setHeader("Cache-Control", "no-cache") /// You can override that by setting those headers yourself, e.g.: setHeader("Cache-Control", "no-cache")
pub fn sendFile(self: *const Self, file_path: []const u8) !void { pub fn sendFile(self: *const Request, file_path: []const u8) !void {
if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0) if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0)
return error.SendFile; return error.SendFile;
self.markAsFinished(true); self.markAsFinished(true);
@ -561,7 +586,7 @@ pub fn sendFile(self: *const Self, file_path: []const u8) !void {
/// - application/x-www-form-urlencoded /// - application/x-www-form-urlencoded
/// - application/json /// - application/json
/// - multipart/form-data /// - multipart/form-data
pub fn parseBody(self: *const Self) HttpError!void { pub fn parseBody(self: *const Request) HttpError!void {
if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody; if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody;
} }
@ -570,12 +595,12 @@ pub fn parseBody(self: *const Self) HttpError!void {
/// object that doesn't have a hash map at its root. /// object that doesn't have a hash map at its root.
/// ///
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice() /// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
pub fn parseQuery(self: *const Self) void { pub fn parseQuery(self: *const Request) void {
fio.http_parse_query(self.h); fio.http_parse_query(self.h);
} }
/// Parse received cookie headers /// Parse received cookie headers
pub fn parseCookies(self: *const Self, url_encoded: bool) void { pub fn parseCookies(self: *const Request, url_encoded: bool) void {
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0); fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
} }
@ -606,7 +631,7 @@ pub const AcceptItem = struct {
const AcceptHeaderList = std.ArrayList(AcceptItem); const AcceptHeaderList = std.ArrayList(AcceptItem);
/// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest /// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest
pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !AcceptHeaderList { pub fn parseAcceptHeaders(self: *const Request, allocator: Allocator) !AcceptHeaderList {
const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader; const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader;
const comma_count = std.mem.count(u8, accept_str, ","); const comma_count = std.mem.count(u8, accept_str, ",");
@ -652,7 +677,7 @@ pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !Acce
} }
/// Set a response cookie /// Set a response cookie
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { pub fn setCookie(self: *const Request, args: CookieArgs) HttpError!void {
const c: fio.http_cookie_args_s = .{ const c: fio.http_cookie_args_s = .{
.name = util.toCharPtr(args.name), .name = util.toCharPtr(args.name),
.name_len = @as(isize, @intCast(args.name.len)), .name_len = @as(isize, @intCast(args.name.len)),
@ -681,7 +706,7 @@ pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
} }
/// Returns named cookie. Works like getParamStr(). /// Returns named cookie. Works like getParamStr().
pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { pub fn getCookieStr(self: *const Request, a: Allocator, name: []const u8) !?[]const u8 {
if (self.h.*.cookies == 0) return null; if (self.h.*.cookies == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); defer fio.fiobj_free_wrapped(key);
@ -689,13 +714,15 @@ pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, a
if (value == fio.FIOBJ_INVALID) { if (value == fio.FIOBJ_INVALID) {
return null; return null;
} }
return try util.fio2strAllocOrNot(a, value, always_alloc); // we are not entirely sure if cookies fiobjs are always strings
// hence. fio2strAlloc
return try util.fio2strAlloc(a, value);
} }
/// Returns the number of cookies after parsing. /// Returns the number of cookies after parsing.
/// ///
/// Parse with parseCookies() /// Parse with parseCookies()
pub fn getCookiesCount(self: *const Self) isize { pub fn getCookiesCount(self: *const Request) isize {
if (self.h.*.cookies == 0) return 0; if (self.h.*.cookies == 0) return 0;
return fio.fiobj_obj2num(self.h.*.cookies); return fio.fiobj_obj2num(self.h.*.cookies);
} }
@ -703,20 +730,77 @@ pub fn getCookiesCount(self: *const Self) isize {
/// Returns the number of parameters after parsing. /// Returns the number of parameters after parsing.
/// ///
/// Parse with parseBody() and / or parseQuery() /// Parse with parseBody() and / or parseQuery()
pub fn getParamCount(self: *const Self) isize { pub fn getParamCount(self: *const Request) isize {
if (self.h.*.params == 0) return 0; if (self.h.*.params == 0) return 0;
return fio.fiobj_obj2num(self.h.*.params); return fio.fiobj_obj2num(self.h.*.params);
} }
const CallbackContext_KV = struct {
allocator: Allocator,
params: *std.ArrayList(HttpParamKV),
last_error: ?anyerror = null,
pub fn callback(fiobj_value: fio.FIOBJ, context_: ?*anyopaque) callconv(.C) c_int {
const ctx: *CallbackContext_KV = @as(*CallbackContext_KV, @ptrCast(@alignCast(context_)));
// this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAlloc(ctx.allocator, fiobj_key) catch |err| {
ctx.last_error = err;
return -1;
},
.value = fiobj2HttpParam(ctx.allocator, fiobj_value) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
};
const CallbackContext_StrKV = struct {
allocator: Allocator,
params: *std.ArrayList(HttpParamStrKV),
last_error: ?anyerror = null,
pub fn callback(fiobj_value: fio.FIOBJ, context_: ?*anyopaque) callconv(.C) c_int {
const ctx: *CallbackContext_StrKV = @as(*CallbackContext_StrKV, @ptrCast(@alignCast(context_)));
// this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAlloc(ctx.allocator, fiobj_key) catch |err| {
ctx.last_error = err;
return -1;
},
.value = util.fio2strAlloc(ctx.allocator, fiobj_value) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
};
/// Same as parametersToOwnedStrList() but for cookies /// Same as parametersToOwnedStrList() but for cookies
pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList { pub fn cookiesToOwnedStrList(self: *const Request, a: Allocator) anyerror!HttpParamStrKVList {
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount()))); var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
var context: _parametersToOwnedStrSliceContext = .{ var context: CallbackContext_StrKV = .{
.params = &params, .params = &params,
.allocator = a, .allocator = a,
.always_alloc = always_alloc,
}; };
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParamStr, &context); const howmany = fio.fiobj_each1(self.h.*.cookies, 0, CallbackContext_StrKV.callback, &context);
if (howmany != self.getCookiesCount()) { if (howmany != self.getCookiesCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
@ -724,10 +808,10 @@ pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_all
} }
/// Same as parametersToOwnedList() but for cookies /// Same as parametersToOwnedList() but for cookies
pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList { pub fn cookiesToOwnedList(self: *const Request, a: Allocator) !HttpParamKVList {
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount()))); var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings }; var context: CallbackContext_KV = .{ .params = &params, .allocator = a };
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context); const howmany = fio.fiobj_each1(self.h.*.cookies, 0, CallbackContext_KV.callback, &context);
if (howmany != self.getCookiesCount()) { if (howmany != self.getCookiesCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
@ -747,50 +831,21 @@ pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings:
/// ///
/// Requires parseBody() and/or parseQuery() have been called. /// Requires parseBody() and/or parseQuery() have been called.
/// Returned list needs to be deinited. /// Returned list needs to be deinited.
pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList { pub fn parametersToOwnedStrList(self: *const Request, a: Allocator) anyerror!HttpParamStrKVList {
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount()))); var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
var context: _parametersToOwnedStrSliceContext = .{
var context: CallbackContext_StrKV = .{
.params = &params, .params = &params,
.allocator = a, .allocator = a,
.always_alloc = always_alloc,
}; };
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context);
const howmany = fio.fiobj_each1(self.h.*.params, 0, CallbackContext_StrKV.callback, &context);
if (howmany != self.getParamCount()) { if (howmany != self.getParamCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
return .{ .items = try params.toOwnedSlice(), .allocator = a }; return .{ .items = try params.toOwnedSlice(), .allocator = a };
} }
const _parametersToOwnedStrSliceContext = struct {
allocator: std.mem.Allocator,
params: *std.ArrayList(HttpParamStrKV),
last_error: ?anyerror = null,
always_alloc: bool,
};
fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedStrSliceContext = @as(*_parametersToOwnedStrSliceContext, @ptrCast(@alignCast(context)));
// this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
.value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// Returns the query / body parameters as key/value pairs /// Returns the query / body parameters as key/value pairs
/// Supported param types that will be converted: /// Supported param types that will be converted:
/// ///
@ -804,47 +859,19 @@ fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C)
/// ///
/// Requires parseBody() and/or parseQuery() have been called. /// Requires parseBody() and/or parseQuery() have been called.
/// Returned slice needs to be freed. /// Returned slice needs to be freed.
pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList { pub fn parametersToOwnedList(self: *const Request, a: Allocator) !HttpParamKVList {
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount()))); var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings };
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context); var context: CallbackContext_KV = .{ .params = &params, .allocator = a };
const howmany = fio.fiobj_each1(self.h.*.params, 0, CallbackContext_KV.callback, &context);
if (howmany != self.getParamCount()) { if (howmany != self.getParamCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
return .{ .items = try params.toOwnedSlice(), .allocator = a }; return .{ .items = try params.toOwnedSlice(), .allocator = a };
} }
const _parametersToOwnedSliceContext = struct { /// get named parameter (parsed) as string
params: *std.ArrayList(HttpParamKV),
last_error: ?anyerror = null,
allocator: std.mem.Allocator,
dupe_strings: bool,
};
fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedSliceContext = @as(*_parametersToOwnedSliceContext, @ptrCast(@alignCast(context)));
// this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
.value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// get named parameter as string
/// Supported param types that will be converted: /// Supported param types that will be converted:
/// ///
/// - Bool /// - Bool
@ -856,8 +883,8 @@ fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_
/// So, for JSON body payloads: parse the body instead. /// So, for JSON body payloads: parse the body instead.
/// ///
/// Requires parseBody() and/or parseQuery() have been called. /// Requires parseBody() and/or parseQuery() have been called.
/// The returned string needs to be deinited with .deinit() /// The returned string needs to be deallocated.
pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { pub fn getParamStr(self: *const Request, a: Allocator, name: []const u8) !?[]const u8 {
if (self.h.*.params == 0) return null; if (self.h.*.params == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); defer fio.fiobj_free_wrapped(key);
@ -865,14 +892,14 @@ pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, al
if (value == fio.FIOBJ_INVALID) { if (value == fio.FIOBJ_INVALID) {
return null; return null;
} }
return try util.fio2strAllocOrNot(a, value, always_alloc); return try util.fio2strAlloc(a, value);
} }
/// similar to getParamStr, except it will return the part of the querystring /// similar to getParamStr, except it will return the part of the querystring
/// after the equals sign, non-decoded, and always as character slice. /// after the equals sign, non-decoded, and always as character slice.
/// - no allocation! /// - no allocation!
/// - does not requre parseQuery() or anything to be called in advance /// - does not requre parseQuery() or anything to be called in advance
pub fn getParamSlice(self: *const Self, name: []const u8) ?[]const u8 { pub fn getParamSlice(self: *const Request, name: []const u8) ?[]const u8 {
if (self.query) |query| { if (self.query) |query| {
var amp_it = std.mem.tokenizeScalar(u8, query, '&'); var amp_it = std.mem.tokenizeScalar(u8, query, '&');
while (amp_it.next()) |maybe_pair| { while (amp_it.next()) |maybe_pair| {
@ -895,13 +922,13 @@ pub const ParameterSlices = struct { name: []const u8, value: []const u8 };
pub const ParamSliceIterator = struct { pub const ParamSliceIterator = struct {
amp_it: std.mem.TokenIterator(u8, .scalar), amp_it: std.mem.TokenIterator(u8, .scalar),
pub fn init(query: []const u8) @This() { pub fn init(query: []const u8) ParamSliceIterator {
return .{ return .{
.amp_it = std.mem.tokenizeScalar(u8, query, '&'), .amp_it = std.mem.tokenizeScalar(u8, query, '&'),
}; };
} }
pub fn next(self: *@This()) ?ParameterSlices { pub fn next(self: *ParamSliceIterator) ?ParameterSlices {
while (self.amp_it.next()) |maybe_pair| { while (self.amp_it.next()) |maybe_pair| {
if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| { if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| {
const pname = maybe_pair[0..pos_of_eq]; const pname = maybe_pair[0..pos_of_eq];
@ -918,11 +945,11 @@ pub const ParamSliceIterator = struct {
/// Returns an iterator that yields all query parameters on next() in the /// Returns an iterator that yields all query parameters on next() in the
/// form of a ParameterSlices struct { .name, .value } /// form of a ParameterSlices struct { .name, .value }
/// As with getParamSlice(), the value is not decoded /// As with getParamSlice(), the value is not decoded
pub fn getParamSlices(self: *const Self) ParamSliceIterator { pub fn getParamSlices(self: *const Request) ParamSliceIterator {
const query = self.query orelse ""; const query = self.query orelse "";
return ParamSliceIterator.init(query); return ParamSliceIterator.init(query);
} }
pub fn methodAsEnum(self: *const Self) http.Method { pub fn methodAsEnum(self: *const Request) http.Method {
return http.methodToEnum(self.method); return http.methodToEnum(self.method);
} }

View file

@ -9,10 +9,10 @@ const RouterError = error{
EmptyPath, EmptyPath,
}; };
const Self = @This(); const Router = @This();
/// This is a singleton /// This is a singleton
var _instance: *Self = undefined; var _instance: *Router = undefined;
/// Options to pass to init() /// Options to pass to init()
pub const Options = struct { pub const Options = struct {
@ -21,7 +21,7 @@ pub const Options = struct {
}; };
const CallbackTag = enum { bound, unbound }; const CallbackTag = enum { bound, unbound };
const BoundHandler = *fn (*const anyopaque, zap.Request) void; const BoundHandler = *fn (*const anyopaque, zap.Request) anyerror!void;
const Callback = union(CallbackTag) { const Callback = union(CallbackTag) {
bound: struct { instance: usize, handler: usize }, bound: struct { instance: usize, handler: usize },
unbound: zap.HttpRequestFn, unbound: zap.HttpRequestFn,
@ -31,7 +31,7 @@ routes: std.StringHashMap(Callback),
not_found: ?zap.HttpRequestFn, not_found: ?zap.HttpRequestFn,
/// Create a new Router /// Create a new Router
pub fn init(allocator: Allocator, options: Options) Self { pub fn init(allocator: Allocator, options: Options) Router {
return .{ return .{
.routes = std.StringHashMap(Callback).init(allocator), .routes = std.StringHashMap(Callback).init(allocator),
@ -40,12 +40,13 @@ pub fn init(allocator: Allocator, options: Options) Self {
} }
/// Deinit the router /// Deinit the router
pub fn deinit(self: *Self) void { pub fn deinit(self: *Router) void {
self.routes.deinit(); self.routes.deinit();
} }
/// Call this to add a route with an unbound handler: a handler that is not member of a struct. /// Call this to add a route with an unbound handler: a handler that is not member of a struct.
pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { /// To be precise: a handler that doesn't take an instance pointer as first argument.
pub fn handle_func_unbound(self: *Router, path: []const u8, h: zap.HttpRequestFn) !void {
if (path.len == 0) { if (path.len == 0) {
return RouterError.EmptyPath; return RouterError.EmptyPath;
} }
@ -71,7 +72,7 @@ pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn)
/// ///
/// my_router.handle_func("/getA", &handler_instance, HandlerType.getA); /// my_router.handle_func("/getA", &handler_instance, HandlerType.getA);
/// ``` /// ```
pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler: anytype) !void { pub fn handle_func(self: *Router, path: []const u8, instance: *anyopaque, handler: anytype) !void {
// TODO: assert type of instance has handler // TODO: assert type of instance has handler
// Introspection checks on handler type // Introspection checks on handler type
@ -81,10 +82,10 @@ pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler:
// Need to check: // Need to check:
// 1) handler is function pointer // 1) handler is function pointer
const f = blk: { const f = blk: {
if (hand_info == .Pointer) { if (hand_info == .pointer) {
const inner = @typeInfo(hand_info.Pointer.child); const inner = @typeInfo(hand_info.pointer.child);
if (inner == .Fn) { if (inner == .@"fn") {
break :blk inner.Fn; break :blk inner.@"fn";
} }
} }
@compileError("Expected handler to be a function pointer. Found " ++ @compileError("Expected handler to be a function pointer. Found " ++
@ -103,8 +104,14 @@ pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler:
// 3) handler returns void // 3) handler returns void
const ret_info = @typeInfo(f.return_type.?); const ret_info = @typeInfo(f.return_type.?);
if (ret_info != .Void) { if (ret_info != .error_union) {
@compileError("Expected handler's return type to be void. Found " ++ @compileError("Expected handler's return type to be !void. Found " ++
@typeName(f.return_type.?));
}
const payload = @typeInfo(ret_info.error_union.payload);
if (payload != .void) {
@compileError("Expected handler's return type to be !void. Found " ++
@typeName(f.return_type.?)); @typeName(f.return_type.?));
} }
} }
@ -124,29 +131,29 @@ pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler:
} }
/// Get the zap request handler function needed for a listener /// Get the zap request handler function needed for a listener
pub fn on_request_handler(self: *Self) zap.HttpRequestFn { pub fn on_request_handler(self: *Router) zap.HttpRequestFn {
_instance = self; _instance = self;
return zap_on_request; return zap_on_request;
} }
fn zap_on_request(r: zap.Request) void { fn zap_on_request(r: zap.Request) !void {
return serve(_instance, r); return serve(_instance, r);
} }
fn serve(self: *Self, r: zap.Request) void { fn serve(self: *Router, r: zap.Request) !void {
const path = r.path orelse "/"; const path = r.path orelse "/";
if (self.routes.get(path)) |routeInfo| { if (self.routes.get(path)) |routeInfo| {
switch (routeInfo) { switch (routeInfo) {
.bound => |b| @call(.auto, @as(BoundHandler, @ptrFromInt(b.handler)), .{ @as(*anyopaque, @ptrFromInt(b.instance)), r }), .bound => |b| try @call(.auto, @as(BoundHandler, @ptrFromInt(b.handler)), .{ @as(*anyopaque, @ptrFromInt(b.instance)), r }),
.unbound => |h| h(r), .unbound => |h| try h(r),
} }
} else if (self.not_found) |handler| { } else if (self.not_found) |handler| {
// not found handler // not found handler
handler(r); try handler(r);
} else { } else {
// default 404 output // default 404 output
r.setStatus(.not_found); r.setStatus(.not_found);
r.sendBody("404 Not Found") catch return; try r.sendBody("404 Not Found");
} }
} }

View file

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Authenticators = zap.Auth; const Authenticators = zap.Auth;
const Endpoint = zap.Endpoint;
const fio = zap; const fio = zap;
const util = zap; const util = zap;
@ -102,23 +101,6 @@ const HTTP_RESPONSE: []const u8 =
; ;
var received_response: []const u8 = "null"; var received_response: []const u8 = "null";
fn endpoint_http_get(e: *Endpoint, r: zap.Request) void {
_ = e;
r.sendBody(HTTP_RESPONSE) catch return;
received_response = HTTP_RESPONSE;
std.time.sleep(1 * std.time.ns_per_s);
zap.stop();
}
fn endpoint_http_unauthorized(e: *Endpoint, r: zap.Request) void {
_ = e;
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return;
received_response = "UNAUTHORIZED";
std.time.sleep(1 * std.time.ns_per_s);
zap.stop();
}
// //
// http client code for in-process sending of http request // http client code for in-process sending of http request
// //
@ -165,6 +147,32 @@ fn makeRequestThread(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReq
return try std.Thread.spawn(.{}, makeRequest, .{ a, url, auth }); return try std.Thread.spawn(.{}, makeRequest, .{ a, url, auth });
} }
pub const Endpoint = struct {
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .raise,
pub fn get(e: *Endpoint, r: zap.Request) !void {
_ = e;
r.sendBody(HTTP_RESPONSE) catch return;
received_response = HTTP_RESPONSE;
std.time.sleep(1 * std.time.ns_per_s);
zap.stop();
}
pub fn unauthorized(e: *Endpoint, r: zap.Request) !void {
_ = e;
r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return;
received_response = "UNAUTHORIZED";
std.time.sleep(1 * std.time.ns_per_s);
zap.stop();
}
pub fn post(_: *Endpoint, _: zap.Request) !void {}
pub fn put(_: *Endpoint, _: zap.Request) !void {}
pub fn delete(_: *Endpoint, _: zap.Request) !void {}
pub fn patch(_: *Endpoint, _: zap.Request) !void {}
pub fn options(_: *Endpoint, _: zap.Request) !void {}
};
// //
// end of http client code // end of http client code
// //
@ -187,11 +195,9 @@ test "BearerAuthSingle authenticateRequest OK" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create authenticator // create authenticator
const Authenticator = Authenticators.BearerSingle; const Authenticator = Authenticators.BearerSingle;
@ -199,10 +205,10 @@ test "BearerAuthSingle authenticateRequest OK" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("\n\n*******************************************\n", .{}); // std.debug.print("\n\n*******************************************\n", .{});
@ -240,11 +246,9 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
const Set = std.StringHashMap(void); const Set = std.StringHashMap(void);
var set = Set.init(a); // set var set = Set.init(a); // set
@ -258,12 +262,12 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; try listener.listen();
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Bearer invalid\r", .{}); // std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Bearer invalid\r", .{});
@ -277,6 +281,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
}); });
try std.testing.expectEqualStrings("UNAUTHORIZED", received_response); try std.testing.expectEqualStrings("UNAUTHORIZED", received_response);
std.debug.print("\nI made it\n", .{});
} }
test "BearerAuthMulti authenticateRequest OK" { test "BearerAuthMulti authenticateRequest OK" {
@ -297,11 +302,9 @@ test "BearerAuthMulti authenticateRequest OK" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create authenticator // create authenticator
const Authenticator = Authenticators.BearerSingle; const Authenticator = Authenticators.BearerSingle;
@ -309,12 +312,12 @@ test "BearerAuthMulti authenticateRequest OK" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; try listener.listen();
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client_runner http://127.0.0.1:3000/test Bearer " ++ token ++ "\r", .{}); // std.debug.print("./zig-out/bin/http_client_runner http://127.0.0.1:3000/test Bearer " ++ token ++ "\r", .{});
@ -348,11 +351,9 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create authenticator // create authenticator
const Authenticator = Authenticators.BearerSingle; const Authenticator = Authenticators.BearerSingle;
@ -360,10 +361,10 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -399,11 +400,9 @@ test "BasicAuth Token68 authenticateRequest" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create a set of Token68 entries // create a set of Token68 entries
const Set = std.StringHashMap(void); const Set = std.StringHashMap(void);
var set = Set.init(a); // set var set = Set.init(a); // set
@ -416,10 +415,10 @@ test "BasicAuth Token68 authenticateRequest" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -455,11 +454,9 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create a set of Token68 entries // create a set of Token68 entries
const Set = std.StringHashMap(void); const Set = std.StringHashMap(void);
var set = Set.init(a); // set var set = Set.init(a); // set
@ -472,10 +469,10 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -510,11 +507,9 @@ test "BasicAuth UserPass authenticateRequest" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create a set of User -> Pass entries // create a set of User -> Pass entries
const Map = std.StringHashMap([]const u8); const Map = std.StringHashMap([]const u8);
@ -538,10 +533,10 @@ test "BasicAuth UserPass authenticateRequest" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -576,11 +571,9 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoint.init(.{ var ep: Endpoint = .{
.path = "/test", .path = "/test",
.get = endpoint_http_get, };
.unauthorized = endpoint_http_unauthorized,
});
// create a set of User -> Pass entries // create a set of User -> Pass entries
const Map = std.StringHashMap([]const u8); const Map = std.StringHashMap([]const u8);
@ -605,10 +598,10 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
defer authenticator.deinit(); defer authenticator.deinit();
// create authenticating endpoint // create authenticating endpoint
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator); const BearerAuthEndpoint = zap.Endpoint.Authenticating(Endpoint, Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.register(auth_ep.endpoint()); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});

View file

@ -26,30 +26,27 @@ test "http parameters" {
var strParams: ?zap.Request.HttpParamStrKVList = null; var strParams: ?zap.Request.HttpParamStrKVList = null;
var params: ?zap.Request.HttpParamKVList = null; var params: ?zap.Request.HttpParamKVList = null;
var paramOneStr: ?zap.FreeOrNot = null; var paramOneStr: ?[]const u8 = null;
var paramOneSlice: ?[]const u8 = null; var paramOneSlice: ?[]const u8 = null;
var paramSlices: zap.Request.ParamSliceIterator = undefined; var paramSlices: zap.Request.ParamSliceIterator = undefined;
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
ran = true; ran = true;
r.parseQuery(); r.parseQuery();
param_count = r.getParamCount(); param_count = r.getParamCount();
// true -> make copies of temp strings // true -> make copies of temp strings
strParams = r.parametersToOwnedStrList(alloc, true) catch unreachable; strParams = r.parametersToOwnedStrList(alloc) catch unreachable;
// true -> make copies of temp strings // true -> make copies of temp strings
params = r.parametersToOwnedList(alloc, true) catch unreachable; params = r.parametersToOwnedList(alloc) catch unreachable;
var maybe_str = r.getParamStr(alloc, "one", true) catch unreachable; paramOneStr = r.getParamStr(alloc, "one") catch unreachable;
if (maybe_str) |*s| {
paramOneStr = s.*;
}
paramOneSlice = blk: { // we need to dupe it here because the request object r will get
if (r.getParamSlice("one")) |val| break :blk alloc.dupe(u8, val) catch unreachable; // invalidated at the end of the function but we need to check
break :blk null; // its correctness later in the test
}; paramOneSlice = if (r.getParamSlice("one")) |slice| alloc.dupe(u8, slice) catch unreachable else null;
paramSlices = r.getParamSlices(); paramSlices = r.getParamSlices();
} }
@ -84,44 +81,44 @@ test "http parameters" {
if (Handler.params) |*p| { if (Handler.params) |*p| {
p.deinit(); p.deinit();
} }
if (Handler.paramOneStr) |*p| {
// allocator.free(p); if (Handler.paramOneStr) |p| {
p.deinit(); allocator.free(p);
} }
if (Handler.paramOneSlice) |p| { if (Handler.paramOneSlice) |p| {
Handler.alloc.free(p); allocator.free(p);
} }
} }
try std.testing.expectEqual(Handler.ran, true); try std.testing.expectEqual(true, Handler.ran);
try std.testing.expectEqual(Handler.param_count, 5); try std.testing.expectEqual(5, Handler.param_count);
try std.testing.expect(Handler.paramOneStr != null); try std.testing.expect(Handler.paramOneStr != null);
try std.testing.expectEqualStrings(Handler.paramOneStr.?.str, "1"); try std.testing.expectEqualStrings("1", Handler.paramOneStr.?);
try std.testing.expect(Handler.paramOneSlice != null); try std.testing.expect(Handler.paramOneSlice != null);
try std.testing.expectEqualStrings(Handler.paramOneSlice.?, "1"); try std.testing.expectEqualStrings("1", Handler.paramOneSlice.?);
try std.testing.expect(Handler.strParams != null); try std.testing.expect(Handler.strParams != null);
for (Handler.strParams.?.items, 0..) |kv, i| { for (Handler.strParams.?.items, 0..) |kv, i| {
switch (i) { switch (i) {
0 => { 0 => {
try std.testing.expectEqualStrings(kv.key.str, "one"); try std.testing.expectEqualStrings("one", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "1"); try std.testing.expectEqualStrings("1", kv.value);
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(kv.key.str, "two"); try std.testing.expectEqualStrings("two", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "2"); try std.testing.expectEqualStrings("2", kv.value);
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(kv.key.str, "string"); try std.testing.expectEqualStrings("string", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "hello world"); try std.testing.expectEqualStrings("hello world", kv.value);
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(kv.key.str, "float"); try std.testing.expectEqualStrings("float", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "6.28"); try std.testing.expectEqualStrings("6.28", kv.value);
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool"); try std.testing.expectEqualStrings("bool", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "true"); try std.testing.expectEqualStrings("true", kv.value);
}, },
else => return error.TooManyArgs, else => return error.TooManyArgs,
} }
@ -131,24 +128,24 @@ test "http parameters" {
while (Handler.paramSlices.next()) |param| { while (Handler.paramSlices.next()) |param| {
switch (pindex) { switch (pindex) {
0 => { 0 => {
try std.testing.expectEqualStrings(param.name, "one"); try std.testing.expectEqualStrings("one", param.name);
try std.testing.expectEqualStrings(param.value, "1"); try std.testing.expectEqualStrings("1", param.value);
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(param.name, "two"); try std.testing.expectEqualStrings("two", param.name);
try std.testing.expectEqualStrings(param.value, "2"); try std.testing.expectEqualStrings("2", param.value);
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(param.name, "string"); try std.testing.expectEqualStrings("string", param.name);
try std.testing.expectEqualStrings(param.value, "hello+world"); try std.testing.expectEqualStrings("hello+world", param.value);
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(param.name, "float"); try std.testing.expectEqualStrings("float", param.name);
try std.testing.expectEqualStrings(param.value, "6.28"); try std.testing.expectEqualStrings("6.28", param.value);
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(param.name, "bool"); try std.testing.expectEqualStrings("bool", param.name);
try std.testing.expectEqualStrings(param.value, "true"); try std.testing.expectEqualStrings("true", param.value);
}, },
else => return error.TooManyArgs, else => return error.TooManyArgs,
} }
@ -158,42 +155,42 @@ test "http parameters" {
for (Handler.params.?.items, 0..) |kv, i| { for (Handler.params.?.items, 0..) |kv, i| {
switch (i) { switch (i) {
0 => { 0 => {
try std.testing.expectEqualStrings(kv.key.str, "one"); try std.testing.expectEqualStrings("one", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 1), .Int => |n| try std.testing.expectEqual(1, n),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(kv.key.str, "two"); try std.testing.expectEqualStrings("two", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 2), .Int => |n| try std.testing.expectEqual(2, n),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(kv.key.str, "string"); try std.testing.expectEqualStrings("string", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.String => |s| try std.testing.expectEqualStrings(s.str, "hello world"), .String => |s| try std.testing.expectEqualStrings("hello world", s),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(kv.key.str, "float"); try std.testing.expectEqualStrings("float", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Float => |f| try std.testing.expectEqual(f, 6.28), .Float => |f| try std.testing.expectEqual(6.28, f),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool"); try std.testing.expectEqualStrings("bool", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Bool => |b| try std.testing.expectEqual(b, true), .Bool => |b| try std.testing.expectEqual(true, b),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },

View file

@ -24,7 +24,7 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url }); return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
} }
pub fn on_request(r: zap.Request) void { pub fn on_request(r: zap.Request) !void {
r.sendFile("src/tests/testfile.txt") catch unreachable; r.sendFile("src/tests/testfile.txt") catch unreachable;
} }

View file

@ -6,6 +6,9 @@ const zap = @import("zap.zig");
/// note: since this is called from within request functions, we don't make /// note: since this is called from within request functions, we don't make
/// copies. Also, we return temp memory from fio. -> don't hold on to it outside /// copies. Also, we return temp memory from fio. -> don't hold on to it outside
/// of a request function. FIO temp memory strings do not need to be freed. /// of a request function. FIO temp memory strings do not need to be freed.
///
/// IMPORTANT!!! The "temp" memory can refer to a shared buffer that subsequent
/// calls to this function will **overwrite**!!!
pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
if (o == 0) return null; if (o == 0) return null;
const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o);
@ -14,39 +17,21 @@ pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
return x.data[0..x.len]; return x.data[0..x.len];
} }
/// A "string" type used internally that carries a flag whether its buffer needs
/// to be freed or not - and honors it in `deinit()`. That way, it's always
/// safe to call deinit().
/// For instance, slices taken directly from the zap.Request need not be freed.
/// But the ad-hoc created string representation of a float parameter must be
/// freed after use.
pub const FreeOrNot = struct {
str: []const u8,
freeme: bool,
allocator: ?std.mem.Allocator = null,
pub fn deinit(self: *const @This()) void {
if (self.freeme) {
self.allocator.?.free(self.str);
}
}
};
/// Used internally: convert a FIO object into its string representation. /// Used internally: convert a FIO object into its string representation.
/// Depending on the type of the object, a buffer will be created. Hence a /// This always allocates, so choose your allocator wisely.
/// FreeOrNot type is used as the return type. /// Let's never use that
pub fn fio2strAllocOrNot(a: std.mem.Allocator, o: fio.FIOBJ, always_alloc: bool) !FreeOrNot { pub fn fio2strAlloc(a: std.mem.Allocator, o: fio.FIOBJ) ![]const u8 {
if (o == 0) return .{ .str = "null", .freeme = false }; if (o == 0) return try a.dupe(u8, "null");
if (o == fio.FIOBJ_INVALID) return .{ .str = "invalid", .freeme = false }; if (o == fio.FIOBJ_INVALID) return try a.dupe(u8, "invalid");
return switch (fio.fiobj_type(o)) { return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_TRUE => .{ .str = "true", .freeme = false }, fio.FIOBJ_T_TRUE => try a.dupe(u8, "true"),
fio.FIOBJ_T_FALSE => .{ .str = "false", .freeme = false }, fio.FIOBJ_T_FALSE => try a.dupe(u8, "false"),
// according to fio2str above, the orelse should never happen // according to fio2str above, the orelse should never happen
fio.FIOBJ_T_NUMBER => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a }, fio.FIOBJ_T_NUMBER => try a.dupe(u8, fio2str(o) orelse "null"),
fio.FIOBJ_T_FLOAT => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a }, fio.FIOBJ_T_FLOAT => try a.dupe(u8, fio2str(o) orelse "null"),
// the string comes out of the request, so it is safe to not make a copy // if the string comes out of the request, it is safe to not make a copy
fio.FIOBJ_T_STRING => .{ .str = if (always_alloc) try a.dupe(u8, fio2str(o) orelse "") else fio2str(o) orelse "", .freeme = if (always_alloc) true else false, .allocator = a }, fio.FIOBJ_T_STRING => try a.dupe(u8, fio2str(o) orelse ""),
else => .{ .str = "unknown_type", .freeme = false }, else => try a.dupe(u8, "unknown_type"),
}; };
} }
@ -74,12 +59,12 @@ pub fn stringifyBuf(
buffer: []u8, buffer: []u8,
value: anytype, value: anytype,
options: std.json.StringifyOptions, options: std.json.StringifyOptions,
) ?[]const u8 { ) ![]const u8 {
var fba = std.heap.FixedBufferAllocator.init(buffer); var fba = std.heap.FixedBufferAllocator.init(buffer);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
if (std.json.stringify(value, options, string.writer())) { if (std.json.stringify(value, options, string.writer())) {
return string.items; return string.items;
} else |_| { // error } else |err| { // error
return null; return err;
} }
} }

View file

@ -9,53 +9,11 @@ pub const fio = @import("fio.zig");
/// Server-Side TLS function wrapper /// Server-Side TLS function wrapper
pub const Tls = @import("tls.zig"); pub const Tls = @import("tls.zig");
/// Endpoint and supporting types.
/// Create one and pass in your callbacks. Then,
/// pass it to a HttpListener's `register()` function to register with the
/// listener.
///
/// **NOTE**: A common endpoint pattern for zap is to create your own struct
/// that embeds an Endpoint, provides specific callbacks, and uses
/// `@fieldParentPtr` to get a reference to itself.
///
/// Example:
/// A simple endpoint listening on the /stop route that shuts down zap.
/// The main thread usually continues at the instructions after the call to zap.start().
///
/// ```zig
/// const StopEndpoint = struct {
/// ep: zap.Endpoint = undefined,
///
/// pub fn init(
/// path: []const u8,
/// ) StopEndpoint {
/// return .{
/// .ep = zap.Endpoint.init(.{
/// .path = path,
/// .get = get,
/// }),
/// };
/// }
///
/// // access the internal Endpoint
/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint {
/// return &self.ep;
/// }
///
/// fn get(e: *zap.Endpoint, r: zap.Request) void {
/// const self: *StopEndpoint = @fieldParentPtr("ep", e);
/// _ = self;
/// _ = r;
/// zap.stop();
/// }
/// };
/// ```
pub const Endpoint = @import("endpoint.zig"); pub const Endpoint = @import("endpoint.zig");
pub const Router = @import("router.zig"); pub const Router = @import("router.zig");
pub usingnamespace @import("util.zig"); pub const App = @import("App.zig");
pub usingnamespace @import("http.zig");
/// A struct to handle Mustache templating. /// A struct to handle Mustache templating.
/// ///
@ -80,9 +38,8 @@ pub const Middleware = @import("middleware.zig");
pub const WebSockets = @import("websockets.zig"); pub const WebSockets = @import("websockets.zig");
pub const Log = @import("log.zig"); pub const Log = @import("log.zig");
const http = @import("http.zig"); pub const http = @import("http.zig");
pub const util = @import("util.zig");
const util = @import("util.zig");
// TODO: replace with comptime debug logger like in log.zig // TODO: replace with comptime debug logger like in log.zig
var _debug: bool = false; var _debug: bool = false;
@ -177,7 +134,7 @@ pub const ContentType = enum {
pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
/// Zap Http request callback function type. /// Zap Http request callback function type.
pub const HttpRequestFn = *const fn (Request) void; pub const HttpRequestFn = *const fn (Request) anyerror!void;
/// websocket connection upgrade callback type /// websocket connection upgrade callback type
/// fn(request, targetstring) /// fn(request, targetstring)
@ -215,11 +172,10 @@ pub const HttpListenerSettings = struct {
pub const HttpListener = struct { pub const HttpListener = struct {
settings: HttpListenerSettings, settings: HttpListenerSettings,
const Self = @This();
var the_one_and_only_listener: ?*HttpListener = null; var the_one_and_only_listener: ?*HttpListener = null;
/// Create a listener /// Create a listener
pub fn init(settings: HttpListenerSettings) Self { pub fn init(settings: HttpListenerSettings) HttpListener {
std.debug.assert(settings.on_request != null); std.debug.assert(settings.on_request != null);
return .{ return .{
.settings = settings, .settings = settings,
@ -247,8 +203,10 @@ pub const HttpListener = struct {
req.markAsFinished(false); req.markAsFinished(false);
std.debug.assert(l.settings.on_request != null); std.debug.assert(l.settings.on_request != null);
if (l.settings.on_request) |on_request| { if (l.settings.on_request) |on_request| {
// l.settings.on_request.?(req); on_request(req) catch |err| {
on_request(req); // TODO: log / handle the error in a better way
std.debug.print("zap on_request error: {}", .{err});
};
} }
} }
} }
@ -270,7 +228,10 @@ pub const HttpListener = struct {
var user_context: Request.UserContext = .{}; var user_context: Request.UserContext = .{};
req._user_context = &user_context; req._user_context = &user_context;
l.settings.on_response.?(req); l.settings.on_response.?(req) catch |err| {
// TODO: log / handle the error in a better way
std.debug.print("zap on_response error: {}", .{err});
};
} }
} }
@ -304,7 +265,7 @@ pub const HttpListener = struct {
} }
/// Start listening /// Start listening
pub fn listen(self: *Self) !void { pub fn listen(self: *HttpListener) !void {
var pfolder: [*c]const u8 = null; var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0; var pfolder_len: usize = 0;
@ -315,10 +276,10 @@ pub const HttpListener = struct {
} }
const x: fio.http_settings_s = .{ const x: fio.http_settings_s = .{
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null, .on_request = if (self.settings.on_request) |_| HttpListener.theOneAndOnlyRequestCallBack else null,
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null, .on_upgrade = if (self.settings.on_upgrade) |_| HttpListener.theOneAndOnlyUpgradeCallBack else null,
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null, .on_response = if (self.settings.on_response) |_| HttpListener.theOneAndOnlyResponseCallBack else null,
.on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null, .on_finish = if (self.settings.on_finish) |_| HttpListener.theOneAndOnlyFinishCallBack else null,
.udata = null, .udata = null,
.public_folder = pfolder, .public_folder = pfolder,
.public_folder_length = pfolder_len, .public_folder_length = pfolder_len,
@ -356,7 +317,7 @@ pub const HttpListener = struct {
// the request if it isn't set. hence, if started under full load, the // the request if it isn't set. hence, if started under full load, the
// first request(s) might not be serviced, as long as it takes from // first request(s) might not be serviced, as long as it takes from
// fio.http_listen() to here // fio.http_listen() to here
Self.the_one_and_only_listener = self; HttpListener.the_one_and_only_listener = self;
} }
}; };
@ -376,10 +337,8 @@ pub const LowLevel = struct {
keepalive_timeout_s: u8 = 5, keepalive_timeout_s: u8 = 5,
log: bool = false, log: bool = false,
const Self = @This();
/// Create settings with defaults /// Create settings with defaults
pub fn init() Self { pub fn init() ListenSettings {
return .{}; return .{};
} }
}; };

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.Request) void { fn on_request(r: zap.Request) !void {
r.setStatus(.not_found); r.setStatus(.not_found);
r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return; r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return;
} }

View file

@ -1,70 +0,0 @@
# axum
```console
zap on  newwrk [$!?] via ↯ v0.11.0-dev.2837+b55b8e774 via  impure (nix-shell)
➜ wrk/measure.sh axum
Finished release [optimized] target(s) in 0.05s
========================================================================
axum
========================================================================
Running 10s test @ http://127.0.0.1:3000
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 527.01us 260.08us 8.47ms 74.31%
Req/Sec 151.11k 4.06k 166.63k 71.25%
Latency Distribution
50% 518.00us
75% 644.00us
90% 811.00us
99% 1.39ms
6014492 requests in 10.01s, 768.61MB read
Requests/sec: 600582.38
Transfer/sec: 76.75MB
zap on  newwrk [$!?] via ↯ v0.11.0-dev.2837+b55b8e774 via  impure (nix-shell) took 11s
➜ wrk/measure.sh axum
Finished release [optimized] target(s) in 0.05s
========================================================================
axum
========================================================================
Running 10s test @ http://127.0.0.1:3000
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 534.89us 280.25us 7.37ms 76.81%
Req/Sec 150.03k 4.26k 162.67k 72.75%
Latency Distribution
50% 520.00us
75% 647.00us
90% 831.00us
99% 1.50ms
5969526 requests in 10.01s, 762.86MB read
Requests/sec: 596134.58
Transfer/sec: 76.18MB
zap on  newwrk [$!?] via ↯ v0.11.0-dev.2837+b55b8e774 via  impure (nix-shell) took 11s
➜ wrk/measure.sh axum
Finished release [optimized] target(s) in 0.05s
========================================================================
axum
========================================================================
Running 10s test @ http://127.0.0.1:3000
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 519.96us 269.86us 11.92ms 76.98%
Req/Sec 151.29k 4.32k 164.52k 69.75%
Latency Distribution
50% 509.00us
75% 635.00us
90% 800.00us
99% 1.41ms
6021199 requests in 10.01s, 769.46MB read
Requests/sec: 601482.51
Transfer/sec: 76.86MB
zap on  newwrk [$!?] via ↯ v0.11.0-dev.2837+b55b8e774 via  impure (nix-shell) took 11s
```
# sanic

View file

@ -1,752 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-trait"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43"
dependencies = [
"async-trait",
"axum-core",
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"tower-layer",
"tower-service",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "hello-axum"
version = "0.1.0"
dependencies = [
"axum",
"tokio",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "matchit"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.45.0",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "serde_json"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "tokio"
version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View file

@ -1,129 +0,0 @@
# 60 dependencies!!!
```console
➜ cargo build
Updating crates.io index
Downloaded percent-encoding v2.2.0
Downloaded sync_wrapper v0.1.2
Downloaded async-trait v0.1.68
Downloaded unicode-ident v1.0.8
Downloaded tracing v0.1.37
Downloaded mime v0.3.17
Downloaded http-body v0.4.5
Downloaded bytes v1.4.0
Downloaded httpdate v1.0.2
Downloaded httparse v1.8.0
Downloaded http v0.2.9
Downloaded lock_api v0.4.9
Downloaded num_cpus v1.15.0
Downloaded parking_lot v0.12.1
Downloaded pin-project v1.0.12
Downloaded parking_lot_core v0.9.7
Downloaded form_urlencoded v1.1.0
Downloaded log v0.4.17
Downloaded memchr v2.5.0
Downloaded matchit v0.5.0
Downloaded pin-project-lite v0.2.9
Downloaded scopeguard v1.1.0
Downloaded pin-utils v0.1.0
Downloaded once_cell v1.17.1
Downloaded serde_urlencoded v0.7.1
Downloaded pin-project-internal v1.0.12
Downloaded ryu v1.0.13
Downloaded quote v1.0.26
Downloaded proc-macro2 v1.0.56
Downloaded signal-hook-registry v1.4.1
Downloaded socket2 v0.4.9
Downloaded tower-service v0.3.2
Downloaded tower-layer v0.3.2
Downloaded tower v0.4.13
Downloaded autocfg v1.1.0
Downloaded syn v2.0.15
Downloaded try-lock v0.2.4
Downloaded futures-task v0.3.28
Downloaded futures-core v0.3.28
Downloaded fnv v1.0.7
Downloaded bitflags v1.3.2
Downloaded futures-util v0.3.28
Downloaded hyper v0.14.26
Downloaded axum v0.5.17
Downloaded smallvec v1.10.0
Downloaded want v0.3.0
Downloaded axum-core v0.2.9
Downloaded mio v0.8.6
Downloaded tokio-macros v2.1.0
Downloaded serde v1.0.160
Downloaded tower-http v0.3.5
Downloaded serde_json v1.0.96
Downloaded syn v1.0.109
Downloaded futures-channel v0.3.28
Downloaded tracing-core v0.1.30
Downloaded itoa v1.0.6
Downloaded cfg-if v1.0.0
Downloaded http-range-header v0.3.0
Downloaded libc v0.2.142
Downloaded tokio v1.28.0
Downloaded 60 crates (4.1 MB) in 2.81s
Compiling proc-macro2 v1.0.56
Compiling unicode-ident v1.0.8
Compiling quote v1.0.26
Compiling libc v0.2.142
Compiling cfg-if v1.0.0
Compiling autocfg v1.1.0
Compiling log v0.4.17
Compiling futures-core v0.3.28
Compiling pin-project-lite v0.2.9
Compiling bytes v1.4.0
Compiling itoa v1.0.6
Compiling futures-task v0.3.28
Compiling parking_lot_core v0.9.7
Compiling futures-util v0.3.28
Compiling syn v1.0.109
Compiling smallvec v1.10.0
Compiling scopeguard v1.1.0
Compiling once_cell v1.17.1
Compiling pin-utils v0.1.0
Compiling fnv v1.0.7
Compiling serde v1.0.160
Compiling tower-service v0.3.2
Compiling futures-channel v0.3.28
Compiling httparse v1.8.0
Compiling tower-layer v0.3.2
Compiling async-trait v0.1.68
Compiling try-lock v0.2.4
Compiling serde_json v1.0.96
Compiling ryu v1.0.13
Compiling memchr v2.5.0
Compiling percent-encoding v2.2.0
Compiling http-range-header v0.3.0
Compiling httpdate v1.0.2
Compiling bitflags v1.3.2
Compiling mime v0.3.17
Compiling sync_wrapper v0.1.2
Compiling matchit v0.5.0
Compiling http v0.2.9
Compiling tracing-core v0.1.30
Compiling form_urlencoded v1.1.0
Compiling lock_api v0.4.9
Compiling tokio v1.28.0
Compiling want v0.3.0
Compiling tracing v0.1.37
Compiling syn v2.0.15
Compiling http-body v0.4.5
Compiling num_cpus v1.15.0
Compiling socket2 v0.4.9
Compiling signal-hook-registry v1.4.1
Compiling mio v0.8.6
Compiling parking_lot v0.12.1
Compiling serde_urlencoded v0.7.1
Compiling tokio-macros v2.1.0
Compiling pin-project-internal v1.0.12
Compiling axum-core v0.2.9
Compiling pin-project v1.0.12
Compiling tower v0.4.13
Compiling hyper v0.14.26
Compiling tower-http v0.3.5
Compiling axum v0.5.17
Compiling hello-axum v0.1.0 (/home/rs/code/github.com/zigzap/zap/wrk/axum/hello-axum)
Finished dev [unoptimized + debuginfo] target(s) in 53.19s
```

View file

@ -1,10 +0,0 @@
[package]
name = "hello-axum"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.5"
tokio = { version = "1", features = ["full"] }

View file

@ -1,29 +0,0 @@
use axum::{routing::get, Router};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// Route all requests on "/" endpoint to anonymous handler.
//
// A handler is an async function which returns something that implements
// `axum::response::IntoResponse`.
// A closure or a function can be used as handler.
let app = Router::new().route("/", get(handler));
// Router::new().route("/", get(|| async { "Hello, world!" }));
// Address that server will bind to.
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
// Use `hyper::server::Server` which is re-exported through `axum::Server` to serve the app.
axum::Server::bind(&addr)
// Hyper server takes a make service.
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> &'static str {
"Hello from axum!!"
}

View file

@ -1,39 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "cpp-beast",
.target = target,
.optimize = optimize,
});
exe.addIncludePath(.{ .path = "." });
exe.addCSourceFiles(&.{"main.cpp"}, &.{
"-Wall",
"-Wextra",
"-Wshadow",
});
const libasio_dep = b.dependency("beast", .{
.target = target,
.optimize = optimize,
});
const libasio = libasio_dep.artifact("beast");
for (libasio.include_dirs.items) |include| {
exe.include_dirs.append(include) catch {};
}
exe.linkLibrary(libasio);
exe.linkLibCpp();
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run C++ Http Server");
run_step.dependOn(&run_cmd.step);
}

View file

@ -1,10 +0,0 @@
.{
.name = "cpp-beast",
.version = "0.1.0",
.dependencies = .{
.beast = .{
.url = "https://github.com/kassane/beast/archive/df69ba4d48fbe874730f6a28e9528d9ef7a9547c.tar.gz",
.hash = "1220548f8727394522081ab48ed2f7111c20fa5f051ff287ec3c3f82340faa5d68c2",
},
},
}

View file

@ -1 +0,0 @@
Hello from C++!!

View file

@ -1,80 +0,0 @@
#include <iostream>
#include <fstream>
#include <string>
#include <boost/beast.hpp>
#include <boost/asio/thread_pool.hpp>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
std::string read_html_file(const std::string& file_path) {
std::ifstream file(file_path);
if (!file) {
return "File not found: " + file_path;
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return content;
}
void handle_client(tcp::socket socket, const std::string& msg) {
try {
// Construct an HTTP response with the HTML content
http::response<http::string_body> response;
response.version(11);
response.result(http::status::ok);
response.reason("OK");
response.set(http::field::server, "C++ Server");
response.set(http::field::content_type, "text/html");
response.body() = msg;
response.prepare_payload();
// Send the response to the client
http::write(socket, response);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
int main() {
try {
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
// Create an endpoint to bind to
tcp::endpoint endpoint(tcp::v4(), 8070);
// Create and bind the acceptor
tcp::acceptor acceptor(io_context, endpoint);
std::cout << "Server listening on port 8070..." << std::endl;
// static 17-byte string
std::string msg = "Hello from C++!!!";
// or
// Read HTML content from a file (e.g., "index.html")
// std::string html_content = read_html_file("hello.html");
// std::cout << "str len: " << (html_content.length() == msg.length()) << std::boolalpha << "\n";
// Create a thread pool with 4 threads
net::thread_pool pool(4);
while (true) {
// Wait for a client to connect
tcp::socket socket(io_context);
acceptor.accept(socket);
// Post a task to the thread pool to handle the client request
net::post(pool, [socket = std::move(socket), msg]() mutable {
handle_client(std::move(socket), msg);
});
}
// The thread pool destructor will ensure that all threads are joined properly.
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}

View file

@ -1,8 +0,0 @@
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
var app = builder.Build();
app.MapGet("/", () => "Hello from C#");
app.Run();

View file

@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5026",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
</PropertyGroup>
</Project>

View file

@ -1,16 +0,0 @@
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello from GO!!!\n")
}
func main() {
print("listening on 0.0.0.0:8090\n")
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}

View file

@ -1,90 +0,0 @@
import re
import os
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from collections import defaultdict
import statistics
directory = "./wrk" # Replace with the actual directory path
requests_sec = defaultdict(list)
transfers_sec = defaultdict(list)
mean_requests = {}
mean_transfers = {}
def plot(kind='', title='', ylabel='', means=None):
# Sort the labels and requests_sec lists together based on the requests_sec values
labels = []
values = []
# silly, I know
for k, v in means.items():
labels.append(k)
values.append(v)
# sort the labels and value lists
labels, values = zip(*sorted(zip(labels, values), key=lambda x: x[1], reverse=True))
# Plot the graph
plt.figure(figsize=(10, 6)) # Adjust the figure size as needed
bars = plt.bar(labels, values)
plt.xlabel("Subject")
plt.ylabel(ylabel)
plt.title(title)
plt.xticks(rotation=45) # Rotate x-axis labels for better readability
# Display the actual values on top of the bars
for bar in bars:
yval = bar.get_height()
plt.text(bar.get_x() + bar.get_width() / 2, yval, f'{yval:,.2f}', ha='center', va='bottom')
plt.tight_layout() # Adjust the spacing of the graph elements
png_name = f"{directory}/{kind.lower()}_graph.png"
plt.savefig(png_name) # Save the graph as a PNG file
print(f"Generated: {png_name}")
if __name__ == '__main__':
if not os.path.isdir(".git"):
print("Please run from root directory of the repository!")
print("e.g. python wrk/graph.py")
import sys
sys.exit(1)
# Iterate over the files in the directory
for filename in os.listdir(directory):
if filename.endswith(".perflog"):
label = os.path.splitext(filename)[0]
file_path = os.path.join(directory, filename)
with open(file_path, "r") as file:
lines = file.readlines()
for line in lines:
# Extract the Requests/sec value using regular expressions
match = re.search(r"Requests/sec:\s+([\d.]+)", line)
if match:
requests_sec[label].append(float(match.group(1)))
match = re.search(r"Transfer/sec:\s+([\d.]+)", line)
if match:
value = float(match.group(1))
if 'KB' in line:
value *= 1024
elif 'MB' in line:
value *= 1024 * 1024
value /= 1024.0 * 1024
transfers_sec[label].append(value)
# calculate means
for k, v in requests_sec.items():
mean_requests[k] = statistics.mean(v)
for k, v in transfers_sec.items():
mean_transfers[k] = statistics.mean(v)
# save the plots
plot(kind='req_per_sec', title='Requests/sec Comparison',
ylabel='requests/sec', means=mean_requests)
plot(kind='xfer_per_sec', title='Transfer/sec Comparison',
ylabel='transfer/sec [MB]', means=mean_transfers)

View file

@ -1,100 +0,0 @@
#! /usr/bin/env bash
THREADS=4
CONNECTIONS=400
DURATION_SECONDS=10
SUBJECT=$1
TSK_SRV="taskset -c 0,1,2,3"
TSK_LOAD="taskset -c 4,5,6,7"
if [ "$SUBJECT" = "" ] ; then
echo "usage: $0 subject # subject: zig or go"
exit 1
fi
if [ "$SUBJECT" = "zig-zap" ] ; then
zig build -Doptimize=ReleaseFast wrk > /dev/null
$TSK_SRV ./zig-out/bin/wrk &
PID=$!
URL=http://127.0.0.1:3000
fi
if [ "$SUBJECT" = "zigstd" ] ; then
zig build -Doptimize=ReleaseFast wrk_zigstd > /dev/null
$TSK_SRV ./zig-out/bin/wrk_zigstd &
PID=$!
URL=http://127.0.0.1:3000
fi
if [ "$SUBJECT" = "go" ] ; then
cd wrk/go && go build main.go
$TSK_SRV ./main &
PID=$!
URL=http://127.0.0.1:8090/hello
fi
if [ "$SUBJECT" = "python" ] ; then
$TSK_SRV python wrk/python/main.py &
PID=$!
URL=http://127.0.0.1:8080
fi
if [ "$SUBJECT" = "python-sanic" ] ; then
$TSK_SRV python wrk/sanic/sanic-app.py &
PID=$!
URL=http://127.0.0.1:8000
fi
if [ "$SUBJECT" = "rust-bythebook" ] ; then
cd wrk/rust/bythebook && cargo build --release
$TSK_SRV ./target/release/hello &
PID=$!
URL=http://127.0.0.1:7878
fi
if [ "$SUBJECT" = "rust-bythebook-improved" ] ; then
cd wrk/rust/bythebook-improved && cargo build --release
$TSK_SRV ./target/release/hello &
PID=$!
URL=http://127.0.0.1:7878
fi
if [ "$SUBJECT" = "rust-clean" ] ; then
cd wrk/rust/clean && cargo build --release
$TSK_SRV ./target/release/hello &
PID=$!
URL=http://127.0.0.1:7878
fi
if [ "$SUBJECT" = "rust-axum" ] ; then
cd wrk/axum/hello-axum && cargo build --release
$TSK_SRV ./target/release/hello-axum &
PID=$!
URL=http://127.0.0.1:3000
fi
if [ "$SUBJECT" = "csharp" ] ; then
cd wrk/csharp && dotnet publish csharp.csproj -o ./out
$TSK_SRV ./out/csharp --urls "http://127.0.0.1:5026" &
PID=$!
URL=http://127.0.0.1:5026
fi
if [ "$SUBJECT" = "cpp-beast" ] ; then
cd wrk/cpp && zig build -Doptimize=ReleaseFast
$TSK_SRV ./zig-out/bin/cpp-beast 127.0.0.1 8070 . &
PID=$!
URL=http://127.0.0.1:8070
fi
sleep 1
echo "========================================================================"
echo " $SUBJECT"
echo "========================================================================"
$TSK_LOAD wrk -c $CONNECTIONS -t $THREADS -d $DURATION_SECONDS --latency $URL
kill $PID

View file

@ -1,32 +0,0 @@
#! /usr/bin/env bash
if [ ! -d ".git" ] ; then
echo "This script must be run from the root directory of the repository!"
echo "./wrk/measure_all.sh"
exit 1
fi
SUBJECTS="$1"
if [ "$SUBJECTS" = "README" ] ; then
rm -f wrk/*.perflog
SUBJECTS="zig-zap go python-sanic rust-axum csharp cpp-beast"
# above targets csharp and cpp-beast are out of date!
SUBJECTS="zig-zap go python-sanic rust-axum"
fi
if [ -z "$SUBJECTS" ] ; then
SUBJECTS="zig-zap go python python-sanic rust-bythebook rust-bythebook-improved rust-clean rust-axum csharp cpp-beast"
# above targets csharp and cpp-beast are out of date!
SUBJECTS="zig-zap go python python-sanic rust-bythebook rust-bythebook-improved rust-clean rust-axum"
fi
for S in $SUBJECTS; do
L="$S.perflog"
rm -f wrk/$L
for R in 1 2 3 ; do
./wrk/measure.sh $S | tee -a wrk/$L
done
done
echo "Finished"

View file

@ -1,52 +0,0 @@
# other measurements
## zap wrk 'example' with and without logging
**NO** performance regressions observable:
With `logging=true`:
```
[nix-shell:~/code/github.com/renerocksai/zap]$ ./wrk/measure.sh zig > out 2> /dev/null
[nix-shell:~/code/github.com/renerocksai/zap]$ cat out
========================================================================
zig
========================================================================
Running 10s test @ http://127.0.0.1:3000
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 343.91us 286.75us 18.37ms 95.58%
Req/Sec 162.61k 3.61k 174.96k 76.75%
Latency Distribution
50% 302.00us
75% 342.00us
90% 572.00us
99% 697.00us
6470789 requests in 10.01s, 0.96GB read
Requests/sec: 646459.59
Transfer/sec: 98.03MB
```
With `logging=false`:
```
[nix-shell:~/code/github.com/renerocksai/zap]$ ./wrk/measure.sh zig
Listening on 0.0.0.0:3000
========================================================================
zig
========================================================================
Running 10s test @ http://127.0.0.1:3000
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 336.10us 122.28us 14.67ms 88.55%
Req/Sec 159.82k 7.71k 176.75k 56.00%
Latency Distribution
50% 310.00us
75% 343.00us
90% 425.00us
99% 699.00us
6359415 requests in 10.01s, 0.94GB read
Requests/sec: 635186.96
Transfer/sec: 96.32MB
```

View file

@ -1,32 +0,0 @@
# Python 3 server example
from http.server import BaseHTTPRequestHandler, HTTPServer
hostName = "127.0.0.1"
serverPort = 8080
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
try:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("HI FROM PYTHON!!!", "utf-8"))
except:
pass
def log_message(self, format, *args):
return
if __name__ == "__main__":
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")

View file

@ -1,14 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

View file

@ -1,9 +0,0 @@
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# crossbeam = { version = "0.8.2", features = ["crossbeam-channel"] }

View file

@ -1,101 +0,0 @@
//Crossbeam should, but does not make this faster.
//use crossbeam::channel::bounded;
use std::{net::TcpStream, sync::mpsc, thread};
type Job = (fn(TcpStream), TcpStream);
type Sender = mpsc::Sender<Job>;
//type Sender = crossbeam::channel::Sender<Job>;
type Receiver = mpsc::Receiver<Job>;
//type Receiver = crossbeam::channel::Receiver<Job>;
pub struct ThreadPool {
workers: Vec<Worker>,
senders: Vec<Sender>,
next_sender: usize,
}
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let mut workers = Vec::with_capacity(size);
let mut senders = Vec::with_capacity(size);
for id in 0..size {
//let (sender, receiver) = bounded(2);
let (sender, receiver) = mpsc::channel();
senders.push(sender);
workers.push(Worker::new(id, receiver));
}
ThreadPool {
workers,
senders,
next_sender: 0,
}
}
/// round robin over available workers to ensure we never have to buffer requests
pub fn execute(&mut self, handler: fn(TcpStream), stream: TcpStream) {
let job = (handler, stream);
self.senders[self.next_sender].send(job).unwrap();
//self.senders[self.next_sender].try_send(job).unwrap();
self.next_sender += 1;
if self.next_sender == self.senders.len() {
self.next_sender = 0;
}
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
self.senders.clear();
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Receiver) -> Worker {
let thread = thread::spawn(move || Self::work(receiver));
Worker {
id,
thread: Some(thread),
}
}
fn work(receiver: Receiver) {
loop {
let message = receiver.recv();
match message {
Ok((handler, stream)) => {
// println!("Worker got a job; executing.");
handler(stream);
}
Err(_) => {
// println!("Worker disconnected; shutting down.");
break;
}
}
}
}
}

View file

@ -1,34 +0,0 @@
use hello::ThreadPool;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
//Creating a massive amount of threads so we can always have one ready to go.
let mut pool = ThreadPool::new(128);
for stream in listener.incoming() {
let stream = stream.unwrap();
//handle_connection(stream);
pool.execute(handle_connection, stream);
}
println!("Shutting down.");
}
fn handle_connection(mut stream: TcpStream) {
stream.set_nodelay(true).expect("set_nodelay call failed");
loop{
let mut buffer = [0; 1024];
match stream.read(&mut buffer){
Err(_)=>return,
Ok(0)=>return,
Ok(_v)=>{},
}
let response_bytes = b"HTTP/1.1 200 OK\r\nContent-Length: 16\r\nConnection: keep-alive\r\n\r\nHELLO from RUST!";
stream.write_all(response_bytes).unwrap();
}
}

View file

@ -1,14 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

View file

@ -1,8 +0,0 @@
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -1 +0,0 @@
Hello from RUST!

View file

@ -1,92 +0,0 @@
use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(sender),
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop(self.sender.take());
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv();
match message {
Ok(job) => {
// println!("Worker got a job; executing.");
job();
}
Err(_) => {
// println!("Worker disconnected; shutting down.");
break;
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}

View file

@ -1,43 +0,0 @@
use hello::ThreadPool;
// use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
// use std::thread;
// use std::time::Duration;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
// for stream in listener.incoming().take(2) {
for stream in listener.incoming() {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
println!("Shutting down.");
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
let status_line = "HTTP/1.1 200 OK";
let contents = "HELLO from RUST!";
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n{}",
status_line,
contents.len(),
contents
);
stream.write_all(response.as_bytes()).unwrap();
stream.flush().unwrap();
}

View file

@ -1,14 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

View file

@ -1,8 +0,0 @@
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[dependencies]

View file

@ -1 +0,0 @@
Hello from RUST!

View file

@ -1,101 +0,0 @@
//Crossbeam should, but does not make this faster.
//use crossbeam::channel::bounded;
use std::{net::TcpStream, sync::mpsc, thread};
type Job = (fn(TcpStream), TcpStream);
type Sender = mpsc::Sender<Job>;
//type Sender = crossbeam::channel::Sender<Job>;
type Receiver = mpsc::Receiver<Job>;
//type Receiver = crossbeam::channel::Receiver<Job>;
pub struct ThreadPool {
workers: Vec<Worker>,
senders: Vec<Sender>,
next_sender: usize,
}
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let mut workers = Vec::with_capacity(size);
let mut senders = Vec::with_capacity(size);
for id in 0..size {
//let (sender, receiver) = bounded(2);
let (sender, receiver) = mpsc::channel();
senders.push(sender);
workers.push(Worker::new(id, receiver));
}
ThreadPool {
workers,
senders,
next_sender: 0,
}
}
/// round robin over available workers to ensure we never have to buffer requests
pub fn execute(&mut self, handler: fn(TcpStream), stream: TcpStream) {
let job = (handler, stream);
self.senders[self.next_sender].send(job).unwrap();
//self.senders[self.next_sender].try_send(job).unwrap();
self.next_sender += 1;
if self.next_sender == self.senders.len() {
self.next_sender = 0;
}
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
self.senders.clear();
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Receiver) -> Worker {
let thread = thread::spawn(move || Self::work(receiver));
Worker {
id,
thread: Some(thread),
}
}
fn work(receiver: Receiver) {
loop {
let message = receiver.recv();
match message {
Ok((handler, stream)) => {
// println!("Worker got a job; executing.");
handler(stream);
}
Err(_) => {
// println!("Worker disconnected; shutting down.");
break;
}
}
}
}
}

View file

@ -1,32 +0,0 @@
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
//handle_connection(stream);
std::thread::spawn(||{handle_connection(stream)});
}
println!("Shutting down.");
}
fn handle_connection(mut stream: TcpStream) {
stream.set_nodelay(true).expect("set_nodelay call failed");
loop{
let mut buffer = [0; 1024];
match stream.read(&mut buffer){
Err(_)=>return,
Ok(0)=>return,
Ok(_v)=>{},
}
let response_bytes = b"HTTP/1.1 200 OK\r\nContent-Length: 16\r\nConnection: keep-alive\r\n\r\nHELLO from RUST!";
stream.write_all(response_bytes).unwrap();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,12 +0,0 @@
from sanic import Sanic
from sanic.response import html
app = Sanic("sanic-app")
@app.route('/')
async def test(request):
return html("Hello from sanic!", 200)
if __name__ == '__main__':
app.run()

View file

@ -1,24 +0,0 @@
const std = @import("std");
const zap = @import("zap");
fn on_request_minimal(r: zap.Request) void {
r.sendBody("Hello from ZAP!!!") catch return;
}
pub fn main() !void {
var listener = zap.HttpListener.init(.{
.port = 3000,
.on_request = on_request_minimal,
.log = false,
.max_clients = 100000,
});
try listener.listen();
std.debug.print("Listening on 0.0.0.0:3000\n", .{});
// start worker threads
zap.start(.{
.threads = 4,
.workers = 4, // empirical tests: yield best perf on my machine
});
}

View file

@ -1,33 +0,0 @@
const std = @import("std");
pub fn main() !void {
// var gpa = std.heap.GeneralPurposeAllocator(.{
// .thread_safe = true,
// }){};
// const allocator = gpa.allocator();
const address = try std.net.Address.parseIp("127.0.0.1", 3000);
var http_server = try address.listen(.{
.reuse_address = true,
});
var read_buffer: [2048]u8 = undefined;
// const max_header_size = 8192;
while (true) {
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";
try request.respond(server_body, .{
.extra_headers = &.{
.{ .name = "content_type", .value = "text/plain" },
.{ .name = "connection", .value = "close" },
},
});
}
}