Repository navigation
Upgrade to AsyncHttpClient 3.0.14 - #1481
Merged
Merged
Conversation
Shade AHC 3.0.14 with Netty 4.2, keep annotation classes and any other unrelocated class out of the shaded jar, and adapt Play WS and the README examples to AHC 3's API. AHC 3 removed the StreamedAsyncHandler that DefaultStreamedAsyncHandler.onStream belonged to, so remove that method and record the change for MiMa; use StandaloneWSRequest.stream() instead. Bridge AHC 3's ResponseBodyControl to a Reactive Streams publisher with real backpressure. Suspend transport reads only on the channel event loop, where AHC applies them inline, so a suspension can never override a newer resume.
Cover the response publisher with deterministic lost-resume and suspend-on-loop specs, the Reactive Streams TCK, and live HTTP/1.1 and h2c streaming tests against backpressure-aware test servers. Consume filtered streamed responses without blocking, so the filter spec does not depend on a short blocking timeout under load. Run the integration tests with paranoid leak detection for both the test servers' Netty and the shaded Netty inside AHC, which reads its own relocated property.
AHC 3.0.14 queues a control call made off the event loop and applies it later without checking again. A stale resume can then let one more socket read through with no demand left, which inflates to tens of MiB for a compressed body. Schedule the resume decision as a drain() pass on the event loop instead, and pin the drain race that a direct demand check would reintroduce in a deterministic test. Replace this once AHC offers a resume that is evaluated on the event loop.
Stream 64 MiB of gzipped zeros with early demand, three times, and require each run to buffer less than 10 MiB beyond demand. Measured: 1,875,916 bytes with the event-loop re-check, about 35.6 MB without it.
Play WS adapts Source, File and input-stream-supplier bodies to a one-shot stream. A redirect or retry that would send it again now fails explicitly instead of subscribing twice.
Keep the Signpost-based OAuth 1 calculator, verified against AHC 2.16.1's signatures in a test-only differential test, and cover SHA-256 Digest challenges.
Apply the AHC 2 text charset rule once at final request construction so filters see the original Content-Type, and share the normalization across Scala and Java builders. Bypass the Play WS cache only for the streamed handler, retain failures for unsupported handlers, and document cache ByteBuf ownership.
Add HTTP/2 settings (HTTP/2 off by default), the decompressed-response limit and event-loop shutdown durations, with validation and documentation. Keep AhcWSClientConfig's 12-argument constructor, apply, copy and 12-field extractor on Scala 2 and Scala 3, and record the remaining binary changes for MiMa.
Play WS's TLS engine factory does not offer ALPN, so HTTPS stays on HTTP/1.1 even with http2Enabled = true. Test that, test h2c when enabled explicitly, and document that the HTTP/2 window counts wire bytes rather than decoded data.
Add unset-by-default settings for AHC 3.0.14's scheme-downgrade and cross-origin-body redirect refusals, and set them on AHC only when configured. Document that AHC resends the body by default, that a same-host move to https is exempt, and how the refusals differ from credential stripping. Cover unset, enabled and explicitly disabled values through the Scala and Java APIs, including same-origin replay, and assert that refused redirects never contact their target.
Describe API and binary changes, redirect semantics, cookie and connection-pooling changes against AHC 2, and the security fixes included in AHC 3.0.14.
With a cookie store, AHC 3.0.14 re-encodes a raw Cookie header on redirects and authentication retries, which can reorder or drop pairs and fails on values its strict encoder rejects. Remove this note when AHC keeps the caller's text.
AHC 3.0.14 inflates a whole socket read before demand is checked, as AHC 2 did. Adjust this note when AHC bounds decompression for suspended HTTP/1.1 responses.
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upgrades Play WS from AsyncHttpClient 2.16.1 to AsyncHttpClient 3.0.14 with Netty 4.2.18, both still shaded into
play.shaded.ahc.Summary
StreamedAsyncHandler. Play WS now bridges AHC 3'sResponseBodyControlto a Reactive Streams publisher that suspends socket reads while there is no downstream demand.OAuthSignatureCalculator. Play WS now signs with its shaded Signpost dependency, and a test-only differential test checks the signatures against AHC 2.16.1's.AhcWSClientConfiggains new AHC 3 settings but keeps its 3.0 constructor,apply,copyand 12-field extractor on Scala 2 and Scala 3. Only the generatedtupledandcurriedhelpers are gone (see "Binary and API changes").Commits
The history is split for review. Only the tip was built and tested; the individual commits were not.
play/shaded/ends up in it, and JetBrains annotation classes are excluded.ResponseBodyControl. It suspends reads only on the channel event loop, where AHC applies them inline, so a suspension can never override a newer resume.drain()pass on the event loop. It finds that loop with the shaded Netty's internalThreadExecutorMap.Source,Fileand input-stream-supplier bodies are one-shot. A redirect or retry that would send one again now fails explicitly instead of subscribing twice.http2Enabled = true, and that h2c works when enabled explicitly.useCookieStore = true.Commits 3, 12 and 13 exist only because of AHC 3.0.14's current behavior. Each is self-contained, so it can be changed on its own once AHC releases a fix (see "Known limitations"):
Binary and API changes
MiMa (against 3.0.0) needs six filters. Three cover APIs that exposed AHC 2 types; the other three cover helpers that Scala generated for
AhcWSClientConfig's companion:DefaultStreamedAsyncHandlerno longer extends the shaded AHC 2StreamedAsyncHandler, and itsonStreammethod is gone. The class is public, but Play WS creates it internally for streamed requests, and AHC 2 calledonStreamitself. UseStandaloneWSRequest.stream()andbodyAsSource, which are unchanged. The old interface no longer exists in AHC 3.play.libs.oauth.OAuth.OAuthCalculator.getCalculator()now returns the shadedSignatureCalculatorinterface instead of the removedOAuthSignatureCalculator. Signing withrequest.sign(new OAuthCalculator(key, token))is unaffected. Only code that callsgetCalculator()directly is affected: it must be recompiled, and adapted if it refers to the removedOAuthSignatureCalculatortype.AhcWSClientConfig's companion no longer extendsFunction12on Scala 2.13, soAhcWSClientConfig.tupledand.curriedare gone. The constructor,apply,copy, 12-field pattern matching andAhcWSClientConfigFactorykeep working. Scala 3 companions never extended function types.Code that uses shaded AHC types directly, for example through
underlying, must be updated for AHC 3.Behavior changes against AHC 2
The README's "AHC 3 migration notes" cover these in detail:
POSTrequests; onlyPOSTis rewritten to a bodylessGET.HEADandOPTIONS.GETcan now fail explicitly.Cookieheader is merged with API and cookie-store cookies on every hop.Cookieheaders are folded into one.Content-Encodinga server sends, even when Play WS did not ask for compression.AsyncHttpClienthanded to Play WS uses its own defaults, not all of Play WS's.New configuration (
play.ws.ahc.*)http2Enabledfalsehttp2CleartextEnabled, set throughmodifyUnderlying.http2InitialWindowSizehttp2MaxConcurrentStreamsmaxDecompressedResponseSizerefuseSchemeDowngradeOnRedirectfalse)refuseCrossOriginBodyOnRedirectfalse)shutdownQuietPeriod,shutdownTimeout0 millisecondsBecause AHC is shaded, AHC system properties keep the
play.shaded.ahc.prefix, for exampleplay.shaded.ahc.org.asynchttpclient.*. Netty's leak detector inside the shaded jar readsplay.shaded.ahc.io.netty.leakDetection.level.Known limitations and follow-ups
These come from AHC 3.0.14's current behavior. Play WS works around the first and documents the other two:
Cookieheader is re-encoded with a cookie store (commit 12). WithuseCookieStore = true, redirects and authentication retries re-encode the raw header: pairs can be reordered or dropped, and values that AHC's strict encoder rejects make the redirect or retry fail. An upstream fix is being prepared.Not in this PR: HTTP/2 over HTTPS (ALPN). It needs an ALPN-capable SSL engine factory that offers
h2only when bothhttp2Enabledand AHC's per-connectionhttp2Allowedare set. Turning HTTP/2 on by default would be a separate decision.Testing
Local runs on the tip commit, all passing:
validateCode,mimaReportBinaryIssuesanddoc, plus allplay-ahc-ws-standalonetests (18 suites) andintegration-tests(15 suites);Test/compileandmimaReportBinaryIssues.The CI matrix (JDK 17/21/25 × Scala 2.13/3.3/3.9/3.next) runs on this PR.
New tests include:
AhcWSClientConfigsource and binary compatibility;