Skip to content

[Core] Honour Expect: 100-continue in the JDK HTTP client - #50311

Open
gunjansingh-msft wants to merge 4 commits into
mainfrom
core/jdk-expect-continue
Open

[Core] Honour Expect: 100-continue in the JDK HTTP client#50311
gunjansingh-msft wants to merge 4 commits into
mainfrom
core/jdk-expect-continue

Conversation

@gunjansingh-msft

Copy link
Copy Markdown
Member

AzureJdkHttpRequest.expectContinue() returned a hardcoded false, so java.net.http never performed the Expect: 100-continue handshake. 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 true when the request carries the header. The JDK then adds the header and withholds the body until the service responds 100 Continue. The restricted header filter stripping our copy is harmless for the same reason.

Verification

JdkHttpClientExpectContinueTests drives the client against a raw socket that deliberately delays its 100 Continue, and asserts on what reached the wire and when:

Before After
Expect header on the wire absent present
Body bytes before 100 Continue 1024 (sent immediately) 0
Body delivered after 100 Continue n/a 1024

A 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 main without this change (52 tests / 48 errors on main, 54 tests / 48 errors here, the two extra being the new passing tests).

Context

This came out of adding Expect: 100-continue support to the Storage blob clients (#50094). Of the four transports, only azure-core-http-okhttp performs the handshake today; azure-core-http-vertx sends the body immediately despite the Vert.x client exposing sendHead() and continueHandler(), and azure-core-http-netty has no client side support in netty or reactor-netty. A companion change for vertx follows.

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

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 return true when the request indicates 100-continue.
  • Added socket-level tests that assert the Expect handshake 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.

Comment on lines +61 to +62
this.expectContinue
= "100-continue".equalsIgnoreCase(azureCoreRequest.getHeaders().getValue(HttpHeaderName.EXPECT));
Comment on lines +119 to +121
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.
@gunjansingh-msft

Copy link
Copy Markdown
Member Author

#50094 (Storage blob Expect: 100-continue) is waiting on this one. Its transport matrix currently records this client as not performing the handshake, and those expectations flip once this ships, so the intent is to merge this first and update Storage in the same change that bumps the core dependency.

gunjansingh-msft added a commit that referenced this pull request Sep 1, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure.Core azure-core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants