Sans-IO Protocols

The hface.protocols module is the lowest layer of hface. It provides sans-IO (bring-your-own-IO) implementation of HTTP protocols.

The sans-IO approach means that the HTTP implementation never touch a network:

  • When you submit HTTP actions, you should ask for data to be sent over the network.

  • When you feed data from the network, you should ask for HTTP events.

Sans-IO example

The following example shows how a protocol class builds an HTTP request and parses an HTTP response:

from hface.events import DataReceived, HeadersReceived
from hface.protocols.http1 import HTTP1ClientFactory


def main():
    protocol_factory = HTTP1ClientFactory()
    protocol = protocol_factory()

    stream_id = protocol.get_available_stream_id()
    headers = [
        (b":method", b"GET"),
        (b":scheme", b"https"),
        (b":authority", b"localhost"),
        (b":path", b"/"),
    ]
    protocol.submit_headers(stream_id, headers, end_stream=True)
    assert protocol.bytes_to_send() == b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"

    protocol.bytes_received(b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n")
    assert protocol.next_event() == HeadersReceived(
        stream_id,
        [(b":status", b"200"), (b"content-length", b"10")],
    )

    protocol.bytes_received(b"It works!\n")
    assert protocol.next_event() == DataReceived(
        stream_id,
        b"It works!\n",
        end_stream=True,
    )


if __name__ == "__main__":
    main()

(The example should run as it is. It is available in examples/protocol.py)

Notice that the example uses an HTTP/1 protocol, but the class takes HTTP/2-like headers. The protocol class translates the headers internally. This is how hface can offer one interface for all HTTP versions.

How does it work?

The central point of the sans-IO layers is the HTTPProtocol interface. Instances of this interface represent an HTTP connection. Implementations have to translate HTTP events to network traffic and vice versa.

Note

The protocol interface is heavily inspired by the existing sans-IO libraries: h11, h2, and aioquic. At the same time, these libraries are used internally by hface.

The lowest level of hface can be seen as a unifying interface on top of the existing HTTP implementations.

The HTTP-facing side is defined in the HTTPProtocol interface – it includes methods like submit_headers() or submit_data() for sending. For receiving, we have the next_event() method.

The network-facing side is defined in subclasses:

All of the above classes are abstract. They define the interfaces, maybe provide some common functionality, but the actual protocol implementation must be implemented in subclasses.

Protocol implementations are not initialized directly, but through factories. Thanks to the factories, higher layers (like HTTPListener and HTTPOpener) have a well-defined interface how to construct protocols for new connections.

hface offers default protocols for all HTTP versions:

Known protocol implementations are tracked by ProtocolRegistry and its global instance protocol_registry. More implementations can be registered using setuptools entrypoints.

Protocol API

Protocols

class hface.protocols.HTTPProtocol

Sans-IO representation of an HTTP connection

abstract property http_version: str

An HTTP version as a string.

abstract property multiplexed: bool

Whether this connection supports multiple parallel streams.

Returns True for HTTP/2 and HTTP/3 connections.

abstract property error_codes: HTTPErrorCodes

Error codes for the HTTP version of this protocol.

These error codes can be used when a stream is reset or when a GOAWAY frame is sent.

abstractmethod is_available()

Return whether this connection is capable to open new streams.

Return type

bool

abstractmethod get_available_stream_id()

Return an ID that can be used to create a new stream.

Use the returned ID with submit_headers() to create the stream. This method may or may not return one value until that method is called.

Returns

stream ID

Return type

int

abstractmethod submit_headers(stream_id, headers, end_stream=False)

Submit a frame with HTTP headers.

If this is a client connection, this method starts an HTTP request. If this is a server connection, it starts an HTTP response.

Parameters
  • stream_id (int) – stream ID

  • headers (Sequence[Tuple[bytes, bytes]]) – HTTP headers

  • end_stream (bool) – whether to close the stream for sending

abstractmethod submit_data(stream_id, data, end_stream=False)

Submit a frame with HTTP data.

Parameters
  • stream_id (int) – stream ID

  • data (bytes) – payload

  • end_stream (bool) – whether to close the stream for sending

abstractmethod submit_stream_reset(stream_id, error_code=0)

Immediate terminate a stream.

Stream reset is used to request cancellation of a stream or to indicate that an error condition has occurred.

Use error_codes to obtain error codes for common problems.

Parameters
  • stream_id (int) – stream ID

  • error_code (int) – indicates why the stream is being terminated

abstractmethod submit_close(error_code=0)

Submit graceful close the connection.

Use error_codes to obtain error codes for common problems.

Parameters

error_code (int) – indicates why the connections is being closed

abstractmethod next_event()

Consume next HTTP event.

Returns

an event instance

Return type

hface.events._events.Event | None

class hface.protocols.HTTPOverTCPProtocol

HTTPProtocol over a TCP connection

An interface for HTTP/1 and HTTP/2 protocols. Extends HTTPProtocol.

abstractmethod connection_lost()

Called when the connection is lost or closed.

abstractmethod eof_received()

Called when the other end signals it won’t send any more data.

abstractmethod bytes_received(data)

Called when some data is received.

abstractmethod bytes_to_send()

Returns data for sending out of the internal data buffer.

Return type

bytes

class hface.protocols.HTTPOverQUICProtocol

HTTPProtocol over a QUIC connection

Abstract base class for HTTP/3 protocols. Extends HTTPProtocol.

abstract property connection_ids: Sequence[bytes]

QUIC connection IDs

This property can be used to assign UDP packets to QUIC connections.

Returns

a sequence of connection IDs

abstractmethod clock(now)

Notify the protocol that time has moved.

The clock value set by this method can be used in subsequent calls to other methods. When the time is after the value of get_timer(), the protocol can handle various timeouts.

Parameters

now (float) – current time in seconds. Typically, the event loop’s internal clock.

abstractmethod get_timer()

Return a clock value when the protocol wants to be notified.

If the protocol implementation needs to handle any timeouts, it should return the closes timeout from this method.

Returns

time in seconds or None if no timer is necessary

Return type

float | None

abstractmethod connection_lost()

Called when the connection is lost or closed.

abstractmethod datagram_received(datagram)

Called when some data is received.

Parameters

datagram (Tuple[bytes, Tuple[str, int]]) – the received datagram.

abstractmethod datagrams_to_send()

Returns data for sending out of the internal data buffer.

Returns

datagrams to send

Return type

Sequence[Tuple[bytes, Tuple[str, int]]]

class hface.protocols.HTTP1Protocol

Sans-IO representation of an HTTP/1 connection

An interface for HTTP/1 implementations. Extends HTTPOverTCPProtocol.

class hface.protocols.HTTP2Protocol

Sans-IO representation of an HTTP/2 connection

An abstract base class for HTTP/2 implementations. Extends HTTPOverTCPProtocol.

class hface.protocols.HTTP3Protocol

Sans-IO representation of an HTTP/2 connection

An abstract base class for HTTP/3 implementations. Extends HTTPOverQUICProtocol

Factories

class hface.protocols.HTTPOverTCPFactory

Interface for factories that create HTTPOverTCPProtocol instances.

abstractmethod __call__(*, tls_version=None, alpn_protocol=None)

Create HTTPOverTCPProtocol managing an HTTP connection.

Parameters
  • tls_version (Optional[str]) – TLS version if the connection is secure. This will be None for insecure connection.

  • alpn_protocol (Optional[str]) – an ALPN protocol negotiated during a TLS handshake. This will be None for insecure connection and for HTTP/3, because the TLS handshake happens at the QUIC layer.

Returns

a fresh instance of an HTTP protocol

Return type

HTTPOverTCPProtocol

abstract property alpn_protocols: Sequence[str]

ALPN protocols to be offered in TLS handshake.

Ordered from the most preferred to the least.

class hface.protocols.HTTPOverQUICClientFactory

Interface for factories that create HTTPOverQUICProtocol for clients.

abstractmethod __call__(*, remote_address, server_name, tls_config)

Create HTTPOverQUICProtocol managing an HTTP connection.

Parameters
  • remote_address (Tuple[str, int]) – network address of the peer. This is necessary for client HTTP/3 connections because destination addresses for UDP packets are select at the QUIC layer.

  • server_name (str) – a server name sent in SNI. This is necessary for HTTP/3 connections because TLS handshake happens at the QUIC layer.

Param

tls_config: TLS configuration This is necessary for HTTP/3 connections because TLS handshake happens at the QUIC layer.

Returns

a fresh instance of an HTTP protocol

Return type

HTTPOverQUICProtocol

class hface.protocols.HTTPOverQUICServerFactory

Interface for factories that create HTTPOverQUICProtocol for servers.

abstractmethod __call__(*, tls_config)

Create HTTPOverQUICProtocol managing an HTTP connection.

Param

tls_config: TLS configuration This is necessary for HTTP/3 connections because TLS handshake happens at the QUIC layer.

Returns

a fresh instance of an HTTP protocol

Return type

HTTPOverQUICProtocol

abstract property quic_connection_id_length: int

Length in bytes of QUIC connection IDs.

Can be used by servers to sniff and route QUIC packets before thay are passed to a protocol instance.

abstract property quic_supported_versions: Sequence[int]

List of supported QUIC versions.

Can be used by servers to sniff and route QUIC packets before thay are passed to a protocol instance.

class hface.protocols.ALPNHTTPFactory(factories, default_alpn_protocol='http/1.1')

A factory that select between other factories based on ALPN.

Implements HTTPOverTCPFactory.

Parameters
  • factories (Sequence[HTTPOverTCPFactory]) – supported protocols

  • default_alpn_protocol (str) – ALPN of the default protocol

Registry of implementations

class hface.protocols.ProtocolRegistry

Registry of protocol implementations

http1_servers: dict[str, hface.protocols._factories.HTTPOverTCPFactory]

HTTP/1 server implementations

http2_servers: dict[str, hface.protocols._factories.HTTPOverTCPFactory]

HTTP/2 server implementations

http3_servers: dict[str, hface.protocols._factories.HTTPOverQUICServerFactory]

HTTP/3 server implementations

http1_clients: dict[str, hface.protocols._factories.HTTPOverTCPFactory]

HTTP/1 client implementations

http2_clients: dict[str, hface.protocols._factories.HTTPOverTCPFactory]

HTTP/2 client implementations

http3_clients: dict[str, hface.protocols._factories.HTTPOverQUICClientFactory]

HTTP/3 client implementations

load()

Load known implementations.

Combines load_defaults() and load_entry_points().

load_defaults()

Load default protocol implementations.

load_entry_points(prefix='hface.protocols')

Load protocol implementations registered with setuptools entrypoints.

Name of the entrypoint must follow the format: "hface.protocols.http{1,2,3}_{servers,clients}"

Value of the entrypoint must be in the format "<module>:<attr>", where <module> is a dotted path to Python module and <attr> is an attribute in that module.

hface.protocols.protocol_registry

Global instance of ProtocolRegistry

Use setuptools entrypoints to register new implementations: See ProtocolRegistry.load_entry_points() how.

Type

ProtocolRegistry

Default implementations

class hface.protocols.http1.HTTP1ClientFactory

Creates a default HTTP/1 protocol for client-side usage.

The HTTP/1 implementation is built on the top of the h11 library.

Implements HTTPOverTCPFactory.

class hface.protocols.http1.HTTP1ServerFactory

Creates a default HTTP/1 protocol for server-side usage.

The HTTP/1 implementation is built on the top of the h11 library.

Implements HTTPOverTCPFactory.

class hface.protocols.http2.HTTP2ClientFactory

Creates a default HTTP/2 protocol for client-side usage.

The HTTP/2 implementation is built on the top of the Hyper h2 library.

Implements HTTPOverTCPFactory.

class hface.protocols.http2.HTTP2ServerFactory

Creates a default HTTP/2 protocol for server-side usage.

The HTTP/2 implementation is built on the top of the Hyper h2 library.

Implements HTTPOverTCPFactory.

class hface.protocols.http3.HTTP3ClientFactory

Creates a default HTTP/3 protocol for client-side usage.

The HTTP/3 implementation is built on the top of the aioquic library.

Implements HTTPOverQUICClientFactory.

class hface.protocols.http3.HTTP3ServerFactory

Creates a default HTTP/3 protocol for server-side usage.

The HTTP/3 implementation is built on the top of the aioquic library.

Implements HTTPOverQUICServerFactory.