Skip to content

2.3.4

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Nov 10:19
· 2 commits to main since this release
2.3.4
072b610

Added

  • BREAKING (library API). path_type = "REGEX_PATH" (CLI --path-regex-path, PathRuleKind::REGEX_PATH = 3) matches a regex, anchored at both ends, against the request path up to its first ? only, the Gateway API HTTPRoute RegularExpression path match. REGEX keeps matching the path together with its query string, so /api/.+ matches /api/?x=1 under REGEX but not REGEX_PATH, and ^/api/v2$ matches /api/v2?x=1 only under REGEX_PATH. It ranks as REGEX does. With rewrite_path, its $PATH[n] captures come from the path alone and the original ?query is appended to a rewritten path that has no ? of its own; a redirect Location follows it. REGEX, PREFIX and EQUALS are unchanged. Library API: PathRule::from_cli_options (sozu-command-lib) takes a fourth path_regex_path argument, and PathRule (sozu-lib) and PathRuleType (sozu-command-lib) gain a RegexPath variant, which breaks exhaustive matches on them. A worker that predates the kind refuses such a frontend as an invalid path rule, so upgrade every worker before declaring one (#1909).

  • An HTTP listener can serve cleartext HTTP/2 by prior knowledge with the new opt-in h2c_prior_knowledge option (HttpListenerConfig field 38, UpdateHttpListenerConfig field 44; TOML h2c_prior_knowledge = true; CLI sozu listener http add --h2c-prior-knowledge, sozu listener http update --h2c-prior-knowledge / --no-h2c-prior-knowledge). A connection whose first 24 bytes are the HTTP/2 connection preface is served as an HTTP/2 frontend, as an HTTPS connection that negotiated h2 through ALPN is, with the listener's H2 flood thresholds and tuning; every other connection stays HTTP/1.x. The first bytes are peeked, never consumed: a strict prefix of the preface waits for more bytes within request_timeout, and a diverging byte decides HTTP/1.x. A client that sends nothing, or only part of the preface, before request_timeout is closed without the 408 an HTTP/1-only listener answers. The scheme stays http (:scheme, X-Forwarded-Proto, Forwarded: proto), the option composes with expect_proxy, and Upgrade: h2c remains unsupported. Such a listener is held to the H2 minimum buffer_size (16393) at configuration load, like an HTTPS listener advertising h2. Off by default: without it a preface is answered 400 Bad Request as before. The use case is cleartext gRPC, e.g. the Kubernetes Gateway API GRPCRoute. New metrics http.h2c.prior_knowledge (connections served as h2c) and http.upgrade.h2c.failed. A listener update applies to new connections only (#1911).

Fixed

  • A worker now leaves its event loop when its command channel reports an error, as it already did on a hang-up. The main process closing the channel with a worker answer still unread resets the connection, which the worker used to log and ignore, so it kept running with nothing left to drive it and a caller joining its thread waited forever (#1912).

  • An HTTP/1.1 backend now receives a single Host header when a frontend sets rewrite_host. The router added a Host header block on top of the rewritten authority, which the HTTP/1.1 converter already writes as Host, so the backend saw two, and servers that refuse a duplicate Host (Go's net/http) answered 400. A frontend request header named Host now replaces the request authority instead of adding a second, different Host line; rewrite_host still takes precedence over it, and an HTTP/2 backend now receives that value as :authority where the header was previously dropped (#1908).

  • A fast HTTP transfer is no longer cut at a random offset. When the inner loop of Mux::ready_inner reached MAX_LOOP_ITERATIONS (10,000) in one ready() call, it closed the session unless output was still queued, even if every iteration had forwarded bytes. When the client and the backend both kept up for that many iterations (about 160 MiB with 16 KiB buffers), the client saw a clean close in the middle of a Content-Length body ("Premature end of Content-Length delimited message body"), with http.infinite_loop.error incremented and, before 2.3.0, no log at the default level. Downloads and uploads over plaintext and TLS HTTP/1.1 were measured exposed, and the listener's ALPN list played no part; HTTP/2 frontends run the same loop. A call that reaches the budget after forwarding at least one stream byte per budgeted iteration to either peer (Context::stream_bytes_out) now yields: it keeps its readiness, counts the new http.mux.loop_budget_yield counter, and the worker resumes it after its next poll, which does not block while a session is queued (ProxySession::yielded, Server::resume_yielded_sessions). A fast transfer neither is cut nor holds the single-threaded worker past one budget per turn. A call that forwarded nothing keeps the existing http.infinite_loop.error path. Reproduced deterministically by a_transfer_progressing_at_the_loop_limit_yields_and_resumes_without_a_new_event, and end to end in large_response_tests.rs (256 MiB over loopback: 4/40 plaintext and 5/60 TLS downloads, 10/20 TLS uploads cut on main). ProxySession gains a yielded method with a default false, so other implementors are unaffected (#1906).

  • A response in progress is no longer cut by the timeout of the peer that is not holding it up. back_timeout fired while Sōzu itself had stopped reading the backend because a slow client left response bytes queued, and cut the response (backend_response_timeout); front_timeout fired while a client that had taken every byte waited on a slow backend, and closed the session silently, because kawa's is_completed (nothing queued) was read as "response fully proxied". Each timeout now defers to the other while the response waits on the other peer (response_waits_on_client); the two conditions are complementary, so a client that stops reading still meets front_timeout and a silent backend still meets back_timeout. Reproduced end to end with 4 s pauses against 2 s timeouts, cut at 4.0 and 6.0 of 8 MiB before the fix (#1906).

Upgrade notes

  • The command schema gains PathRuleKind::REGEX_PATH = 3 and the optional h2c_prior_knowledge fields (HttpListenerConfig field 38, UpdateHttpListenerConfig field 44). Configuration and saved state written by 2.3.3 load unchanged. Upgrade the main process and every worker before declaring a REGEX_PATH frontend or enabling h2c_prior_knowledge: a worker that predates them refuses a REGEX_PATH frontend as an invalid path rule, and ignores the unknown h2c_prior_knowledge field, so it keeps answering a preface 400 Bad Request. A main-only replacement does not qualify the transition. The release gate exercises the published 2.3.3 binary with two workers, saved state and HTTP/HTTPS/TCP/UDP traffic before, during and after replacement. Existing 2.2.1 and 2.3.1 compatibility cases remain separate.
  • Library API: PathRule::from_cli_options (sozu-command-lib) takes a fourth argument, and PathRule (sozu-lib) and PathRuleType (sozu-command-lib) gain a RegexPath variant. ProxySession (sozu-lib) gains a yielded method whose default returns false, so other implementors are unaffected.