wsgi.input: Make send_hundred_continue_headers a public API
Per HTTP RFC 7231 (http://tools.ietf.org/html/rfc7231#section-6.2) a client is required to be able to process one or more 100 continue responses. -- RFC 7231 -------------------- 6.2. Informational 1xx [snip] A client MUST be able to parse one or more 1xx responses received prior to a final response, even if the client does not expect one. A user agent MAY ignore unexpected 1xx responses. ... -------------------------------- This patch adds a send_hundred_continue_headers() public API method to wsgi.input, thus allowing WSGI apps to send more than one 100- continue response. This does not change existing semantics for the first 100-continue response sent on read() or readline().
This commit is contained in:
@@ -88,8 +88,9 @@ class Input(object):
|
|||||||
# (optional) headers to send with a "100 Continue" response. Set by
|
# (optional) headers to send with a "100 Continue" response. Set by
|
||||||
# calling set_hundred_continue_respose_headers() on env['wsgi.input']
|
# calling set_hundred_continue_respose_headers() on env['wsgi.input']
|
||||||
self.hundred_continue_headers = None
|
self.hundred_continue_headers = None
|
||||||
|
self.is_hundred_continue_response_sent = False
|
||||||
|
|
||||||
def _send_hundred_continue_response(self):
|
def send_hundred_continue_response(self):
|
||||||
towrite = []
|
towrite = []
|
||||||
|
|
||||||
# 100 Continue status line
|
# 100 Continue status line
|
||||||
@@ -105,13 +106,16 @@ class Input(object):
|
|||||||
towrite.append(b'\r\n')
|
towrite.append(b'\r\n')
|
||||||
|
|
||||||
self.wfile.writelines(towrite)
|
self.wfile.writelines(towrite)
|
||||||
self.wfile = None
|
|
||||||
self.wfile_line = None
|
# Reinitialize chunk_length (expect more data)
|
||||||
|
self.chunk_length = -1
|
||||||
|
|
||||||
def _do_read(self, reader, length=None):
|
def _do_read(self, reader, length=None):
|
||||||
if self.wfile is not None:
|
if self.wfile is not None and \
|
||||||
|
not self.is_hundred_continue_response_sent:
|
||||||
# 100 Continue response
|
# 100 Continue response
|
||||||
self._send_hundred_continue_response()
|
self.send_hundred_continue_response()
|
||||||
|
self.is_hundred_continue_response_sent = True
|
||||||
if length is None and self.content_length is not None:
|
if length is None and self.content_length is not None:
|
||||||
length = self.content_length - self.position
|
length = self.content_length - self.position
|
||||||
if length and length > self.content_length - self.position:
|
if length and length > self.content_length - self.position:
|
||||||
@@ -126,9 +130,11 @@ class Input(object):
|
|||||||
return read
|
return read
|
||||||
|
|
||||||
def _chunked_read(self, rfile, length=None, use_readline=False):
|
def _chunked_read(self, rfile, length=None, use_readline=False):
|
||||||
if self.wfile is not None:
|
if self.wfile is not None and \
|
||||||
|
not self.is_hundred_continue_response_sent:
|
||||||
# 100 Continue response
|
# 100 Continue response
|
||||||
self._send_hundred_continue_response()
|
self.send_hundred_continue_response()
|
||||||
|
self.is_hundred_continue_response_sent = True
|
||||||
try:
|
try:
|
||||||
if length == 0:
|
if length == 0:
|
||||||
return ""
|
return ""
|
||||||
|
@@ -830,6 +830,160 @@ class TestHttpd(_TestBase):
|
|||||||
fd.close()
|
fd.close()
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
|
def test_024b_expect_100_continue_with_headers_multiple_chunked(self):
|
||||||
|
def wsgi_app(environ, start_response):
|
||||||
|
environ['wsgi.input'].set_hundred_continue_response_headers(
|
||||||
|
[('Hundred-Continue-Header-1', 'H1'),
|
||||||
|
('Hundred-Continue-Header-2', 'H2')])
|
||||||
|
text = environ['wsgi.input'].read()
|
||||||
|
|
||||||
|
environ['wsgi.input'].set_hundred_continue_response_headers(
|
||||||
|
[('Hundred-Continue-Header-3', 'H3')])
|
||||||
|
environ['wsgi.input'].send_hundred_continue_response()
|
||||||
|
|
||||||
|
text += environ['wsgi.input'].read()
|
||||||
|
|
||||||
|
start_response('200 OK', [('Content-Length', str(len(text)))])
|
||||||
|
return [text]
|
||||||
|
self.site.application = wsgi_app
|
||||||
|
sock = eventlet.connect(('localhost', self.port))
|
||||||
|
fd = sock.makefile('rwb')
|
||||||
|
fd.write(b'PUT /a HTTP/1.1\r\n'
|
||||||
|
b'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
b'Transfer-Encoding: chunked\r\n'
|
||||||
|
b'Expect: 100-continue\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect 1st 100-continue response
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
|
||||||
|
headers = dict((k, v) for k, v in (h.split(b': ', 1)
|
||||||
|
for h in header_lines[1:]))
|
||||||
|
assert b'Hundred-Continue-Header-1' in headers
|
||||||
|
assert b'Hundred-Continue-Header-2' in headers
|
||||||
|
self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1'])
|
||||||
|
self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2'])
|
||||||
|
|
||||||
|
# Send message 1
|
||||||
|
fd.write(b'5\r\nfirst\r\n8\r\n message\r\n0\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect a 2nd 100-continue response
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
|
||||||
|
headers = dict((k, v) for k, v in (h.split(b': ', 1)
|
||||||
|
for h in header_lines[1:]))
|
||||||
|
assert b'Hundred-Continue-Header-3' in headers
|
||||||
|
self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3'])
|
||||||
|
|
||||||
|
# Send message 2
|
||||||
|
fd.write(b'8\r\n, second\r\n8\r\n message\r\n0\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect final 200-OK
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
|
||||||
|
|
||||||
|
self.assertEqual(fd.read(29), b'first message, second message')
|
||||||
|
fd.close()
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
def test_024c_expect_100_continue_with_headers_multiple_nonchunked(self):
|
||||||
|
def wsgi_app(environ, start_response):
|
||||||
|
|
||||||
|
environ['wsgi.input'].set_hundred_continue_response_headers(
|
||||||
|
[('Hundred-Continue-Header-1', 'H1'),
|
||||||
|
('Hundred-Continue-Header-2', 'H2')])
|
||||||
|
text = environ['wsgi.input'].read(13)
|
||||||
|
|
||||||
|
environ['wsgi.input'].set_hundred_continue_response_headers(
|
||||||
|
[('Hundred-Continue-Header-3', 'H3')])
|
||||||
|
environ['wsgi.input'].send_hundred_continue_response()
|
||||||
|
|
||||||
|
text += environ['wsgi.input'].read(16)
|
||||||
|
|
||||||
|
start_response('200 OK', [('Content-Length', str(len(text)))])
|
||||||
|
return [text]
|
||||||
|
|
||||||
|
self.site.application = wsgi_app
|
||||||
|
sock = eventlet.connect(('localhost', self.port))
|
||||||
|
fd = sock.makefile('rwb')
|
||||||
|
fd.write(b'PUT /a HTTP/1.1\r\n'
|
||||||
|
b'Host: localhost\r\nConnection: close\r\n'
|
||||||
|
b'Content-Length: 29\r\n'
|
||||||
|
b'Expect: 100-continue\r\n\r\n')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect 1st 100-continue response
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
|
||||||
|
headers = dict((k, v) for k, v in (h.split(b': ', 1)
|
||||||
|
for h in header_lines[1:]))
|
||||||
|
assert b'Hundred-Continue-Header-1' in headers
|
||||||
|
assert b'Hundred-Continue-Header-2' in headers
|
||||||
|
self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1'])
|
||||||
|
self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2'])
|
||||||
|
|
||||||
|
# Send message 1
|
||||||
|
fd.write(b'first message')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect a 2nd 100-continue response
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
|
||||||
|
headers = dict((k, v) for k, v in (h.split(b': ', 1)
|
||||||
|
for h in header_lines[1:]))
|
||||||
|
assert b'Hundred-Continue-Header-3' in headers
|
||||||
|
self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3'])
|
||||||
|
|
||||||
|
# Send message 2
|
||||||
|
fd.write(b', second message\r\n')
|
||||||
|
fd.flush()
|
||||||
|
|
||||||
|
# Expect final 200-OK
|
||||||
|
header_lines = []
|
||||||
|
while True:
|
||||||
|
line = fd.readline()
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header_lines.append(line.strip())
|
||||||
|
assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
|
||||||
|
|
||||||
|
self.assertEqual(fd.read(29), b'first message, second message')
|
||||||
|
fd.close()
|
||||||
|
sock.close()
|
||||||
|
|
||||||
def test_025_accept_errors(self):
|
def test_025_accept_errors(self):
|
||||||
debug.hub_exceptions(True)
|
debug.hub_exceptions(True)
|
||||||
listener = greensocket.socket()
|
listener = greensocket.socket()
|
||||||
|
Reference in New Issue
Block a user