Wait for a non-empty chunk in WSGIContext._app_call

We're functioning as a WSGI server here, so this bit from PEP-3333 seems
to apply:

> The start_response callable must not actually transmit the response
> headers. Instead, it must store them for the server or gateway to
> transmit only after the first iteration of the application return
> value that yields a non-empty bytestrin ... . In other words, response
> headers must not be sent until there is actual body data available, or
> until the application's returned iterable is exhausted.

Plus, it mirrors what swob.Request.call_application does.

Change-Id: I1e8501f8ce91ea912780db64fee1c56bef809a98
This commit is contained in:
Tim Burke
2016-08-12 05:46:33 +00:00
parent 1f90e8a4ac
commit f7a820ed3a
2 changed files with 8 additions and 11 deletions

View File

@@ -42,7 +42,7 @@ from swift.common.swob import Request
from swift.common.utils import capture_stdio, disable_fallocate, \ from swift.common.utils import capture_stdio, disable_fallocate, \
drop_privileges, get_logger, NullLogger, config_true_value, \ drop_privileges, get_logger, NullLogger, config_true_value, \
validate_configuration, get_hub, config_auto_int_value, \ validate_configuration, get_hub, config_auto_int_value, \
CloseableChain reiterate
# Set maximum line size of message headers to be accepted. # Set maximum line size of message headers to be accepted.
wsgi.MAX_HEADER_LINE = constraints.MAX_HEADER_SIZE wsgi.MAX_HEADER_LINE = constraints.MAX_HEADER_SIZE
@@ -1053,16 +1053,11 @@ class WSGIContext(object):
self._response_headers = None self._response_headers = None
self._response_exc_info = None self._response_exc_info = None
resp = self.app(env, self._start_response) resp = self.app(env, self._start_response)
# if start_response has been called, just return the iter # if start_response has not been called, iterate until we've got a
if self._response_status is not None: # non-empty chunk, by which time the app *should* have called it
return resp if self._response_status is None:
resp = iter(resp) resp = reiterate(resp)
try: return resp
first_chunk = next(resp)
except StopIteration:
return iter([])
else: # We got a first_chunk
return CloseableChain([first_chunk], resp)
def _get_status_int(self): def _get_status_int(self):
""" """

View File

@@ -1242,6 +1242,8 @@ class TestWSGIContext(unittest.TestCase):
def test_app_iter_is_closable(self): def test_app_iter_is_closable(self):
def app(env, start_response): def app(env, start_response):
yield ''
yield ''
start_response('200 OK', [('Content-Length', '25')]) start_response('200 OK', [('Content-Length', '25')])
yield 'aaaaa' yield 'aaaaa'
yield 'bbbbb' yield 'bbbbb'