AtlatestRepositorysigil-http
1# Changelog
2
3All notable changes to **sigil-http** are documented in this file.
4
5The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
8## [0.18.6] - 2026-09-06
9
10### Fixed
12- Accept bytevector request bodies in `http-fetch-bytes`, preserving binary
13 uploads without UTF-8 conversion. Compute Content-Length from the bytevector
14 and replace conflicting length/transfer-encoding headers case-insensitively.
15 Existing string request bodies keep their UTF-8 behavior.
16- Exercise binary assembly and actual empty/2 MB upload round trips alongside
17 the HTTP regression suite. CI now uses Sigil 0.21.0, matching the test runner's
18 existing minimum dependency, and runs the loopback upload checks with Node.
20## [0.18.5] - 2026-08-27
22### Added
24- **`http-stream-response`: incremental, byte-faithful HTTP response bodies.**
25 The callback API publishes the parsed status and ordered response headers
26 once, then publishes raw body bytevectors as they arrive. Content-Length and
27 connection-close bodies are supported, and HTTP/1.1 chunk framing is decoded
28 incrementally across arbitrary transport boundaries. Request construction,
29 TLS, deadlines, and connection handling remain shared with the existing
30 client rather than delegated to an external process.
32### Fixed
34- **Framed streaming reads tolerate both non-blocking would-block values.**
35 Sigil's socket layer can surface a transient read as either `#f` or an EOF
36 sentinel. Content-Length and chunked streams now retry both until their
37 framing declares completion or the idle deadline expires.
39## [0.18.3] - 2026-08-04
41### Changed
43- **`connect-timeout:` now bounds the TLS handshake as well as the TCP
44 connect.** The connect timeout stopped at the moment the peer accepted, so a
45 server that accepted and then never sent a ServerHello left the handshake
46 read blocking with no way out. Under Sigil's cooperative scheduler that
47 freezes the whole process rather than failing one request, which is how a
48 production monitoring service ran for 55 days while its process, service
49 status and listening port all reported it healthy. Widening the existing
50 keyword rather than adding a new one means every caller already passing
51 `connect-timeout:` is fixed by upgrading alone. Each phase gets the full
52 value rather than a shared split, so a slow but working connect cannot
53 consume the handshake's budget. Requires sigil-tls 0.16.5.
54- **`connect-timeout:` now applies to plain HTTP too.** It resolves the host
55 and connects to each address under a deadline, giving each address a fair
56 slice of what remains, so a blackholed first address cannot burn the whole
57 budget and leave a working address untried.
58- **`timeout:` now bounds the request write.** A request usually fits the
59 socket buffer, so the write usually returns at once, but a peer that never
60 drains its receive queue fills the window and a large body blocks
61 indefinitely. A write that runs out of time raises `HTTP request timed out:
62 write deadline exceeded`, deliberately NOT marked ack-unconfirmed: the
63 deadline can only fire with bytes still unsent, so the server holds a partial
64 request it cannot act on and retrying is safe.
66### Fixed
68- **`http-fetch-bytes` was not bounded by its own `timeout` argument.** Its
69 idle deadline loops detect "no data yet" from an empty read, which only a
70 non-blocking connection returns, and it never set one. Its reads blocked and
71 every deadline check between them was unreachable, so the argument looked
72 like a safeguard and did nothing. The connection is now non-blocking, which
73 is what those loops were written for, and they sleep between retries rather
74 than spinning.
76### Known limitations
78- **`http-download` is not bounded at all.** It takes no timeout keywords and
79 is blocking end to end: connect, write, header read and body stream. It is
80 the API most likely to be pointed at a large, slow remote and it is the one
81 still able to freeze the process. Bounding it means bounding a streaming
82 read too, which is a larger change than this release.
83- **`timeout:` is a per-phase bound, not a total.** The write and the read
84 each get the full value, so `(http-get url timeout: 5)` can spend up to 5
85 seconds writing and a further 5 reading. Sharing one deadline would have
86 silently shortened the response window existing callers sized their value
87 against; callers who read `timeout:` as a total should halve it.
88- **DNS resolution remains unbounded.** `getaddrinfo(3)` is a blocking call
89 with no deadline and no portable cancellation, and both paths into it are
90 inside native code. A resolver that stops answering still blocks a request
91 for as long as the system resolver takes to give up, and neither `timeout:`
92 nor `connect-timeout:` affects that. Stated here rather than left implicit:
93 "sigil-http bounds its blocking segments" must not be read as "sigil-http
94 cannot block".
97## [0.18.2] - 2026-07-23
99### Added
101- **`http-fetch-bytes`: a byte-faithful, in-memory HTTP fetch.** It returns a
102 response as `{ status, headers, body }` where the body is a raw bytevector and
103 is never decoded to a string, so binary payloads (wasm modules, tarballs,
104 images) round-trip uncorrupted. It preserves header order and duplicates (an
105 ordered alist of lowercased-name/value pairs), so a relay can faithfully
106 forward repeated headers like `Set-Cookie`, and it does not follow redirects
107 (a 3xx is returned as-is, which is what a faithful proxy needs). It handles
108 `Content-Length`, chunked, and connection-close framing, with a
109 single-allocation body assembly. `http-request` stays the string-oriented
110 convenience path; reach for `http-fetch-bytes` when the bytes must survive
111 exactly.
114## [0.18.1] - 2026-07-11
116### Fixed
118- **`http-server-stop` now actually stops a running serve loop.** The server
119 record is immutable — `http-server-start` and the serve loop derive updated
120 copies — so a caller that held the record it passed to `http-server-start`
121 never shared state with the loop, and stopping from another task silently
122 did nothing (the loop kept running forever; with the loop now suspending on
123 the scheduler, it would have kept a periodic wait alive indefinitely). The
124 stop signal now lives in a one-slot vector created per `make-http-server`
125 and carried by reference through every derived record; `http-server-stop`
126 sets it and the loop — whose waits are all deadline-bounded — sees it
127 within the sweep interval, closes the sockets it owns, and
128 `http-server-start` returns. Stopping an idle server completes in ~100ms
129 (probe-verified; hung forever before). Covered by phase 3 of
130 `test/integration/run-starvation-test.sh`.
132- **Server loop no longer starves other async tasks' socket I/O.** While at
133 least one client was connected (e.g. a browser holding an SSE stream), the
134 server loop's drain phase busy-spun a short-timeout native `socket-select`.
135 Because the async scheduler only polls socket io-waiters when its run queue
136 is empty, that busy loop kept the queue perpetually non-empty and every
137 OTHER task's socket read starved — first reads on outbound connections
138 (IRC-style clients, websocket clients, IPC sockets) were delayed for as long
139 as any HTTP client stayed connected, while timer- and channel-driven work
140 kept running. The drain now suspends on its whole socket set
141 (listen + clients) through the scheduler via `await-readable-any`, waking
142 immediately on readable data and sweeping connection timeouts on a bounded
143 deadline (1s), so sibling io-waiters are serviced within milliseconds.
144 Regression test: `test/integration/run-starvation-test.sh` (compiled probe;
145 measures a sibling socket's first-read latency with a client parked on the
146 server).
148 **Requires** sigil 0.17.10 or newer (the first release providing
149 `(sigil async)` `await-readable-any`); the package's `sigil:` requirement is
150 bumped to `^0.17.10` accordingly.
152## [0.18.0] - 2026-07-10
154### Added
156- **Persistent connections (keep-alive).** Non-streaming HTTP/1.1 responses now
157 keep the TCP connection open and serve subsequent requests on the same socket
158 instead of forcing `Connection: close` after every response. The server honors
159 an explicit `Connection: close` and defaults HTTP/1.0 to close; the age-based
160 timeout is refreshed per request so it acts as an idle timeout for kept-alive
161 connections. This removes the connection-churn that amplified image-heavy page
162 loads.
163- **Chunked response transfer-encoding.** Streaming responses whose length is
164 unknown (SSE and bare procedure bodies) are now framed with
165 `Transfer-Encoding: chunked` and a terminating `0\r\n\r\n`, instead of relying
166 on connection close for framing. `http-response/file` keeps its
167 `Content-Length` framing (byte-exact).
168- **`Range:` requests and `206 Partial Content`.** `http-response/file` accepts
169 a `range:` keyword (the raw request `Range` header) and honors byte ranges:
170 `bytes=A-B`, open-ended `bytes=A-`, and suffix `bytes=-N`. Satisfiable ranges
171 yield `206` with `Content-Range`; unsatisfiable ranges yield `416 Range Not
172 Satisfiable` with `Content-Range: bytes */<total>`; an absent or malformed
173 header falls back to a full `200`. Adds the `HTTP-RANGE-NOT-SATISFIABLE`
174 constant and exports `resolve-range`.
176### Notes
178- Reusing a **streamed** connection for keep-alive is not yet supported — a
179 streaming response still closes when the stream ends (the select loop would
180 need to re-adopt the goroutine-owned socket). HTTP request pipelining is also
181 unsupported. Both are tracked follow-ups.
183## [0.17.0] - 2026-07-10
185### Changed
187- Streaming responses (`http-response/file`, SSE) now work from a bare
188 `http-serve` / `http-server-start` **without** wrapping the call in
189 `with-async`. The server establishes its own async scheduler when none is
190 active, and reuses the caller's when one is present. This fixes streaming and
191 SSE routes throwing `go: not running in an async context` on a default
192 server, which caused large assets to intermittently fail to download.
193- `HEAD` requests now return identical status and headers (including a correct
194 `Content-Length`) with **zero body bytes**, instead of sending the full body.
196### Added
198- Opt-in automatic gzip via a `gzip:` option on `make-http-server` /
199 `http-serve` (default `#f`, so the default server is unchanged). When enabled,
200 non-streaming responses are gzip-encoded per the request's `Accept-Encoding`
201 (compressible content types over a size floor), adding `Content-Encoding:
202 gzip` and `Vary: Accept-Encoding`. Streaming and incompressible bodies are
203 left untouched.
205## [0.16.7] - 2026-07-07
207### Changed
209- Realigned the sigil toolchain pin to `^0.17`.
210- `http-response/json` now accepts either a dict or an already-encoded string.
211- `http-response/sse-broadcast` defaults its message handler to identity.
213## [0.16.6] - 2026-06-30
215### Fixed
217- HTTP client `timeout:` callers can now distinguish a request that was never
218 sent from one that was delivered but whose ack is unconfirmed.
220## [0.16.5] - 2026-06-30
222### Changed
224- Threaded a `connect-timeout:` option through the HTTP client and relocked onto
225 sigil-tls 0.16.2.
227## [0.16.4] - 2026-06-30
229### Fixed
231- Response reads now terminate on HTTP framing rather than on connection close.
233## [0.16.3] - 2026-06-29
235### Added
237- Opt-in `timeout:` keyword for HTTP client reads.
239## [0.16.2] - 2026-06-24
241### Changed
243- Repointed sigil-test dev-dependencies at the reintegrated monorepo.
245## [0.16.1] - 2026-05-31
247### Added
249- `http-response-gzip` response helper (gzip content negotiation).
251### Changed
253- Response send loop rewritten to be O(n) via offset-based partial writes,
254 avoiding O(n²) recopying of large bodies over slow links.
256## [0.16.0] - 2026-05-06
258Initial tagged release of the standalone `sigil-http` package, extracted from
259the sigil monorepo (requires sigil `^0.16`).
261### Fixed
263- Non-blocking HTTP response writes.
264- HTTP request body byte handling (Content-Length counted in bytes).
266### Added
268- Server crash diagnostics and guard blocks around the handler, streaming
269 goroutine, and server loop; an outer server-loop guard that restarts on an
270 otherwise-escaping exception.
271- SSE heartbeat utility for dead-client detection.
273[0.17.0]: https://codeberg.org/sigil/sigil-http/compare/v0.16.7...v0.17.0
274[0.16.7]: https://codeberg.org/sigil/sigil-http/compare/v0.16.6...v0.16.7
275[0.16.6]: https://codeberg.org/sigil/sigil-http/compare/v0.16.5...v0.16.6
276[0.16.5]: https://codeberg.org/sigil/sigil-http/compare/v0.16.4...v0.16.5
277[0.16.4]: https://codeberg.org/sigil/sigil-http/compare/v0.16.3...v0.16.4
278[0.16.3]: https://codeberg.org/sigil/sigil-http/compare/v0.16.2...v0.16.3
279[0.16.2]: https://codeberg.org/sigil/sigil-http/compare/v0.16.1...v0.16.2
280[0.16.1]: https://codeberg.org/sigil/sigil-http/compare/v0.16.0...v0.16.1
281[0.16.0]: https://codeberg.org/sigil/sigil-http/releases/tag/v0.16.0