Refactoring and cleanup.
This commit is contained in:
		| @@ -272,84 +272,20 @@ class ASGIApp: | ||||
|                 yield data | ||||
|  | ||||
|     def respond(self, response): | ||||
|         headers: List[Tuple[bytes, bytes]] = [] | ||||
|         cookies: Dict[str, str] = {} | ||||
|         try: | ||||
|             cookies = { | ||||
|                 v.key: v | ||||
|                 for _, v in list( | ||||
|                     filter( | ||||
|                         lambda item: item[0].lower() == "set-cookie", | ||||
|                         response.headers.items(), | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|             headers += [ | ||||
|                 (str(name).encode("latin-1"), str(value).encode("latin-1")) | ||||
|                 for name, value in response.headers.items() | ||||
|                 if name.lower() not in ["set-cookie"] | ||||
|             ] | ||||
|         except AttributeError: | ||||
|             logger.error( | ||||
|                 "Invalid response object for url %s, " | ||||
|                 "Expected Type: HTTPResponse, Actual Type: %s", | ||||
|                 self.request.url, | ||||
|                 type(response), | ||||
|             ) | ||||
|             exception = ServerError("Invalid response type") | ||||
|             response = self.sanic_app.error_handler.response( | ||||
|                 self.request, exception | ||||
|             ) | ||||
|             headers = [ | ||||
|                 (str(name).encode("latin-1"), str(value).encode("latin-1")) | ||||
|                 for name, value in response.headers.items() | ||||
|                 if name not in (b"Set-Cookie",) | ||||
|             ] | ||||
|         response.stream, self.response = self, response | ||||
|         return response | ||||
|  | ||||
|         if "content-length" not in response.headers and not isinstance( | ||||
|             response, StreamingHTTPResponse | ||||
|         ): | ||||
|             headers += [ | ||||
|                 (b"content-length", str(len(response.body)).encode("latin-1")) | ||||
|             ] | ||||
|  | ||||
|         if "content-type" not in response.headers: | ||||
|             headers += [ | ||||
|                 (b"content-type", str(response.content_type).encode("latin-1")) | ||||
|             ] | ||||
|  | ||||
|         if response.cookies: | ||||
|             cookies.update( | ||||
|                 { | ||||
|                     v.key: v | ||||
|                     for _, v in response.cookies.items() | ||||
|                     if v.key not in cookies.keys() | ||||
|                 } | ||||
|             ) | ||||
|  | ||||
|         headers += [ | ||||
|             (b"set-cookie", cookie.encode("utf-8")) | ||||
|             for k, cookie in cookies.items() | ||||
|         ] | ||||
|         self.response_start = { | ||||
|             "type": "http.response.start", | ||||
|             "status": response.status, | ||||
|             "headers": headers, | ||||
|         } | ||||
|         self.response_body = response.body | ||||
|         return self | ||||
|  | ||||
|     async def send(self, data=None, end_stream=None): | ||||
|         if data is None is end_stream: | ||||
|             end_stream = True | ||||
|         if self.response_start: | ||||
|             await self.transport.send(self.response_start) | ||||
|             self.response_start = None | ||||
|             if self.response_body: | ||||
|                 data = ( | ||||
|                     self.response_body + data if data else self.response_body | ||||
|                 ) | ||||
|                 self.response_body = None | ||||
|     async def send(self, data, end_stream): | ||||
|         if self.response: | ||||
|             response, self.response = self.response, None | ||||
|             await self.transport.send({ | ||||
|                 "type": "http.response.start", | ||||
|                 "status": response.status, | ||||
|                 "headers": response.full_headers, | ||||
|             }) | ||||
|             response_body = getattr(response, "body", None) | ||||
|             if response_body: | ||||
|                 data = response_body + data if data else response_body | ||||
|         await self.transport.send( | ||||
|             { | ||||
|                 "type": "http.response.body", | ||||
|   | ||||
| @@ -77,7 +77,7 @@ class Http: | ||||
|                 if self.stage is Stage.HANDLER: | ||||
|                     raise ServerError("Handler produced no response") | ||||
|                 if self.stage is Stage.RESPONSE: | ||||
|                     await self.send(end_stream=True) | ||||
|                     await self.response.send(end_stream=True) | ||||
|             except CancelledError: | ||||
|                 # Write an appropriate response before exiting | ||||
|                 e = self.exception or ServiceUnavailable(f"Cancelled") | ||||
| @@ -178,13 +178,15 @@ class Http: | ||||
|     def http1_response_header(self, data, end_stream) -> bytes: | ||||
|         res = self.response | ||||
|         # Compatibility with simple response body | ||||
|         if not data and res.body: | ||||
|         if not data and getattr(res, "body", None): | ||||
|             data, end_stream = res.body, True | ||||
|         size = len(data) | ||||
|         status = res.status | ||||
|         headers = res.headers | ||||
|         if res.content_type and "content-type" not in headers: | ||||
|             headers["content-type"] = res.content_type | ||||
|         status = res.status | ||||
|         if not isinstance(status, int) or status < 200: | ||||
|             raise RuntimeError(f"Invalid response status {status!r}") | ||||
|         # Not Modified, Precondition Failed | ||||
|         if status in (304, 412): | ||||
|             headers = remove_entity_headers(headers) | ||||
| @@ -374,19 +376,10 @@ class Http: | ||||
|         if self.stage is not Stage.HANDLER: | ||||
|             self.stage = Stage.FAILED | ||||
|             raise RuntimeError("Response already started") | ||||
|         if not isinstance(response.status, int) or response.status < 200: | ||||
|             raise RuntimeError(f"Invalid response status {response.status!r}") | ||||
|         self.response = response | ||||
|         return self | ||||
|         self.response, response.stream = response, self | ||||
|         return response | ||||
|  | ||||
|     async def send(self, data=None, end_stream=None): | ||||
|         """Send any pending response headers and the given data as body. | ||||
|          :param data: str or bytes to be written | ||||
|          :end_stream: whether to close the stream after this block | ||||
|         """ | ||||
|         if data is None and end_stream is None: | ||||
|             end_stream = True | ||||
|         data = data.encode() if hasattr(data, "encode") else data or b"" | ||||
|     async def send(self, data, end_stream): | ||||
|         data = self.response_func(data, end_stream) | ||||
|         if not data: | ||||
|             return | ||||
|   | ||||
| @@ -106,15 +106,17 @@ class Request: | ||||
|         ) | ||||
|  | ||||
|     def respond( | ||||
|         self, status=200, headers=None, content_type=DEFAULT_HTTP_CONTENT_TYPE | ||||
|         self, response=None, *, status=200, headers=None, content_type=None | ||||
|     ): | ||||
|         return self.stream.respond( | ||||
|             status | ||||
|             if isinstance(status, HTTPResponse) | ||||
|             else HTTPResponse( | ||||
|                 status=status, headers=headers, content_type=content_type, | ||||
|         # This logic of determining which response to use is subject to change | ||||
|         if response is None: | ||||
|             response = self.stream.response or HTTPResponse( | ||||
|                 status=status, | ||||
|                 headers=headers, | ||||
|                 content_type=content_type, | ||||
|             ) | ||||
|         ) | ||||
|         # Connect the response and return it | ||||
|         return self.stream.respond(response) | ||||
|  | ||||
|     async def receive_body(self): | ||||
|         self.body = b"".join([data async for data in self.stream]) | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class BaseHTTPResponse: | ||||
|         return data.encode() if hasattr(data, "encode") else data | ||||
|  | ||||
|     def _parse_headers(self): | ||||
|         return format_http1(self.headers.items()) | ||||
|         return format_http1(self.full_headers) | ||||
|  | ||||
|     @property | ||||
|     def cookies(self): | ||||
| @@ -34,6 +34,43 @@ class BaseHTTPResponse: | ||||
|             self._cookies = CookieJar(self.headers) | ||||
|         return self._cookies | ||||
|  | ||||
|     @property | ||||
|     def full_headers(self): | ||||
|         """Obtain an encoded tuple of headers for a response to be sent.""" | ||||
|         headers = [] | ||||
|         cookies = {} | ||||
|         if self.content_type and not "content-type" in self.headers: | ||||
|             headers += (b"content-type", self.content_type.encode()), | ||||
|         for name, value in self.headers.items(): | ||||
|             name = f"{name}" | ||||
|             if name.lower() == "set-cookie": | ||||
|                 cookies[value.key] = value | ||||
|             else: | ||||
|                 headers += (name.encode("ascii"), f"{value}".encode()), | ||||
|  | ||||
|         if self.cookies: | ||||
|             cookies.update( | ||||
|                 (v.key, v) | ||||
|                 for v in self.cookies.values() | ||||
|                 if v.key not in cookies | ||||
|             ) | ||||
|  | ||||
|         headers += [ | ||||
|             (b"set-cookie", cookie.encode("utf-8")) | ||||
|             for k, cookie in cookies.items() | ||||
|         ] | ||||
|         return headers | ||||
|  | ||||
|     async def send(self, data=None, end_stream=None): | ||||
|         """Send any pending response headers and the given data as body. | ||||
|          :param data: str or bytes to be written | ||||
|          :end_stream: whether to close the stream after this block | ||||
|         """ | ||||
|         if data is None and end_stream is None: | ||||
|             end_stream = True | ||||
|         data = data.encode() if hasattr(data, "encode") else data or b"" | ||||
|         await self.stream.send(data, end_stream=end_stream) | ||||
|  | ||||
|  | ||||
| class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|     __slots__ = ( | ||||
| @@ -42,9 +79,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         "status", | ||||
|         "content_type", | ||||
|         "headers", | ||||
|         "chunked", | ||||
|         "_cookies", | ||||
|         "send", | ||||
|     ) | ||||
|  | ||||
|     def __init__( | ||||
| @@ -53,13 +88,12 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         status=200, | ||||
|         headers=None, | ||||
|         content_type="text/plain; charset=utf-8", | ||||
|         chunked=True, | ||||
|         chunked="deprecated", | ||||
|     ): | ||||
|         self.content_type = content_type | ||||
|         self.streaming_fn = streaming_fn | ||||
|         self.status = status | ||||
|         self.headers = Header(headers or {}) | ||||
|         self.chunked = chunked | ||||
|         self._cookies = None | ||||
|  | ||||
|     async def write(self, data): | ||||
| @@ -70,9 +104,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         await self.send(self._encode_body(data)) | ||||
|  | ||||
|     async def stream(self, request): | ||||
|         self.send = request.respond( | ||||
|             self.status, self.headers, self.content_type, | ||||
|         ).send | ||||
|         request.respond(self) | ||||
|         await self.streaming_fn(self) | ||||
|         await self.send(end_stream=True) | ||||
|  | ||||
| @@ -94,16 +126,6 @@ class HTTPResponse(BaseHTTPResponse): | ||||
|         self.headers = Header(headers or {}) | ||||
|         self._cookies = None | ||||
|  | ||||
|     def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): | ||||
|         body = b"" | ||||
|         if has_message_body(self.status): | ||||
|             body = self.body | ||||
|             self.headers["Content-Length"] = self.headers.get( | ||||
|                 "Content-Length", len(self.body) | ||||
|             ) | ||||
|  | ||||
|         return self.get_headers(version, keep_alive, keep_alive_timeout, body) | ||||
|  | ||||
|     @property | ||||
|     def cookies(self): | ||||
|         if self._cookies is None: | ||||
| @@ -265,7 +287,7 @@ async def file_stream( | ||||
|     mime_type=None, | ||||
|     headers=None, | ||||
|     filename=None, | ||||
|     chunked=True, | ||||
|     chunked="deprecated", | ||||
|     _range=None, | ||||
| ): | ||||
|     """Return a streaming response object with file data. | ||||
| @@ -314,7 +336,6 @@ async def file_stream( | ||||
|         status=status, | ||||
|         headers=headers, | ||||
|         content_type=mime_type, | ||||
|         chunked=chunked, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @@ -323,7 +344,7 @@ def stream( | ||||
|     status=200, | ||||
|     headers=None, | ||||
|     content_type="text/plain; charset=utf-8", | ||||
|     chunked=True, | ||||
|     chunked="deprecated", | ||||
| ): | ||||
|     """Accepts an coroutine `streaming_fn` which can be used to | ||||
|     write chunks to a streaming response. Returns a `StreamingHTTPResponse`. | ||||
| @@ -349,7 +370,6 @@ def stream( | ||||
|         headers=headers, | ||||
|         content_type=content_type, | ||||
|         status=status, | ||||
|         chunked=chunked, | ||||
|     ) | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 L. Kärkkäinen
					L. Kärkkäinen