Spaces:
Sleeping
Sleeping
| # High level events that make up HTTP/1.1 conversations. Loosely inspired by | |
| # the corresponding events in hyper-h2: | |
| # | |
| # http://python-hyper.org/h2/en/stable/api.html#events | |
| # | |
| # Don't subclass these. Stuff will break. | |
| import re | |
| from abc import ABC | |
| from dataclasses import dataclass | |
| from typing import List, Tuple, Union | |
| from ._abnf import method, request_target | |
| from ._headers import Headers, normalize_and_validate | |
| from ._util import bytesify, LocalProtocolError, validate | |
| # Everything in __all__ gets re-exported as part of the h11 public API. | |
| __all__ = [ | |
| "Event", | |
| "Request", | |
| "InformationalResponse", | |
| "Response", | |
| "Data", | |
| "EndOfMessage", | |
| "ConnectionClosed", | |
| ] | |
| method_re = re.compile(method.encode("ascii")) | |
| request_target_re = re.compile(request_target.encode("ascii")) | |
| class Event(ABC): | |
| """ | |
| Base class for h11 events. | |
| """ | |
| __slots__ = () | |
| class Request(Event): | |
| """The beginning of an HTTP request. | |
| Fields: | |
| .. attribute:: method | |
| An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte | |
| string. :term:`Bytes-like objects <bytes-like object>` and native | |
| strings containing only ascii characters will be automatically | |
| converted to byte strings. | |
| .. attribute:: target | |
| The target of an HTTP request, e.g. ``b"/index.html"``, or one of the | |
| more exotic formats described in `RFC 7320, section 5.3 | |
| <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte | |
| string. :term:`Bytes-like objects <bytes-like object>` and native | |
| strings containing only ascii characters will be automatically | |
| converted to byte strings. | |
| .. attribute:: headers | |
| Request headers, represented as a list of (name, value) pairs. See | |
| :ref:`the header normalization rules <headers-format>` for details. | |
| .. attribute:: http_version | |
| The HTTP protocol version, represented as a byte string like | |
| ``b"1.1"``. See :ref:`the HTTP version normalization rules | |
| <http_version-format>` for details. | |
| """ | |
| __slots__ = ("method", "headers", "target", "http_version") | |
| method: bytes | |
| headers: Headers | |
| target: bytes | |
| http_version: bytes | |
| def __init__( | |
| self, | |
| *, | |
| method: Union[bytes, str], | |
| headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], | |
| target: Union[bytes, str], | |
| http_version: Union[bytes, str] = b"1.1", | |
| _parsed: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| if isinstance(headers, Headers): | |
| object.__setattr__(self, "headers", headers) | |
| else: | |
| object.__setattr__( | |
| self, "headers", normalize_and_validate(headers, _parsed=_parsed) | |
| ) | |
| if not _parsed: | |
| object.__setattr__(self, "method", bytesify(method)) | |
| object.__setattr__(self, "target", bytesify(target)) | |
| object.__setattr__(self, "http_version", bytesify(http_version)) | |
| else: | |
| object.__setattr__(self, "method", method) | |
| object.__setattr__(self, "target", target) | |
| object.__setattr__(self, "http_version", http_version) | |
| # "A server MUST respond with a 400 (Bad Request) status code to any | |
| # HTTP/1.1 request message that lacks a Host header field and to any | |
| # request message that contains more than one Host header field or a | |
| # Host header field with an invalid field-value." | |
| # -- https://tools.ietf.org/html/rfc7230#section-5.4 | |
| host_count = 0 | |
| for name, value in self.headers: | |
| if name == b"host": | |
| host_count += 1 | |
| if self.http_version == b"1.1" and host_count == 0: | |
| raise LocalProtocolError("Missing mandatory Host: header") | |
| if host_count > 1: | |
| raise LocalProtocolError("Found multiple Host: headers") | |
| validate(method_re, self.method, "Illegal method characters") | |
| validate(request_target_re, self.target, "Illegal target characters") | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| class _ResponseBase(Event): | |
| __slots__ = ("headers", "http_version", "reason", "status_code") | |
| headers: Headers | |
| http_version: bytes | |
| reason: bytes | |
| status_code: int | |
| def __init__( | |
| self, | |
| *, | |
| headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], | |
| status_code: int, | |
| http_version: Union[bytes, str] = b"1.1", | |
| reason: Union[bytes, str] = b"", | |
| _parsed: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| if isinstance(headers, Headers): | |
| object.__setattr__(self, "headers", headers) | |
| else: | |
| object.__setattr__( | |
| self, "headers", normalize_and_validate(headers, _parsed=_parsed) | |
| ) | |
| if not _parsed: | |
| object.__setattr__(self, "reason", bytesify(reason)) | |
| object.__setattr__(self, "http_version", bytesify(http_version)) | |
| if not isinstance(status_code, int): | |
| raise LocalProtocolError("status code must be integer") | |
| # Because IntEnum objects are instances of int, but aren't | |
| # duck-compatible (sigh), see gh-72. | |
| object.__setattr__(self, "status_code", int(status_code)) | |
| else: | |
| object.__setattr__(self, "reason", reason) | |
| object.__setattr__(self, "http_version", http_version) | |
| object.__setattr__(self, "status_code", status_code) | |
| self.__post_init__() | |
| def __post_init__(self) -> None: | |
| pass | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| class InformationalResponse(_ResponseBase): | |
| """An HTTP informational response. | |
| Fields: | |
| .. attribute:: status_code | |
| The status code of this response, as an integer. For an | |
| :class:`InformationalResponse`, this is always in the range [100, | |
| 200). | |
| .. attribute:: headers | |
| Request headers, represented as a list of (name, value) pairs. See | |
| :ref:`the header normalization rules <headers-format>` for | |
| details. | |
| .. attribute:: http_version | |
| The HTTP protocol version, represented as a byte string like | |
| ``b"1.1"``. See :ref:`the HTTP version normalization rules | |
| <http_version-format>` for details. | |
| .. attribute:: reason | |
| The reason phrase of this response, as a byte string. For example: | |
| ``b"OK"``, or ``b"Not Found"``. | |
| """ | |
| def __post_init__(self) -> None: | |
| if not (100 <= self.status_code < 200): | |
| raise LocalProtocolError( | |
| "InformationalResponse status_code should be in range " | |
| "[100, 200), not {}".format(self.status_code) | |
| ) | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| class Response(_ResponseBase): | |
| """The beginning of an HTTP response. | |
| Fields: | |
| .. attribute:: status_code | |
| The status code of this response, as an integer. For an | |
| :class:`Response`, this is always in the range [200, | |
| 1000). | |
| .. attribute:: headers | |
| Request headers, represented as a list of (name, value) pairs. See | |
| :ref:`the header normalization rules <headers-format>` for details. | |
| .. attribute:: http_version | |
| The HTTP protocol version, represented as a byte string like | |
| ``b"1.1"``. See :ref:`the HTTP version normalization rules | |
| <http_version-format>` for details. | |
| .. attribute:: reason | |
| The reason phrase of this response, as a byte string. For example: | |
| ``b"OK"``, or ``b"Not Found"``. | |
| """ | |
| def __post_init__(self) -> None: | |
| if not (200 <= self.status_code < 1000): | |
| raise LocalProtocolError( | |
| "Response status_code should be in range [200, 1000), not {}".format( | |
| self.status_code | |
| ) | |
| ) | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| class Data(Event): | |
| """Part of an HTTP message body. | |
| Fields: | |
| .. attribute:: data | |
| A :term:`bytes-like object` containing part of a message body. Or, if | |
| using the ``combine=False`` argument to :meth:`Connection.send`, then | |
| any object that your socket writing code knows what to do with, and for | |
| which calling :func:`len` returns the number of bytes that will be | |
| written -- see :ref:`sendfile` for details. | |
| .. attribute:: chunk_start | |
| A marker that indicates whether this data object is from the start of a | |
| chunked transfer encoding chunk. This field is ignored when when a Data | |
| event is provided to :meth:`Connection.send`: it is only valid on | |
| events emitted from :meth:`Connection.next_event`. You probably | |
| shouldn't use this attribute at all; see | |
| :ref:`chunk-delimiters-are-bad` for details. | |
| .. attribute:: chunk_end | |
| A marker that indicates whether this data object is the last for a | |
| given chunked transfer encoding chunk. This field is ignored when when | |
| a Data event is provided to :meth:`Connection.send`: it is only valid | |
| on events emitted from :meth:`Connection.next_event`. You probably | |
| shouldn't use this attribute at all; see | |
| :ref:`chunk-delimiters-are-bad` for details. | |
| """ | |
| __slots__ = ("data", "chunk_start", "chunk_end") | |
| data: bytes | |
| chunk_start: bool | |
| chunk_end: bool | |
| def __init__( | |
| self, data: bytes, chunk_start: bool = False, chunk_end: bool = False | |
| ) -> None: | |
| object.__setattr__(self, "data", data) | |
| object.__setattr__(self, "chunk_start", chunk_start) | |
| object.__setattr__(self, "chunk_end", chunk_end) | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| # XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that | |
| # are forbidden to be sent in a trailer, since processing them as if they were | |
| # present in the header section might bypass external security filters." | |
| # https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part | |
| # Unfortunately, the list of forbidden fields is long and vague :-/ | |
| class EndOfMessage(Event): | |
| """The end of an HTTP message. | |
| Fields: | |
| .. attribute:: headers | |
| Default value: ``[]`` | |
| Any trailing headers attached to this message, represented as a list of | |
| (name, value) pairs. See :ref:`the header normalization rules | |
| <headers-format>` for details. | |
| Must be empty unless ``Transfer-Encoding: chunked`` is in use. | |
| """ | |
| __slots__ = ("headers",) | |
| headers: Headers | |
| def __init__( | |
| self, | |
| *, | |
| headers: Union[ | |
| Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None | |
| ] = None, | |
| _parsed: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| if headers is None: | |
| headers = Headers([]) | |
| elif not isinstance(headers, Headers): | |
| headers = normalize_and_validate(headers, _parsed=_parsed) | |
| object.__setattr__(self, "headers", headers) | |
| # This is an unhashable type. | |
| __hash__ = None # type: ignore | |
| class ConnectionClosed(Event): | |
| """This event indicates that the sender has closed their outgoing | |
| connection. | |
| Note that this does not necessarily mean that they can't *receive* further | |
| data, because TCP connections are composed to two one-way channels which | |
| can be closed independently. See :ref:`closing` for details. | |
| No fields. | |
| """ | |
| pass | |