Commits
Reject ambiguous Content-Length and Host headers
https://github.com/karlseguin/http.zig/issues/170
Fixes: https://github.com/karlseguin/http.zig/issues/193
Zig 0.16's networking is incomplete. Io.Threaded treats AGAIN as a "programmer
bug". Fair because using only std.Io.net you can't put a socket in nonblocking
mode, but unfair because there's nothing inherently wrong about mixing
nonblocking sockets with threads - zig's Io.Threaded just doesn't support it
[and they decide to (a) panic and (b) blame the user).
As with most things with Zig 0.16, the solution is to avoid std.Io.net as much
as possible. Most of httpz was already using posix.zig, but I left this one
case in
Cannot resize from fixed allocator to arena/fallback. Fixes
https://github.com/karlseguin/http.zig/issues/192
(Also, add format to Config.Address)
Websocket is currently disabled. It'll come very soon.
The heading `Configuration` was used twice, the TOC misleadingly linked
to the first one which was the subheading for Configuration of the
Router.
https://github.com/karlseguin/http.zig/pull/188
https://github.com/karlseguin/http.zig/issues/187
https://github.com/karlseguin/http.zig/issues/186
Also add .any as an alternative to the first example in the readme
See: https://github.com/karlseguin/http.zig/pull/185
he reason I didn't think it was possible is that, once we upgrade to websockets, we switch the to ONESHOT / EV_DISPATCH. And I _thought_ this would be enough to ensure that we only ever process 1 message at a time. The re-arming only happens as the last step the worker thread does..so while 2 threads could technically be running on the same connection, one of those threads would be in the process of shutting down and 100% NOT be doing anything with the connection. And that all works.
EXCEPT for a tiny window during our initial switch to EV_DISPATCH. Say a connection is upgraded and also sends 2 websocket messages. While a single poll (`kevent`) won't return 2 distinct events for that 1 connection, they could get split into 2 separate polls. We end up with:
```
wait#1
[signal, fd#1.recv#1]
wait#2
[fd#1.recv#2]
```
and this happens because as we're processing the wait#1 and before we switch to EV_DISPATCH, `fd#1.recv#2` has already been received and queued. Even if you delete and re-add, it seems like that only deals with future events, it doesn't clear our any already/pending events. So we need to mimic what we alreayd do for HTTP connections, which is as a guard clause for a connection that's already being processed. But we still keep the ONESHOT / DISPATCH because it does work beyond the initial window, and that just helps prevent wakeups that we'll just have to reject via the guard.
Fix shutdown on 32-bit Linux: eventfd write must be 8 bytes
* Introduce Config.AddressConfig.
* Let callers provide a pre-parsed Address for the listener
* Express the default listener address in a simpler way
* update readme to show the new way of setting listener address
Add errdefer to unlock mutex when listen() fails before reaching
the success paths. Without this, errors during socket setup, binding,
or worker initialization leave the mutex locked, causing subsequent
calls to listen() or listenInNewThread() to deadlock.
This bug is particularly severe with listenInNewThread() because the
main thread waits on a condition variable, and when the spawned thread
exits with the mutex locked, the condition wakes the main thread which
then hangs forever trying to reacquire the mutex.
Adds test that verifies mutex is properly released by calling listen()
twice when bind fails. Without the fix, the second call deadlocks.
https://github.com/karlseguin/http.zig/issues/178
https://github.com/karlseguin/http.zig/pull/173
* Enhance CORS to include credentials handling and allow multi cors origin values
Added support for credentials in CORS configuration.
Allow multi cors origin values
* Enhance CORS middleware: support wildcard & multiple origins, credentials; add tests
- Add Origin union and parseOrigin() to middleware/Cors.zig; init now takes MiddlewareConfig and uses arena for origin parsing
- Support wildcard ("*") and comma-separated origin lists; set Access-Control-Allow-Origin to "*" for wildcard, or echo allowed origin for lists
- Preserve and emit Access-Control-Allow-Credentials, Allow-Methods, Allow-Headers, and Max-Age for preflight requests
- Update tests: increase test thread array size, spawn dedicated CORS servers (wildcard, single, multiple), add comprehensive tests for GET and OPTIONS preflight flows and negative cases
- Ensure Origin header is sent in relevant test requests
* Fix parsing of multiple CORS origins by initializing count to 0
* tests: stop and deinit CORS test servers in tests:afterAll
* docs(readme): cleanup formatting, fix typos, and expand CORS documentation
- Normalize whitespace, punctuation and list formatting across README
- Fix typos and improve wording for clarity
- Expand CORS middleware section with examples and behavior:
- document wildcard, single and multiple origins
- add credentials option and note about wildcard+credentials restriction
- show preflight headers and 204 behavior
- Update middleware path reference to httpz.middleware.Cors
- Minor formatting tweaks in many sections (headers, code blocks, comments)
https://github.com/karlseguin/http.zig/issues/156
* fix worker KQueue signal() and stop() implementation
* fix response writer double buffering
Based on: https://github.com/karlseguin/http.zig/pull/150
https://github.com/karlseguin/http.zig/issues/108#issuecomment-3123612037
https://github.com/karlseguin/http.zig/issues/108#issuecomment-3123612037
When res.disown() is called, the caller is assumed to take over ownership of
the socket. We can't assume anything about the socket after res.disown()
returns, e.g. the caller might immediately close the socket.
Currently, after disown, we transfer the connection back to the worker to remove
it from the loop. This happens via the signal mechanism. So removal happens at
some point in the future. Its possible for the connection to have been closed
by then, which causes a segfault when we try to remove.
When res.disown() is called, we now immediately remove the socket from the loop
before turning ownership of the socket to the caller. This requires some hack
code since the HTTPConn previously had no knowledge/link to the loop.
When res.disown() is called, the caller is assumed to take over ownership of
the socket. We can't assume anything about the socket after res.disown()
returns, e.g. the caller might immediately close the socket.
Currently, after disown, we transfer the connection back to the worker to remove
it from the loop. This happens via the signal mechanism. So removal happens at
some point in the future. Its possible for the connection to have been closed
by then, which causes a segfault when we try to remove.
When res.disown() is called, we now immediately remove the socket from the loop
before turning ownership of the socket to the caller. This requires some hack
code since the HTTPConn previously had no knowledge/link to the loop.
This allows specifying multiple filters, allowing for e.g.:
```
zig build test -Dtsan -Dtest-filter="tests:beforeAll" -Dtest-filter="httpz: no route"
```
Signed-off-by: Wolfgang E. Sanyer <wolfgangesanyer@gmail.com>
This allows specifying multiple filters, allowing for e.g.:
```
zig build test -Dtsan -Dtest-filter="tests:beforeAll" -Dtest-filter="httpz: no route"
```
Signed-off-by: Wolfgang E. Sanyer <wolfgangesanyer@gmail.com>
explicitly set root_module in b.addTest and b.addExecutable
as .root_source_file, .target, .optimize, etc will
be deprecated in the future.
Fixes: https://github.com/karlseguin/http.zig/issues/193
Zig 0.16's networking is incomplete. Io.Threaded treats AGAIN as a "programmer
bug". Fair because using only std.Io.net you can't put a socket in nonblocking
mode, but unfair because there's nothing inherently wrong about mixing
nonblocking sockets with threads - zig's Io.Threaded just doesn't support it
[and they decide to (a) panic and (b) blame the user).
As with most things with Zig 0.16, the solution is to avoid std.Io.net as much
as possible. Most of httpz was already using posix.zig, but I left this one
case in
See: https://github.com/karlseguin/http.zig/pull/185
he reason I didn't think it was possible is that, once we upgrade to websockets, we switch the to ONESHOT / EV_DISPATCH. And I _thought_ this would be enough to ensure that we only ever process 1 message at a time. The re-arming only happens as the last step the worker thread does..so while 2 threads could technically be running on the same connection, one of those threads would be in the process of shutting down and 100% NOT be doing anything with the connection. And that all works.
EXCEPT for a tiny window during our initial switch to EV_DISPATCH. Say a connection is upgraded and also sends 2 websocket messages. While a single poll (`kevent`) won't return 2 distinct events for that 1 connection, they could get split into 2 separate polls. We end up with:
```
wait#1
[signal, fd#1.recv#1]
wait#2
[fd#1.recv#2]
```
and this happens because as we're processing the wait#1 and before we switch to EV_DISPATCH, `fd#1.recv#2` has already been received and queued. Even if you delete and re-add, it seems like that only deals with future events, it doesn't clear our any already/pending events. So we need to mimic what we alreayd do for HTTP connections, which is as a guard clause for a connection that's already being processed. But we still keep the ONESHOT / DISPATCH because it does work beyond the initial window, and that just helps prevent wakeups that we'll just have to reject via the guard.
Add errdefer to unlock mutex when listen() fails before reaching
the success paths. Without this, errors during socket setup, binding,
or worker initialization leave the mutex locked, causing subsequent
calls to listen() or listenInNewThread() to deadlock.
This bug is particularly severe with listenInNewThread() because the
main thread waits on a condition variable, and when the spawned thread
exits with the mutex locked, the condition wakes the main thread which
then hangs forever trying to reacquire the mutex.
Adds test that verifies mutex is properly released by calling listen()
twice when bind fails. Without the fix, the second call deadlocks.
* Enhance CORS to include credentials handling and allow multi cors origin values
Added support for credentials in CORS configuration.
Allow multi cors origin values
* Enhance CORS middleware: support wildcard & multiple origins, credentials; add tests
- Add Origin union and parseOrigin() to middleware/Cors.zig; init now takes MiddlewareConfig and uses arena for origin parsing
- Support wildcard ("*") and comma-separated origin lists; set Access-Control-Allow-Origin to "*" for wildcard, or echo allowed origin for lists
- Preserve and emit Access-Control-Allow-Credentials, Allow-Methods, Allow-Headers, and Max-Age for preflight requests
- Update tests: increase test thread array size, spawn dedicated CORS servers (wildcard, single, multiple), add comprehensive tests for GET and OPTIONS preflight flows and negative cases
- Ensure Origin header is sent in relevant test requests
* Fix parsing of multiple CORS origins by initializing count to 0
* tests: stop and deinit CORS test servers in tests:afterAll
* docs(readme): cleanup formatting, fix typos, and expand CORS documentation
- Normalize whitespace, punctuation and list formatting across README
- Fix typos and improve wording for clarity
- Expand CORS middleware section with examples and behavior:
- document wildcard, single and multiple origins
- add credentials option and note about wildcard+credentials restriction
- show preflight headers and 204 behavior
- Update middleware path reference to httpz.middleware.Cors
- Minor formatting tweaks in many sections (headers, code blocks, comments)
When res.disown() is called, the caller is assumed to take over ownership of
the socket. We can't assume anything about the socket after res.disown()
returns, e.g. the caller might immediately close the socket.
Currently, after disown, we transfer the connection back to the worker to remove
it from the loop. This happens via the signal mechanism. So removal happens at
some point in the future. Its possible for the connection to have been closed
by then, which causes a segfault when we try to remove.
When res.disown() is called, we now immediately remove the socket from the loop
before turning ownership of the socket to the caller. This requires some hack
code since the HTTPConn previously had no knowledge/link to the loop.
When res.disown() is called, the caller is assumed to take over ownership of
the socket. We can't assume anything about the socket after res.disown()
returns, e.g. the caller might immediately close the socket.
Currently, after disown, we transfer the connection back to the worker to remove
it from the loop. This happens via the signal mechanism. So removal happens at
some point in the future. Its possible for the connection to have been closed
by then, which causes a segfault when we try to remove.
When res.disown() is called, we now immediately remove the socket from the loop
before turning ownership of the socket to the caller. This requires some hack
code since the HTTPConn previously had no knowledge/link to the loop.