Skip to content

gh-42550: Add 'Expect: 100-Continue' support to httplib#133276

Open
IngridMorstrad wants to merge 4 commits into
python:mainfrom
IngridMorstrad:fix-issue-42550
Open

gh-42550: Add 'Expect: 100-Continue' support to httplib#133276
IngridMorstrad wants to merge 4 commits into
python:mainfrom
IngridMorstrad:fix-issue-42550

Conversation

@IngridMorstrad

@IngridMorstrad IngridMorstrad commented May 1, 2025

Copy link
Copy Markdown

Previously, http.client would always send content body immediately and ignore any 100 responses. This change makes HTTPClient.request() wait for a Continue response if the Expect: 100-Continue header is set, and adds a parameter to HTTPClient.getresponse() that will cause it to return 100 responses instead of eating them.

#42550
https://bugs.python.org/issue1346874


📚 Documentation preview 📚: https://cpython-previews--133276.org.readthedocs.build/

Previously, http.client would always send content body immediately and ignore any 100 responses. This change
makes HTTPClient.request() wait for a `Continue` response if the `Expect: 100-Continue` header is set, and
adds a parameter to HTTPClient.getresponse() that will cause it to return 100 responses instead of eating them.
@IngridMorstrad IngridMorstrad changed the title Add 'Expect: 100-Continue' support to httplib gh-42550: Add 'Expect: 100-Continue' support to httplib May 1, 2025

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

General triage nits.

Comment thread Doc/library/http.client.rst Outdated
Comment thread Doc/library/http.client.rst Outdated
Comment thread Doc/library/http.client.rst Outdated
Comment thread Doc/library/http.client.rst Outdated
Comment thread Doc/library/http.client.rst Outdated
Comment thread Misc/NEWS.d/next/Library/2019-07-22-00-57-26.bpo-1346874.TentEE.rst Outdated
IngridMorstrad and others added 2 commits May 21, 2025 15:23
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
@IngridMorstrad

Copy link
Copy Markdown
Author

Hi, checking for a possible review on this. @ZeroIntensity

@ZeroIntensity

Copy link
Copy Markdown
Member

Sorry, I was only triaging. I don't have enough knowledge on the http module to review this. You could take a look at the experts index.

Comment thread Doc/library/http.client.rst Outdated
Comment on lines +49 to +52
sending a file-like message body. Finally, the optional *continue_timeout*
parameter controls how long the connection will wait for a ``100 Continue``
response from the server before sending the body, if the request included
an ``Expect: 100-Continue`` header.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this need to be a constructor parameter?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I was thinking that since it is a property of the server (how quickly it responds with 100 Continue, or whether it understands Expect at all), it should be in the constructor, not the individual request. The other per-request parameters (expect_continue, ignore_100_continue) could vary per-request. LMK if that makes sense or we need to change this and I can update the PR.

@AA-Turner

Copy link
Copy Markdown
Member

You have merge conflicts, please resolve them before requesting a re-review.

@IngridMorstrad

Copy link
Copy Markdown
Author

Sorry for the delay here, I just replied to the question above. If that response makes sense, I can send an updated PR with the merge conflicts addressed.

@IngridMorstrad

Copy link
Copy Markdown
Author

Hi,

I have fixed the merge conflicts and CI is green. The PR has been open since May 2025. I am not sure who to request a review from as there is no reviewer listed for the http module on the Developer's Guide. Any help is appreciated, thanks!

Ashwin

@merwok merwok requested a review from orsenthil March 3, 2026 01:53
@IngridMorstrad

Copy link
Copy Markdown
Author

Hi @orsenthil , could I please get a review of this? CI is green and I keep needing to rebase over time.

@IngridMorstrad

Copy link
Copy Markdown
Author

CC @gpshead as the networking head listed here, wondering if you could help me get a reviewer for this PR.

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 24, 2026

@gpshead gpshead left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left a pile of comments.

We've gone forever without this in our HTTP client. It should be possible to make work, but it might get weird in our stack in the modern TLS HTTPSConnection that everyone should be using instead of HTTPConnection... So maybe something to figure out there. Does this actually work properly in that scenario?

Comment thread Lib/http/client.py
return version, status, reason

def begin(self, *, _max_headers=None):
def begin(self, ignore_100_continue=True, _max_headers=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the * was important, it makes these keyword only arguments which are easier to support on public APIs like this method. (rule of thumb, when adding a new parameter it either has to be added at the end and if there are either a lot of arguments or are very infrequently passed should be added as keyword only so that it must be passed by name)

Suggested change
def begin(self, ignore_100_continue=True, _max_headers=None):
def begin(self, *, ignore_100_continue=True, _max_headers=None):

Comment thread Lib/http/client.py

def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
source_address=None, blocksize=8192, *, max_response_headers=None):
source_address=None, blocksize=8192, continue_timeout=2.5, *,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

continue_timeout should be a keyword only argument.... but is the constructur really where we want it rather than on the request() method where headers are specified? That'd also avoid the need to plumb this through HTTPSConnection.__init__.

From an API design perspective, on the HTTPConnection.request method which takes a headers={} dict as well as some flags such as potentially this... If someone wants to use this feature, it is ergonomically nicer if we had a flag on the request_api that explicitly adds the correct Expect: 100-Continue header rather than expecting people to fill in that level of implementation detail header value directly themselves. (I think the code still needs detect if the header was added manually regardless, kind of like it does for some of the other headers)

also, assert that continue_timeout > 0.0.

Comment thread Lib/http/client.py
Comment on lines +1452 to +1458
def getresponse(self, ignore_100_continue=True):
"""Get the response from the server.

If the HTTPConnection is in the correct state, returns an
instance of HTTPResponse.
instance of HTTPResponse or of whatever object is returned by the
response_class variable. The connection will wait for a response other
than code 100 ('Continue') unless ignore_100_continue is set to False.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"ignore..." is a somewhat of a negative term in English for use in an API which leads to awkward descriptions of the behavior. it defaults to True and people have to turn the negative off with False leading to double negatives to reason about.

As this defaults to not supporting 100 continue behavior, why not call it support_continue=False so that enabling support for it involves setting something to True instead of setting an ignore= to False?

(apply this everywhere)

also we should make it keyword only so that it has to be spelled out when anyone wants to use it for better code readability. (blah.getresponse(True) doesn't indicate much to the reader).

I also think eliding the number from the name is a good idea. 100 is defined as continue so no need to mention that internal http detail.

Comment thread Lib/test/test_httplib.py
def create_connection(self, *pos, **kw):
return FakeSocket(*self.fake_socket_args)

def fake_select(self, rlist, wlist, xlist, timeout=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this doesn't use self, make it a @staticmethod or a top level function?

>>> print(response.status, response.reason)
200, OK

Since version 3.15, conditional transmission of the body is supported when an

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't mention the version here. think of docs as being read in the future, version specific changes are normally footnotes via the version added and version changed markers. (this is a feature so it won't be in 3.15)

Comment thread Lib/http/client.py

if message_body is not None:
if expect_continue and not self.__response:
read_ready, _, _ = self._select([self.sock], [], [],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

select won't work properly on a HTTPSConnection. There's a .pending method on SSLSocket that could be used in this scenario if read_ready.

Investigate what is plausible and add some tests covering the HTTPSConnection test case.

Comment thread Lib/http/client.py
else:
response = self.response_class(self.sock,
method=self._method)
response.begin(ignore_100_continue=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

_max_headers= should also be passed here.

Comment thread Lib/http/client.py
response.begin(ignore_100_continue=False)
if response.code != CONTINUE:
# Break without sending the body
self.__pending_response = response

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wherever self.__response is cleared (= None) such as putrequest, self.__pending_response needs to cleared as well. tests should cover that this happens by running through a scenario that'd lead to this such as maybe a non-100 early response that gets ignored before another request is sent on the same connection?

@@ -0,0 +1,2 @@
:class:`http.client.HTTPConnection` now waits for a ``100 Continue`` response

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should highlight that this is a behavior change for any code that explicitly added the Extra: 100-Continue to their request headers as those will now wait for the timeout to elapse before sending the body if the server doesn't do 100s.

Also add an entry to the whatsnew 3.16.rst doc.

@gpshead gpshead removed the stale Stale PR or inactive for long period of time. label May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants