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:
HTTPOverTCPProtocol
should be extended by HTTP/1 and HTTP/2 implementations. It has methods likebytes_received()
andbytes_to_send()
.HTTPOverQUICProtocol
should be extended by HTTP/3 implementations. It has methods likedatagram_received()
anddatagrams_to_send()
.
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.
HTTPOverTCPFactory
is an interface for creatingHTTPOverTCPProtocol
instances.HTTPOverQUICServerFactory
andHTTPOverQUICClientFactory
are interfaces for creatingHTTPOverQUICProtocol
instances.
hface offers default protocols for all HTTP versions:
hface.protocols.http1
offersHTTP1ClientFactory
andHTTP1ServerFactory
hface.protocols.http2
offersHTTP2ClientFactory
andHTTP2ServerFactory
hface.protocols.http3
offersHTTP3ClientFactory
andHTTP3ServerFactory
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 connectionAn 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 connectionAbstract 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
- 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
- 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
- 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()
andload_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
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
.