Refactoring and cleanup.
This commit is contained in:
parent
990ac52a1a
commit
d348bb4ff4
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user