# in the case of a 100-continue. Document or change?
yield self._read_message(delegate)
else:
- if headers.get("Expect") == "100-continue":
+ if (headers.get("Expect") == "100-continue" and
+ not self._write_finished):
self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
if not skip_body:
body_future = self._read_body(headers, delegate)
proxy_password=None, allow_nonstandard_methods=None,
validate_cert=None, ca_certs=None,
allow_ipv6=None,
- client_key=None, client_cert=None, body_producer=None):
+ client_key=None, client_cert=None, body_producer=None,
+ expect_100_continue=False):
r"""All parameters except ``url`` are optional.
:arg string url: URL to fetch
note below when used with ``curl_httpclient``.
:arg string client_cert: Filename for client SSL certificate, if any.
See note below when used with ``curl_httpclient``.
+ :arg bool expect_100_continue: If true, send the
+ ``Expect: 100-continue`` header and wait for a continue response
+ before sending the request body. Only supported with
+ simple_httpclient.
+
.. note::
self.allow_ipv6 = allow_ipv6
self.client_key = client_key
self.client_cert = client_cert
+ self.expect_100_continue = expect_100_continue
self.start_time = time.time()
@property
raise AssertionError(
'Body must be empty for "%s" request'
% self.request.method)
+ if self.request.expect_100_continue:
+ self.request.headers["Expect"] = "100-continue"
if self.request.body is not None:
# When body_producer is used the caller is responsible for
# setting Content-Length (or else chunked encoding will be used).
start_line, self.request.headers,
has_body=(self.request.body is not None or
self.request.body_producer is not None))
+ if self.request.expect_100_continue:
+ self._read_response()
+ else:
+ self._write_body(True)
+
+ def _write_body(self, start_read):
if self.request.body is not None:
self.connection.write(self.request.body)
self.connection.finish()
def on_body_written(fut):
fut.result()
self.connection.finish()
- self._read_response()
+ if start_read:
+ self._read_response()
self.io_loop.add_future(fut, on_body_written)
return
self.connection.finish()
- self._read_response()
+ if start_read:
+ self._read_response()
+
def _read_response(self):
# Ensure that any exception raised in read_response ends up in our
raise HTTPError(599, message)
def headers_received(self, first_line, headers):
+ if self.request.expect_100_continue and first_line.code == 100:
+ self._write_body(False)
+ return
self.headers = headers
self.code = first_line.code
self.reason = first_line.reason
from tornado.test import httpclient_test
from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, bind_unused_port, ExpectLog
from tornado.test.util import unittest, skipOnTravis
-from tornado.web import RequestHandler, Application, asynchronous, url
+from tornado.web import RequestHandler, Application, asynchronous, url, stream_request_body
class SimpleHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
self.write(self.request.body)
+@stream_request_body
+class RespondInPrepareHandler(RequestHandler):
+ def prepare(self):
+ self.set_status(403)
+ self.finish("forbidden")
+
+
class SimpleHTTPClientTestMixin(object):
def get_app(self):
# callable objects to finish pending /trigger requests
url("/host_echo", HostEchoHandler),
url("/no_content_length", NoContentLengthHandler),
url("/echo_post", EchoPostHandler),
+ url("/respond_in_prepare", RespondInPrepareHandler),
], gzip=True)
def test_singleton(self):
response.rethrow()
self.assertEqual(response.body, b"12345678")
+ def test_100_continue(self):
+ response = self.fetch("/echo_post", method="POST",
+ body=b"1234",
+ expect_100_continue=True)
+ self.assertEqual(response.body, b"1234")
+
+ def test_100_continue_early_response(self):
+ def body_producer(write):
+ raise Exception("should not be called")
+ response = self.fetch("/respond_in_prepare", method="POST",
+ body_producer=body_producer,
+ expect_100_continue=True)
+ self.assertEqual(response.code, 403)
+
class SimpleHTTPClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPTestCase):
def setUp(self):