Skip to content

Commit 3c37a5e

Browse files
Add 'Expect: 100-Continue' support to httplib
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.
1 parent c141340 commit 3c37a5e

5 files changed

Lines changed: 287 additions & 26 deletions

File tree

Doc/library/http.client.rst

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The module provides the following classes:
3434

3535

3636
.. class:: HTTPConnection(host, port=None[, timeout], source_address=None, \
37-
blocksize=8192)
37+
blocksize=8192, continue_timeout=2.5)
3838

3939
An :class:`HTTPConnection` instance represents one transaction with an HTTP
4040
server. It should be instantiated by passing it a host and optional port
@@ -46,7 +46,10 @@ The module provides the following classes:
4646
The optional *source_address* parameter may be a tuple of a (host, port)
4747
to use as the source address the HTTP connection is made from.
4848
The optional *blocksize* parameter sets the buffer size in bytes for
49-
sending a file-like message body.
49+
sending a file-like message body. Finally, the optional *continue_timeout*
50+
parameter controls how long the connection will wait for a ``100 Continue``
51+
response from the server before sending the body, if the request included
52+
an ``Expect: 100-Continue`` header.
5053

5154
For example, the following calls all create instances that connect to the server
5255
at the same host and port::
@@ -66,6 +69,9 @@ The module provides the following classes:
6669
.. versionchanged:: 3.7
6770
*blocksize* parameter was added.
6871

72+
.. versionchanged:: 3.14
73+
*continue_timeout* parameter was added.
74+
6975

7076
.. class:: HTTPSConnection(host, port=None, *[, timeout], \
7177
source_address=None, context=None, \
@@ -321,11 +327,24 @@ HTTPConnection Objects
321327
No attempt is made to determine the Content-Length for file
322328
objects.
323329

324-
.. method:: HTTPConnection.getresponse()
330+
.. versionchanged:: 3.14
331+
If the headers include ``Expect: 100-Continue`` and *body* is set,
332+
the body will not be sent until either a ``100 Continue`` response is
333+
received from the server or the :class:`HTTPConnection`'s *continue_timeout*
334+
is reached; if a response code other than 100 is received, the body
335+
will not be sent at all.
336+
337+
.. method:: HTTPConnection.getresponse(ignore_100_continue=True)
325338

326339
Should be called after a request is sent to get the response from the server.
327340
Returns an :class:`HTTPResponse` instance.
328341

342+
By default, a server response of ``100 Continue`` will be ignored and the
343+
call will not return until a response other than code 100 is received.
344+
Setting *ignore_100_continue* to ``False`` will allow a 100 response to be
345+
returned; you will then need to call :meth:`getresponse` a second time
346+
(after transmitting the body) to get the final response.
347+
329348
.. note::
330349

331350
Note that you must have read the whole response before you can send a new
@@ -336,6 +355,10 @@ HTTPConnection Objects
336355
:class:`HTTPConnection` object will be ready to reconnect when
337356
a new request is sent.
338357

358+
.. versionchanged:: 3.14
359+
Added the *ignore_100_continue* parameter. (In prior versions
360+
a ``Continue`` response was always ignored.)
361+
339362

340363
.. method:: HTTPConnection.set_debuglevel(level)
341364

@@ -439,11 +462,17 @@ also send your request step by step, by using the four functions below.
439462
an argument.
440463

441464

442-
.. method:: HTTPConnection.endheaders(message_body=None, *, encode_chunked=False)
465+
.. method:: HTTPConnection.endheaders(message_body=None, *, encode_chunked=False, \
466+
expect_continue=False)
443467

444468
Send a blank line to the server, signalling the end of the headers. The
445469
optional *message_body* argument can be used to pass a message body
446-
associated with the request.
470+
associated with the request. If a body is provided, setting
471+
*expect_continue* to ``True`` will wait for a ``100 Continue`` response
472+
from the server before sending the body. (This should generally be
473+
used only when an ``Expect: 100-Continue`` header has been sent.) If no
474+
response is received within the :class:`HTTPConnection`'s *continue_timeout*,
475+
the body will be sent regardless.
447476

448477
If *encode_chunked* is ``True``, the result of each iteration of
449478
*message_body* will be chunk-encoded as specified in :rfc:`7230`,
@@ -464,6 +493,9 @@ also send your request step by step, by using the four functions below.
464493
.. versionchanged:: 3.6
465494
Added chunked encoding support and the *encode_chunked* parameter.
466495

496+
.. versionadded:: 3.14
497+
The *expect_continue* parameter was added.
498+
467499

468500
.. method:: HTTPConnection.send(data)
469501

@@ -642,6 +674,48 @@ method attribute. Here is an example session that uses the ``PUT`` method::
642674
>>> print(response.status, response.reason)
643675
200, OK
644676

677+
Since version 3.14, conditional transmission of the body is supported when an
678+
``Expect: 100-Continue`` header is set. To use this in a simple case, just
679+
set the header, and optionally the time for which the client should wait for
680+
a ``100 Continue`` response before sending the body regardless::
681+
682+
>>> import http.client
683+
>>> BODY = "***filecontents***"
684+
>>> conn = http.client.HTTPConnection("localhost", 8080, continue_timeout=1.0)
685+
>>> conn.request("PUT", "/file", BODY, headers={'Expect': '100-Continue'})
686+
>>> response = conn.getresponse()
687+
>>> # You will not see the '100' response, as it is handled internally
688+
>>> print(response.status, response.reason)
689+
200, OK
690+
691+
Here is a more complex example in which we manually check the response and
692+
decide whether to send the body. This may be useful if the body must be
693+
generated by some resource-intensive process which should be skipped if the
694+
server will not accept it. ::
695+
696+
>>> import http.client
697+
>>> conn = http.client.HTTPConnection("localhost", 8080)
698+
>>> conn.putrequest("PUT", "/file")
699+
>>> conn.putheader('Expect', '100-Continue')
700+
>>> # Assuming you know in advance what the length will be
701+
>>> # If not, you will need to encode it as chunked
702+
>>> conn.putheader('Content-Length', '42')
703+
>>> conn.endheaders()
704+
>>> response = conn.getresponse(ignore_100_continue=False)
705+
>>> print(response.status, response.reason)
706+
100, Continue
707+
>>> BODY = resource_intensive_calculation()
708+
>>> conn.send(BODY)
709+
>>> response = conn.getresponse()
710+
>>> print(response.status, response.reason)
711+
200, OK
712+
713+
.. note::
714+
715+
The *continue_timeout* setting does not apply when directly using
716+
:meth:`HTTPConnection.getresponse`, so use the above example only if you are confident
717+
that the server respects the ``Expect: 100-Continue`` header.
718+
645719
.. _httpmessage-objects:
646720

647721
HTTPMessage Objects

Lib/http/client.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import http
7575
import io
7676
import re
77+
import select
7778
import socket
7879
import sys
7980
import collections.abc
@@ -321,15 +322,16 @@ def _read_status(self):
321322
raise BadStatusLine(line)
322323
return version, status, reason
323324

324-
def begin(self):
325+
def begin(self, ignore_100_continue=True):
325326
if self.headers is not None:
326327
# we've already started reading the response
327328
return
328329

329330
# read until we get a non-100 response
331+
# (unless caller has requested return of 100 responses)
330332
while True:
331333
version, status, reason = self._read_status()
332-
if status != CONTINUE:
334+
if not (ignore_100_continue and status == CONTINUE):
333335
break
334336
# skip the header from the 100 response
335337
skipped_headers = _read_headers(self.fp)
@@ -865,13 +867,15 @@ def _get_content_length(body, method):
865867
return None
866868

867869
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
868-
source_address=None, blocksize=8192):
870+
source_address=None, blocksize=8192, continue_timeout=2.5):
869871
self.timeout = timeout
872+
self.continue_timeout = continue_timeout
870873
self.source_address = source_address
871874
self.blocksize = blocksize
872875
self.sock = None
873876
self._buffer = []
874877
self.__response = None
878+
self.__pending_response = None
875879
self.__state = _CS_IDLE
876880
self._method = None
877881
self._tunnel_host = None
@@ -883,9 +887,10 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
883887

884888
self._validate_host(self.host)
885889

886-
# This is stored as an instance variable to allow unit
887-
# tests to replace it with a suitable mockup
890+
# These are stored as instance variables to allow unit
891+
# tests to replace them with a suitable mockup
888892
self._create_connection = socket.create_connection
893+
self._select = select.select
889894

890895
def set_tunnel(self, host, port=None, headers=None):
891896
"""Set up host and port for HTTP CONNECT tunnelling.
@@ -1021,6 +1026,10 @@ def close(self):
10211026
self.sock = None
10221027
sock.close() # close it manually... there may be other refs
10231028
finally:
1029+
pending_response = self.__pending_response
1030+
if pending_response:
1031+
self.__pending_response = None
1032+
pending_response.close()
10241033
response = self.__response
10251034
if response:
10261035
self.__response = None
@@ -1081,7 +1090,8 @@ def _read_readable(self, readable):
10811090
datablock = datablock.encode("iso-8859-1")
10821091
yield datablock
10831092

1084-
def _send_output(self, message_body=None, encode_chunked=False):
1093+
def _send_output(self, message_body=None, encode_chunked=False,
1094+
expect_continue=False):
10851095
"""Send the currently buffered request and clear the buffer.
10861096
10871097
Appends an extra \\r\\n to the buffer.
@@ -1093,6 +1103,23 @@ def _send_output(self, message_body=None, encode_chunked=False):
10931103
self.send(msg)
10941104

10951105
if message_body is not None:
1106+
if expect_continue and not self.__response:
1107+
read_ready, _, _ = self._select([self.sock], [], [],
1108+
self.continue_timeout)
1109+
if read_ready:
1110+
if self.debuglevel > 0:
1111+
response = self.response_class(self.sock,
1112+
self.debuglevel,
1113+
method=self._method)
1114+
else:
1115+
response = self.response_class(self.sock,
1116+
method=self._method)
1117+
response.begin(ignore_100_continue=False)
1118+
if response.code != CONTINUE:
1119+
# Break without sending the body
1120+
self.__pending_response = response
1121+
return
1122+
response.close()
10961123

10971124
# create a consistent interface to message_body
10981125
if hasattr(message_body, 'read'):
@@ -1319,7 +1346,8 @@ def putheader(self, header, *values):
13191346
header = header + b': ' + value
13201347
self._output(header)
13211348

1322-
def endheaders(self, message_body=None, *, encode_chunked=False):
1349+
def endheaders(self, message_body=None, *, encode_chunked=False,
1350+
expect_continue=False):
13231351
"""Indicate that the last header line has been sent to the server.
13241352
13251353
This method sends the request to the server. The optional message_body
@@ -1330,7 +1358,8 @@ def endheaders(self, message_body=None, *, encode_chunked=False):
13301358
self.__state = _CS_REQ_SENT
13311359
else:
13321360
raise CannotSendHeader()
1333-
self._send_output(message_body, encode_chunked=encode_chunked)
1361+
self._send_output(message_body, encode_chunked=encode_chunked,
1362+
expect_continue=expect_continue)
13341363

13351364
def request(self, method, url, body=None, headers={}, *,
13361365
encode_chunked=False):
@@ -1375,20 +1404,33 @@ def _send_request(self, method, url, body, headers, encode_chunked):
13751404
else:
13761405
encode_chunked = False
13771406

1407+
# If the Expect: 100-continue header is set, we will try to honor it
1408+
# (if possible). We can only do so if 1) the request has a body, and
1409+
# 2) there is no current incomplete response (since we need to read
1410+
# the response stream to check if the code is 100 or not)
1411+
expect_continue = (
1412+
body and not self.__response
1413+
and 'expect' in header_names
1414+
and '100-continue' in {v.lower() for k, v in headers.items()
1415+
if k.lower() == 'expect'}
1416+
)
1417+
13781418
for hdr, value in headers.items():
13791419
self.putheader(hdr, value)
13801420
if isinstance(body, str):
13811421
# RFC 2616 Section 3.7.1 says that text default has a
13821422
# default charset of iso-8859-1.
13831423
body = _encode(body, 'body')
1384-
self.endheaders(body, encode_chunked=encode_chunked)
1424+
self.endheaders(body, encode_chunked=encode_chunked,
1425+
expect_continue=expect_continue)
13851426

1386-
def getresponse(self):
1427+
def getresponse(self, ignore_100_continue=True):
13871428
"""Get the response from the server.
13881429
13891430
If the HTTPConnection is in the correct state, returns an
1390-
instance of HTTPResponse or of whatever object is returned by
1391-
the response_class variable.
1431+
instance of HTTPResponse or of whatever object is returned by the
1432+
response_class variable. The connection will wait for a response other
1433+
than code 100 ('Continue') unless ignore_100_continue is set to False.
13921434
13931435
If a request has not been sent or if a previous response has
13941436
not be handled, ResponseNotReady is raised. If the HTTP
@@ -1419,27 +1461,32 @@ def getresponse(self):
14191461
if self.__state != _CS_REQ_SENT or self.__response:
14201462
raise ResponseNotReady(self.__state)
14211463

1422-
if self.debuglevel > 0:
1464+
if self.__pending_response:
1465+
response = self.__pending_response
1466+
self.__pending_response = None
1467+
elif self.debuglevel > 0:
14231468
response = self.response_class(self.sock, self.debuglevel,
14241469
method=self._method)
14251470
else:
14261471
response = self.response_class(self.sock, method=self._method)
14271472

14281473
try:
14291474
try:
1430-
response.begin()
1475+
response.begin(ignore_100_continue=ignore_100_continue)
14311476
except ConnectionError:
14321477
self.close()
14331478
raise
14341479
assert response.will_close != _UNKNOWN
1435-
self.__state = _CS_IDLE
1480+
if response.code != 100:
1481+
# Code 100 is effectively 'not a response' for this purpose
1482+
self.__state = _CS_IDLE
14361483

1437-
if response.will_close:
1438-
# this effectively passes the connection to the response
1439-
self.close()
1440-
else:
1441-
# remember this, so we can tell when it is complete
1442-
self.__response = response
1484+
if response.will_close:
1485+
# this effectively passes the connection to the response
1486+
self.close()
1487+
else:
1488+
# remember this, so we can tell when it is complete
1489+
self.__response = response
14431490

14441491
return response
14451492
except:

0 commit comments

Comments
 (0)