v1.75.0 improves HTTP parsing and serialization, URI processing, cached file serving, and client connection handling. It also fixes cookie injection, request/response framing problems, and excessive CPU or memory consumption from certain inputs.
Go 1.26 or newer is now required. #2395
Performance improvements
- HTTP parsing: request headers are parsed and validated in one pass, with common methods and HTTP/1.1 recognized directly. Header values, case conversion, host validation, and control-byte checks process multiple bytes at a time. The parser also avoids repeatedly scanning the same incomplete header block when data arrives in small reads. #2413, #2414, #2428, #2423, #2412, #2438
- HTTP writing: fewer header and response copies, cached serialized
ServerandDatelines, faster HTTP date formatting, and fewer flushes around chunked trailers. Large buffered responses can usewritevon TCP and Unix connections; TLS and streamed bodies retain the buffered path. #2425, #2415, #2434, #2426, #2433 - URI processing: memoized authority parsing, a fast path for
/, faster quoting and validation, and linear-time path normalization. #2411, #2423, #2453 - Static files: cached files of up to 8 KiB can be served from memory, avoiding a file read on each cache hit and releasing the file descriptor. The new small-file content cache is bounded to 4096 files, or at most 32 MiB per
FS; the existing cache metadata is separate. Changes to these files become visible after cache expiration (CacheDuration, 10 seconds by default). #2446 - Client and server overhead: clients avoid redundant deadline updates when no timeout is active. The opt-in
Server.LazyRequestTimeavoids reading the clock untilRequestCtx.Time()is called; with this option, the returned time is the time of the first call. The benchmark comparison below uses the default setting. #2439, #2404
Security fixes and hardening
- Streamed request bodies are drained before connection reuse. With
StreamRequestBodyenabled, the server drains unread body data before reusing the connection, with a 256 KiB drain limit, or closes the connection when safe draining is not possible. Responses are flushed before draining so early replies reach clients still uploading, and response streams can consume the request body before its remaining size is checked. #2368, #2461 - Rejected
100-continuerequests close the connection. A denied request could previously leave its body unread and have those bytes interpreted as the next request, desynchronizing a reused connection. #2450 - Cookie setters prevent additional-cookie injection.
RequestHeader.SetCookieand its byte variants now sanitize semicolons in names and values, so one logical cookie cannot become multiple cookies on the wire. #2393 - Bounded zstd decoding limits decoder memory demand as well as output. A small compressed frame could previously declare a large decoder window even when a decoded-body limit was configured. Positive
BodyUnzstdWithLimitlimits now select a memory-limited decoder, with caps rounded up in 8 MiB buckets; exceeding the decoder limit returnsErrBodyTooLarge. #2407 - Path normalization avoids quadratic work. Repeated separators and dot segments are processed in linear time, reducing the CPU amplification possible with crafted request targets. Incomplete headers arriving one byte at a time also avoid repeated full-block scans. #2453, #2413
- Bodyless responses preserve framing. HEAD and status-defined bodyless responses no longer emit trailer bytes that can be mistaken for the next response. Serialization suppresses forbidden
Content-LengthandTransfer-Encodingon 1xx/204 responses. Explicit content-type metadata and permitted 304 metadata remain supported. #2441, #2447, #2457 - Pipeline clients gain a response-body limit and better failure cleanup. Set the new
PipelineClient.MaxResponseBodySizeto a positive value to reject oversized responses, including withResponse.StreamBody; the default remains unlimited. Idle workers now retire after errors, abandoned requests release their streams, and failed connection establishment is throttled. #2427, #2458
Argument sorting now honors any negative comparator result in Args.Sort and Args.SortKeys, and static-file serving rejects zero-length suffix byte ranges (bytes=-0) with 416 Range Not Satisfiable. #2459, #2460
Other significant changes include opt-in ETags for FS, a net/http request conversion function, improved adaptor handling of flushes, aborts and trailers, and fixes to shutdown and per-IP connection lifetimes. #2399, #2104, #2429, #2445, #2352, #2402
Benchmark results
Compared v1.74.0 with master 6141d3a on Ubuntu 26.04, an AMD EPYC 9454, and Go 1.27.1. Each benchmark has ten samples per revision, run alternately with the same inputs and CPU affinity. Negative percentages mean less time per operation. All rows below have p < 0.001.
| Benchmark | v1.74.0 | Release candidate | Time/op change |
|---|---|---|---|
| Minimal GET, pipeline depth 64 | 484.30 ns | 278.05 ns | -42.6% |
| Browser-shaped GET, pipeline depth 16 | 1.080 µs | 722.55 ns | -33.1% |
| Request-header parsing | 561.80 ns | 348.15 ns | -38.0% |
| Response-header parsing | 466.95 ns | 277.55 ns | -40.6% |
| Request-header writing | 49.84 ns | 37.45 ns | -24.8% |
| Response-header writing | 68.64 ns | 40.99 ns | -40.3% |
| Cached 1 KiB file, directory | 2.324 µs | 611.85 ns | -73.7% |
Cached 1 KiB file, os.DirFS
| 3.805 µs | 612.30 ns | -83.9% |
| URI path with query string | 91.39 ns | 46.06 ns | -49.6% |
| HTTP date formatting | 108.25 ns | 28.59 ns | -73.6% |
| Trickled ~4 KiB headers, one byte per read | 1385.784 µs | 191.257 µs | -86.2% |
8 KiB path of repeated /
| 352.529 µs | 54.330 µs | -84.6% |
~8 KiB path of repeated /a/..
| 96.733 µs | 22.253 µs | -77.0% |
| TCP client/server round trips, small response | 5.193 µs | 5.030 µs | -3.1% |
| TCP client/server round trips, 64 KiB response | 15.302 µs | 13.478 µs | -11.9% |
The pipelined serving and file benchmarks use an in-memory connection with writes discarded. The two TCP rows include real loopback networking. These results describe the selected workloads; TCP gains are smaller than the gains in parsing and serialization. Server.LazyRequestTime remains disabled in this comparison.
What's Changed
Compatibility
- Require Go 1.26 for golang.org/x by @erikdubbelboer in #2395
Security and hardening
- discard unread streamed request body before the next request by @alhudz in #2368
- close the connection when ContinueHandler denies a request by @alhudz in #2450
- limit zstd decoder memory in WithLimit methods by @erikdubbelboer in #2407
- Strip semicolons in RequestHeader.SetCookie by @youdie006 in #2393
- make normalizePath linear in the length of the path by @alhudz in #2453
- Omit the trailer section from a response without a body by @h2zi in #2441
- Omit Transfer-Encoding on 1xx and 204 responses by @davidscottpope-gif in #2457
- Omit Content-Length/Content-Type on a parsed bodyless response when re-serializing by @januththedev in #2447
- Add MaxResponseBodySize to PipelineClient by @erikdubbelboer in #2427
- fix: stop idle PipelineClient workers after errors by @erikdubbelboer in #2458
Performance
- perf: parse request headers in one validating pass by @h2zi in #2413
- perf: scan the request header block once while parsing by @gaby in #2424
- perf: serve small cached files from memory in FS by @gaby in #2446
- perf: serialize responses with fewer copies and send large ones with writev by @h2zi in #2415
- perf: cut redundant copies out of header serialization by @gaby in #2425
- perf: scan header values, control bytes and case eight bytes at a time by @gaby in #2428
- perf: speed up host validation and query/path quoting- #154 by @gaby in #2423
- perf: memoize authority parsing and fast-path the root path by @h2zi in #2411
- perf: recognize GET, POST, HEAD and HTTP/1.1 without byte-wise validation by @h2zi in #2414
- perf: scan the request target for control bytes a word at a time by @h2zi in #2412
- perf: skip redundant deadline updates on client connections by @gaby in #2439
- perf: preform the Date header line once a second by @h2zi in #2434
- perf: format HTTP dates without time.AppendFormat by @gaby in #2426
- perf: keep the last chunk buffered for the trailer section by @h2zi in #2433
- perf: keep the control-byte scans inlinable by @h2zi in #2438
- fix: request time left at zero, and an opt in Server.LazyRequestTime by @ReneWerner87 in #2404
Features
- Add opt-in ETag support to FS by @anandghegde in #2399
- Add ConvertNetHttpRequestToFastHttpRequest adaptor function by @aaydin-tr in #2104
Fixes
- fix: preserve response behavior while draining streamed requests by @erikdubbelboer in #2461
- fix: per-IP wrapper lifetime, TLS state unwrapping and ConnState timing by @h2zi in #2352
- fix: carry net/http handler contracts through the fasthttpadaptor by @h2zi in #2429
- Carry response trailers through the fasthttpadaptor by @h2zi in #2445
- fix: write trailers for buffered bodies in Request.Write and Response.Write by @aaydin-tr in #2378
- Start compressing a streamed body when the server reads it by @h2zi in #2442
- fix: synchronize RequestCtx.Done() with ShutdownWithContext by @cpsc in #2402
- fix: admit TimeoutHandler requests on servers driven by ServeConn by @h2zi in #2431
- fix: serve queued waiters when SetMaxConns grows the limit by @h2zi in #2430
- fix: recheck idle connections before queuing waiters by @HarveyBase in #2394
- fix: recognize case-insensitive and multi-value Connection: close (#2418) by @littfed in #2420
- fix: preserve zero Max-Age when parsing cookies by @jakezwang in #2405
- fix: accept a negative Max-Age when parsing cookies by @SulimanAbdulrazzaq in #2416
- Preserve NoDefaultContentType across client response resets by @CSXizhang in #2455
- fix(fs): reject zero-length suffix byte ranges by @kevin9327 in #2460
- fix(args): use comparator sign in Sort and SortKeys by @sergioperezcheco in #2459
- fix: preserve redirect path normalization setting in Request.CopyTo by @cuishuang in #2389
- fix: normalize a trailing /. path segment by @sachhg in #2392
- fix: mask ConnRequestNum into its 32-bit RequestCtx.ID field by @h2zi in #2432
- Return no values from PeekAll for absent special headers by @youdie006 in #2401
- Leave an empty upfront Server or User-Agent out of PeekAll by @h2zi in #2443
- Skip RFC-valid empty elements in VisitHeaderParams by @vzer200 in #2388
- fix(request): warn when Request.Body() is called on streamed requests by @VedantMadane in #2350
Documentation, tests and tooling
- docs: show how to read a bounded response prefix by @fzlzjerry in #2369
- docs: clarify that ShutdownWithContext error does not freeze the Server by @hazyhaar in #2422
- test: add benchmarks for pipelined and browser-shaped serving by @h2zi in #2409
- test: scale hard-coded deadlines with testTimeout by @h2zi in #2410
- Update golangci-lint to v2.13.2 and get rid of the separate gosec action by @erikdubbelboer in #2386
Dependency updates
- chore(deps): bump github.com/klauspost/compress from 1.20.0 to 1.20.1 by @dependabot[bot] in #2448
- chore(deps): bump golang.org/x/net from 0.58.0 to 0.59.0 by @dependabot[bot] in #2397
- chore(deps): bump github.com/molecule-man/go-brrr from 1.0.1 to 1.1.1 by @dependabot[bot] in #2417
New Contributors
- @vzer200 made their first contribution in #2388
- @youdie006 made their first contribution in #2393
- @aaydin-tr made their first contribution in #2378
- @sachhg made their first contribution in #2392
- @cpsc made their first contribution in #2402
- @jakezwang made their first contribution in #2405
- @SulimanAbdulrazzaq made their first contribution in #2416
- @HarveyBase made their first contribution in #2394
- @anandghegde made their first contribution in #2399
- @littfed made their first contribution in #2420
- @januththedev made their first contribution in #2447
- @hazyhaar made their first contribution in #2422
- @CSXizhang made their first contribution in #2455
- @VedantMadane made their first contribution in #2350
- @davidscottpope-gif made their first contribution in #2457
- @sergioperezcheco made their first contribution in #2459
- @kevin9327 made their first contribution in #2460
Full Changelog: v1.74.0...v1.75.0