diff --git a/sanic/cookies.py b/sanic/cookies.py index 19907945..ed672fba 100644 --- a/sanic/cookies.py +++ b/sanic/cookies.py @@ -130,6 +130,10 @@ class Cookie(dict): :return: Cookie encoded in a codec of choosing. :except: UnicodeEncodeError """ + return str(self).encode(encoding) + + def __str__(self): + """Format as a Set-Cookie header value.""" output = ["%s=%s" % (self.key, _quote(self.value))] for key, value in self.items(): if key == "max-age": @@ -147,4 +151,4 @@ class Cookie(dict): else: output.append("%s=%s" % (self._keys[key], value)) - return "; ".join(output).encode(encoding) + return "; ".join(output) diff --git a/sanic/headers.py b/sanic/headers.py index 6c9fa221..e1ac48b3 100644 --- a/sanic/headers.py +++ b/sanic/headers.py @@ -1,9 +1,9 @@ import re -from typing import Dict, Iterable, Optional, Tuple +from typing import Any, Dict, Iterable, Optional, Tuple from urllib.parse import unquote - +HeaderIterable = Iterable[Tuple[str, Any]] # Values convertible to str Options = Dict[str, str] # key=value fields in various headers OptionsIterable = Iterable[Tuple[str, str]] # May contain duplicate keys @@ -165,3 +165,12 @@ def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]: return None, None host, port = m.groups() return host.lower(), port and int(port) + + +def format_http1(headers: HeaderIterable) -> bytes: + """Convert a headers iterable into HTTP/1 header format. + + - Outputs UTF-8 bytes where each header line ends with \\r\\n. + - Values are converted into strings if necessary. + """ + return "".join(f"{name}: {val}\r\n" for name, val in headers).encode() diff --git a/sanic/response.py b/sanic/response.py index 6f937c95..83eacd53 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -7,6 +7,7 @@ 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 @@ -30,20 +31,7 @@ class BaseHTTPResponse: return str(data).encode() def _parse_headers(self): - headers = b"" - for name, value in self.headers.items(): - try: - headers += b"%b: %b\r\n" % ( - name.encode(), - value.encode("utf-8"), - ) - except AttributeError: - headers += b"%b: %b\r\n" % ( - str(name).encode(), - str(value).encode("utf-8"), - ) - - return headers + return format_http1(self.headers.items()) @property def cookies(self):