[Core] Honour Expect: 100-continue in the JDK HTTP client - #50311
[Core] Honour Expect: 100-continue in the JDK HTTP client#50311gunjansingh-msft wants to merge 4 commits into
Conversation
AzureJdkHttpRequest.expectContinue() returned a hardcoded false, so java.net.http never performed the handshake and the Expect header was dropped by the restricted header filter. The body was therefore always sent immediately, and a service could not reject a request before receiving it. Return true when the request carries the header. The JDK adds the header itself and withholds the body until the service responds 100 Continue. Adds wire level tests that drive the client against a socket which delays its 100 Continue, asserting that no body bytes arrive before the response and that the full body arrives after it.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 35 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR fixes the azure-core-http-jdk-httpclient transport to correctly opt into the JDK Expect: 100-continue handshake when the Azure Core request includes the Expect: 100-continue header, preventing the request body from being sent until the server responds with 100 Continue.
Changes:
- Implemented
AzureJdkHttpRequest.expectContinue()to returntruewhen the request indicates100-continue. - Added socket-level tests that assert the
Expecthandshake behavior on the wire (header present and body deferred). - Documented the behavioral change in the module changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| sdk/core/azure-core-http-jdk-httpclient/src/main/java/com/azure/core/http/jdk/httpclient/implementation/AzureJdkHttpRequest.java | Enables JDK-level expect-continue behavior based on request headers. |
| sdk/core/azure-core-http-jdk-httpclient/src/test/java/com/azure/core/http/jdk/httpclient/JdkHttpClientExpectContinueTests.java | Adds coverage verifying on-the-wire deferment of the body until 100 Continue. |
| sdk/core/azure-core-http-jdk-httpclient/CHANGELOG.md | Notes the new behavior for requests carrying Expect: 100-continue. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| this.expectContinue | ||
| = "100-continue".equalsIgnoreCase(azureCoreRequest.getHeaders().getValue(HttpHeaderName.EXPECT)); |
| Thread.sleep(BODY_SETTLE_MILLIS); | ||
| bodyBytesBeforeContinue = in.available(); | ||
|
|
The core modules compile with -Werror, and URL(String) is deprecated as of Java 20, so the test failed to compile on the JDK 25 build agents.
ec0126a to
8e04c98
Compare
|
#50094 (Storage blob |
Records the behaviour those transports have once the azure-core fixes in #50311 and #50312 ship. Until Storage picks up those versions the four cases fail, which is the intended signal rather than a silent mismatch. Netty stays as it is: the change there observes the interim response and does not defer the body.
Expect is a comma separated list and values may carry whitespace, so compare each expectation rather than the header as a whole. Otherwise "100-Continue", " 100-continue " or "100-continue, foo" would silently disable the handshake. The test server now reads for the whole settle window rather than taking an available() snapshot after a sleep, so a body already in flight is counted rather than missed. Adds the header spelling variants as cases.
JdkAsyncHttpClient is unsupported on Java 11 and below, so the tests errored on the JDK 11 agent instead of being skipped. The other test classes in this module already carry the same guard.
AzureJdkHttpRequest.expectContinue()returned a hardcodedfalse, sojava.net.httpnever performed theExpect: 100-continuehandshake. The header was also dropped by the restricted header filter, so it never reached the wire either. The result was that a request carrying the expectation behaved exactly like one without it: the body was sent immediately and the service had no opportunity to reject it first.The JDK implements the handshake itself once the request opts in, so the fix is to return
truewhen the request carries the header. The JDK then adds the header and withholds the body until the service responds100 Continue. The restricted header filter stripping our copy is harmless for the same reason.Verification
JdkHttpClientExpectContinueTestsdrives the client against a raw socket that deliberately delays its100 Continue, and asserts on what reached the wire and when:Expectheader on the wire100 Continue100 ContinueA second test asserts that a request without the header still sends its body immediately, so the behaviour is scoped to requests that opt in.
The 48 pre-existing errors in this module's suite are unrelated - they reproduce identically on
mainwithout this change (52 tests / 48 errors onmain, 54 tests / 48 errors here, the two extra being the new passing tests).Context
This came out of adding
Expect: 100-continuesupport to the Storage blob clients (#50094). Of the four transports, onlyazure-core-http-okhttpperforms the handshake today;azure-core-http-vertxsends the body immediately despite the Vert.x client exposingsendHead()andcontinueHandler(), andazure-core-http-nettyhas no client side support in netty or reactor-netty. A companion change for vertx follows.