Unify response header processing of ASGI and asyncio modes.
This commit is contained in:
parent
d348bb4ff4
commit
5351cda979
|
@ -281,7 +281,7 @@ class ASGIApp:
|
|||
await self.transport.send({
|
||||
"type": "http.response.start",
|
||||
"status": response.status,
|
||||
"headers": response.full_headers,
|
||||
"headers": response.processed_headers,
|
||||
})
|
||||
response_body = getattr(response, "body", None)
|
||||
if response_body:
|
||||
|
|
|
@ -7,6 +7,7 @@ from sanic.helpers import STATUS_CODES
|
|||
|
||||
|
||||
HeaderIterable = Iterable[Tuple[str, Any]] # Values convertible to str
|
||||
HeaderBytesIterable = Iterable[Tuple[bytes, bytes]]
|
||||
Options = Dict[str, Union[int, str]] # key=value fields in various headers
|
||||
OptionsIterable = Iterable[Tuple[str, str]] # May contain duplicate keys
|
||||
|
||||
|
@ -175,26 +176,13 @@ def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]:
|
|||
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()
|
||||
|
||||
|
||||
def format_http1_response(
|
||||
status: int, headers: HeaderIterable, body=b""
|
||||
status: int, headers: HeaderBytesIterable, body=b""
|
||||
) -> bytes:
|
||||
"""Format a full HTTP/1.1 response.
|
||||
|
||||
- If `body` is included, content-length must be specified in headers.
|
||||
"""
|
||||
headerbytes = format_http1(headers)
|
||||
"""Format a full HTTP/1.1 response."""
|
||||
return b"HTTP/1.1 %d %b\r\n%b\r\n%b" % (
|
||||
status,
|
||||
STATUS_CODES.get(status, b"UNKNOWN"),
|
||||
headerbytes,
|
||||
b"".join(b"%b: %b\r\n" % h for h in headers),
|
||||
body,
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ from sanic.exceptions import (
|
|||
ServiceUnavailable,
|
||||
)
|
||||
from sanic.headers import format_http1_response
|
||||
from sanic.helpers import has_message_body, remove_entity_headers
|
||||
from sanic.helpers import has_message_body
|
||||
from sanic.log import access_logger, logger
|
||||
|
||||
|
||||
|
@ -182,14 +182,9 @@ class Http:
|
|||
data, end_stream = res.body, True
|
||||
size = len(data)
|
||||
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)
|
||||
if not has_message_body(status):
|
||||
# Header-only response status
|
||||
self.response_func = None
|
||||
|
@ -227,7 +222,7 @@ class Http:
|
|||
data = b""
|
||||
self.response_func = self.head_response_ignored
|
||||
headers["connection"] = "keep-alive" if self.keep_alive else "close"
|
||||
ret = format_http1_response(status, headers.items(), data)
|
||||
ret = format_http1_response(status, res.processed_headers, data)
|
||||
# Send a 100-continue if expected and not Expectation Failed
|
||||
if self.expecting_continue:
|
||||
self.expecting_continue = False
|
||||
|
|
|
@ -7,8 +7,7 @@ from urllib.parse import quote_plus
|
|||
|
||||
from sanic.compat import Header, open_async
|
||||
from sanic.cookies import CookieJar
|
||||
from sanic.headers import format_http1
|
||||
from sanic.helpers import has_message_body
|
||||
from sanic.helpers import has_message_body, remove_entity_headers
|
||||
|
||||
|
||||
try:
|
||||
|
@ -25,9 +24,6 @@ class BaseHTTPResponse:
|
|||
def _encode_body(self, data):
|
||||
return data.encode() if hasattr(data, "encode") else data
|
||||
|
||||
def _parse_headers(self):
|
||||
return format_http1(self.full_headers)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
if self._cookies is None:
|
||||
|
@ -35,14 +31,22 @@ class BaseHTTPResponse:
|
|||
return self._cookies
|
||||
|
||||
@property
|
||||
def full_headers(self):
|
||||
"""Obtain an encoded tuple of headers for a response to be sent."""
|
||||
def processed_headers(self):
|
||||
"""Obtain a list of header tuples encoded in bytes for sending.
|
||||
|
||||
Add and remove headers based on status and content_type.
|
||||
"""
|
||||
headers = []
|
||||
cookies = {}
|
||||
if self.content_type and not "content-type" in self.headers:
|
||||
headers += (b"content-type", self.content_type.encode()),
|
||||
status = self.status
|
||||
# TODO: Make a blacklist set of header names and then filter with that
|
||||
if status in (304, 412): # Not Modified, Precondition Failed
|
||||
self.headers = remove_entity_headers(self.headers)
|
||||
if has_message_body(status):
|
||||
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}"
|
||||
name = f"{name}".lower()
|
||||
if name.lower() == "set-cookie":
|
||||
cookies[value.key] = value
|
||||
else:
|
||||
|
@ -126,12 +130,6 @@ class HTTPResponse(BaseHTTPResponse):
|
|||
self.headers = Header(headers or {})
|
||||
self._cookies = None
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
if self._cookies is None:
|
||||
self._cookies = CookieJar(self.headers)
|
||||
return self._cookies
|
||||
|
||||
|
||||
def empty(status=204, headers=None):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user