diff --git "a/database/web.json" "b/database/web.json" new file mode 100644--- /dev/null +++ "b/database/web.json" @@ -0,0 +1 @@ +[{"avatar_url": "https://avatars.githubusercontent.com/u/125233599?v=4", "name": "zap", "full_name": "zigzap/zap", "created_at": "2023-01-12T21:36:31Z", "description": "blazingly fast backends in zig", "default_branch": "master", "open_issues": 20, "stargazers_count": 2825, "forks_count": 92, "watchers_count": 2825, "tags_url": "https://api.github.com/repos/zigzap/zap/tags", "license": "-", "topics": ["api", "blazingly", "fast", "http", "rest", "zig", "zig-package"], "size": 11378, "fork": false, "updated_at": "2025-04-12T21:03:27Z", "has_build_zig": true, "has_build_zig_zon": true, "readme_content": "# \u26a1zap\u26a1 - blazingly fast backends in zig\n\n  [](https://discord.gg/jQAAN6Ubyj)\n\nZap is the [zig](https://ziglang.org) replacement for the REST APIs I used to\nwrite in [python](https://python.org) with\n[Flask](https://flask.palletsprojects.com) and\n[mongodb](https://www.mongodb.com), etc. It can be considered to be a\nmicroframework for web applications.\n\nWhat I needed as a replacement was a blazingly fast and robust HTTP server that\nI could use with Zig, and I chose to wrap the superb evented networking C\nlibrary [facil.io](https://facil.io). Zap wraps and patches [facil.io - the C\nweb application framework](https://facil.io).\n\n## **\u26a1ZAP\u26a1 IS FAST, ROBUST, AND STABLE**\n\n\nAfter having used ZAP in production for years, I can confidently assert that it\nproved to be:\n\n- \u26a1 **blazingly fast** \u26a1\n- \ud83d\udcaa **extremely robust** \ud83d\udcaa\n\n## FAQ:\n\n- Q: **What version of Zig does Zap support?**\n - Zap uses the latest stable zig release (0.14.0), so you don't have to keep\n up with frequent breaking changes. It's an \"LTS feature\".\n- Q: **Can Zap build with Zig's master branch?**\n - See the `zig-master` branch. Please note that the zig-master branch is not\n the official master branch of ZAP. Be aware that I don't provide tagged\n releases for it. If you know what you are doing, that shouldn't stop you\n from using it with zig master though.\n- Q: **Where is the API documentation?**\n - Docs are a work in progress. You can check them out\n [here](https://zigzap.org/zap).\n - Run `zig build run-docserver` to serve them locally.\n- Q: **Does ZAP work on Windows?**\n - No. This is due to the underlying facil.io C library. Future versions\n of facil.io might support Windows but there is no timeline yet. Your best\n options on Windows are **WSL2 or a docker container**.\n- Q: **Does ZAP support TLS / HTTPS?**\n - Yes, ZAP supports using the system's openssl. See the\n [https](./examples/https/https.zig) example and make sure to build with\n the `-Dopenssl` flag or the environment variable `ZAP_USE_OPENSSL=true`:\n - `.openssl = true,` (in dependent projects' build.zig,\n `b.dependency(\"zap\" .{...})`)\n - `ZAP_USE_OPENSSL=true zig build https`\n - `zig build -Dopenssl=true https`\n\n## Here's what works\n\nI recommend checking out **the new App-based** or the Endpoint-based\nexamples, as they reflect how I intended Zap to be used.\n\nMost of the examples are super stripped down to only include what's necessary to\nshow a feature.\n\n**To see API docs, run `zig build run-docserver`.** To specify a custom\nport and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989\n--docs=path/to/docs`.\n\n### New App-Based Examples\n\n- **[app_basic](examples/app/basic.zig)**: Shows how to use zap.App with a\nsimple Endpoint.\n- **[app_auth](examples/app/auth.zig)**: Shows how to use zap.App with an\nEndpoint using an Authenticator.\n\nSee the other examples for specific uses of Zap.\n\nBenefits of using `zap.App`:\n\n- Provides a global, user-defined \"Application Context\" to all endpoints.\n- Made to work with \"Endpoints\": an endpoint is a struct that covers a `/slug`\n of the requested URL and provides a callback for each supported request method\n (get, put, delete, options, post, head, patch).\n- Each request callback receives:\n - a per-thread arena allocator you can use for throwaway allocations without\n worrying about freeing them.\n - the global \"Application Context\" of your app's choice\n- Endpoint request callbacks are allowed to return errors:\n - you can use `try`.\n - the endpoint's ErrorStrategy defines if runtime errors should be reported to\n the console, to the response (=browser for debugging), or if the error\n should be returned.\n\n### Legacy Endpoint-based examples\n\n- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring a\n `/users` endpoint for performing PUT/DELETE/GET/POST operations and listing\n users, together with a simple frontend to play with. **It also introduces a\n `/stop` endpoint** that shuts down Zap, so **memory leak detection** can be\n performed in main().\n - Check out how [main.zig](examples/endpoint/main.zig) uses ZIG's awesome\n `GeneralPurposeAllocator` to report memory leaks when ZAP is shut down.\n The [StopEndpoint](examples/endpoint/stopendpoint.zig) just stops ZAP when\n receiving a request on the `/stop` route.\n- **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a\n simple authenticated endpoint. Read more about authentication\n [here](./doc/authentication.md).\n\n\n### Legacy Middleware-Style examples\n\n- **[MIDDLEWARE support](examples/middleware/middleware.zig)**: chain together\n request handlers in middleware style. Provide custom context structs, totally\n type-safe. If you come from GO this might appeal to you.\n- **[MIDDLEWARE with endpoint\n support](examples/middleware_with_endpoint/middleware_with_endpoint.zig)**:\n Same as the example above, but this time we use an endpoint at the end of the\n chain, by wrapping it via `zap.Middleware.EndpointHandler`. Mixing endpoints\n in your middleware chain allows for usage of Zap's authenticated endpoints and\n your custom endpoints. Since Endpoints use a simpler API, you have to use\n `r.setUserContext()` and `r.getUserContext()` with the request if you want to\n access the middleware context from a wrapped endpoint. Since this mechanism\n uses an `*anyopaque` pointer underneath (to not break the Endpoint API), it is\n less type-safe than `zap.Middleware`'s use of contexts.\n- [**Per Request Contexts**](./src/zap.zig#L102) : With the introduction of\n `setUserContext()` and `getUserContext()`, you can, of course use those two in\n projects that don't use `zap.Endpoint` or `zap.Middleware`, too, if you\n really, really, absolutely don't find another way to solve your context\n problem. **We recommend using a `zap.Endpoint`** inside of a struct that\n can provide all the context you need **instead**. You get access to your\n struct in the callbacks via the `@fieldParentPtr()` trick that is used\n extensively in Zap's examples, like the [endpoint\n example](examples/endpoint/endpoint.zig).\n\n### Specific and Very Basic Examples\n\n- **[hello](examples/hello/hello.zig)**: welcomes you with some static HTML\n- **[routes](examples/routes/routes.zig)**: a super easy example dispatching on\n the HTTP path. **NOTE**: The dispatch in the example is a super-basic\n DIY-style dispatch. See endpoint-based examples for more realistic use cases.\n- [**simple_router**](examples/simple_router/simple_router.zig): See how you\n can use `zap.Router` to dispatch to handlers by HTTP path.\n- **[serve](examples/serve/serve.zig)**: the traditional static web server with\n optional dynamic request handling\n- **[sendfile](examples/sendfile/sendfile.zig)**: simple example of how to send\n a file, honoring compression headers, etc.\n- **[bindataformpost](examples/bindataformpost/bindataformpost.zig)**: example\n to receive binary files via form post.\n- **[hello_json](examples/hello_json/hello_json.zig)**: serves you json\n dependent on HTTP path\n- **[mustache](examples/mustache/mustache.zig)**: a simple example using\n [mustache](https://mustache.github.io/) templating.\n- **[http parameters](examples/http_params/http_params.zig)**: a simple example\n sending itself query parameters of all supported types.\n- **[cookies](examples/cookies/cookies.zig)**: a simple example sending itself a\n cookie and responding with a session cookie.\n- **[websockets](examples/websockets/)**: a simple websockets chat for the\n browser.\n- **[Username/Password Session\n Authentication](./examples/userpass_session_auth/)**: A convenience\n authenticator that redirects un-authenticated requests to a login page and\n sends cookies containing session tokens based on username/password pairs\n received via POST request.\n- [**Error Trace Responses**](./examples/senderror/senderror.zig): You can now\n call `r.sendError(err, status_code)` when you catch an error and a stack trace\n will be returned to the client / browser.\n- [**HTTPS**](examples/https/https.zig): Shows how easy it is to use facil.io's\n openssl support. Must be compiled with `-Dopenssl=true` or the environment\n variable `ZAP_USE_OPENSSL` set to `true` and requires openssl dev dependencies\n (headers, lib) to be installed on the system.\n - run it like this: `ZAP_USE_OPENSSL=true zig build run-https`\n OR like this: `zig build -Dopenssl=true run-https`\n - it will tell you how to generate certificates\n\n\n## \u26a1blazingly fast\u26a1\n\nClaiming to be blazingly fast is the new black. At least, Zap doesn't slow you\ndown and if your server performs poorly, it's probably not exactly Zap's fault.\nZap relies on the [facil.io](https://facil.io) framework and so it can't really\nclaim any performance fame for itself. In this initial implementation of Zap,\nI didn't care about optimizations at all.\n\nBut, how fast is it? Being blazingly fast is relative. When compared with a\nsimple GO HTTP server, a simple Zig Zap HTTP server performed really well on my\nmachine (x86_64-linux):\n\n- Zig Zap was nearly 30% faster than GO\n- Zig Zap had over 50% more throughput than GO\n- **YMMV!!!**\n\nSo, being somewhere in the ballpark of basic GO performance, zig zap seems to be\n... of reasonable performance \ud83d\ude0e.\n\nI can rest my case that developing ZAP was a good idea because it's faster than\nboth alternatives: a) staying with Python, and b) creating a GO + Zig hybrid.\n\n### On (now missing) Micro-Benchmarks\n\nI used to have some micro-benchmarks in this repo, showing that Zap beat all the\nother things I tried, and eventually got tired of the meaningless discussions\nthey provoked, the endless issues and PRs that followed, wanting me to add and\nmaintain even more contestants, do more justice to beloved other frameworks,\netc.\n\nCase in point, even for me the micro-benchmarks became meaningless. They were\njust some rough indicator to me confirming that I didn't do anything terribly\nwrong to facil.io, and that facil.io proved to be a reasonable choice, also from\na performance perspective.\n\nHowever, none of the projects I use Zap for, ever even remotely resembled\nanything close to a static HTTP response micro-benchmark.\n\nFor my more CPU-heavy than IO-heavy use-cases, a thread-based microframework\nthat's super robust is still my preferred choice, to this day.\n\nHaving said that, I would **still love** for other, pure-zig HTTP frameworks to\neventually make Zap obsolete. Now, in 2025, the list of candidates is looking\nreally promising.\n\n### \ud83d\udce3 Shout-Outs\n\n- [http.zig](https://github.com/karlseguin/http.zig) : Pure Zig! Close to Zap's\n model. Performance = good!\n- [jetzig](https://github.com/jetzig-framework/jetzig) : Comfortably develop\n modern web applications quickly, using http.zig under the hood\n- [zzz](https://github.com/tardy-org/zzz) : Super promising, super-fast,\n especially for IO-heavy tasks, io_uring support - need I say more?\n\n\n## \ud83d\udcaa Robust\n\nZAP is **very robust**. In fact, it is so robust that I was confidently able to\nonly work with in-memory data (RAM) in all my ZAP projects so far: over 5 large\nonline research experiments. No database, no file persistence, until I hit\n\"save\" at the end \ud83d\ude0a.\n\nSo I was able to postpone my cunning data persistence strategy that's similar to\na mark-and-sweep garbage collector and would only persist \"dirty\" data when\ntraffic is low, in favor of getting stuff online more quickly. But even if\nimplemented, such a persistence strategy is risky because when traffic is not\nlow, it means the system is under (heavy) load. Would you confidently NOT save\ndata when load is high and the data changes most frequently -> the potential\ndata loss is maximized?\n\nTo answer that question, I just skipped it. I skipped saving any data until\nreceiving a \"save\" signal via API. And it worked. ZAP just kept on zapping. When\ntraffic calmed down or all experiment participants had finished, I hit \"save\"\nand went on analyzing the data.\n\nHandling all errors does pay off after all. No hidden control flow, no hidden\nerrors or exceptions is one of Zig's strengths.\n\nTo be honest: There are still pitfalls. E.g. if you request large stack sizes\nfor worker threads, Zig won't like that and panic. So make sure you don't have\nlocal variables that require tens of megabytes of stack space.\n\n\n### \ud83d\udee1\ufe0f Memory-safe\n\nSee the [StopEndpoint](examples/endpoint/stopendpoint.zig) in the\n[endpoint](examples/endpoint) example. The `StopEndpoint` just stops ZAP when\nreceiving a request on the `/stop` route. That example uses ZIG's awesome\n`GeneralPurposeAllocator` in [main.zig](examples/endpoint/main.zig) to report\nmemory leaks when ZAP is shut down.\n\nYou can use the same strategy in your debug builds and tests to check if your\ncode leaks memory.\n\n\n\n## Getting started\n\nMake sure you have **zig 0.14.0** installed. Fetch it from\n[here](https://ziglang.org/download).\n\n```shell\n$ git clone https://github.com/zigzap/zap.git\n$ cd zap\n$ zig build run-hello\n$ # open http://localhost:3000 in your browser\n```\n... and open [http://localhost:3000](http://localhost:3000) in your browser.\n\n## Using \u26a1zap\u26a1 in your own projects\n\nMake sure you have **the latest zig release (0.14.0)** installed. Fetch it from\n[here](https://ziglang.org/download).\n\nIf you don't have an existing zig project, create one like this:\n\n```shell\n$ mkdir zaptest && cd zaptest\n$ zig init\n```\n\nWith an existing Zig project, adding Zap to it is easy:\n\n1. Zig fetch zap\n2. Add zap to your `build.zig`\n\nIn your zig project folder (where `build.zig` is located), run:\n\n\n```\nzig fetch --save \"git+https://github.com/zigzap/zap#v0.10.1\"\n```\n\n\nThen, in your `build.zig`'s `build` function, add the following before\n`b.installArtifact(exe)`:\n\n```zig\n const zap = b.dependency(\"zap\", .{\n .target = target,\n .optimize = optimize,\n .openssl = false, // set to true to enable TLS support\n });\n\n exe.root_module.addImport(\"zap\", zap.module(\"zap\"));\n```\n\nFrom then on, you can use the Zap package in your project via `const zap =\n@import(\"zap\");`. Check out the examples to see how to use Zap.\n\n\n## Contribute to \u26a1zap\u26a1 - blazingly fast\n\nAt the current time, I can only add to zap what I need for my personal and\nprofessional projects. While this happens **blazingly fast**, some if not all\nnice-to-have additions will have to wait. You are very welcome to help make the\nworld a blazingly fast place by providing patches or pull requests, add\ndocumentation or examples, or interesting issues and bug reports - you'll know\nwhat to do when you receive your calling \ud83d\udc7c.\n\n**We have our own [ZAP discord](https://discord.gg/jQAAN6Ubyj) server!!!**\n\n## Support \u26a1zap\u26a1\n\nBeing blazingly fast requires a constant feed of caffeine. I usually manage to\nprovide that to myself for myself. However, to support keeping the juices\nflowing and putting a smile on my face and that warm and cozy feeling into my\nheart, you can always [buy me a coffee](https://buymeacoffee.com/renerocksai)\n\u2615. All donations are welcomed \ud83d\ude4f blazingly fast! That being said, just saying\n\"hi\" also works wonders with the smiles, warmth, and coziness \ud83d\ude0a.\n\n## Examples\n\nYou build and run the examples via:\n\n```shell\n$ zig build [EXAMPLE]\n$ ./zig-out/bin/[EXAMPLE]\n```\n\n... where `[EXAMPLE]` is one of `hello`, `routes`, `serve`, ... see the [list of\nexamples above](#heres-what-works).\n\nExample: building and running the hello example:\n\n```shell\n$ zig build hello\n$ ./zig-out/bin/hello\n```\n\nTo just run an example, like `routes`, without generating an executable, run:\n\n```shell\n$ zig build run-[EXAMPLE]\n```\n\nExample: building and running the routes example:\n\n```shell\n$ zig build run-routes\n```\n\n### [hello](examples/hello/hello.zig)\n\n```zig\nconst std = @import(\"std\");\nconst zap = @import(\"zap\");\n\nfn on_request(r: zap.Request) void {\n if (r.path) |the_path| {\n std.debug.print(\"PATH: {s}\\n\", .{the_path});\n }\n\n if (r.query) |the_query| {\n std.debug.print(\"QUERY: {s}\\n\", .{the_query});\n }\n r.sendBody(\"
httpz.blockingMode()
returns true), please read the section as this mode of operation is more susceptible to DOS.\n\n# Metrics\nA few basic metrics are collected using [metrics.zig](https://github.com/karlseguin/metrics.zig), a prometheus-compatible library. These can be written to an `std.io.Writer` using `try httpz.writeMetrics(writer)`. As an example:\n\n```zig\npub fn metrics(_: *httpz.Request, res: *httpz.Response) !void {\n const writer = res.writer();\n try httpz.writeMetrics(writer);\n\n // if we were also using pg.zig \n // try pg.writeMetrics(writer);\n}\n```\n\nSince httpz does not provide any authorization, care should be taken before exposing this. \n\nThe metrics are:\n\n* `httpz_connections` - counts each TCP connection\n* `httpz_requests` - counts each request (should be >= httpz_connections due to keepalive)\n* `httpz_timeout_active` - counts each time an \"active\" connection is timed out. An \"active\" connection is one that has (a) just connected or (b) started to send bytes. The timeout is controlled by the `timeout.request` configuration.\n* `httpz_timeout_keepalive` - counts each time an \"keepalive\" connection is timed out. A \"keepalive\" connection has already received at least 1 response and the server is waiting for a new request. The timeout is controlled by the `timeout.keepalive` configuration.\n* `httpz_alloc_buffer_empty` - counts number of bytes allocated due to the large buffer pool being empty. This may indicate that `workers.large_buffer_count` should be larger.\n* `httpz_alloc_buffer_large` - counts number of bytes allocated due to the large buffer pool being too small. This may indicate that `workers.large_buffer_size` should be larger.\n* `httpz_alloc_unescape` - counts number of bytes allocated due to unescaping query or form parameters. This may indicate that `request.buffer_size` should be larger.\n* `httpz_internal_error` - counts number of unexpected errors within httpz. Such errors normally result in the connection being abruptly closed. For example, a failing syscall to epoll/kqueue would increment this counter.\n* `httpz_invalid_request` - counts number of requests which httpz could not parse (where the request is invalid).\n* `httpz_header_too_big` - counts the number of requests which httpz rejects due to a header being too big (does not fit in `request.buffer_size` config).\n* `httpz_body_too_big` - counts the number of requests which httpz rejects due to a body being too big (is larger than `request.max_body_size` config).\n\n# Testing\nThe `httpz.testing` namespace exists to help application developers setup an `*httpz.Request` and assert an `*httpz.Response`.\n\nImagine we have the following partial action:\n\n```zig\nfn search(req: *httpz.Request, res: *httpz.Response) !void {\n const query = try req.query();\n const search = query.get(\"search\") orelse return missingParameter(res, \"search\");\n\n // TODO ...\n}\n\nfn missingParameter(res: *httpz.Response, parameter: []const u8) !void {\n res.status = 400;\n return res.json(.{.@\"error\" = \"missing parameter\", .parameter = parameter}, .{});\n}\n```\n\nWe can test the above error case like so:\n\n```zig\nconst ht = @import(\"httpz\").testing;\n\ntest \"search: missing parameter\" {\n // init takes the same Configuration used when creating the real server\n // but only the config.request and config.response settings have any impact\n var web_test = ht.init(.{});\n defer web_test.deinit();\n\n try search(web_test.req, web_test.res);\n try web_test.expectStatus(400);\n try web_test.expectJson(.{.@\"error\" = \"missing parameter\", .parameter = \"search\"});\n}\n```\n\n## Building the test Request\nThe testing structure returns from httpz.testing.init
exposes helper functions to set param, query and query values as well as the body:\n\n```zig\nvar web_test = ht.init(.{});\ndefer web_test.deinit();\n\nweb_test.param(\"id\", \"99382\");\nweb_test.query(\"search\", \"tea\");\nweb_test.header(\"Authorization\", \"admin\");\n\nweb_test.body(\"over 9000!\");\n// OR\nweb_test.json(.{.over = 9000});\n// OR \n// This requires ht.init(.{.request = .{.max_form_count = 10}})\nweb_test.form(.{.over = \"9000\"});\n\n// at this point, web_test.req has a param value, a query string value, a header value and a body.\n```\n\nAs an alternative to the `query` function, the full URL can also be set. If you use `query` AND `url`, the query parameters of the URL will be ignored:\n\n```zig\nweb_test.url(\"/power?over=9000\");\n```\n\n## Asserting the Response\nThere are various methods to assert the response:\n\n```zig\ntry web_test.expectStatus(200);\ntry web_test.expectHeader(\"Location\", \"/\");\ntry web_test.expectHeader(\"Location\", \"/\");\ntry web_test.expectBody(\"{\\\"over\\\":9000}\");\n```\n\nIf the expected body is in JSON, there are two helpers available. First, to assert the entire JSON body, you can use `expectJson`:\n\n```zig\ntry web_test.expectJson(.{.over = 9000});\n```\n\nOr, you can retrieve a `std.json.Value` object by calling `getJson`:\n\n```zig\nconst json = try web_test.getJson();\ntry std.testing.expectEqual(@as(i64, 9000), json.Object.get(\"over\").?.Integer);\n```\n\nFor more advanced validation, use the `parseResponse` function to return a structure representing the parsed response:\n\n```zig\nconst res = try web_test.parseResponse();\ntry std.testing.expectEqual(@as(u16, 200), res.status);\n// use res.body for a []const u8 \n// use res.headers for a std.StringHashMap([]const u8)\n// use res.raw for the full raw response\n```\n\n# HTTP Compliance\nThis implementation may never be fully HTTP/1.1 compliant, as it is built with the assumption that it will sit behind a reverse proxy that is tolerant of non-compliant upstreams (e.g. nginx). (One example I know of is that the server doesn't include the mandatory Date header in the response.)\n\n# Server Side Events\nServer Side Events can be enabled by calling `res.startEventStream()`. This method takes an arbitrary context and a function pointer. The provided function will be executed in a new thread, receiving the provided context and an `std.net.Stream`. Headers can be added (via `res.headers.add`) before calling `startEventStream()`. `res.body` must not be set (directly or indirectly).\n\nCalling `startEventStream()` automatically sets the `Content-Type`, `Cache-Control` and `Connection` header.\n\n```zig\nfn handler(_: *Request, res: *Response) !void {\n try res.startEventStream(StreamContext{}, StreamContext.handle);\n}\n\nconst StreamContext = struct {\n fn handle(self: StreamContext, stream: std.net.Stream) void {\n while (true) {\n // some event loop\n stream.writeAll(\"event: ....\") catch return;\n }\n }\n}\n```\n\n# Websocket\nhttp.zig integrates with [https://github.com/karlseguin/websocket.zig](https://github.com/karlseguin/websocket.zig) by calling `httpz.upgradeWebsocket()`. First, your handler must have a `WebsocketHandler` declaration which is the WebSocket handler type used by `websocket.Server(H)`.\n\n```zig\nconst websocket = httpz.websocket;\n\nconst Handler = struct {\n // App-specific data you want to pass when initializing\n // your WebSocketHandler\n const WebsocketContext = struct {\n\n };\n\n // See the websocket.zig documentation. But essentially this is your\n // Application's wrapper around 1 websocket connection\n pub const WebsocketHandler = struct {\n conn: *websocket.Conn,\n\n // ctx is arbitrary data you passs to httpz.upgradeWebsocket\n pub fn init(conn: *websocket.Conn, _: WebsocketContext) {\n return .{\n .conn = conn,\n }\n }\n\n // echo back\n pub fn clientMessage(self: *WebsocketHandler, data: []const u8) !void {\n try self.conn.write(data);\n }\n } \n};\n```\n\nWith this in place, you can call httpz.upgradeWebsocket() within an action:\n\n```zig\nfn ws(req: *httpz.Request, res: *httpz.Response) !void {\n if (try httpz.upgradeWebsocket(WebsocketHandler, req, res, WebsocketContext{}) == false) {\n // this was not a valid websocket handshake request\n // you should probably return with an error\n res.status = 400;\n res.body = \"invalid websocket handshake\";\n return;\n }\n // Do not use `res` from this point on\n}\n```\n\nIn websocket.zig, `init` is passed a `websocket.Handshake`. This is not the case with the httpz integration - you are expected to do any necessary validation of the request in the action.\n\nIt is an undefined behavior if `Handler.WebsocketHandler` is not the same type passed to `httpz.upgradeWebsocket`.\n"}, {"avatar_url": "https://avatars.githubusercontent.com/u/206480?v=4", "name": "websocket.zig", "full_name": "karlseguin/websocket.zig", "created_at": "2022-05-28T14:35:01Z", "description": "A websocket implementation for zig", "default_branch": "master", "open_issues": 5, "stargazers_count": 393, "forks_count": 37, "watchers_count": 393, "tags_url": "https://api.github.com/repos/karlseguin/websocket.zig/tags", "license": "-", "topics": ["websocket", "zig", "zig-library", "zig-package"], "size": 524, "fork": false, "updated_at": "2025-04-07T04:21:41Z", "has_build_zig": true, "has_build_zig_zon": true, "readme_content": "# A zig websocket server.\nThis project follows Zig master. See available branches if you're targeting a specific version.\n\nSkip to the [client section](#client).\n\nIf you're upgrading from a previous version, check out the [Server Migration](https://github.com/karlseguin/websocket.zig/wiki/Server-Migration) and [Client Migration](https://github.com/karlseguin/websocket.zig/wiki/Client-Migration) wikis.\n\n# Server\n```zig\nconst std = @import(\"std\");\nconst ws = @import(\"websocket\");\n\npub fn main() !void {\n var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n const allocator = gpa.allocator();\n\n var server = try ws.Server(Handler).init(allocator, .{\n .port = 9224,\n .address = \"127.0.0.1\",\n .handshake = .{\n .timeout = 3,\n .max_size = 1024,\n // since we aren't using hanshake.headers\n // we can set this to 0 to save a few bytes.\n .max_headers = 0,\n },\n });\n\n // Arbitrary (application-specific) data to pass into each handler\n // Pass void ({}) into listen if you have none\n var app = App{};\n\n // this blocks\n try server.listen(&app);\n}\n\n// This is your application-specific wrapper around a websocket connection\nconst Handler = struct {\n app: *App,\n conn: *ws.Conn,\n\n // You must define a public init function which takes\n pub fn init(h: ws.Handshake, conn: *ws.Conn, app: *App) !Handler {\n // `h` contains the initial websocket \"handshake\" request\n // It can be used to apply application-specific logic to verify / allow\n // the connection (e.g. valid url, query string parameters, or headers)\n\n _ = h; // we're not using this in our simple case\n\n return .{\n .app = app,\n .conn = conn,\n };\n }\n\n // You must defined a public clientMessage method\n pub fn clientMessage(self: *Handler, data: []const u8) !void {\n try self.conn.write(data); // echo the message back\n }\n};\n\n// This is application-specific you want passed into your Handler's\n// init function.\nconst App = struct {\n // maybe a db pool\n // maybe a list of rooms\n};\n```\n\n## Handler\nWhen you create a `websocket.Server(Handler)`, the specified `Handler` is your structure which will receive messages. It must have a public `init` function and `clientMessage` method. Other methods, such as `close` can optionally be defined.\n\n### init\nThe `init` method is called with a `websocket.Handshake`, a `*websocket.Conn` and whatever app-specific value was passed into `Server(H).init`. \n\nWhen `init` is called, the handshake response has not yet been sent to the client (this allows your `init` method to return an error which will cause websocket.zig to send an error response and close the connection). As such, you should not use/write to the `*websocket.Conn` at this point. Instead, use the `afterInit` method, described next.\n\nThe websocket specification requires the initial \"handshake\" to contain certain headers and values. The library validates these headers. However applications may have additional requirements before allowing the connection to be \"upgraded\" to a websocket connection. For example, a one-time-use token could be required in the querystring. Applications should use the provided `websocket.Handshake` to apply any application-specific verification and optionally return an error to terminate the connection.\n\nThe `websocket.Handshake` exposes the following fields:\n\n* `url: []const u8` - URL of the request in its original casing\n* `method: []const u8` - Method of the request in its original casing\n* `raw_headers: []const u8` - The raw \"key1: value1\\r\\nkey2: value2\\r\\n\" headers. Keys are lowercase.\n\nIf you set the `max_headers` configuration value to > 0, then you can use `req.headers.get(\"HEADER_NAME\")` to extract a header value from the given name:\n\n```zig\n// the last parameter, an *App in this case, is an application-specific\n// value that you passed into server.listen()\npub fn init(h: websocket.Handshake, conn: websocket.Conn, app: *App) !Handler {\n // get returns a ?[]const u8\n // the name is lowercase\n // the value is in its original case\n const token = handshake.headers.get(\"authorization\") orelse {\n return error.NotAuthorized;\n }\n\n return .{\n .app = app,\n .conn = conn,\n };\n}\n\n```\n\nYou can iterate through all the headers:\n```zig\nvar it = handshake.headers.iterator();\nwhile (it.next) |kv| {\n std.debug.print(\"{s} = {s}\\n\", .{kv.key, kv.value});\n}\n```\n\nMemory referenced by the `websocket.Handshake`, including headers from `handshake.headers` will be freed after the call to `init` completes. Application that need these values to exist beyond the call to `init` must make a copy.\n\n### afterInit\nIf your handler defines a `afterInit(handler: *Handler) !void` method, the method is called after the handshake response has been sent. This is the first time the connection can safely be used.\n\n`afterInit` supports two overloads:\n\n```zig\npub fn afterInit(handler: *Handler) !void\npub fn afterInit(handler: *Handler, ctx: anytype) !void\n```\n\nThe `ctx` is the same `ctx` passed into `init`. It is passed here for cases where the value is only needed once when the connection is established.\n\n### clientMessage\nThe `clientMessage` method is called whenever a text or binary message is received.\n\nThe `clientMessage` method can take one of four shapes. The simplest, shown in the first example, is:\n\n```zig\n// simple clientMessage\nclientMessage(h: *Handler, data: []u8) !void\n```\n\nThe Websocket specific has a distinct message type for text and binary. Text messages must be valid UTF-8. Websocket.zig does not do this validation (it's expensive and most apps don't care). However, if you do care about the distinction, your `clientMessage` can take another parameter:\n\n```zig\n// clientMessage that accepts a tpe to differentiate between messages\n// sent as `text` vs those sent as `binary`. Either way, Websocket.zig\n// does not validate that text data is valid UTF-8.\nclientMessage(h: *Handler, data: []u8, tpe: ws.MessageTextType) !void\n```\n\nFinally, `clientMessage` can take an optional `std.mem.Allocator`. If you need to dynamically allocate memory within `clientMessage`, consider using this allocator. It is a fast thread-local buffer that fallsback to an arena allocator. Allocations made with this allocator are freed after `clientMessage` returns:\n\n```zig\n// clientMessage that takes an allocator\nclientMessage(h: *Handler, allocator: Allocator, data: []u8) !void\n\n// cilentMessage that takes an allocator AND a MessageTextType\nclientMessage(h: *Handler, allocator: Allocator, data: []u8, tpe: ws.MessageTextType) !void`\n```\n\nIf `clientMessage` returns an error, the connection is closed. You can also call `conn.close()` within the method.\n\n### close\nIf your handler defines a `close(handler: *Handler)` method, the method is called whenever the connection is being closed. Guaranteed to be called exactly once, so it is safe to deinitialize the `handler` at this point. This is called no mater the reason for the closure (on shutdown, if the client closed the connection, if your code close the connection, ...)\n\nThe socket may or may not still be alive.\n\n### clientClose\nIf your handler defines a `clientClose(handler: *Handler, data: []u8) !void` method, the function will be called whenever a `close` message is received from the client. \n\nYou almost certainly *do not* want to define this method and instead want to use `close()`. When not defined, websocket.zig follows the websocket specific and replies with its own matching close message.\n\n### clientPong\nIf your handler defines a `clientPong(handler: *Handler, data: []u8) !void` method, the function will be called whenever a `pong` message is received from the client. When not defined, no action is taken.\n\n### clientPing\nIf your handler defines a `clientPing(handler: *Handler, data: []u8) !void` method, the function will be called whenever `ping` message is received from the client. When not defined, websocket.zig will write a corresponding `pong` reply.\n\n## websocket.Conn\nThe call to `init` includes a `*websocket.Conn`. It is expected that handlers will keep a reference to it. The main purpose of the `*Conn` is to write data via `conn.write([]const u8)` and `conn.writeBin([]const u8)`. The websocket protocol differentiates between a \"text\" and \"binary\" message, with the only difference that \"text\" must be valid UTF-8. This library does not enforce this. Which you use really depends on what your client expects. For browsers, text messages appear as strings, and binary messages appear as a Blob or ArrayBuffer (depending on how the client is configured).\n\n`conn.close(.{})` can also be called to close the connection. Calling `conn.close()` **will** result in the handler's `close` callback being called. \n\n`close` takes an optional value where you can specify the `code` and/or `reason`: `conn.close(.{.code = 4000, .reason = \"bye bye\"})` Refer to [RFC6455](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1) for valid codes. The `reason` must be <= 123 bytes.\n\n### Writer\nIt's possible to get a `std.io.Writer` from a `*Conn`. Because websocket messages are framed, the writter will buffer the message in memory and requires an explicit \"flush\". Buffering requires an allocator. \n\n```zig\n// .text or .binary\nvar wb = conn.writeBuffer(allocator, .text);\ndefer wb.deinit();\ntry std.fmt.format(wb.writer(), \"it's over {d}!!!\", .{9000});\ntry wb.flush();\n```\n\nConsider using the `clientMessage` overload which accepts an allocator. Not only is this allocator fast (it's a thread-local buffer than fallsback to an arena), but it also eliminates the need to call `deinit`:\n\n```zig\npub fn clientMessage(h: *Handler, allocator: Allocator, data: []const u8) !void {\n // Use the provided allocator.\n // It's faster and doesn't require `deinit` to be called\n\n var wb = conn.writeBuffer(allocator, .text);\n try std.fmt.format(wb.writer(), \"it's over {d}!!!\", .{9000});\n try wb.flush();\n}\n```\n\n## Thread Safety\nWebsocket.zig ensures that only 1 message per connection/handler is processed at a time. Therefore, you will never have concurrent calls to `clientMessage`, `clientPing`, `clientPong` or `clientClose`. Conversely, concurrent calls to methods of `*websocket.Conn` are allowed (i.e. `conn.write` and `conn.close`). \n\n## Config\nThe 2nd parameter to `Server(H).init` is a configuration object. \n\n```zig\npub const Config = struct {\n port: u16 = 9882,\n\n // Ignored if unix_path is set\n address: []const u8 = \"127.0.0.1\",\n\n // Not valid on windows\n unix_path: ?[]const u8 = null,\n\n // In nonblocking mode (Linux/Mac/BSD), sets the number of\n // listening threads. Defaults to 1.\n // In blocking mode, this is ignored and always set to 1.\n worker_count: ?u8 = null,\n\n // The maximum number of connections, per worker.\n // default: 16_384\n max_conn: ?usize = null,\n\n // The maximium allows message size. \n // A websocket message can have up to 14 bytes of overhead/header\n // Default: 65_536\n max_message_size: ?usize = null,\n\n handshake: Config.Handshake = .{},\n thread_pool: ThreadPool = .{},\n buffers: Config.Buffers = .{},\n\n // compression is disabled by default\n compression: ?Compression = null,\n\n // In blocking mode the thread pool isn't used \n pub const ThreadPool = struct {\n // Number of threads to process messages.\n // These threads are where your `clientXYZ` method will execute.\n // Default: 4.\n count: ?u16 = null,\n\n // The maximum number of pending requests that the thread pool will accept\n // This applies back pressure to worker and ensures that, under load\n // pending requests get precedence over processing new requests.\n // Default: 500.\n backlog: ?u32 = null,\n\n // Size of the static buffer to give each thread. Memory usage will be \n // `count * buffer_size`.\n // If clientMessage isn't defined with an Allocator, this defaults to 0.\n // Else it default to 32768\n buffer_size: ?usize = null,\n };\n\n const Handshake = struct {\n // time, in seconds, to timeout the initial handshake request\n timeout: u32 = 10,\n\n // Max size, in bytes, allowed for the initial handshake request.\n // If you're expected a large handshake (many headers, large cookies, etc)\n // you'll need to set this larger.\n // Default: 1024\n max_size: ?u16 = null,\n\n // Max number of headers to capture. These become available as\n // handshake.headers.get(...).\n // Default: 0\n max_headers: ?u16 = null,\n\n // Count of handshake objects to keep in a pool. More are created\n // as needed.\n // Default: 32\n count: ?u16 = null,\n };\n\n const Buffers = struct {\n // The number of \"small\" buffers to keep pooled.\n //\n // When `null`, the small buffer pool is disabled and each connection\n // gets its own dedicated small buffer (of `size`). This is reasonable\n // when you expect most clients to be sending a steady stream of data.\n \n // When set > 0, a pool is created (of `size` buffers) and buffers are \n // assigned as messages are received. This is reasonable when you expect\n // sporadic and messages from clients.\n //\n // Default: `null`\n small_pool: ?usize = null,\n\n // The size of each \"small\" buffer. Depending on the value of `pool`\n // this is either a per-connection buffer, or the size of pool buffers\n // shared between all connections\n // Default: 2048\n small_size: ?usize = null,\n\n // The number of large buffers to have in the pool.\n // Messages larger than `buffers.small_size` but smaller than `max_message_size`\n // will attempt to use a large buffer from the pool.\n // If the pool is empty, a dynamic buffer is created.\n // Default: 8\n large_pool: ?u16 = null,\n\n // The size of each large buffer.\n // Default: min(2 * buffers.small_size, max_message_size)\n large_size: ?usize = null,\n };\n\n // Compression is disabled by default, to enable it and accept the default\n // values, set it to a defautl struct: .{}\n const Compression = struct {\n // The mimimum size of data before messages will be compressed\n // null = message are never compressed when writing messages to the client\n // If you want to enable compression, 512 is a reasonable default\n write_threshold: ?usize = null,\n\n // When compression is enable, and write_treshold != null, every connection\n // gets an std.ArrayList(u8) to write the compressed message to. When this\n // is true, the memory allocated to the ArrayList is kept for subsequent\n // messages (i.e. it calls `clearRetainingCapacity`). When false, the memory\n // is freed after each message. \n // true = more memory, but fewer allocations\n retain_write_buffer: bool = true,\n\n // Advanced options that are part of the permessage-deflate specification.\n // You can set these to true to try and save a bit of memory. But if you\n // want to save memory, don't use compression at all.\n client_no_context_takeover: bool = false,\n server_no_context_takeover: bool = false,\n };\n}\n```\n\n## Logging\nwebsocket.zig uses Zig's built-in scope logging. You can control the log level by having an `std_options` decleration in your program's main file:\n\n```zig\npub const std_options = std.Options{\n .log_scope_levels = &[_]std.log.ScopeLevel{\n .{ .scope = .websocket, .level = .err },\n }\n};\n```\n\n## Advanced\n\n### Pre-Framed Comptime Message\nWebsocket message have their own special framing. When you use `conn.write` or `conn.writeBin` the data you provide is \"framed\" into a correct websocket message. Framing is fast and cheap (e.g., it DOES NOT require an O(N) loop through the data). Nonetheless, there may be be cases where pre-framing messages at compile-time is desired. The `websocket.frameText` and `websocket.frameBin` can be used for this purpose:\n\n```zig\nconst UNKNOWN_COMMAND = websocket.frameText(\"unknown command\");\n...\n\npub fn clientMessage(self: *Handler, data: []const u8) !void {\n if (std.mem.startsWith(u8, data, \"join: \")) {\n self.handleJoin(data)\n } else if (std.mem.startsWith(u8, data, \"leave: \")) {\n self.handleLead(data)\n } else {\n try self.conn.writeFramed(UNKNOWN_COMMAND);\n }\n}\n```\n\n### Blocking Mode\nkqueue (BSD, MacOS) or epoll (Linux) are used on supported platforms. On all other platforms (most notably Windows), a more naive thread-per-connection with blocking sockets is used.\n\nThe comptime-safe, `websocket.blockingMode() bool` function can be called to determine which mode websocket is running in (when it returns `true`, then you're running the simpler blocking mode).\n\n### Per-Connection Buffers\nIn non-blocking mode, the `buffers.small_pool` and `buffers.small_size` should be set for your particular use case. When `buffers.small_pool == null`, each connection gets its own buffer of `buffers.small_size` bytes. This is a good option if you expect most of your clients to be sending a steady stream of data. While it might take more memory (# of connections * buffers.small_size), its faster and minimizes multi-threading overhead.\n\nHowever, if you expect clients to only send messages sporadically, such as a chat application, enabling the pool can reduce memory usage at the cost of a bit of overhead.\n\nIn blocking mode, these settings are ignored and each connection always gets its own buffer (though there is still a shared large buffer pool).\n\n### Stopping\n`server.stop()` can be called to stop the webserver. It is safe to call this from a different thread (i.e. a `sigaction` handler).\n\n## Testing\nThe library comes with some helpers for testing.\n\n```zig\nconst wt = @import(\"websocket\").testing;\n\ntest \"handler: echo\" {\n var wtt = wt.init();\n defer wtt.deinit();\n\n // create an instance of your handler (however you want)\n // and use &tww.conn as the *ws.Conn field\n var handler = Handler{\n .conn = &wtt.conn,\n };\n\n // execute the methods of your handler\n try handler.clientMessage(\"hello world\");\n\n // assert what the client should have received\n try wtt.expectMessage(.text, \"hello world\");\n}\n```\n\nBesides `expectMessage` you can also call `expectClose()`.\n\nNote that this testing is heavy-handed. It opens up a pair of sockets with one side listening on `127.0.0.1` and accepting a connection from the other. `wtt.conn` is the \"server\" side of the connection, and assertion happens on the client side.\n\n# Client\nThe `*websocket.Client` can be used in one of two ways. At its simplest, after creating a client and initiating a handshake, you simply use write
to send messages and read
to receive them. First, we create the client and initiate the handshake:\n\n```zig\npub fn main() !void {\n var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n const allocator = gpa.allocator();\n\n // create the client\n var client = try websocket.Client.init(allocator, .{\n .port = 9224,\n .host = \"localhost\",\n });\n defer client.deinit();\n\n // send the initial handshake request\n const request_path = \"/ws\";\n try client.handshake(request_path, .{\n .timeout_ms = 1000,\n // Raw headers to send, if any. \n // A lot of servers require a Host header.\n // Separate multiple headers using \\r\\n\n .headers = \"Host: localhost:9224\",\n });\n}\n```\n\nWe can then use `read` and `write`. By default, `read` blocks until a message is received (or an error occurs). We can make it return `null` by setting a timeout:\n\n```zig\n // optional, read will return null after 1 second\n try client.readTimeout(std.time.ms_per_s * 1);\n\n // echo messages back to the server until the connection is closed\n while (true) {\n // since we didn't set a timeout, client.read() will either\n // return a message or an error (i.e. it won't return null)\n const message = (try client.read()) orelse {\n // no message after our 1 second\n std.debug.print(\".\", .{});\n continue;\n };\n\n // must be called once you're done processing the request\n defer client.done(message);\n\n switch (message.type) {\n .text, .binary => {\n std.debug.print(\"received: {s}\\n\", .{message.data});\n try client.write(message.data);\n },\n .ping => try client.writePong(message.data),\n .pong => {},\n .close => {\n try client.close(.{});\n break;\n }\n }\n }\n}\n```\n\n### Config\nWhen creating a Client, the 2nd parameter is a configuration object:\n\n* `port` - The port to connect to. Required.\n* `host` - The host/IP address to connect to. The Host:IP value IS NOT automatically put in the header of the handshake request. Required.\n* `max_size` - Maximum incoming message size to allow. The library will dynamically allocate up to this much space per request. Default: `65536`.\n* `buffer_size` - Size of the static buffer that's available for the client to process incoming messages. While there's other overhead, the minimal memory usage of the server will be `# of active clients * buffer_size`. Default: `4096`.\n* `tls` - Whether or not to connect over TLS. Only TLS 1.3 is supported. Default: `false`.\n* `ca_bundle` - Provide a custom `std.crypto.Certificate.Bundle`. Only meaningful when `tls = true`. Default: `null`.\n\nSetting `max_size == buffer_size` is valid and will ensure that no dynamic memory allocation occurs once the connection is established.\n\nZig only supports TLS 1.3, so this library can only connect to hosts using TLS 1.3. If no `ca_bundle` is provided, the library will create a default bundle per connection.\n\n### Handshake\n`client.handshake()` takes two parameters. The first is the request path. The second is handshake configuration value:\n\n* `timeout_ms` - Timeout, in milliseconds, for the handshake. Default: `10_000` (10 seconds).\n* `headers` - Raw headers to include in the handshake. Multiple headers should be separated by by \"\\r\\n\". Many servers require a Host header. Example: `\"Host: server\\r\\nAuthorization: Something\"`. Defaul: `null`\n\n### Custom Wrapper\nIn more advanced cases, you'll likely want to wrap a `*ws.Client` in your own type and use a background read loop with \"callback\" methods. Like in the above example, you'll first want to create a client and initialize a handshake:\n\n```zig\nconst ws = @import(\"websocket\");\n\nconst Handler = struct {\n client: ws.Client,\n\n fn init(allocator: std.mem.Allocator) !Handler {\n var client = try ws.Client.init(allocator, .{\n .port = 9224,\n .host = \"localhost\",\n });\n defer client.deinit();\n\n // send the initial handshake request\n const request_path = \"/ws\";\n try client.handshake(request_path, .{\n .timeout_ms = 1000,\n .headers = \"host: localhost:9224\\r\\n\",\n });\n\n return .{\n .client = client,\n };\n }\n ```\n\nYou can then call `client.readLoopInNewThread()` to start a background listener. Your handler must define a `serverMessage` method:\n\n```zig\n pub fn startLoop(self: *Handler) !void {\n // use readLoop for a blocking version\n const thread = try self.client.readLoopInNewThread(self);\n thread.detach();\n }\n\n pub fn serverMessage(self: *Handler, data: []u8) !void {\n // echo back to server\n return self.client.write(data);\n }\n}\n```\n\nWebsockets have a number of different message types. `serverMessage` only receives text and binary messages. If you care about the distinction, you can use an overload:\n\n```zig\npub fn serverMessage(self: *Handler, data: []u8, tpe: ws.MessageTextType) !void\n```\n\nwhere `tpe` will be either `.text` or `.binary`. Different callbacks are used for the other message types.\n\n#### Optional Callbacks\nIn addition to the required `serverMessage`, you can define optional callbacks.\n\n```zig\n// Guaranteed to be called exactly once when the readLoop exits\npub fn close(self: *Handler) void\n\n// If omitted, websocket.zig will automatically reply with a pong\npub fn serverPing(self: *Handler, data: []u8) !void\n\n// If omitted, websocket.zig will ignore this message\npub fn serverPong(self: *Handler) !void\n\n// If omitted, websocket.zig will automatically reply with a close message\npub fn serverClose(self: *Handler) !void\n```\n\nYou almost certainly **do not** want to define a `serverClose` method, but instead what do define a `close` method. In your `close` callback, you should call `client.close(.{})` (and optionally pass a code and reason).\n\n## Client\nWhether you're calling `client.read()` explicitly or using `client.readLoopInNewThread()` (or `client.readLoop()`), the `client` API is the same. In both cases, the various `write` methods, as well as `close()` are thread-safe.\n\n### Writing\nIt may come as a surprise, but every variation of `write` expects a `[]u8`, not a `[]const u8`. Websocket payloads sent from a client need to be masked, which the websocket.zig library handles. It is obviously more efficient to mutate the given payload versus creating a copy. By taking a `[]u8`, applications with mutable buffers benefit from avoiding the clone. Applications that have immutable buffers will need to create a mutable clone.\n\n```zig\n// write a text message\npub fn write(self: *Client, data: []u8) !void\n\n// write a text message (same as client.write(data))\npub fn writeText(self: *Client, data: []u8) !void\n\n// write a binary message\npub fn writeBin(self: *Client, data: []u8) !void\n\n// write a ping message\npub fn writePing(self: *Client, data: []u8) !void\n\n// write a pong message\n// if you don't define a handlePong message, websocket.zig\n// will automatically answer any ping with a pong\npub fn writePong(self: *Client, data: []u8) !void\n\n// lower-level, use by all of the above\npub fn writeFrame(self: *Client, op_code: proto.OpCode, data: []u8) !void\n```\n\n### Reading\nAs seen above, most applications will either chose to call `read()` explicitly or use a `readLoop`. It is **not safe* to call `read` while the read loop is running.\n\n```zig\n// Reads 1 message. Returns null on timeout\n// Set a timeout using `client.readTimeout(ms)`\npub fn read(self: *Client) !?ws.Message\n\n\n// Starts a readloop in the calling thread. \n// `@TypeOf(handler)` must define the `serverMessage` callback\n// (and may define other optional callbacks)\npub fn readLoop(self: *Client, handler: anytype) !void\n\n// Same as `readLoop` but starts the readLoop in a new thread\npub fn readLoopInNewThread(self: *Client, h: anytype) !std.Thread\n```\n\n### Closing\nUse `try client.close(.{.code = 4000, .reason = \"bye\"})` to both send a close frame and close the connection. Noop if the connection is already known to be close. Thread safe.\n\nBoth `code` and `reason` are optional. Refer to [RFC6455](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1) for valid codes. The `reason` must be <= 123 bytes.\n\n\n### Performance Optimization 1 - CA Bundle\nFor a high number of connections, it might be beneficial to manage our own CA bundle:\n\n```zig\n// some global data\nvar ca_bundle = std.crypto.Certificate.Bundle{}\ntry ca_bundle.rescan(allocator);\ndefer ca_bundle.deinit(allocator);\n```\n\nAnd then assign this `ca_bundle` into the the configuration's `ca_bundle` field. This way the library does not have to create and scan the installed CA certificates for each client connection.\n\n### Performance Optimization 2 - Buffer Provider\nFor a high nummber of connections a large buffer pool can be created and provided to each client:\n\n```zig\n// Create a buffer pool of 10 buffers, each being 32K\nconst buffer_provider = try websocket.bufferProvider(allocator, 10, 32768);\ndefer buffer_provider.deinit();\n\n\n// create your client(s) using the above created buffer_provider\nvar client = try websocket.connect(allocator, \"localhost\", 9001, .{\n ...\n .buffer_provider = buffer_provider,\n});\n```\n\nThis allows each client to have a reasonable `buffer_size` that can accomodate most messages, while having an efficient fallback for the occasional large message. When `max_size` is greater than the large buffer pool size (32K in the above example) or when all pooled buffers are used, a dynamic buffer is created.\n"}, {"avatar_url": "https://avatars.githubusercontent.com/u/3932972?v=4", "name": "zig-network", "full_name": "ikskuh/zig-network", "created_at": "2020-04-28T11:39:38Z", "description": "A smallest-common-subset of socket functions for crossplatform networking, TCP & UDP", "default_branch": "master", "open_issues": 11, "stargazers_count": 544, "forks_count": 67, "watchers_count": 544, "tags_url": "https://api.github.com/repos/ikskuh/zig-network/tags", "license": "-", "topics": ["networking", "tcp", "tcp-client", "tcp-server", "udp", "zig", "zig-package"], "size": 287, "fork": false, "updated_at": "2025-04-08T01:56:25Z", "has_build_zig": true, "has_build_zig_zon": true, "readme_content": "# Zig Network Abstraction\n\nSmall network abstraction layer around TCP & UDP.\n\n## Features\n- Implements the minimal API surface for basic networking\n- Makes cross-platform abstractions\n- Supports blocking and non-blocking I/O via `select`/`poll`\n- UDP multicast support\n\n## Usage\n\n### Use with the package manager\n\n`build.zig.zon`:\n```zig\n.{\n .name = \"appname\",\n .version = \"0.0.0\",\n .dependencies = .{\n .network = .{\n .url = \"https://github.com/MasterQ32/zig-network/archive/