Note: This is currently in a massive state of flux.
The HTTP/2 implementation is built around the nghttp2 library, which does most
of the heavy lifting. The nghttp2 library has been added into deps/nghttp2.
The src/node_http2.cc
class is largely a wrapper for that nghttp2 API.
Defined within that file the following classes (currently):
node::http2::Http2Header
- Wraps thenghttp_nv
struct used to represent header name-value pairs.node::http2::Http2DataProvider
- Wraps the data provider construct used by nghttp2 to provide data to data framesnode::http2::Http2Stream
- Represents an nghttp2 streamnode::http2::Http2Session
- Wraps the nghttp2_session struct.
The code within lib/internal/http2.js
provides the actual implementation of
the HTTP/2 server. At this point in time, the client code has not yet been
implemented.
Note: My process up to this point has been on getting something working then iterating on the design and implementation to improve it. As such, the current implementation leaves much to be desired. I will be iterating and refining it as we move forward.
The server is implemented as follows:
First we create either a net.Server
or a tls.Server
, depending on whether
TLS is being used or not. This server instance is passed a connectionListener
callback. When a new socket connection is established and the callback is
invoked, a new Http2Session
object instance is created and associated with
that socket. Because the HTTP/2 session lives for lifetime of socket connection,
this session is persistent.
A series of event handlers and registered on both the socket and the
Http2Session
to facilitate the flow of data back and forth between the two.
Note, however, that the performance of this could be improved by moving the
socket handling into the native layer. Doing so would allow us to skip the
boundary crossing that has to occur.
const fs = require('fs');
const http2 = require('http').HTTP2;
const options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
const server = http2.createSecureServer(options, (req, res) => {
res.writeHead(200, {'content-type': 'text/html'});
const favicon = res.createPushResponse();
favicon.path = '/favicon.ico';
favicon.push((req, res) => {
res.setHeader('content-type', 'image/jpeg');
fs.createReadStream('/some/image.jpg').pipe(res);
});
const pushResponse = res.createPushResponse();
pushResponse.path = '/image.jpg';
pushResponse.push((req, res) => {
res.setHeader('content-type', 'image/jpeg');
fs.createReadStream('/some/image/jpg').pipe(res);
});
res.end('<html><head><link rel="preload" href="/favicon.ico"/></head><body><h1>this is some data</h2><img src="/image.jpg" /></body></html>');
});
server.listen(8000);
Encapsulates the HTTP/2 settings supported by this implementation.
Encapsulates an individual HTTP/2 header.
The 'send'
event is emitted whenever the HTTP2.Http2Session
instance has
data prepared to send to a remote peer. The event callback is invoked with a
single Buffer
argument containing the serialized frames to be sent.
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('send', (buffer) => socket.write(buffer));
The 'begin-headers'
event is emitted at the beginning of a new HEADERS
frame. The event callback is invoked with two arguments: an Http2Stream
object representing the associated HTTP/2 stream, and a category identifying
the type of HEADERS frame received. This type is determined by the underlying
nghttp2 library based on the HTTP/2 stream state.
const constants = require('http').HTTP2.constants;
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('begin-headers', (stream, category) => {
console.log(stream.id);
switch (category) {
case constants.NGHTTP2_HCAT_REQUEST:
case constants.NGHTTP2_HCAT_RESPONSE:
case constants.NGHTTP2_HCAT_PUSH_RESPONSE:
case constants.NGHTTP2_HCAT_HEADERS:
}
});
The 'header'
event is emitted once for each header name-value pair received
during the processing of a HEADERS frame. The event may be called zero-or-more
times following the emission of the 'begin-headers'
event. The callback is
invoked with three arguments: an Http2Stream
object representing the
associated HTTP/2 stream, the header field name passed as a String, and the
header field value passed as a String.
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('header', (stream, name, value) => {
console.log('Header Field:', name);
console.log('Header Value:', value);
});
The 'headers-complete'
event is emitted once a complete HEADERS frame has
been processed and all 'header'
events have been emitted. The callback is
invoked with two arguments: an Http2Stream
object representing the
associated HTTP/2 stream, and a finished
boolean used to indicate if the
HEADERS block concluded the HTTP/2 stream or not.
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('headers-complete', (stream, finished) => {
// ...
});
The 'stream-close'
event is emitted whenever an HTTP/2 stream is closed,
either by normal or early termination. The callback is invoked with two
arguments: an Http2Stream
object representing the associated HTTP/2 stream,
and an Unsigned 32-bit integer that represents the error code (if any).
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('stream-close', (stream, code) => {
console.log(`Stream ${stream.id} closed with code ${code}`);
});
The 'data-chunk'
event is emitted whenever a chunk of data from a DATA frame
has been received. The callback is invoked with two arguments: an
Http2Stream
object representing the associated stream, and a Buffer
instance containing the chunk of data.
const session = getSessionSomehow();
const socket = getSocketSomehow();
session.on('data-chunk', (stream, chunk) => {
// ...
});
The 'data'
event is emitted whenever a complete DATA frame has been
processed. This event will follow zero-or-more 'data-chunk'
events. The
callback is invoked with three arguments: an Http2Stream
object representing
the associated HTTP/2 stream, a boolean indicating whether or not the DATA
frame completed the stream, and a non-negative integer indicating the number
of padding bytes included in the data frame.
The 'frame-sent'
event is emitted whenever a compete HTTP/2 frame has been
sent.
The 'goaway'
event is emitted when a GOAWAY frame is received.
The 'rst-stream'
event is emitted when a RST-STREAM frame is received.
options
{Object}maxDeflateDynamicTableSize
{Number}maxReservedRemoteStreams
{Number}maxSendHeaderBlockLength
{Number}noAutoPingAck
{Boolean}noAutoWindowUpdate
{Boolean}noHttpMessaging
{Boolean}noRecvClientMagic
{Boolean}peerMaxConcurrentStreams
{Number}
options
{Object}maxDeflateDynamicTableSize
{Number}maxReservedRemoteStreams
{Number}maxSendHeaderBlockLength
{Number}noAutoPingAck
{Boolean}noAutoWindowUpdate
{Boolean}noHttpMessaging
{Boolean}noRecvClientMagic
{Boolean}peerMaxConcurrentStreams
{Number}