Backport stream header fix (#1959)
Resolve headers as body in ASGI mode * Bump version to 19.12.3 * Update multidict==5.0.0
This commit is contained in:
		| @@ -1 +1 @@ | |||||||
| __version__ = "19.12.2" | __version__ = "19.12.3" | ||||||
|   | |||||||
| @@ -347,6 +347,8 @@ class ASGIApp: | |||||||
|                 if name not in (b"Set-Cookie",) |                 if name not in (b"Set-Cookie",) | ||||||
|             ] |             ] | ||||||
|  |  | ||||||
|  |         response.asgi = True | ||||||
|  |  | ||||||
|         if "content-length" not in response.headers and not isinstance( |         if "content-length" not in response.headers and not isinstance( | ||||||
|             response, StreamingHTTPResponse |             response, StreamingHTTPResponse | ||||||
|         ): |         ): | ||||||
|   | |||||||
| @@ -22,6 +22,9 @@ except ImportError: | |||||||
|  |  | ||||||
|  |  | ||||||
| class BaseHTTPResponse: | class BaseHTTPResponse: | ||||||
|  |     def __init__(self): | ||||||
|  |         self.asgi = False | ||||||
|  |  | ||||||
|     def _encode_body(self, data): |     def _encode_body(self, data): | ||||||
|         try: |         try: | ||||||
|             # Try to encode it regularly |             # Try to encode it regularly | ||||||
| @@ -59,6 +62,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|         content_type="text/plain", |         content_type="text/plain", | ||||||
|         chunked=True, |         chunked=True, | ||||||
|     ): |     ): | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|         self.content_type = content_type |         self.content_type = content_type | ||||||
|         self.streaming_fn = streaming_fn |         self.streaming_fn = streaming_fn | ||||||
|         self.status = status |         self.status = status | ||||||
| @@ -94,6 +99,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|             keep_alive=keep_alive, |             keep_alive=keep_alive, | ||||||
|             keep_alive_timeout=keep_alive_timeout, |             keep_alive_timeout=keep_alive_timeout, | ||||||
|         ) |         ) | ||||||
|  |         if not getattr(self, "asgi", False): | ||||||
|             await self.protocol.push_data(headers) |             await self.protocol.push_data(headers) | ||||||
|             await self.protocol.drain() |             await self.protocol.drain() | ||||||
|         await self.streaming_fn(self) |         await self.streaming_fn(self) | ||||||
| @@ -145,6 +151,8 @@ class HTTPResponse(BaseHTTPResponse): | |||||||
|         content_type=None, |         content_type=None, | ||||||
|         body_bytes=b"", |         body_bytes=b"", | ||||||
|     ): |     ): | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|         self.content_type = content_type |         self.content_type = content_type | ||||||
|  |  | ||||||
|         if body is not None: |         if body is not None: | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								setup.py
									
									
									
									
									
								
							| @@ -5,7 +5,6 @@ import codecs | |||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from distutils.util import strtobool | from distutils.util import strtobool | ||||||
|  |  | ||||||
| from setuptools import setup | from setuptools import setup | ||||||
| @@ -39,9 +38,7 @@ def open_local(paths, mode="r", encoding="utf8"): | |||||||
|  |  | ||||||
| with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | ||||||
|     try: |     try: | ||||||
|         version = re.findall( |         version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0] | ||||||
|             r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M |  | ||||||
|         )[0] |  | ||||||
|     except IndexError: |     except IndexError: | ||||||
|         raise RuntimeError("Unable to determine version.") |         raise RuntimeError("Unable to determine version.") | ||||||
|  |  | ||||||
| @@ -71,9 +68,7 @@ setup_kwargs = { | |||||||
|     ], |     ], | ||||||
| } | } | ||||||
|  |  | ||||||
| env_dependency = ( | env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||||
|     '; sys_platform != "win32" ' 'and implementation_name == "cpython"' |  | ||||||
| ) |  | ||||||
| ujson = "ujson>=1.35" + env_dependency | ujson = "ujson>=1.35" + env_dependency | ||||||
| uvloop = "uvloop>=0.5.3" + env_dependency | uvloop = "uvloop>=0.5.3" + env_dependency | ||||||
|  |  | ||||||
| @@ -83,13 +78,13 @@ requirements = [ | |||||||
|     ujson, |     ujson, | ||||||
|     "aiofiles>=0.3.0", |     "aiofiles>=0.3.0", | ||||||
|     "websockets>=7.0,<9.0", |     "websockets>=7.0,<9.0", | ||||||
|     "multidict>=4.0,<5.0", |     "multidict==5.0.0", | ||||||
|     "httpx==0.9.3", |     "httpx==0.9.3", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| tests_require = [ | tests_require = [ | ||||||
|     "pytest==5.2.1", |     "pytest==5.2.1", | ||||||
|     "multidict>=4.0,<5.0", |     "multidict==5.0.0", | ||||||
|     "gunicorn", |     "gunicorn", | ||||||
|     "pytest-cov", |     "pytest-cov", | ||||||
|     "httpcore==0.3.0", |     "httpcore==0.3.0", | ||||||
|   | |||||||
| @@ -232,6 +232,12 @@ def test_chunked_streaming_returns_correct_content(streaming_app): | |||||||
|     assert response.text == "foo,bar" |     assert response.text == "foo,bar" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): | ||||||
|  |     request, response = await streaming_app.asgi_client.get("/") | ||||||
|  |     assert response.text == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||||
|     request, response = non_chunked_streaming_app.test_client.get("/") |     request, response = non_chunked_streaming_app.test_client.get("/") | ||||||
|     assert "Transfer-Encoding" not in response.headers |     assert "Transfer-Encoding" not in response.headers | ||||||
| @@ -239,6 +245,16 @@ def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | |||||||
|     assert response.headers["Content-Length"] == "7" |     assert response.headers["Content-Length"] == "7" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_non_chunked_streaming_adds_correct_headers_asgi( | ||||||
|  |     non_chunked_streaming_app, | ||||||
|  | ): | ||||||
|  |     request, response = await non_chunked_streaming_app.asgi_client.get("/") | ||||||
|  |     assert "Transfer-Encoding" not in response.headers | ||||||
|  |     assert response.headers["Content-Type"] == "text/csv" | ||||||
|  |     assert response.headers["Content-Length"] == "7" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_non_chunked_streaming_returns_correct_content( | def test_non_chunked_streaming_returns_correct_content( | ||||||
|     non_chunked_streaming_app, |     non_chunked_streaming_app, | ||||||
| ): | ): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Adam Hopkins
					Adam Hopkins