Pure Ruby implementation of HTTP/2 protocol
Pure Ruby, framework and transport agnostic, implementation of HTTP/2 protocol and HPACK header compression with support for:
Protocol specifications:
$> gem install http-2This implementation makes no assumptions as how the data is delivered: it could be a regular Ruby TCP socket, your custom eventloop, or whatever other transport you wish to use - e.g. ZeroMQ, avian carriers, etc.
Your code is responsible for feeding data into the parser, which performs all of the necessary HTTP/2 decoding, state management and the rest, and vice versa, the parser will emit bytes (encoded HTTP/2 frames) that you can then route to the destination. Roughly, this works as follows:
require 'http/2'
socket = YourTransport.new
conn = HTTP2::Client.new
conn.on(:frame) {|bytes| socket 'post', ...}, end_stream: false)
stream.data(payload, end_stream: true)Events emitted by the connection object:
:promise
client role only, fires once for each new push promise
:stream
server role only, fires once for each new client stream
:frame
fires once for every encoded HTTP/2 frame that needs to be sent to the peerConnection shutdown happens in two steps. Once a GOAWAY frame is sent or
received, no new streams may be opened: closed? turns true and new_stream
raises ConnectionClosed — new work belongs on a new connection. After a
graceful GOAWAY (:no_error), streams the peer may still complete keep
running: closing? distinguishes this draining state, in which the connection
keeps processing frames — including connection-level flow control — until the
last tracked stream closes. Streams the GOAWAY refused (id above its
last-stream-id) are reported via the :goaway event, so their callers can
retry them on a fresh connection.
A single HTTP/2 connection can multiplex multiple streams in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
The good news is, all of the stream management, and state transitions, and error checking is handled by the library. All you have to do is subscribe to appropriate events (marked with ":" prefix in diagram below) and provide your application logic to handle request and response processing.
…Events emitted by the Stream object:
:reserved
fires exactly once when a push stream is initialized
:active
fires exactly once when the stream become active and is counted towards the open stream limit
:headers
fires once for each received header block (multi-frame blocks are reassembled before emitting this event)
:data
fires once for every DATA frame (no buffering)
:half_close
fires exactly once when the opposing peer closes its end of connection (e.g. client indicating that request is finished, or server indicating that response is finished)
:close
fires exactly once when both peers close the stream, or if the stream is reset
:priority
fires once for each received priority update (server only)Each HTTP/2 stream has a priority value that can be sent when the new stream is initialized, and optionally reprioritized later:
client = HTTP2::Client.new
default_priority_stream = client.new_stream
custom_priority_stream = client.new_stream(priority: 42)
# sometime later: change priority value
custom_priority_stream.reprioritize(32000) # emits PRIORITY frameOn the opposite side, the server can optimize its stream processing order or resource allocation by accessing the stream priority value (stream.priority).
Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP/2 provides a simple mechanism for stream and connection flow control.
Connection and stream flow control is handled by the library: all streams are initialized with the default window size (64KB), and send/receive window updates are automatically processed - i.e. window is decremented on outgoing data transfers, and incremented on receipt of window frames. Similarly, if the window is exceeded, then data frames are automatically buffered until window is updated.
The only thing left is for your application to specify the logic as to when to emit window updates:
conn.buffered_amount # check amount of buffered data
conn.window # check current window size
conn.window_update(1024) # increment connection window by 1024 bytes
stream.buffered_amount # check amount of buffered data
stream.window # check current window size
stream.window_update(2048) # increment stream window by 2048 bytesAn HTTP/2 server can send multiple replies to a single client request. To do so, first it emits a "push promise" frame which contains the headers of the promised resource, followed by the response to the original request, as well as promised resource payloads (which may be interleaved). A simple example is in order:
…When a new push promise stream is sent by the server, the client is notified via the :promise event:
conn = HTTP2::Client.new
conn.on(:promise) do |push|
# process push stream
endThe client can cancel any given push stream (via .close), or disable server push entirely by sending the appropriate settings frame:
client.settings(settings_enable_push: 0)To run specs:
rake(MIT License) - Copyright (c) 2013-2019 Ilya Grigorik (MIT License) - Copyright (c) 2019 Tiago Cardoso
No open issues yet, or sync has not completed.