From 7dc683913f6da0c252d8a08b717fb23c6f578659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=2E=20K=C3=A4rkk=C3=A4inen?= Date: Thu, 5 Sep 2019 11:43:23 +0300 Subject: [PATCH] format_http1_response --- sanic/headers.py | 20 ++++++++++++++++ sanic/response.py | 60 ++++++++++++----------------------------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/sanic/headers.py b/sanic/headers.py index ec3dc237..cc9a0dcf 100644 --- a/sanic/headers.py +++ b/sanic/headers.py @@ -3,6 +3,8 @@ import re from typing import Any, Dict, Iterable, Optional, Tuple from urllib.parse import unquote +from sanic.helpers import STATUS_CODES + HeaderIterable = Iterable[Tuple[str, Any]] # Values convertible to str Options = Dict[str, str] # key=value fields in various headers @@ -175,3 +177,21 @@ def format_http1(headers: HeaderIterable) -> bytes: - Values are converted into strings if necessary. """ return "".join(f"{name}: {val}\r\n" for name, val in headers).encode() + + +def format_http1_response( + status: int, headers: HeaderIterable, body=b"" +) -> bytes: + """Format a full HTTP/1.1 response. + + - If `body` is included, content-length must be specified in headers. + """ + headers = format_http1(headers) + if status == 200: + return b"HTTP/1.1 200 OK\r\n%b\r\n%b" % (headers, body) + return b"HTTP/1.1 %d %b\r\n%b\r\n%b" % ( + status, + STATUS_CODES.get(status, b"UNKNOWN"), + headers, + body, + ) diff --git a/sanic/response.py b/sanic/response.py index 83eacd53..92362c3d 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -7,8 +7,8 @@ from aiofiles import open as open_async from sanic.compat import Header from sanic.cookies import CookieJar -from sanic.headers import format_http1 -from sanic.helpers import STATUS_CODES, has_message_body, remove_entity_headers +from sanic.headers import format_http1, format_http1_response +from sanic.helpers import has_message_body, remove_entity_headers try: @@ -104,33 +104,17 @@ class StreamingHTTPResponse(BaseHTTPResponse): def get_headers( self, version="1.1", keep_alive=False, keep_alive_timeout=None ): - # This is all returned in a kind-of funky way - # We tried to make this as fast as possible in pure python - timeout_header = b"" + if "Content-Type" not in self.headers: + self.headers["Content-Type"] = self.content_type + if keep_alive and keep_alive_timeout is not None: - timeout_header = b"Keep-Alive: %d\r\n" % keep_alive_timeout + self.headers["Keep-Alive"] = keep_alive_timeout if self.chunked and version == "1.1": self.headers["Transfer-Encoding"] = "chunked" self.headers.pop("Content-Length", None) - self.headers["Content-Type"] = self.headers.get( - "Content-Type", self.content_type - ) - headers = self._parse_headers() - - if self.status == 200: - status = b"OK" - else: - status = STATUS_CODES.get(self.status) - - return (b"HTTP/%b %d %b\r\n" b"%b" b"%b\r\n") % ( - version.encode(), - self.status, - status, - timeout_header, - headers, - ) + return format_http1_response(self.status, self.headers.items()) class HTTPResponse(BaseHTTPResponse): @@ -156,11 +140,8 @@ class HTTPResponse(BaseHTTPResponse): self._cookies = None def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): - # This is all returned in a kind-of funky way - # We tried to make this as fast as possible in pure python - timeout_header = b"" - if keep_alive and keep_alive_timeout is not None: - timeout_header = b"Keep-Alive: %d\r\n" % keep_alive_timeout + if "Content-Type" not in self.headers: + self.headers["Content-Type"] = self.content_type body = b"" if has_message_body(self.status): @@ -176,24 +157,13 @@ class HTTPResponse(BaseHTTPResponse): if self.status in (304, 412): self.headers = remove_entity_headers(self.headers) - headers = self._parse_headers() + if keep_alive and keep_alive_timeout is not None: + self.headers["Connection"] = "keep-alive" + self.headers["Keep-Alive"] = keep_alive_timeout + elif not keep_alive: + self.headers["Connection"] = "close" - if self.status == 200: - status = b"OK" - else: - status = STATUS_CODES.get(self.status, b"UNKNOWN RESPONSE") - - return ( - b"HTTP/%b %d %b\r\n" b"Connection: %b\r\n" b"%b" b"%b\r\n" b"%b" - ) % ( - version.encode(), - self.status, - status, - b"keep-alive" if keep_alive else b"close", - timeout_header, - headers, - body, - ) + return format_http1_response(self.status, self.headers.items(), body) @property def cookies(self):