Behaviours: gen_server.
A minimal HTTP/2 client to support gRPC.
The client implements only those features that are required by gRPC, it is
not a complete implementation of the HTTP/2 spec. In particular is does not
support:
- Push promise. The gRPC protocol does not use it.
The initial SETTINGS frame sent by the client will disable push promise
messages, and if a push promise is received from the server this will
result in a protocol error.
- Priorization and dependency between streams. The API will not offer any
support for sending PRIORITY frames to the server, and any PRIORITY
frame that is received will be ignored.
General structure:
Starting a new connection will spawn a process. This process opens a
socket. The socket is set to 'active_once', so that it will send messages
to the connection process. The api will also send messages to the
process, in order to send or retrieve messages.
(Note that there may be another process involved in the gRPC framework
to deal with streams, in order to be able to provide a synchronous API on
that level).
The connection process will manage the following information:
- socket (so that the certificate and other properties can be accessed)
- streams, each with its own set of information
- flow control information on the level of the connection, for both sides
(client and server).
- header compression contexts: "One compression context and one
decompression context are used for the entire connection." (All HEADERS
and CONTINUATION frames must be decompressed! Even PUSH_PROMISE frames
must be decompressed, otherwise this will be screwed.)
- Settings, as exchanged via SETTINGS frames. Both for the client and for
the server side.
- MAX_CONCURRENT_STREAMS (if we want to support it)
- INITIAL_FLOW_CONTROL_WINDOW
- SETTINGS_HEADER_TABLE_SIZE
(- SETTINGS_ENABLE_PUSH - Client will always disable it, server is
irrelevant)
- SETTINGS_INITIAL_WINDOW_SIZE
- SETTINGS_MAX_FRAME_SIZE
- SETTINGS_MAX_HEADER_LIST_SIZE (perhaps this can be igored)
- Id of the last stream created, so that the correct id can be assigned
to the next stream.
for each stream it will manage:
- stream id
- state (idle, open, half-open)
- flow control information on the level of the connection, for both sides
(client and server).
flow control information will be:
- remaining bytes in window - what can still be sent/received (client and server)
- window size (client and server)
For the client side also information for the management of the flow
control is required: a callback function and its state.
When the socket receives a packet, it will send a message to the connection
process (because of the active_once setting). This will take care of the
http/2 framing and if it decides that a complete frame has been received,
it will send a message to the process that created the stream. (Note
that that is not the Connection process itself, which receives the
messages from the socket).
The picture below shows how grpc_client interfaces with http2_client. http2_client
can of course also be used by other applications that need a simple http/2 client.
+<--scope of--->+<------- scope of http2_client -------->+
+ grpc_client + +
+ + +
Application + + Connection Socket + Server
| + + | | + |
|------+-----new-------+------>| | | + |
| + host, scheme | |-------new--------->| | + |
| + + | | | |--connect----> | |
| + + | | | | + |
|<-----+----c_pid------+-------| | | | + |
| + + | | + |
| + Stream + | | + |
| + | + | | + |
|-----new---->| | + | | + |
| c_pid | |--new stream->| | | + |
| + | | s_pid | |-------preface----->| | + |
| + | | + | | +settings | |-------+------->|
| + | | + | | | + |
| + | | + | | | |<---settings----|
| + | | + | |<-------------------| | + |
| + | | + | | | + |
| + | | + | | | |<-----ack-------|
| + | | + | |<-------------------| | + |
| + | | + | | | + |
| + | | + | |--------ack-------->| | + |
| + | | + |Register stream | | + |
|<----s_pid---| |<-----ok------| | | |-------+------->|
| + | + | | + |
| + | + | | + |
|-----send--->| | + | | + |
| + | |------+------>| | | + |
| + | + | |-------message----->| | + |
|-----rcv---->| | + | | |-------+------->|
| + |Wait... + | | + |
| + | | + | | + |
| + | | + | | + |
| + | | + | | |<---message-----|
| + | | + | |<-------------------| | + |
| + | | + |Find stream | | + |
| + | |<-----+-------| | | | + |
|<-----+------| | + | | + |
| + | + | | + |
| + | + | | + |
connection() = #{socket := gen_tcp:socket() | ssl:sslsocket(), state := open | closed_by_peer, buffer := <<>>, host := string(), port := integer(), transport := gen_tcp | ssl, streams := [stream()], client_settings := settings(), server_settings := settings(), client_window := integer(), window_mgmt_fun := function(), window_mgmt_data := any(), server_window := integer(), decode_state := cow_hpack:state(), encode_state := cow_hpack:state(), last_stream := integer(), continuation := continuation_data() | undefined, ping_sent := [{binary(), integer()}], stream_count := integer()}
connection_option() = {window_mgmt_fun, window_manager()} | {window_mgmt_data, any()} | {max_concurrent_streams, integer() | undefined} | {initial_window_size, integer()} | {max_frame_size, integer()} | {transport_options, [ssl:ssl_option()] | [gen_tcp:option()]}
continuation_data() = #{stream_id => integer(), buffer := binary(), end_stream := boolean()}
header() = [{Name::binary, Value::binary()}]
send_option() = {end_stream, boolean()}
settings() = #{max_concurrent_streams := integer() | undefined, initial_window_size := integer(), max_frame_size := integer()}
stream() = #{id := integer(), client_window := integer(), window_mgmt_fun := function(), window_mgmt_data := any(), server_window := integer(), owner := pid()}
stream_id() = integer()
stream_option() = {window_mgmt_fun, function()} | {window_mgmt_data, any()}
window_manager() = fun((integer(), integer(), integer(), any()) -> {{window_update, integer()}, any} | {ok, any()})
| close/1 | Close (stop/clean up) the connection. |
| connection_window_update/2 | Increment the connection window. |
| new_connection/4 | Open a new connection. |
| new_stream/2 | Create a new stream. |
| peercert/1 | The peer certificate is returned as a DER-encoded binary. |
| ping/1 | Equivalent to ping(Connection, infinity).
|
| ping/2 | Send a PING frame. |
| rst_stream/3 | Send a RST_STREAM frame. |
| send_data/4 | Send a data frame. |
| send_headers/4 | Send headers. |
| stream_window_update/3 | Increment the stream window. |
close(Pid::connection()) -> ok
Close (stop/clean up) the connection.
connection_window_update(Pid::connection(), Increment::integer()) -> ok | {error, term()}
Increment the connection window. Note that this typically handled via the window management function (either a custom one if that has been provided when the stream was created, or the default one).
new_connection(Transport::tcp | ssl, Host::string(), Port::integer(), Options::[connection_option()]) -> {ok, connection()} | {error, term()}
Open a new connection.
new_stream(Connection::connection(), Options::[stream_option()]) -> {ok, stream_id()} | {error, term()}
Create a new stream.
peercert(Pid::pid()) -> {ok, Cert::binary()} | {error, Reason::term()}
The peer certificate is returned as a DER-encoded binary. The certificate can be decoded with public_key:pkix_decode_cert/2.
ping(Connection::pid()) -> {ok, RoundTripTime::integer()} | {error, term()}
Equivalent to ping(Connection, infinity).
ping(Connection::pid(), Timeout::timeout()) -> {ok, RoundTripTime::integer()} | {error, term()}
Send a PING frame.
The PING frame is a mechanism for measuring a minimal round-trip time from the sender, as well as determining whether an idle connection is still functional.
Will return {error, timeout} if no answer was received within the specified period.rst_stream(Pid::connection(), StreamId::stream_id(), ErrorCode::integer()) -> ok | {error, term()}
Send a RST_STREAM frame
send_data(Pid::connection(), StreamId::stream_id(), Data::binary(), Options::[send_option()]) -> ok | {error, term()}
Send a data frame.
send_headers(Pid::connection(), StreamId::stream_id(), Headers::[header()], Options::[send_option()]) -> ok | {error, term()}
Send headers.
stream_window_update(Pid::connection(), StreamId::stream_id(), Increment::integer()) -> ok | {error, term()}
Increment the stream window. Note that this typically handled via the window management function (either a custom one if that has been provided when the stream was created, or the default one).
Generated by EDoc