An HTTP/1.1 server for zig
0

Configure Feed

Select the types of activity you want to include in your feed.

Fix some typos in readme.md

+36 -39
+36 -39
readme.md
··· 1 1 # An HTTP/1.1 server for Zig. 2 2 3 3 ## Zig Version 4 - This is for Zig 0.16.0. Use the [zig-0.15.2](https://github.com/karlseguin/http.zig/tree/zig-0.15) branch for Zig 0.15 or the [dev](https://github.com/karlseguin/http.zig/tree/dev) which may or may not be up to date with zig dev. 4 + This is for Zig 0.16.0. Use the [zig-0.15](https://github.com/karlseguin/http.zig/tree/zig-0.15) branch for Zig 0.15 or the [dev](https://github.com/karlseguin/http.zig/tree/dev) branch which may or may not be up to date with zig dev. 5 5 6 6 This ZIG 0.16 version is not well tested. Like Zig 0.16 itself, consider it experimental! 7 7 ··· 13 13 pub fn main(init: std.process.Init) !void { 14 14 const allocator = init.gpa; 15 15 16 - // More advance cases will use a custom "Handler" instead of "void". 17 - // The last parameter is our handler instance, since we have a "void" 16 + // More advanced cases will use a custom "Handler" instead of "void". 17 + // The last parameter is our handler instance; since we have a "void" 18 18 // handler, we passed a void ({}) value. 19 19 var server = try httpz.Server(void).init(init.io, allocator, .{ 20 20 // use .all(5882) to bind to all interfaces, i.e. 0.0.0.0 21 21 .address = .localhost(5882), 22 22 }, {}); 23 23 defer { 24 - // clean shutdown, finishes serving any live request 24 + // clean shutdown, finishes serving any live requests 25 25 server.stop(); 26 26 server.deinit(); 27 27 } ··· 64 64 65 65 # Versions 66 66 67 - The `master` branch targets the latest stable of Zig (0.15.1). The `dev` branch targets the latest version of Zig. If you're looking for an older version, look for an zig-X.YZ branches. 67 + The `master` branch targets the latest **stable** release of Zig; the `dev` branch targets the latest version of Zig. If you're looking for an older version, look for `zig-X.YY` branches. 68 68 69 69 # Examples 70 70 ··· 78 78 # Installation 79 79 80 80 1. Add http.zig as a dependency in your `build.zig.zon`: 81 - 82 - ```bash 83 - zig fetch --save "git+https://github.com/karlseguin/http.zig#master" 84 - ``` 81 + ```bash 82 + zig fetch --save "git+https://github.com/karlseguin/http.zig#master" 83 + ``` 84 + The library will track Zig master. If you're using a specific version of Zig, use the appropriate branch. 85 85 86 86 2. In your `build.zig`, add the `httpz` module as a dependency to your program: 87 - 88 - ```zig 89 - const httpz = b.dependency("httpz", .{ 90 - .target = target, 91 - .optimize = optimize, 92 - }); 87 + ```zig 88 + const httpz = b.dependency("httpz", .{ 89 + .target = target, 90 + .optimize = optimize, 91 + }); 92 + 93 + // the executable from your call to b.addExecutable(...) 94 + exe.root_module.addImport("httpz", httpz.module("httpz")); 95 + ``` 93 96 94 - // the executable from your call to b.addExecutable(...) 95 - exe.root_module.addImport("httpz", httpz.module("httpz")); 96 - ``` 97 - 98 - The library tracks Zig master. If you're using a specific version of Zig, use the appropriate branch. 99 97 100 98 # Alternatives 101 99 ··· 236 234 237 235 ## Error Handler 238 236 239 - If your handler has a public `uncaughtError` method, it will be called whenever there's an unhandled error. This could be due to some internal httpz bug, or because your action return an error. 237 + If your handler has a public `uncaughtError` method, it will be called whenever there's an unhandled error. This could be due to some internal httpz bug, or because your action returns an error. 240 238 241 239 ```zig 242 240 const App = struct { ··· 252 250 253 251 ## Takeover 254 252 255 - For the most control, you can define a `handle` method. This circumvents most of Httpz's dispatching, including routing. Frameworks like JetZig hook use `handle` in order to provide their own routing and dispatching. When you define a `handle` method, then any `dispatch`, `notFound` and `uncaughtError` methods are ignored by httpz. 253 + For the most control, you can define a `handle` method. This circumvents most of Httpz's dispatching, including routing. Frameworks like JetZig hook `handle` in order to provide their own routing and dispatching. When you define a `handle` method, then any `dispatch`, `notFound` and `uncaughtError` methods are ignored by httpz. 256 254 257 255 ```zig 258 256 const App = struct { ··· 262 260 }; 263 261 ``` 264 262 265 - The behavior `httpz.Server(H)` is controlled by 266 263 The library supports both simple and complex use cases. A simple use case is shown below. It's initiated by the call to `httpz.Server()`: 267 264 268 265 ```zig ··· 337 334 338 335 Alternatively, you can explicitly call `res.write()`. Once `res.write()` returns, the response is sent and your action can cleanup/release any resources. 339 336 340 - `res.arena` is actually a configurable-sized thread-local buffer that fallsback to an `std.heap.ArenaAllocator`. In other words, it's fast so it should be your first option for data that needs to live only until your action exits. 337 + `res.arena` is actually a configurable-sized thread-local buffer that falls back to an `std.heap.ArenaAllocator`. In other words, it's fast so it should be your first option for data that needs to live only until your action exits. 341 338 342 - To align the `Response.writer()` with the new `*std.Io.Writer` interface (Zig 0.15) , a buffer must be provided. For the most part, you should pass in an empty buffer (`&.{}`) as httpz does its own buffer (as I expect most network applications would do). Still, it appears that some parts of std require a writer buffer. This seems like a really bad design to me, but if you find yourself using code that requires a writer buffer, then, of course, you'll have to provide one when creating the `response.writer(...)`. 339 + To align the `Response.writer()` with the new `*std.Io.Writer` interface (Zig 0.15), a buffer must be provided. For the most part, you should pass in an empty buffer (`&.{}`) as httpz does its own buffering (as I expect most network applications would do). Still, it appears that some parts of std require a writer buffer. This seems like a really bad design to me, but if you find yourself using code that requires a writer buffer, then, of course, you'll have to provide one when creating the `response.writer(...)`. 343 340 344 341 # httpz.Request 345 342 ··· 347 344 348 345 - `method` - httpz.Method enum 349 346 - `method_string` - Only set if `method == .OTHER`, else empty. Used when using custom methods. 350 - - `arena` - A fast thread-local buffer that fallsback to an ArenaAllocator, same as `res.arena`. 347 + - `arena` - A fast thread-local buffer that falls back to an ArenaAllocator, same as `res.arena`. 351 348 - `url.path` - the path of the request (`[]const u8`) 352 349 - `address` - the std.net.Address of the client 353 350 ··· 418 415 }; 419 416 ``` 420 417 421 - On first call, the `query` function attempts to parse the querystring. This requires memory allocations to unescape encoded values. The parsed value is internally cached, so subsequent calls to `query()` are fast and cannot fail. 418 + On first call, the `query` function attempts to parse the query string. This requires memory allocations to unescape encoded values. The parsed value is internally cached, so subsequent calls to `query()` are fast and cannot fail. 422 419 423 420 The original casing of both the key and the name are preserved. 424 421 ··· 472 469 473 470 ### Form Data 474 471 475 - The body of the request, if any, can be parsed as a "x-www-form-urlencoded "value using `req.formData()`. The `request.max_form_count` configuration value must be set to the maximum number of form fields to support. This defaults to 0. 472 + The body of the request, if any, can be parsed as a `x-www-form-urlencoded` value using `req.formData()`. The `request.max_form_count` configuration value must be set to the maximum number of form fields to support. This defaults to 0. 476 473 477 474 This behaves similarly to `query()`. 478 475 ··· 494 491 495 492 ### Multi Part Form Data 496 493 497 - Similar to the above, `req.multiFormData()` can be called to parse requests with a "multipart/form-data" content type. The `request.max_multiform_count` configuration value must be set to the maximum number of form fields to support. This defaults to 0. 494 + Similar to the above, `req.multiFormData()` can be called to parse requests with a `multipart/form-data` content type. The `request.max_multiform_count` configuration value must be set to the maximum number of form fields to support. This defaults to 0. 498 495 499 496 This is a different API than `formData` because the return type is different. Rather than a simple string=>value type, the multi part form data value consists of a `value: []const u8` and a `filename: ?[]const u8`. 500 497 ··· 561 558 562 559 - `status` - set the status code, by default, each response starts off with a 200 status code 563 560 - `content_type` - an httpz.ContentType enum value. This is a convenience and optimization over using the `res.header` function. 564 - - `arena` - A fast thread-local buffer that fallsback to an ArenaAllocator, same as `req.arena`. 561 + - `arena` - A fast thread-local buffer that falls back to an ArenaAllocator, same as `req.arena`. 565 562 566 563 The `status` field is a `u16`. You can alternatively use `res.setStatus(.ok)` if you prefer to use the `std.http.Status` enum. 567 564 ··· 647 644 648 645 ## Writing 649 646 650 - By default, httpz will automatically flush your response. In more advance cases, you can use `res.write()` to explicitly flush it. This is useful in cases where you have resources that need to be freed/released only after the response is written. For example, my [LRU cache](https://github.com/karlseguin/cache.zig) uses atomic referencing counting to safely allow concurrent access to cached data. This requires callers to "release" the cached entry: 647 + By default, httpz will automatically flush your response. In more advanced cases, you can use `res.write()` to explicitly flush it. This is useful in cases where you have resources that need to be freed/released only after the response is written. For example, my [LRU cache](https://github.com/karlseguin/cache.zig) uses atomic referencing counting to safely allow concurrent access to cached data. This requires callers to "release" the cached entry: 651 648 652 649 ```zig 653 650 pub fn info(app: *MyApp, _: *httpz.Request, res: *httpz.Response) !void { ··· 679 676 router.tryGet("/", index, .{}); 680 677 ``` 681 678 682 - The 3rd parameter is a route configuration. It allows you to speficy a different `handler` and/or `dispatch` method and/or `middleware`. 679 + The 3rd parameter is a route configuration. It allows you to specify a different `handler` and/or `dispatch` method and/or `middleware`. 683 680 684 681 ```zig 685 682 // this can panic if it fails to create the route ··· 775 772 router.method("TEA", "/", teaList, .{}); 776 773 ``` 777 774 778 - In such cases, `request.method` will be `.OTHER` and you can use the `reqeust.method_string` for the string value. The method name, `TEA` above, is cloned by the router and does not need to exist beyond the function call. The method name should only be uppercase ASCII letters. 775 + In such cases, `request.method` will be `.OTHER` and you can use the `request.method_string` for the string value. The method name, `TEA` above, is cloned by the router and does not need to exist beyond the function call. The method name should only be uppercase ASCII letters. 779 776 780 777 The `router.all` method **does not** route to custom methods. 781 778 ··· 817 814 // explicitly opts out) 818 815 var router = try server.router(.{.middlewares = &.{cors}}); 819 816 820 - // or we could add middleware on a route-per-route bassis 817 + // or we could add middleware on a route-per-route basis 821 818 router.get("/v1/users", user, .{.middlewares = &.{cors}}); 822 819 823 820 // by default, middlewares on a route are appended to the global middlewares ··· 918 915 919 916 `request.buffer_size` must be large enough to fit the request header. Any extra space might be used to read the body. However, there can be up to `workers.count * workers.max_conn` pending requests, so a large `request.buffer_size` can take up a lot of memory. Instead, consider keeping `request.buffer_size` only large enough for the header (plus a bit of overhead for decoding URL-escape values) and set `workers.large_buffer_size` to a reasonable size for your incoming request bodies. This will take `workers.count * workers.large_buffer_count * workers.large_buffer_size` memory. 920 917 921 - Buffers for request bodies larger than `workers.large_buffer_size` but smaller than `request.max_body_size` will be dynamic allocated. 918 + Buffers for request bodies larger than `workers.large_buffer_size` but smaller than `request.max_body_size` will be dynamically allocated. 922 919 923 920 In addition to a bit of overhead, at a minimum, httpz will use: 924 921 ··· 978 975 // configures the threadpool which processes requests. The threadpool is 979 976 // where your application code runs. 980 977 .thread_pool = .{ 981 - // Number threads. If you're handlers are doing a lot of i/o, a higher 978 + // Number threads. If your handlers are doing a lot of i/o, a higher 982 979 // number might provide better throughput 983 980 // (blocking mode: handled differently) 984 981 .count = 32, 985 982 986 983 // The maximum number of pending requests that the thread pool will accept 987 - // This applies back pressure to the above workers and ensures that, under load 984 + // This applies back pressure to the above workers and ensures that, under load, 988 985 // pending requests get precedence over processing new requests. 989 986 .backlog = 500, 990 987 ··· 1175 1172 1176 1173 ## Building the test Request 1177 1174 1178 - The testing structure returns from <code>httpz.testing.init</code> exposes helper functions to set param, query and query values as well as the body: 1175 + The testing structure returned from <code>httpz.testing.init</code> exposes helper functions to set param, query and header values as well as the body: 1179 1176 1180 1177 ```zig 1181 1178 var web_test = ht.init(.{}); ··· 1279 1276 pub const WebsocketHandler = struct { 1280 1277 conn: *websocket.Conn, 1281 1278 1282 - // ctx is arbitrary data you passs to httpz.upgradeWebsocket 1279 + // ctx is arbitrary data you pass to httpz.upgradeWebsocket 1283 1280 pub fn init(conn: *websocket.Conn, _: WebsocketContext) { 1284 1281 return .{ 1285 1282 .conn = conn,