HTTP1 header formatting moved to headers.format_headers and rewritten. (#1669)

* HTTP1 header formatting moved to headers.format_headers and rewritten.

- New implementation is one line of code and twice faster than the old one.
- Whole header block encoded to UTF-8 in one pass.
- No longer supports custom encode method on header values.
- Cookie objects now have __str__ in addition to encode, to work with this.

* Add an import missed in merge.
This commit is contained in:
L. Kärkkäinen 2019-12-24 01:30:45 +02:00 committed by Stephen Sadowski
parent fccbc1adc4
commit 0a25868a86
3 changed files with 18 additions and 16 deletions

View File

@ -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)

View File

@ -1,9 +1,10 @@
import re
from typing import Dict, Iterable, List, Optional, Tuple, Union
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from urllib.parse import unquote
HeaderIterable = Iterable[Tuple[str, Any]] # Values convertible to str
Options = Dict[str, Union[int, str]] # key=value fields in various headers
OptionsIterable = Iterable[Tuple[str, str]] # May contain duplicate keys
@ -170,3 +171,12 @@ def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]:
return None, None
host, port = m.groups()
return host.lower(), int(port) if port is not None else None
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()

View File

@ -7,6 +7,7 @@ from aiofiles import open as open_async # type: ignore
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):