Commit Graph
240 Commits
Author SHA1 Message Date
codingetandClaude 5b48ab50ca test(http/websocket): add coverage for protocols option, basic-auth URL, abrupt close
- connectWebSocket(dialer, url, { protocols }) now tested: verifies the
  Sec-WebSocket-Protocol request header is sent as a comma-joined list
- Basic-auth credentials in the ws:// URL now tested: verifies Authorization
  header is set correctly
- upgradeWebSocket(req, res, { protocols }) now tested: verifies the server
  echoes Sec-WebSocket-Protocol response header from protocols[0] (server.ts 100%)
- Abrupt transport close (no Close frame) now tested: exercises the
  catch { break } path in WebSocketConnection's async iterator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 11:12:11 +00:00
codingetandClaude aaec794c88 fix(http): prevent pool race when a fetch() response is promoted to WebSocket
Two bugs in fetch.ts caused the upgraded connection to be closed or
reassigned before connectWebSocket() could hijack it:

1. prepareRequest() checked Connection !== "upgrade" case-sensitively.
   A request with Connection: Upgrade (capital U) was silently rewritten
   to Connection: close, making ClientConnection auto-close the transport
   after receiving the 101 (shouldClose returned true, no body → immediate
   close).  Fixed by lowercasing before the comparison.

2. rawFetch() called onPrevBodyFinished().then(done(false)) even for 101
   responses.  A 101 has no body so the promise resolves as a microtask,
   firing done(false) — and thus pool.releaseConnection() — before the
   caller can call connectWebSocket().  With keepAlive:false the pool
   closes the connection immediately; with keepAlive:N it puts it back in
   the idle pool where a concurrent waiter can grab it.  Fixed by calling
   done(true) synchronously for 101, which calls rejectConnection()
   instead, removing the connection from the pool without closing it.

   rawFetchStream() had the same issue in its finally block when the
   caller breaks before calling connectWebSocket(); fixed likewise.

Tests added in websocket.test.ts covering all four affected paths:
  - fetch() + connectWebSocket(res, key)
  - makeFetch(keepAlive:false) + connectWebSocket(res, key)
  - pool race: second request after WebSocket hijack gets a fresh conn
  - fetchStream() + connectWebSocket(r, key) inside the loop
  - makeFetch().stream + connectWebSocket(r, key)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 07:45:30 +00:00
codingetandClaude a8ccea5065 refactor(http/websocket): rework connectWebSocket to use dialer+URL or ClientResponse
Remove the ClientConnection overload. Replace with:

- connectWebSocket(dialer, url, options?) — accepts ws:/wss:/http:/https: URLs,
  dials via RawDialer, handles Host header, basic-auth, and port defaulting
  (mirrors the fetch() API surface).

- connectWebSocket(res, key) — promotes an already-obtained 101 ClientResponse
  to a WebSocketConnection; useful with fetchStream() or any pooled fetch.

The common accept-key verification and hijack logic is shared internally.
Tests updated: withWebSocketPair now uses loopbackListener + dialer URL;
new URL-validation and response-overload suites added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:08:41 +00:00
codingetandClaude 33144f52f0 feat(http): add WebSocket support in @webnet/http/websocket
Server: upgradeWebSocket(req, res) validates the HTTP Upgrade handshake
and returns a WebSocketConnection backed by the hijacked transport.

Client: connectWebSocket(conn, target) sends the Upgrade request, verifies
Sec-WebSocket-Accept, and returns a WebSocketConnection.

WebSocketConnection is an async iterable that yields text/binary messages.
Frame codec handles masking, fragmentation, and the close handshake.
Hashing uses crypto.subtle (Web Crypto API); masking uses
crypto.getRandomValues — no external dependencies.

Exported as @webnet/http/websocket (separate entry point, fully
tree-shakeable from bundles that don't use it).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:10:13 +00:00
codingetandClaude ae391541ba fix(http): exclude unreachable ContinueBodyReader.onFinish from coverage; fix README
Add /* c8 ignore start/stop */ around ContinueBodyReader.onFinish: the
method satisfies the BodyReader interface but has no external callers
because ServerConnection.#prevBody always holds the raw body reader, not
the ContinueBodyReader wrapper. Achieves 100% coverage across all files.

Update AI disclosure to accurately name fetchStream() and f.stream()
alongside requestStream() as the streaming work done in this branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 19:50:43 +00:00
codingetandClaude f10d0140d2 refactor+test(http): fix import style, update exports, improve coverage
Fix inline import() in connection.test.ts to use a top-level import type.

Exports: add ClientInformationalResponse, fetchStream, FetchStream to
client/index.ts and the root index.ts.

Coverage improvements:
- server/objects: sendInformational throws on bare response; uses
  "Unknown Status" for unrecognised status codes
- client/connection: requestStream throws on hijacked connection;
  drains previous body before streaming; closes on parse error;
  closes immediately when shouldClose and response has no body
- client/fetch: early break from fetchStream before final response

Remaining gap: ContinueBodyReader.onFinish (server/connection.ts:57-62)
is a required BodyReader interface method with no external callers in
the current server code path — #prevBody always holds the raw reader,
not the wrapper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 19:40:11 +00:00
codingetandClaude 04e79107ae feat(http): add interim:skip, fix requestStream+hijack, add fetch 1xx+streaming
interim: add "skip" as an explicit value matching undefined behavior, so
callers can be explicit at the call site.

requestStream + hijack: the try/finally in requestStream now checks
this.#hijacked before closing, so breaking out after calling hijack()
on a 101 response keeps the connection open rather than closing it.

fetch: add fetchStream() standalone function and f.stream() method on
makeFetch() results. Both return AsyncGenerator<ClientResponse> and
forward the inner requestStream generator through pool management:
- inner generator cleanup (innerGen.return()) is called in finally to
  ensure the connection closes on early break even when the inner loop
  is abandoned
- on early break (before final): pool.rejectConnection()
- on normal completion without hijack: onPrevBodyFinished triggers
  pool.releaseConnection() or rejectConnection() on error
- on hijack: the hijack listener handles pool.rejectConnection()

fetch() and makeFetch() now forward interim:"collect"/"skip" through
rawFetch → conn.request(), which already handles them.

prepareRequest() helper extracted to share URL parsing, validation,
and header setup between rawFetch and rawFetchStream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 19:18:43 +00:00
codingetandClaude 58d2197187 docs: update AI disclosure for client-side 1xx support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 15:01:41 +00:00
codingetandClaude 9cfcbcdfc5 feat(http): add client-side 1xx support
Default mode: the client now reads past interim 1xx responses
(100/102/103 and unknown 1xx, but NOT 101 Switching Protocols
which terminates the exchange) and returns the final response.
This fixes the longstanding TODO test.

Collect mode (interim: "collect"): interim responses are captured in
res.informational as ClientInformationalResponse objects with full
header access.

Stream mode: requestStream(req) is a new async generator that yields
each interim response and the final response as they arrive, enabling
callers to act on 103 Early Hints before the body is ready. Breaks
out of the loop early (e.g. before the final response) safely close
the connection via a try/finally guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 15:01:30 +00:00
codingetandClaude a4506991e7 feat(http): add 1xx informational response support
Sends 100 Continue lazily when a handler first reads the request body
and the request carried Expect: 100-continue. If the handler returns
without reading the body the connection is closed rather than
attempting to drain (which would deadlock waiting for a body the
client has not yet sent).

Adds res.sendInformational(status, headers?) on ServerResponse so
handlers can push arbitrary 1xx responses (e.g. 103 Early Hints) to
the client before the final response is sent.

Also registers 100/102/103 in statusCodeProperties with responseBody:
false so Content-Length is not added if the final response status
happens to be a 1xx code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:40:06 +00:00
codingetandClaude 9ebbd6ec2d feat(http): export prependTransport via _internals
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:30:28 +00:00
codingetandClaude a952de815b test(http): achieve 100% coverage for hijack and prependTransport
Fix two inline RawTransport imports in server/connection.test.ts (the
type was already imported at the top of the file).

Add prependTransport suite to common/buffer.test.ts covering every
getter and method: read() prefix-then-base, closed, close(), readEnded,
whenClosed, remoteAddr, localAddr, write(), and both branches of the
halfClose conditional.

Add a client/connection test where the server sends the 101 response
and raw protocol bytes in the same write, leaving bytes in the
ReadBuffer when hijack() is called. This exercises the prependTransport
path inside #takeTransport().

Add a fetch() hijack test that sends a 101 response through the full
fetch stack. This exercises the poolDone guard in rawFetch: hijack()
fires the synchronous listener (done(true)) before the
onPrevBodyFinished microtask runs done(false), which hits the guard and
is correctly a no-op.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:17:06 +00:00
codingetandClaude 873f392a79 fix(http): guard hijacked ClientConnection against reuse and fix pool timing
Guard: send() and request() now throw "Cannot use a hijacked connection"
if called after hijack(), preventing accidental state corruption.

Pool timing: the previous onPrevBodyFinished().then(releaseOrReject)
approach queued pool management as a microtask that ran before the
caller's await-continuation, meaning hijack() hadn't been called yet
when the pool decision fired. The fix injects a synchronous hijack
listener via setHijackListener() that calls pool.rejectConnection()
immediately when hijack() is invoked. A poolDone latch ensures only
one of the two paths (listener or body-finish callback) updates the
pool, regardless of call order or async gaps between fetch() and
hijack().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:05:40 +00:00
codingetandClaude dfa4ab69cf feat(http): add sendResponse option to server hijack
hijack({ sendResponse: false }) flushes any pending write-buffer bytes
without sending the response headers/body. This is intended for use
with the upcoming 1xx support: a handler can send a 101 Switching
Protocols as an interim response via the 1xx mechanism, then call
hijack({ sendResponse: false }) to take ownership of the transport
without double-sending the response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:34:24 +00:00
codingetandClaude f905206441 docs: update AI disclosure for hijack() implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:52:42 +00:00
codingetandClaude a70ba05ce3 feat(http): add hijack() to server and client responses for HTTP upgrades
ServerResponse.hijack() sends the current response then returns the
underlying RawTransport so handlers can switch to a different protocol
(WebSocket, raw TCP, etc.) without the server loop taking over again.

ClientResponse.hijack() returns the raw transport after a 101 response
so the caller can take over the connection for the new protocol.

Both variants prepend any bytes the ReadBuffer had already read ahead,
ensuring no data is lost. The server's handle() loop exits cleanly on
hijack and leaves the transport open. The pool integration in fetch.ts
rejects hijacked connections rather than returning them to the pool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:52:26 +00:00
codingetandClaude f0ec13e9b7 docs(agents): keep PR description up to date on each push
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:40:21 +00:00
codingetandClaude 43a3345549 docs(agents): add coding style and dependency guidelines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:33:38 +00:00
codingetandClaude 1014073090 docs: add AGENTS.md with agent workflow instructions
Covers: opening PRs with tea and the WIP: prefix convention, and
updating the README AI disclosure at the end of every branch.
CLAUDE.md is symlinked to AGENTS.md for Claude Code compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:25:15 +00:00
codingetandClaude 1b92611f7e docs: accurately reflect Claude Code contributions to packages/http
The previous disclosure significantly understated Claude Code's
involvement. It contributed the package scaffolding, a major transport
layer review and bugfix pass, the timeout feature, parse error message
improvements, and the full test suite — not just the initial empty
package and light spec guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:10:43 +00:00
codingetandClaude 67994a2d4c docs: update AI disclosure — http test suite and bug fixes
The packages/http test suite was mostly generated by Claude Code, which
also surfaced and fixed three bugs during that work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:04:27 +00:00
codingetandClaude 264c0adb65 refactor(http): remove dead try/catch in PooledDialer.shutdown()
The slot waiters and global waiters are Promise resolve/reject callbacks.
Promise callbacks cannot throw, so the try/catch around them was dead
code that also required c8 ignore annotations to suppress coverage gaps.
Removed both wrappers and moved the errors array to where it's actually
needed (connection close errors).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:54:24 +00:00
codingetandClaude 622707e7b2 chore(http): remove RUN_UNIT / skipIfNotUnit — unit tests always run
Excluding unit tests via an env flag doesn't make sense; the flag only
created confusion. RUN_INTEGRATION=false (via test:unit) is the only
useful gate. Removed the export from flags.ts and the { skip } options
from all unit test suites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:54:00 +00:00
codingetandClaude 02b50c9fd0 fix(http): restore TypeError for connection parameter guards in pool
These are API-boundary parameter checks, not internal invariant
assertions. TypeError is the correct error type and the original message
communicates the problem clearly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:53:09 +00:00
codingetandClaude 570972b304 fix(http): adjust c8 ignore counts after prettier reformatted pool.ts
Prettier expanded two compact try/catch blocks from 1 line to 5 lines
each, and split an if/throw across two lines — bumping next 3 → next 5
for the shutdown() catch bodies and next → next 2 for the setTimeout
invariant guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:55:40 +00:00
codinget fbb65c5d95 test(http): add lcov reporter 2026-05-28 19:52:25 +00:00
codingetandClaude abbaae44a4 test(http): 100% branch coverage — new tests and c8 ignore annotations
New tests:
- UnpooledDialer: exercise dialTls success path
- PooledDialer.rejectConnection: cover timeout.has branch by rejecting a
  previously-released connection; also fixes a bug where the connection
  was not removed from slot.idle before decrementing slot.alive
- PooledDialer.shutdown: cover the global-waiter-races-shutdown path
  (releaseConnection fires ok() then shutdown sets #shutdown before the
  microtask continuation runs)

Broken Invariant annotations:
- Standardise all invariant error messages to "Broken Invariant: ..."
- Add /* c8 ignore next */ to guards that TypeScript enforces on callers
  or that cannot fire under correct internal state:
  pool.ts: !connection checks, alive<0, idle>alive, not-in-idle-after-
    setTimeout, catch blocks around Promise resolvers, allSettled error
    collection, #notify's !slot guard
  buffer.ts: slice-returned-fewer-bytes guard
  node/transport.ts: string-chunk guard, closed-with-buffers branch,
    socket write error callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:34:29 +00:00
codingetandClaude 9eee7f51f2 chore: ignore coverage/ directories
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:08:04 +00:00
codingetandClaude bb2ae3c0b4 chore(http): deduplicate :unit scripts by delegating to base scripts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:40:33 +00:00
codingetandClaude 1f9ffc106b test(http): push branch coverage higher by targeting remaining uncovered paths
Cover previously missing branches:
- common/pair.ts: PairSync.close() idempotence and getA()-after-close
- common/reader.ts: _triggerFinishOk/Ko catch blocks (handler throws),
  BasicBodyReader #chunks pre-buffer path (slice returns multiple arrays),
  ChunkedBodyReader already-closed guard and truncated-chunk sanity check
- common/buffer.ts: WriteBuffer.flush() partial write of a second buffer
- client/objects.ts: method defaults to POST when body is truthy
- client/fetch.ts: rejectConnection called when conn.request() throws
- node/transport.ts: fast-fail read() path with null #readError (socket
  destroyed without error event)
- server/connection.ts: console.warn for missing requireHeaders entry,
  non-TimeoutError parse error propagates out of handle()

Result: 100% functions, 97.81% branches — remaining gaps are dead code
(defensive throws/catches that cannot fire under normal API usage).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:38:38 +00:00
codingetandClaude b14ca4fffa test(http): comprehensive test suite — 100% function coverage, bug fixes
Adds full test suites for all implementation files and fixes two transport bugs
discovered during testing:

- fix(loopback): `#maybeEnded` checked `this.closed` (requiring BOTH ends closed)
  instead of just `#readClosed`; a pending read would hang after the peer closed if
  the local write side was still open. Also add fast-fail in `read()` when
  `#readClosed` is already set.
- fix(node): same `#maybeEnded` bug as loopback. `close()` used `socket.end()`
  (FIN — waits for peer FIN) instead of `socket.destroy()` (RST — immediate), which
  caused the test suite to hang. Add `#readError` field so errors seen before
  `read()` is called are preserved for the fast-fail path.

New/extended test files:
- src/client/fetch.test.ts — fetch() and makeFetch() integration suite
- src/client/pool.test.ts — UnpooledDialer and PooledDialer (incl. TLS throw paths)
- src/client/connection.test.ts — extended with more parse/request edge cases
- src/loopback/transport.test.ts — loopback pair, listener, halfClose, error paths
- src/node/transport.test.ts — NodeDialer, NodeTransport, NodeListener (incl.
  buffered-data path, server error event, dialTls ECONNREFUSED)
- src/server/objects.test.ts — ServerRequestImpl, ServerResponseImpl
- src/server/router.test.ts — extended with optional-param and global middleware

Coverage result: 100% functions, ~99.3% lines, ~97.1% branches (remaining branch
gaps are V8/tsx source-map desync artifacts, not missing test paths).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:14:09 +00:00
codingetandClaude 185a528971 test(http): fix audit findings — wrong assertions, async traps, and missing edge cases
- Fix vacuous `!clientTransport.closed || conn.ended` → `serverTransport.readEnded`
- Move HTTP/1.0 success-path tests out of the parse error suite
- Fix fire-and-forget `.then()` in loopback whenClosed test (now awaited)
- Add CRLF split across chunk boundary, 3-buffer slice, multi-buffer forward tests
- Add negative chunk size and post-close onFinish handler tests for readers
- Add non-null body with bodyAllowed=false writer test
- Add maxTargetLength boundary, HEAD wire-level, and body auto-drain server tests
- Add global middleware on 404/405 and HEAD-in-Allow router tests
- Add setOptions effect and 1xx-todo client connection tests
- Add TLS path and multiple concurrent global waiters pool tests
- Add new src/client/utils.test.ts covering combineClientConnectionOptions

One test marked { todo }: 1xx interim responses not yet implemented.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:04:18 +00:00
codingetandClaude 27cc807796 chore(http): exclude test-helpers from build and coverage
Add src/test-helpers to tsconfig.json exclude so flags.ts is not
compiled into dist. Add a second --test-coverage-exclude glob to both
coverage scripts so the helper does not appear in the coverage report
or skew aggregate numbers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:23:14 +00:00
codingetandClaude 9129206916 test(http): default to unit+integration, add :unit script variants
Change RUN_INTEGRATION default to true so npm test / test:coverage /
test:watch run the full 284-test suite (unit + integration) by default.
RUN_EXTERNAL remains independent and must be set explicitly.

Add test:unit, test:coverage:unit and test:watch:unit convenience scripts
(RUN_INTEGRATION=false) for when only the 226 unit tests are wanted.
Remove test:all, which is now redundant with the new defaults.

Default coverage: 90.9% lines / 92.8% branches / 94.1% functions.
Unit-only coverage: 77.5% lines / 94.8% branches / 89.6% functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:34:52 +00:00
codingetandClaude 45667ea9d2 test(http): classify tests as unit/integration/external with env-var gates
Add src/test-helpers/flags.ts (RUN_UNIT, RUN_INTEGRATION, RUN_EXTERNAL)
and apply { skip } to every suite accordingly:

  unit        — common/*, loopback/transport, server/router,
                server/connection parse sub-suite
  integration — client/connection, client/pool,
                server/connection send/handle sub-suites
  external    — node/fetch (hits real internet)

Default (no env vars): RUN_UNIT=true, others false — npm test, test:watch
and test:coverage run only the 226 unit tests. Add test:all convenience
script (RUN_INTEGRATION=true) to run the 284 unit+integration tests.
RUN_EXTERNAL=true also enables RUN_INTEGRATION implicitly.

Unit-only coverage: 77.5% lines / 94.6% branches / 89.6% functions.
Full (unit+integration) coverage: 96.4% lines / 92.4% branches / 95.3%
functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 12:40:37 +00:00
codingetandClaude d026bea748 chore(http): add test:coverage script
Uses Node's built-in --experimental-test-coverage with tsx as an import
hook — no extra dependencies required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:36:50 +00:00
codingetandClaude 530835607a test(http): add comprehensive unit test suite (296 tests)
Covers common/{buffer,connection,headers,objects,reader,utils,writer},
loopback/transport, server/{connection,router}, and client/{connection,pool}.
All tests use loopbackTransportPair for in-memory transport — no network
access required. Extends the existing server/connection.test.ts with
send() and handle() suites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:26:38 +00:00
codingetandClaude 075ed71505 fix(http): maxBodyLength error swallowed on final body chunk
When the last chunk pushed bodyLength over the limit, the error was
thrown inside the try/catch and then caught — but because body.closed
was already true at that point, the catch block hit `continue` and
discarded the error silently.

Move the maxBodyLength check (and the yield) outside the try/catch so
only the awaited read is guarded, not the enforcement logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:24:52 +00:00
codinget 4eee5f88fa fix(http): broken exports 2026-05-20 11:14:10 +00:00
codinget 7920a6db96 chore(example-app): import from @webnet/http/server to more clearly state intent 2026-05-19 23:47:09 +00:00
codinget bda28b3cef refactor(http): split exports into internal and external groups + root barrels for the core features 2026-05-19 23:46:37 +00:00
codingetandClaude 2d47f4862b feat(http): add headersTimeout, keepAliveTimeout and bodyTimeout support (#9)
Reviewed-on: #9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Codinget <codinget@codi.moe>
Co-committed-by: Codinget <codinget@codi.moe>
2026-05-19 23:58:27 +02:00
codinget 3c9f21b6ee feat(http): add size limits to http connections and listen lifecycle hooks (#8)
Reviewed-on: #8
Co-authored-by: Codinget <codinget@codi.moe>
Co-committed-by: Codinget <codinget@codi.moe>
2026-05-19 22:50:34 +02:00
codingetandClaude d4b448071e fix(http): include offending values in parse error messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:17:36 +00:00
codingetandClaude 240a7b8929 fix(http): review and improve the low-level transport layer
Bugs:
- ReadableHttpImpl.bytes() was copying data byte-by-byte; use set()
- NodeTransport and LoopbackTransportHalf were not clearing #errcallback
  after a successful read(), causing errors between reads to be silently
  swallowed; also gate #maybeEnded to only fire when a read() is pending
- ServerConnection.handle() was draining the request body after closing
  the connection; skip the drain entirely on close (it only matters for
  keep-alive reuse), and close after draining otherwise
- PooledDialer threw "Pool full" / "Per-origin full" instead of queuing
  waiters; implement queuing via slot.waiting and a new #globalWaiting
  list; also fix #shutdown never being set to true in shutdown(), and
  reserve the slot before the async connect to prevent races

Code quality:
- shouldClose() was comparing Connection header values case-sensitively;
  RFC 7230 requires case-insensitive comparison
- Headers._normalized was public (convention only); make it protected
- Headers.headers returned the original wire-case record while
  MutableHeaders.headers returned lowercase keys; unify both to return
  from _normalized so ServerRequest.headers and ServerResponse.headers
  are consistent (both lowercase)

Enhancements:
- RawTransport: add optional halfClose(), readEnded, whenClosed,
  remoteAddr, localAddr; NodeTransport and LoopbackTransportHalf
  implement all applicable members; addresses cached at construction
  before the socket handle can be invalidated
- RawListener: add optional addr; NodeListener exposes the bound
  address (critical when listening on port 0)
- LoopbackTransportHalf: internalize _closeFn/_writeFn as constructor
  parameters stored in private fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:20:37 +00:00
codinget 56461cdd32 feat(http): basic support for the client 2026-05-11 22:57:18 +02:00
codinget bb0ce0c58e refactor(http): split chunked body writing out of Connection 2026-05-11 22:57:18 +02:00
codinget 4e17939891 refactor(http): split read/write buffer and body readers out of Connection 2026-05-11 22:57:18 +02:00
codinget 2b7c14c790 chore: avoid ambiguous characters 2026-05-11 20:54:56 +00:00
codingetandClaude e2d5017ec2 fix(query-utils): correct ICMP case in ping type error message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:29:34 +00:00