summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Merritt <sam@swiftstack.com>2015-10-08 16:05:57 -0700
committerSamuel Merritt <sam@swiftstack.com>2015-10-08 17:58:23 -0700
commitf1e4d33d0269d74e2c3a8b26a3198720da588f73 (patch)
tree1a1c445f678b673f4107057db5490fa747592a16
parent5a4a7ead2435025e2c727c902d7e985be0204417 (diff)
downloadeventlet-f1e4d33d0269d74e2c3a8b26a3198720da588f73.tar.gz
wsgi: suppress output of 0-byte chunks
Under certain conditions, if the WSGI iterable yields an empty string, it can cause the response to be terminated. The conditions are as follows: * no minimum chunk size (eventlet.wsgi.server() was called with minimum_chunk_size=0, or env['eventlet.minimum_write_chunk_size'] = 0) * chunked transfer-encoding on the response In this case, if the WSGI iterable yields an empty string, then eventlet.wsgi obligingly turns that into "0\r\n\r\n" and writes that to the socket. This, of course, terminates the response as far as the client is concerned. However, eventlet.wsgi doesn't notice, so it'll happily keep calling next(app_iter) and sending the chunks. If Connection: keep-alive is set, the client might then send the next request, read some chunked response body, fail to parse it, and then disconnect. In other cases, either the 0-byte chunk is saved in the chunk buffer until the minimum chunk size is met, or 0 bytes get written to the socket. Those are both no-ops. This commit discards 0-byte chunks from the WSGI iterable.
-rw-r--r--eventlet/wsgi.py3
-rw-r--r--tests/wsgi_test.py15
2 files changed, 17 insertions, 1 deletions
diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py
index c0f1f24..e44f45a 100644
--- a/eventlet/wsgi.py
+++ b/eventlet/wsgi.py
@@ -485,7 +485,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
towrite.append(data)
towrite_size += len(data)
if towrite_size >= minimum_write_chunk_size:
- write(b''.join(towrite))
+ if towrite_size > 0:
+ write(b''.join(towrite))
towrite = []
just_written_size = towrite_size
towrite_size = 0
diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py
index 00a5c45..ef443e6 100644
--- a/tests/wsgi_test.py
+++ b/tests/wsgi_test.py
@@ -1048,6 +1048,21 @@ class TestHttpd(_TestBase):
self.assertNotEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
self.assertEqual(result.body, b"thisischunked")
+ def test_chunked_response_when_app_yields_empty_string(self):
+ def empty_string_chunked_app(env, start_response):
+ env['eventlet.minimum_write_chunk_size'] = 0 # no buffering
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ return iter([b"stuff", b"", b"more stuff"])
+
+ self.site.application = empty_string_chunked_app
+ sock = eventlet.connect(('localhost', self.port))
+
+ sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
+
+ result = read_http(sock)
+ self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
+ self.assertEqual(result.body, b"5\r\nstuff\r\na\r\nmore stuff\r\n0\r\n\r\n")
+
def test_minimum_chunk_size_parameter_leaves_httpprotocol_class_member_intact(self):
start_size = wsgi.HttpProtocol.minimum_chunk_size