| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import re |
| |
|
| | from . import _headers |
| | from ._abnf import request_target |
| | from ._util import bytesify, LocalProtocolError, validate |
| |
|
| | |
| | __all__ = [ |
| | "Request", |
| | "InformationalResponse", |
| | "Response", |
| | "Data", |
| | "EndOfMessage", |
| | "ConnectionClosed", |
| | ] |
| |
|
| | request_target_re = re.compile(request_target.encode("ascii")) |
| |
|
| |
|
| | class _EventBundle: |
| | _fields = [] |
| | _defaults = {} |
| |
|
| | def __init__(self, **kwargs): |
| | _parsed = kwargs.pop("_parsed", False) |
| | allowed = set(self._fields) |
| | for kwarg in kwargs: |
| | if kwarg not in allowed: |
| | raise TypeError( |
| | "unrecognized kwarg {} for {}".format( |
| | kwarg, self.__class__.__name__ |
| | ) |
| | ) |
| | required = allowed.difference(self._defaults) |
| | for field in required: |
| | if field not in kwargs: |
| | raise TypeError( |
| | "missing required kwarg {} for {}".format( |
| | field, self.__class__.__name__ |
| | ) |
| | ) |
| | self.__dict__.update(self._defaults) |
| | self.__dict__.update(kwargs) |
| |
|
| | |
| |
|
| | if "headers" in self.__dict__: |
| | self.headers = _headers.normalize_and_validate( |
| | self.headers, _parsed=_parsed |
| | ) |
| |
|
| | if not _parsed: |
| | for field in ["method", "target", "http_version", "reason"]: |
| | if field in self.__dict__: |
| | self.__dict__[field] = bytesify(self.__dict__[field]) |
| |
|
| | if "status_code" in self.__dict__: |
| | if not isinstance(self.status_code, int): |
| | raise LocalProtocolError("status code must be integer") |
| | |
| | |
| | self.status_code = int(self.status_code) |
| |
|
| | self._validate() |
| |
|
| | def _validate(self): |
| | pass |
| |
|
| | def __repr__(self): |
| | name = self.__class__.__name__ |
| | kwarg_strs = [ |
| | "{}={}".format(field, self.__dict__[field]) for field in self._fields |
| | ] |
| | kwarg_str = ", ".join(kwarg_strs) |
| | return "{}({})".format(name, kwarg_str) |
| |
|
| | |
| | def __eq__(self, other): |
| | return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ |
| |
|
| | |
| | __hash__ = None |
| |
|
| |
|
| | class Request(_EventBundle): |
| | """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. |
| | |
| | """ |
| |
|
| | _fields = ["method", "target", "headers", "http_version"] |
| | _defaults = {"http_version": b"1.1"} |
| |
|
| | def _validate(self): |
| | |
| | |
| | |
| | |
| | |
| | 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(request_target_re, self.target, "Illegal target characters") |
| |
|
| |
|
| | class _ResponseBase(_EventBundle): |
| | _fields = ["status_code", "headers", "http_version", "reason"] |
| | _defaults = {"http_version": b"1.1", "reason": b""} |
| |
|
| |
|
| | 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 _validate(self): |
| | if not (100 <= self.status_code < 200): |
| | raise LocalProtocolError( |
| | "InformationalResponse status_code should be in range " |
| | "[100, 200), not {}".format(self.status_code) |
| | ) |
| |
|
| |
|
| | 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, |
| | 600). |
| | |
| | .. 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 _validate(self): |
| | if not (200 <= self.status_code < 600): |
| | raise LocalProtocolError( |
| | "Response status_code should be in range [200, 600), not {}".format( |
| | self.status_code |
| | ) |
| | ) |
| |
|
| |
|
| | class Data(_EventBundle): |
| | """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. |
| | |
| | """ |
| |
|
| | _fields = ["data", "chunk_start", "chunk_end"] |
| | _defaults = {"chunk_start": False, "chunk_end": False} |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | class EndOfMessage(_EventBundle): |
| | """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. |
| | |
| | """ |
| |
|
| | _fields = ["headers"] |
| | _defaults = {"headers": []} |
| |
|
| |
|
| | class ConnectionClosed(_EventBundle): |
| | """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 |
| |
|