Compare commits

..

1 Commits

Author SHA1 Message Date
L. Kärkkäinen
715388cd0e Simplify HTTP/3. Remove chunked encoding and excessive logging. 2023-09-20 21:18:35 +01:00
2 changed files with 17 additions and 56 deletions

View File

@@ -152,24 +152,20 @@ class HTTPReceiver(Receiver, Stream):
size = len(response.body) if response.body else 0 size = len(response.body) if response.body else 0
headers = response.headers headers = response.headers
status = response.status status = response.status
want_body = (
if not has_message_body(status) and (
size size
or "content-length" in headers or "content-length" in headers
or "transfer-encoding" in headers or "transfer-encoding" in headers
): )
headers.pop("transfer-encoding", None) # Not used with HTTP/3
if want_body and not has_message_body(status):
headers.pop("content-length", None) headers.pop("content-length", None)
headers.pop("transfer-encoding", None)
logger.warning( # no cov logger.warning( # no cov
f"Message body set in response on {self.request.path}. " f"Message body set in response on {self.request.path}. "
f"A {status} response may only have headers, no body." f"A {status} response may only have headers, no body."
) )
elif "content-length" not in headers: elif size and "content-length" not in headers:
if size:
headers["content-length"] = size headers["content-length"] = size
else:
headers["transfer-encoding"] = "chunked"
headers = [ headers = [
(b":status", str(response.status).encode()), (b":status", str(response.status).encode()),
*response.processed_headers, *response.processed_headers,
@@ -195,18 +191,8 @@ class HTTPReceiver(Receiver, Stream):
self.headers_sent = True self.headers_sent = True
self.stage = Stage.RESPONSE self.stage = Stage.RESPONSE
if self.response.body and not self.head_only:
self._send(self.response.body, False)
elif self.head_only:
self.future.cancel()
def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse: def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse:
"""Prepare response to client""" """Prepare response to client"""
logger.debug( # no cov
f"{Colors.BLUE}[respond]:{Colors.END} {response}",
extra={"verbosity": 2},
)
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")
@@ -229,38 +215,14 @@ class HTTPReceiver(Receiver, Stream):
async def send(self, data: bytes, end_stream: bool) -> None: async def send(self, data: bytes, end_stream: bool) -> None:
"""Send data to client""" """Send data to client"""
logger.debug( # no cov
f"{Colors.BLUE}[send]: {Colors.GREEN}data={data.decode()} "
f"end_stream={end_stream}{Colors.END}",
extra={"verbosity": 2},
)
self._send(data, end_stream)
def _send(self, data: bytes, end_stream: bool) -> None:
if not self.headers_sent: if not self.headers_sent:
self.send_headers() self.send_headers()
if self.stage is not Stage.RESPONSE: if self.stage is not Stage.RESPONSE:
raise ServerError(f"not ready to send: {self.stage}") raise ServerError(f"not ready to send: {self.stage}")
# Chunked if data and self.head_only:
if ( data = b""
self.response
and self.response.headers.get("transfer-encoding") == "chunked"
):
size = len(data)
if end_stream:
data = (
b"%x\r\n%b\r\n0\r\n\r\n" % (size, data)
if size
else b"0\r\n\r\n"
)
elif size:
data = b"%x\r\n%b\r\n" % (size, data)
logger.debug( # no cov
f"{Colors.BLUE}[transmitting]{Colors.END}",
extra={"verbosity": 2},
)
self.protocol.connection.send_data( self.protocol.connection.send_data(
stream_id=self.request.stream_id, stream_id=self.request.stream_id,
data=data, data=data,

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import os import os
import ssl import ssl
from pathlib import Path, PurePath
from typing import Any, Dict, Iterable, Optional, Union from typing import Any, Dict, Iterable, Optional, Union
from sanic.log import logger from sanic.log import logger
@@ -40,23 +39,23 @@ def create_context(
def shorthand_to_ctx( def shorthand_to_ctx(
ctxdef: Union[None, ssl.SSLContext, dict, PurePath, str] ctxdef: Union[None, ssl.SSLContext, dict, str]
) -> Optional[ssl.SSLContext]: ) -> Optional[ssl.SSLContext]:
"""Convert an ssl argument shorthand to an SSLContext object.""" """Convert an ssl argument shorthand to an SSLContext object."""
if ctxdef is None or isinstance(ctxdef, ssl.SSLContext): if ctxdef is None or isinstance(ctxdef, ssl.SSLContext):
return ctxdef return ctxdef
if isinstance(ctxdef, (PurePath, str)): if isinstance(ctxdef, str):
return load_cert_dir(Path(ctxdef)) return load_cert_dir(ctxdef)
if isinstance(ctxdef, dict): if isinstance(ctxdef, dict):
return CertSimple(**ctxdef) return CertSimple(**ctxdef)
raise ValueError( raise ValueError(
f"Invalid ssl argument {type(ctxdef)}." f"Invalid ssl argument {type(ctxdef)}."
" Expecting one/list of: certdir | dict | SSLContext" " Expecting a list of certdirs, a dict or an SSLContext."
) )
def process_to_context( def process_to_context(
ssldef: Union[None, ssl.SSLContext, dict, PurePath, str, list, tuple] ssldef: Union[None, ssl.SSLContext, dict, str, list, tuple]
) -> Optional[ssl.SSLContext]: ) -> Optional[ssl.SSLContext]:
"""Process app.run ssl argument from easy formats to full SSLContext.""" """Process app.run ssl argument from easy formats to full SSLContext."""
return ( return (
@@ -66,11 +65,11 @@ def process_to_context(
) )
def load_cert_dir(p: Path) -> ssl.SSLContext: def load_cert_dir(p: str) -> ssl.SSLContext:
if p.is_file(): if os.path.isfile(p):
raise ValueError(f"Certificate folder expected but {p} is a file.") raise ValueError(f"Certificate folder expected but {p} is a file.")
keyfile = p / "privkey.pem" keyfile = os.path.join(p, "privkey.pem")
certfile = p / "fullchain.pem" certfile = os.path.join(p, "fullchain.pem")
if not os.access(keyfile, os.R_OK): if not os.access(keyfile, os.R_OK):
raise ValueError( raise ValueError(
f"Certificate not found or permission denied {keyfile}" f"Certificate not found or permission denied {keyfile}"