Repository navigation
Conversation
NettyRequestFactory added three header values in forms that cost work on every request. A body-less POST, PUT or PATCH set Content-Length from an Integer, which Netty turns into a new "0" String each time. A body's length was boxed into a Long before the same conversion. The configured User-Agent was a String, so HttpHeadersEncoder took its char-by-char setCharSequence path instead of the AsciiString bulk copy. Use HttpHeaderValues.ZERO, setInt for lengths that fit in an int (HttpHeaders has no setLong, so larger lengths keep the old path), and convert the User-Agent to an AsciiString once in the constructor. The bytes on the wire are unchanged: AsciiString maps chars the same way the String path does, and the validating request headers still reject an illegal User-Agent on every request. The value is now read from the config once per factory, as the cookie encoder already is. RequestHeaderValueBenchmark, JMH 1.37, JDK 17.0.2, 3 forks, -prof gc: - Content-Length 0: 817.5 -> 769.5 B/op - body Content-Length: 841.5 -> 817.5 B/op - User-Agent value write: 21.3 +- 6.2 -> 11.1 +- 2.6 ns/op, no alloc Whole-request times were within noise on a loaded host. Local `mvnw clean verify` ran on JDK 17 (no JDK 11 on the host): 1865 tests, 0 failures; Revapi passed. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hyperxpro
approved these changes
Nov 6, 2026
pavel-ptashyts
added a commit
to maygemdev/async-http-client
that referenced
this pull request
Nov 6, 2026
Keeps the branch current with main at f9eafb9 (includes AsyncHttpClient#2372 and AsyncHttpClient#2380). Merged without conflicts. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 tasks done
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.
Summary
NettyRequestFactoryset three header values in forms that cost work on every HTTP/1.1 request:Content-Lengthfrom anIntegerconstant. Netty's header value converter callstoString(), so every such request allocated a new"0"string.Longbefore the same conversion.User-Agentis aString, soHttpHeadersEncoder.writeAsciiencoded it char by char throughsetCharSequenceinstead of theAsciiStringbulk copy.HeaderEncodeBenchmarkmeasured the same effect for header names (Intern known request header names to static AsciiString #2206).Changes:
Content-Length: 0usesHttpHeaderValues.ZERO.intgoes throughsetInt.HttpHeadershas nosetLong, so larger lengths keep the old path.User-Agentis converted to anAsciiStringonce, in the constructor.Behaviour notes
AsciiStringmaps chars the same way theStringencode path does: Latin-1 chars become their own byte, and anything above U+00FF becomes?. A test pins this.User-Agent(CR, LF and the like) with the sameIllegalArgumentException, on every request, as before.User-Agentis now read from the config once per factory, the way the cookie encoder already is. Only a customAsyncHttpClientConfigwhosegetUserAgent()changes between calls would notice.DefaultAsyncHttpClientConfigis immutable.Benchmark
New
RequestHeaderValueBenchmark: builds and encodes a request with the factory's own headers, with each value in its old and new form. JMH 1.37, JDK 17.0.2, Windows,-prof gc, 3 forks, 5 x 1 s warmup, 10 x 1 s measurement.Content-Length: 0Content-Length: 1536User-Agentvalue (writeAsciibranch only)The host was 75-92% busy, so whole-request times (~800 ns/op) stayed within noise. Allocation is the reliable figure. The
User-Agentgain was measured on the encoder's two branches in isolation. Neither branch allocates.Tests
NettyRequestFactoryHeaderValuesTestencodes the factory's requests withHttpRequestEncoderand checks:User-Agentis sent as configured;User-Agentproduces the same bytes as theStringvalue did;User-Agentwith CRLF is still rejected on every request;User-Agentwins over the configured one;Content-Length: 0is sent for body-less POST/PUT/PATCH, and noContent-Lengthfor GET;Integer.MAX_VALUE.Verification
mvnw.cmd -B -ntp clean verify -Dgpg.skip=trueon JDK 17.0.2 (Windows): 1,919 tests, 0 failures, 0 errors, 35 skipped. Error Prone, NullAway and Revapi passed. Local verify ran on JDK 17 because there is no JDK 11 on this machine; CI covers JDK 11.-Dgpg.skipis set only because gpg is not installed here.RedirectConnectionUsageTesttimed out once under load. It fails the same way on unchangedmainand passed in the next runs.Claude Code on behalf of Pavel Ptashyts
🤖 Generated with Claude Code