Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_events.py370 linesDownload Raw Back to h11
1# High level events that make up HTTP/1.1 conversations. Loosely inspired by2# the corresponding events in hyper-h2:3#4#     http://python-hyper.org/h2/en/stable/api.html#events5#6# Don't subclass these. Stuff will break.7 8import re9from abc import ABC10from dataclasses import dataclass, field11from typing import Any, cast, Dict, List, Tuple, Union12 13from ._abnf import method, request_target14from ._headers import Headers, normalize_and_validate15from ._util import bytesify, LocalProtocolError, validate16 17# Everything in __all__ gets re-exported as part of the h11 public API.18__all__ = [19    "Event",20    "Request",21    "InformationalResponse",22    "Response",23    "Data",24    "EndOfMessage",25    "ConnectionClosed",26]27 28method_re = re.compile(method.encode("ascii"))29request_target_re = re.compile(request_target.encode("ascii"))30 31 32class Event(ABC):33    """34    Base class for h11 events.35    """36 37    __slots__ = ()38 39 40@dataclass(init=False, frozen=True)41class Request(Event):42    """The beginning of an HTTP request.43 44    Fields:45 46    .. attribute:: method47 48       An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte49       string. :term:`Bytes-like objects <bytes-like object>` and native50       strings containing only ascii characters will be automatically51       converted to byte strings.52 53    .. attribute:: target54 55       The target of an HTTP request, e.g. ``b"/index.html"``, or one of the56       more exotic formats described in `RFC 7320, section 5.357       <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte58       string. :term:`Bytes-like objects <bytes-like object>` and native59       strings containing only ascii characters will be automatically60       converted to byte strings.61 62    .. attribute:: headers63 64       Request headers, represented as a list of (name, value) pairs. See65       :ref:`the header normalization rules <headers-format>` for details.66 67    .. attribute:: http_version68 69       The HTTP protocol version, represented as a byte string like70       ``b"1.1"``. See :ref:`the HTTP version normalization rules71       <http_version-format>` for details.72 73    """74 75    __slots__ = ("method", "headers", "target", "http_version")76 77    method: bytes78    headers: Headers79    target: bytes80    http_version: bytes81 82    def __init__(83        self,84        *,85        method: Union[bytes, str],86        headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],87        target: Union[bytes, str],88        http_version: Union[bytes, str] = b"1.1",89        _parsed: bool = False,90    ) -> None:91        super().__init__()92        if isinstance(headers, Headers):93            object.__setattr__(self, "headers", headers)94        else:95            object.__setattr__(96                self, "headers", normalize_and_validate(headers, _parsed=_parsed)97            )98        if not _parsed:99            object.__setattr__(self, "method", bytesify(method))100            object.__setattr__(self, "target", bytesify(target))101            object.__setattr__(self, "http_version", bytesify(http_version))102        else:103            object.__setattr__(self, "method", method)104            object.__setattr__(self, "target", target)105            object.__setattr__(self, "http_version", http_version)106 107        # "A server MUST respond with a 400 (Bad Request) status code to any108        # HTTP/1.1 request message that lacks a Host header field and to any109        # request message that contains more than one Host header field or a110        # Host header field with an invalid field-value."111        # -- https://tools.ietf.org/html/rfc7230#section-5.4112        host_count = 0113        for name, value in self.headers:114            if name == b"host":115                host_count += 1116        if self.http_version == b"1.1" and host_count == 0:117            raise LocalProtocolError("Missing mandatory Host: header")118        if host_count > 1:119            raise LocalProtocolError("Found multiple Host: headers")120 121        validate(method_re, self.method, "Illegal method characters")122        validate(request_target_re, self.target, "Illegal target characters")123 124    # This is an unhashable type.125    __hash__ = None  # type: ignore126 127 128@dataclass(init=False, frozen=True)129class _ResponseBase(Event):130    __slots__ = ("headers", "http_version", "reason", "status_code")131 132    headers: Headers133    http_version: bytes134    reason: bytes135    status_code: int136 137    def __init__(138        self,139        *,140        headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],141        status_code: int,142        http_version: Union[bytes, str] = b"1.1",143        reason: Union[bytes, str] = b"",144        _parsed: bool = False,145    ) -> None:146        super().__init__()147        if isinstance(headers, Headers):148            object.__setattr__(self, "headers", headers)149        else:150            object.__setattr__(151                self, "headers", normalize_and_validate(headers, _parsed=_parsed)152            )153        if not _parsed:154            object.__setattr__(self, "reason", bytesify(reason))155            object.__setattr__(self, "http_version", bytesify(http_version))156            if not isinstance(status_code, int):157                raise LocalProtocolError("status code must be integer")158            # Because IntEnum objects are instances of int, but aren't159            # duck-compatible (sigh), see gh-72.160            object.__setattr__(self, "status_code", int(status_code))161        else:162            object.__setattr__(self, "reason", reason)163            object.__setattr__(self, "http_version", http_version)164            object.__setattr__(self, "status_code", status_code)165 166        self.__post_init__()167 168    def __post_init__(self) -> None:169        pass170 171    # This is an unhashable type.172    __hash__ = None  # type: ignore173 174 175@dataclass(init=False, frozen=True)176class InformationalResponse(_ResponseBase):177    """An HTTP informational response.178 179    Fields:180 181    .. attribute:: status_code182 183       The status code of this response, as an integer. For an184       :class:`InformationalResponse`, this is always in the range [100,185       200).186 187    .. attribute:: headers188 189       Request headers, represented as a list of (name, value) pairs. See190       :ref:`the header normalization rules <headers-format>` for191       details.192 193    .. attribute:: http_version194 195       The HTTP protocol version, represented as a byte string like196       ``b"1.1"``. See :ref:`the HTTP version normalization rules197       <http_version-format>` for details.198 199    .. attribute:: reason200 201       The reason phrase of this response, as a byte string. For example:202       ``b"OK"``, or ``b"Not Found"``.203 204    """205 206    def __post_init__(self) -> None:207        if not (100 <= self.status_code < 200):208            raise LocalProtocolError(209                "InformationalResponse status_code should be in range "210                "[100, 200), not {}".format(self.status_code)211            )212 213    # This is an unhashable type.214    __hash__ = None  # type: ignore215 216 217@dataclass(init=False, frozen=True)218class Response(_ResponseBase):219    """The beginning of an HTTP response.220 221    Fields:222 223    .. attribute:: status_code224 225       The status code of this response, as an integer. For an226       :class:`Response`, this is always in the range [200,227       1000).228 229    .. attribute:: headers230 231       Request headers, represented as a list of (name, value) pairs. See232       :ref:`the header normalization rules <headers-format>` for details.233 234    .. attribute:: http_version235 236       The HTTP protocol version, represented as a byte string like237       ``b"1.1"``. See :ref:`the HTTP version normalization rules238       <http_version-format>` for details.239 240    .. attribute:: reason241 242       The reason phrase of this response, as a byte string. For example:243       ``b"OK"``, or ``b"Not Found"``.244 245    """246 247    def __post_init__(self) -> None:248        if not (200 <= self.status_code < 1000):249            raise LocalProtocolError(250                "Response status_code should be in range [200, 1000), not {}".format(251                    self.status_code252                )253            )254 255    # This is an unhashable type.256    __hash__ = None  # type: ignore257 258 259@dataclass(init=False, frozen=True)260class Data(Event):261    """Part of an HTTP message body.262 263    Fields:264 265    .. attribute:: data266 267       A :term:`bytes-like object` containing part of a message body. Or, if268       using the ``combine=False`` argument to :meth:`Connection.send`, then269       any object that your socket writing code knows what to do with, and for270       which calling :func:`len` returns the number of bytes that will be271       written -- see :ref:`sendfile` for details.272 273    .. attribute:: chunk_start274 275       A marker that indicates whether this data object is from the start of a276       chunked transfer encoding chunk. This field is ignored when when a Data277       event is provided to :meth:`Connection.send`: it is only valid on278       events emitted from :meth:`Connection.next_event`. You probably279       shouldn't use this attribute at all; see280       :ref:`chunk-delimiters-are-bad` for details.281 282    .. attribute:: chunk_end283 284       A marker that indicates whether this data object is the last for a285       given chunked transfer encoding chunk. This field is ignored when when286       a Data event is provided to :meth:`Connection.send`: it is only valid287       on events emitted from :meth:`Connection.next_event`. You probably288       shouldn't use this attribute at all; see289       :ref:`chunk-delimiters-are-bad` for details.290 291    """292 293    __slots__ = ("data", "chunk_start", "chunk_end")294 295    data: bytes296    chunk_start: bool297    chunk_end: bool298 299    def __init__(300        self, data: bytes, chunk_start: bool = False, chunk_end: bool = False301    ) -> None:302        object.__setattr__(self, "data", data)303        object.__setattr__(self, "chunk_start", chunk_start)304        object.__setattr__(self, "chunk_end", chunk_end)305 306    # This is an unhashable type.307    __hash__ = None  # type: ignore308 309 310# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that311# are forbidden to be sent in a trailer, since processing them as if they were312# present in the header section might bypass external security filters."313# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part314# Unfortunately, the list of forbidden fields is long and vague :-/315@dataclass(init=False, frozen=True)316class EndOfMessage(Event):317    """The end of an HTTP message.318 319    Fields:320 321    .. attribute:: headers322 323       Default value: ``[]``324 325       Any trailing headers attached to this message, represented as a list of326       (name, value) pairs. See :ref:`the header normalization rules327       <headers-format>` for details.328 329       Must be empty unless ``Transfer-Encoding: chunked`` is in use.330 331    """332 333    __slots__ = ("headers",)334 335    headers: Headers336 337    def __init__(338        self,339        *,340        headers: Union[341            Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None342        ] = None,343        _parsed: bool = False,344    ) -> None:345        super().__init__()346        if headers is None:347            headers = Headers([])348        elif not isinstance(headers, Headers):349            headers = normalize_and_validate(headers, _parsed=_parsed)350 351        object.__setattr__(self, "headers", headers)352 353    # This is an unhashable type.354    __hash__ = None  # type: ignore355 356 357@dataclass(frozen=True)358class ConnectionClosed(Event):359    """This event indicates that the sender has closed their outgoing360    connection.361 362    Note that this does not necessarily mean that they can't *receive* further363    data, because TCP connections are composed to two one-way channels which364    can be closed independently. See :ref:`closing` for details.365 366    No fields.367    """368 369    pass370 
codekingpro/portable-devtools · Team Ai