Fix support for generator-based WSGI apps (#887)

* Handle generator WSGI app

* Lint code

* Add type annotations

* Add more tests

* Refactor test to use application_factory

* Remove content length as it's misleading

* Add test for WSGI generator

* Add test for empty generator

* Remove previous tests

* Move docstring to a comment

* Fix whitespace

* Fix name of function

Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>

* Update tests/test_wsgi.py

Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>

* Update tests/test_wsgi.py

Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>

* Update httpx/_dispatch/wsgi.py

Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
This commit is contained in:
Ed Singleton 2020-03-29 12:13:01 +01:00 committed by GitHub
parent 1c9b852783
commit 94323f98ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 12 deletions

View File

@ -1,4 +1,5 @@
import io
import itertools
import typing
from .._config import TimeoutTypes
@ -7,6 +8,14 @@ from .._models import Request, Response
from .base import SyncDispatcher
def _skip_leading_empty_chunks(body: typing.Iterable) -> typing.Iterable:
body = iter(body)
for chunk in body:
if chunk:
return itertools.chain([chunk], body)
return []
class WSGIDispatch(SyncDispatcher):
"""
A custom SyncDispatcher that handles sending requests directly to an WSGI app.
@ -88,6 +97,9 @@ class WSGIDispatch(SyncDispatcher):
seen_exc_info = exc_info
result = self.app(environ, start_response)
# This is needed because the status returned by start_response
# shouldn't be used until the first non-empty chunk has been served.
result = _skip_leading_empty_chunks(result)
assert seen_status is not None
assert seen_response_headers is not None

View File

@ -5,18 +5,20 @@ import pytest
import httpx
def hello_world(environ, start_response):
status = "200 OK"
output = b"Hello, World!"
def application_factory(output):
def application(environ, start_response):
status = "200 OK"
response_headers = [
("Content-type", "text/plain"),
("Content-Length", str(len(output))),
]
response_headers = [
("Content-type", "text/plain"),
]
start_response(status, response_headers)
start_response(status, response_headers)
return [output]
for item in output:
yield item
return application
def echo_body(environ, start_response):
@ -25,7 +27,6 @@ def echo_body(environ, start_response):
response_headers = [
("Content-type", "text/plain"),
("Content-Length", str(len(output))),
]
start_response(status, response_headers)
@ -56,7 +57,6 @@ def raise_exc(environ, start_response):
response_headers = [
("Content-type", "text/plain"),
("Content-Length", str(len(output))),
]
try:
@ -69,7 +69,7 @@ def raise_exc(environ, start_response):
def test_wsgi():
client = httpx.Client(app=hello_world)
client = httpx.Client(app=application_factory([b"Hello, World!"]))
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Hello, World!"
@ -93,3 +93,19 @@ def test_wsgi_exc():
client = httpx.Client(app=raise_exc)
with pytest.raises(ValueError):
client.get("http://www.example.org/")
def test_wsgi_generator():
output = [b"", b"", b"Some content", b" and more content"]
client = httpx.Client(app=application_factory(output))
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Some content and more content"
def test_wsgi_generator_empty():
output = [b"", b"", b"", b""]
client = httpx.Client(app=application_factory(output))
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == ""