Update 2025-04-13_16:26:04
This commit is contained in:
145
venv/lib/python3.11/site-packages/h11/_writers.py
Normal file
145
venv/lib/python3.11/site-packages/h11/_writers.py
Normal file
@ -0,0 +1,145 @@
|
||||
# Code to read HTTP data
|
||||
#
|
||||
# Strategy: each writer takes an event + a write-some-bytes function, which is
|
||||
# calls.
|
||||
#
|
||||
# WRITERS is a dict describing how to pick a reader. It maps states to either:
|
||||
# - a writer
|
||||
# - or, for body writers, a dict of framin-dependent writer factories
|
||||
|
||||
from typing import Any, Callable, Dict, List, Tuple, Type, Union
|
||||
|
||||
from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response
|
||||
from ._headers import Headers
|
||||
from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER
|
||||
from ._util import LocalProtocolError, Sentinel
|
||||
|
||||
__all__ = ["WRITERS"]
|
||||
|
||||
Writer = Callable[[bytes], Any]
|
||||
|
||||
|
||||
def write_headers(headers: Headers, write: Writer) -> None:
|
||||
# "Since the Host field-value is critical information for handling a
|
||||
# request, a user agent SHOULD generate Host as the first header field
|
||||
# following the request-line." - RFC 7230
|
||||
raw_items = headers._full_items
|
||||
for raw_name, name, value in raw_items:
|
||||
if name == b"host":
|
||||
write(b"%s: %s\r\n" % (raw_name, value))
|
||||
for raw_name, name, value in raw_items:
|
||||
if name != b"host":
|
||||
write(b"%s: %s\r\n" % (raw_name, value))
|
||||
write(b"\r\n")
|
||||
|
||||
|
||||
def write_request(request: Request, write: Writer) -> None:
|
||||
if request.http_version != b"1.1":
|
||||
raise LocalProtocolError("I only send HTTP/1.1")
|
||||
write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target))
|
||||
write_headers(request.headers, write)
|
||||
|
||||
|
||||
# Shared between InformationalResponse and Response
|
||||
def write_any_response(
|
||||
response: Union[InformationalResponse, Response], write: Writer
|
||||
) -> None:
|
||||
if response.http_version != b"1.1":
|
||||
raise LocalProtocolError("I only send HTTP/1.1")
|
||||
status_bytes = str(response.status_code).encode("ascii")
|
||||
# We don't bother sending ascii status messages like "OK"; they're
|
||||
# optional and ignored by the protocol. (But the space after the numeric
|
||||
# status code is mandatory.)
|
||||
#
|
||||
# XX FIXME: could at least make an effort to pull out the status message
|
||||
# from stdlib's http.HTTPStatus table. Or maybe just steal their enums
|
||||
# (either by import or copy/paste). We already accept them as status codes
|
||||
# since they're of type IntEnum < int.
|
||||
write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason))
|
||||
write_headers(response.headers, write)
|
||||
|
||||
|
||||
class BodyWriter:
|
||||
def __call__(self, event: Event, write: Writer) -> None:
|
||||
if type(event) is Data:
|
||||
self.send_data(event.data, write)
|
||||
elif type(event) is EndOfMessage:
|
||||
self.send_eom(event.headers, write)
|
||||
else: # pragma: no cover
|
||||
assert False
|
||||
|
||||
def send_data(self, data: bytes, write: Writer) -> None:
|
||||
pass
|
||||
|
||||
def send_eom(self, headers: Headers, write: Writer) -> None:
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# These are all careful not to do anything to 'data' except call len(data) and
|
||||
# write(data). This allows us to transparently pass-through funny objects,
|
||||
# like placeholder objects referring to files on disk that will be sent via
|
||||
# sendfile(2).
|
||||
#
|
||||
class ContentLengthWriter(BodyWriter):
|
||||
def __init__(self, length: int) -> None:
|
||||
self._length = length
|
||||
|
||||
def send_data(self, data: bytes, write: Writer) -> None:
|
||||
self._length -= len(data)
|
||||
if self._length < 0:
|
||||
raise LocalProtocolError("Too much data for declared Content-Length")
|
||||
write(data)
|
||||
|
||||
def send_eom(self, headers: Headers, write: Writer) -> None:
|
||||
if self._length != 0:
|
||||
raise LocalProtocolError("Too little data for declared Content-Length")
|
||||
if headers:
|
||||
raise LocalProtocolError("Content-Length and trailers don't mix")
|
||||
|
||||
|
||||
class ChunkedWriter(BodyWriter):
|
||||
def send_data(self, data: bytes, write: Writer) -> None:
|
||||
# if we encoded 0-length data in the naive way, it would look like an
|
||||
# end-of-message.
|
||||
if not data:
|
||||
return
|
||||
write(b"%x\r\n" % len(data))
|
||||
write(data)
|
||||
write(b"\r\n")
|
||||
|
||||
def send_eom(self, headers: Headers, write: Writer) -> None:
|
||||
write(b"0\r\n")
|
||||
write_headers(headers, write)
|
||||
|
||||
|
||||
class Http10Writer(BodyWriter):
|
||||
def send_data(self, data: bytes, write: Writer) -> None:
|
||||
write(data)
|
||||
|
||||
def send_eom(self, headers: Headers, write: Writer) -> None:
|
||||
if headers:
|
||||
raise LocalProtocolError("can't send trailers to HTTP/1.0 client")
|
||||
# no need to close the socket ourselves, that will be taken care of by
|
||||
# Connection: close machinery
|
||||
|
||||
|
||||
WritersType = Dict[
|
||||
Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]],
|
||||
Union[
|
||||
Dict[str, Type[BodyWriter]],
|
||||
Callable[[Union[InformationalResponse, Response], Writer], None],
|
||||
Callable[[Request, Writer], None],
|
||||
],
|
||||
]
|
||||
|
||||
WRITERS: WritersType = {
|
||||
(CLIENT, IDLE): write_request,
|
||||
(SERVER, IDLE): write_any_response,
|
||||
(SERVER, SEND_RESPONSE): write_any_response,
|
||||
SEND_BODY: {
|
||||
"chunked": ChunkedWriter,
|
||||
"content-length": ContentLengthWriter,
|
||||
"http/1.0": Http10Writer,
|
||||
},
|
||||
}
|
Reference in New Issue
Block a user