Refactoring and cleanup.
This commit is contained in:
parent
990ac52a1a
commit
d348bb4ff4
|
@ -272,84 +272,20 @@ class ASGIApp:
|
||||||
yield data
|
yield data
|
||||||
|
|
||||||
def respond(self, response):
|
def respond(self, response):
|
||||||
headers: List[Tuple[bytes, bytes]] = []
|
response.stream, self.response = self, response
|
||||||
cookies: Dict[str, str] = {}
|
return response
|
||||||
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",)
|
|
||||||
]
|
|
||||||
|
|
||||||
if "content-length" not in response.headers and not isinstance(
|
async def send(self, data, end_stream):
|
||||||
response, StreamingHTTPResponse
|
if self.response:
|
||||||
):
|
response, self.response = self.response, None
|
||||||
headers += [
|
await self.transport.send({
|
||||||
(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",
|
"type": "http.response.start",
|
||||||
"status": response.status,
|
"status": response.status,
|
||||||
"headers": headers,
|
"headers": response.full_headers,
|
||||||
}
|
})
|
||||||
self.response_body = response.body
|
response_body = getattr(response, "body", None)
|
||||||
return self
|
if response_body:
|
||||||
|
data = response_body + data if data else response_body
|
||||||
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
|
|
||||||
await self.transport.send(
|
await self.transport.send(
|
||||||
{
|
{
|
||||||
"type": "http.response.body",
|
"type": "http.response.body",
|
||||||
|
|
|
@ -77,7 +77,7 @@ class Http:
|
||||||
if self.stage is Stage.HANDLER:
|
if self.stage is Stage.HANDLER:
|
||||||
raise ServerError("Handler produced no response")
|
raise ServerError("Handler produced no response")
|
||||||
if self.stage is Stage.RESPONSE:
|
if self.stage is Stage.RESPONSE:
|
||||||
await self.send(end_stream=True)
|
await self.response.send(end_stream=True)
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
# Write an appropriate response before exiting
|
# Write an appropriate response before exiting
|
||||||
e = self.exception or ServiceUnavailable(f"Cancelled")
|
e = self.exception or ServiceUnavailable(f"Cancelled")
|
||||||
|
@ -178,13 +178,15 @@ class Http:
|
||||||
def http1_response_header(self, data, end_stream) -> bytes:
|
def http1_response_header(self, data, end_stream) -> bytes:
|
||||||
res = self.response
|
res = self.response
|
||||||
# Compatibility with simple response body
|
# 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
|
data, end_stream = res.body, True
|
||||||
size = len(data)
|
size = len(data)
|
||||||
status = res.status
|
|
||||||
headers = res.headers
|
headers = res.headers
|
||||||
if res.content_type and "content-type" not in headers:
|
if res.content_type and "content-type" not in headers:
|
||||||
headers["content-type"] = res.content_type
|
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
|
# Not Modified, Precondition Failed
|
||||||
if status in (304, 412):
|
if status in (304, 412):
|
||||||
headers = remove_entity_headers(headers)
|
headers = remove_entity_headers(headers)
|
||||||
|
@ -374,19 +376,10 @@ class Http:
|
||||||
if self.stage is not Stage.HANDLER:
|
if self.stage is not Stage.HANDLER:
|
||||||
self.stage = Stage.FAILED
|
self.stage = Stage.FAILED
|
||||||
raise RuntimeError("Response already started")
|
raise RuntimeError("Response already started")
|
||||||
if not isinstance(response.status, int) or response.status < 200:
|
self.response, response.stream = response, self
|
||||||
raise RuntimeError(f"Invalid response status {response.status!r}")
|
return response
|
||||||
self.response = response
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def send(self, data=None, end_stream=None):
|
async def send(self, data, end_stream):
|
||||||
"""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""
|
|
||||||
data = self.response_func(data, end_stream)
|
data = self.response_func(data, end_stream)
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
|
@ -106,15 +106,17 @@ class Request:
|
||||||
)
|
)
|
||||||
|
|
||||||
def respond(
|
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(
|
# This logic of determining which response to use is subject to change
|
||||||
status
|
if response is None:
|
||||||
if isinstance(status, HTTPResponse)
|
response = self.stream.response or HTTPResponse(
|
||||||
else HTTPResponse(
|
status=status,
|
||||||
status=status, headers=headers, content_type=content_type,
|
headers=headers,
|
||||||
)
|
content_type=content_type,
|
||||||
)
|
)
|
||||||
|
# Connect the response and return it
|
||||||
|
return self.stream.respond(response)
|
||||||
|
|
||||||
async def receive_body(self):
|
async def receive_body(self):
|
||||||
self.body = b"".join([data async for data in self.stream])
|
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
|
return data.encode() if hasattr(data, "encode") else data
|
||||||
|
|
||||||
def _parse_headers(self):
|
def _parse_headers(self):
|
||||||
return format_http1(self.headers.items())
|
return format_http1(self.full_headers)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cookies(self):
|
def cookies(self):
|
||||||
|
@ -34,6 +34,43 @@ class BaseHTTPResponse:
|
||||||
self._cookies = CookieJar(self.headers)
|
self._cookies = CookieJar(self.headers)
|
||||||
return self._cookies
|
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):
|
class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
|
@ -42,9 +79,7 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
"status",
|
"status",
|
||||||
"content_type",
|
"content_type",
|
||||||
"headers",
|
"headers",
|
||||||
"chunked",
|
|
||||||
"_cookies",
|
"_cookies",
|
||||||
"send",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -53,13 +88,12 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
status=200,
|
status=200,
|
||||||
headers=None,
|
headers=None,
|
||||||
content_type="text/plain; charset=utf-8",
|
content_type="text/plain; charset=utf-8",
|
||||||
chunked=True,
|
chunked="deprecated",
|
||||||
):
|
):
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
self.streaming_fn = streaming_fn
|
self.streaming_fn = streaming_fn
|
||||||
self.status = status
|
self.status = status
|
||||||
self.headers = Header(headers or {})
|
self.headers = Header(headers or {})
|
||||||
self.chunked = chunked
|
|
||||||
self._cookies = None
|
self._cookies = None
|
||||||
|
|
||||||
async def write(self, data):
|
async def write(self, data):
|
||||||
|
@ -70,9 +104,7 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
await self.send(self._encode_body(data))
|
await self.send(self._encode_body(data))
|
||||||
|
|
||||||
async def stream(self, request):
|
async def stream(self, request):
|
||||||
self.send = request.respond(
|
request.respond(self)
|
||||||
self.status, self.headers, self.content_type,
|
|
||||||
).send
|
|
||||||
await self.streaming_fn(self)
|
await self.streaming_fn(self)
|
||||||
await self.send(end_stream=True)
|
await self.send(end_stream=True)
|
||||||
|
|
||||||
|
@ -94,16 +126,6 @@ class HTTPResponse(BaseHTTPResponse):
|
||||||
self.headers = Header(headers or {})
|
self.headers = Header(headers or {})
|
||||||
self._cookies = None
|
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
|
@property
|
||||||
def cookies(self):
|
def cookies(self):
|
||||||
if self._cookies is None:
|
if self._cookies is None:
|
||||||
|
@ -265,7 +287,7 @@ async def file_stream(
|
||||||
mime_type=None,
|
mime_type=None,
|
||||||
headers=None,
|
headers=None,
|
||||||
filename=None,
|
filename=None,
|
||||||
chunked=True,
|
chunked="deprecated",
|
||||||
_range=None,
|
_range=None,
|
||||||
):
|
):
|
||||||
"""Return a streaming response object with file data.
|
"""Return a streaming response object with file data.
|
||||||
|
@ -314,7 +336,6 @@ async def file_stream(
|
||||||
status=status,
|
status=status,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
content_type=mime_type,
|
content_type=mime_type,
|
||||||
chunked=chunked,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -323,7 +344,7 @@ def stream(
|
||||||
status=200,
|
status=200,
|
||||||
headers=None,
|
headers=None,
|
||||||
content_type="text/plain; charset=utf-8",
|
content_type="text/plain; charset=utf-8",
|
||||||
chunked=True,
|
chunked="deprecated",
|
||||||
):
|
):
|
||||||
"""Accepts an coroutine `streaming_fn` which can be used to
|
"""Accepts an coroutine `streaming_fn` which can be used to
|
||||||
write chunks to a streaming response. Returns a `StreamingHTTPResponse`.
|
write chunks to a streaming response. Returns a `StreamingHTTPResponse`.
|
||||||
|
@ -349,7 +370,6 @@ def stream(
|
||||||
headers=headers,
|
headers=headers,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
status=status,
|
status=status,
|
||||||
chunked=chunked,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user