Skip to content
This repository was archived by the owner on Jul 6, 2018. It is now read-only.

Commit 4e47406

Browse files
committed
http2: initial partial implementation
Squashed rollup of all the progress to this point
1 parent 7d2b39d commit 4e47406

26 files changed

+5906
-2
lines changed

configure

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ shared_optgroup = optparse.OptionGroup(parser, "Shared libraries",
6464
intl_optgroup = optparse.OptionGroup(parser, "Internationalization",
6565
"Flags that lets you enable i18n features in Node.js as well as which "
6666
"library you want to build against.")
67+
http2_optgroup = optparse.OptionGroup(parser, "HTTP2",
68+
"Flags that allows you to control HTTP2 features in Node.js")
6769

6870
# Options should be in alphabetical order but keep --prefix at the top,
6971
# that's arguably the one people will be looking for most.
@@ -397,6 +399,11 @@ intl_optgroup.add_option('--download-path',
397399

398400
parser.add_option_group(intl_optgroup)
399401

402+
http2_optgroup.add_option('--debug-nghttp2',
403+
action='store_true',
404+
dest='debug_nghttp2',
405+
help='Build nghttp2 with DEBUGBUILD')
406+
400407
parser.add_option('--with-perfctr',
401408
action='store_true',
402409
dest='with_perfctr',
@@ -896,6 +903,11 @@ def configure_node(o):
896903
if options.enable_static:
897904
o['variables']['node_target_type'] = 'static_library'
898905

906+
if options.debug_nghttp2:
907+
o['variables']['debug_nghttp2'] = 1
908+
else:
909+
o['variables']['debug_nghttp2'] = 'false'
910+
899911
o['variables']['node_no_browser_globals'] = b(options.no_browser_globals)
900912
o['variables']['node_shared'] = b(options.shared)
901913
node_module_version = getmoduleversion.get_version()

deps/nghttp2/nghttp2.gyp

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
'target_name': 'nghttp2',
1010
'type': '<(library)',
1111
'include_dirs': ['lib/includes'],
12+
'conditions': [
13+
['debug_nghttp2 == 1', {
14+
'defines': [ 'DEBUGBUILD=1' ]
15+
}]
16+
],
1217
'direct_dependent_settings': {
1318
'include_dirs': [ 'lib/includes' ]
1419
},

doc/http2-implementation-notes.md

+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
**Note:** This is currently in a massive state of flux.
2+
3+
## Overview
4+
5+
The HTTP/2 implementation is built around the nghttp2 library, which does most
6+
of the heavy lifting. The nghttp2 library has been added into deps/nghttp2.
7+
The `src/node_http2.cc` class is largely a wrapper for that nghttp2 API.
8+
Defined within that file the following classes (currently):
9+
10+
* `node::http2::Http2Header` - Wraps the `nghttp_nv` struct used to represent header name-value pairs.
11+
* `node::http2::Http2DataProvider` - Wraps the data provider construct used by nghttp2 to provide data to data frames
12+
* `node::http2::Http2Stream` - Represents an nghttp2 stream
13+
* `node::http2::Http2Session` - Wraps the nghttp2_session struct.
14+
15+
The code within `lib/internal/http2.js` provides the actual implementation of
16+
the HTTP/2 server. At this point in time, the client code has not yet been
17+
implemented.
18+
19+
**Note**: My process up to this point has been on getting something working then
20+
iterating on the design and implementation to improve it. As such, the current
21+
implementation leaves much to be desired. I will be iterating and refining it
22+
as we move forward.
23+
24+
The server is implemented as follows:
25+
26+
First we create either a `net.Server` or a `tls.Server`, depending on whether
27+
TLS is being used or not. This server instance is passed a connectionListener
28+
callback. When a new socket connection is established and the callback is
29+
invoked, a new `Http2Session` object instance is created and associated with
30+
that socket. Because the HTTP/2 session lives for lifetime of socket connection,
31+
this session is persistent.
32+
33+
A series of event handlers and registered on both the socket and the
34+
`Http2Session` to facilitate the flow of data back and forth between the two.
35+
Note, however, that the performance of this could be improved by moving the
36+
socket handling into the native layer. Doing so would allow us to skip the
37+
boundary crossing that has to occur.
38+
39+
## Example
40+
41+
```js
42+
const fs = require('fs');
43+
const http2 = require('http').HTTP2;
44+
const options = {
45+
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
46+
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
47+
};
48+
49+
const server = http2.createSecureServer(options, (req, res) => {
50+
51+
res.writeHead(200, {'content-type': 'text/html'});
52+
53+
const favicon = res.createPushResponse();
54+
favicon.path = '/favicon.ico';
55+
favicon.push((req, res) => {
56+
res.setHeader('content-type', 'image/jpeg');
57+
fs.createReadStream('/some/image.jpg').pipe(res);
58+
});
59+
60+
const pushResponse = res.createPushResponse();
61+
pushResponse.path = '/image.jpg';
62+
pushResponse.push((req, res) => {
63+
res.setHeader('content-type', 'image/jpeg');
64+
fs.createReadStream('/some/image/jpg').pipe(res);
65+
});
66+
67+
res.end('<html><head><link rel="preload" href="/favicon.ico"/></head><body><h1>this is some data</h2><img src="/image.jpg" /></body></html>');
68+
69+
});
70+
server.listen(8000);
71+
```
72+
73+
## class HTTP2.Http2Settings
74+
75+
Encapsulates the HTTP/2 settings supported by this implementation.
76+
77+
### Constructor: `new HTTP2.Http2Settings()`
78+
### Property: `settings.maxHeaderListSize` (Read-Write)
79+
### Property: `settings.maxFrameSize` (Read-Write)
80+
### Property: `settings.initialWindowSize` (Read-Write)
81+
### Property: `settings.maxConcurrentStreams` (Read-Write)
82+
### Property: `settings.enablePush` (Read-Write)
83+
### Property: `settings.headerTableSize` (Read-Write)
84+
### Method: `settings.pack()`
85+
### Method: `settings.reset()`
86+
### Method: `settings.setDefaults()`
87+
88+
## class HTTP2.Http2Header
89+
90+
Encapsulates an individual HTTP/2 header.
91+
92+
### Constructor: `new HTTP2.Http2Header(name, value)`
93+
### Property: `header.name` (Read-only)
94+
### Property: `header.value` (Read-only)
95+
### Property: `header.flags` (Read-Write)
96+
97+
## class HTTP2.Http2Session : EventEmitter {}
98+
99+
### Event: `'send'`
100+
101+
The `'send'` event is emitted whenever the `HTTP2.Http2Session` instance has
102+
data prepared to send to a remote peer. The event callback is invoked with a
103+
single `Buffer` argument containing the serialized frames to be sent.
104+
105+
```js
106+
const session = getSessionSomehow();
107+
const socket = getSocketSomehow();
108+
session.on('send', (buffer) => socket.write(buffer));
109+
```
110+
111+
### Event: `'begin-headers'`
112+
113+
The `'begin-headers'` event is emitted at the beginning of a new HEADERS
114+
frame. The event callback is invoked with two arguments: an `Http2Stream`
115+
object representing the associated HTTP/2 stream, and a category identifying
116+
the type of HEADERS frame received. This type is determined by the underlying
117+
nghttp2 library based on the HTTP/2 stream state.
118+
119+
```js
120+
const constants = require('http').HTTP2.constants;
121+
const session = getSessionSomehow();
122+
const socket = getSocketSomehow();
123+
session.on('begin-headers', (stream, category) => {
124+
console.log(stream.id);
125+
switch (category) {
126+
case constants.NGHTTP2_HCAT_REQUEST:
127+
case constants.NGHTTP2_HCAT_RESPONSE:
128+
case constants.NGHTTP2_HCAT_PUSH_RESPONSE:
129+
case constants.NGHTTP2_HCAT_HEADERS:
130+
}
131+
});
132+
```
133+
134+
### Event: `'header'`
135+
136+
The `'header'` event is emitted once for each header name-value pair received
137+
during the processing of a HEADERS frame. The event may be called zero-or-more
138+
times following the emission of the `'begin-headers'` event. The callback is
139+
invoked with three arguments: an `Http2Stream` object representing the
140+
associated HTTP/2 stream, the header field name passed as a String, and the
141+
header field value passed as a String.
142+
143+
```js
144+
const session = getSessionSomehow();
145+
const socket = getSocketSomehow();
146+
session.on('header', (stream, name, value) => {
147+
console.log('Header Field:', name);
148+
console.log('Header Value:', value);
149+
});
150+
```
151+
152+
### Event: `'headers-complete'`
153+
154+
The `'headers-complete'` event is emitted once a complete HEADERS frame has
155+
been processed and all `'header'` events have been emitted. The callback is
156+
invoked with two arguments: an `Http2Stream` object representing the
157+
associated HTTP/2 stream, and a `finished` boolean used to indicate if the
158+
HEADERS block concluded the HTTP/2 stream or not.
159+
160+
```js
161+
const session = getSessionSomehow();
162+
const socket = getSocketSomehow();
163+
session.on('headers-complete', (stream, finished) => {
164+
// ...
165+
});
166+
```
167+
168+
### Event: `'stream-close'`
169+
170+
The `'stream-close'` event is emitted whenever an HTTP/2 stream is closed,
171+
either by normal or early termination. The callback is invoked with two
172+
arguments: an `Http2Stream` object representing the associated HTTP/2 stream,
173+
and an Unsigned 32-bit integer that represents the error code (if any).
174+
175+
```js
176+
const session = getSessionSomehow();
177+
const socket = getSocketSomehow();
178+
session.on('stream-close', (stream, code) => {
179+
console.log(`Stream ${stream.id} closed with code ${code}`);
180+
});
181+
```
182+
183+
### Event: `'data-chunk'`
184+
185+
The `'data-chunk'` event is emitted whenever a chunk of data from a DATA frame
186+
has been received. The callback is invoked with two arguments: an
187+
`Http2Stream` object representing the associated stream, and a `Buffer`
188+
instance containing the chunk of data.
189+
190+
```js
191+
const session = getSessionSomehow();
192+
const socket = getSocketSomehow();
193+
session.on('data-chunk', (stream, chunk) => {
194+
// ...
195+
});
196+
```
197+
198+
### Event: `'data'`
199+
200+
The `'data'` event is emitted whenever a complete DATA frame has been
201+
processed. This event will follow zero-or-more `'data-chunk'` events. The
202+
callback is invoked with three arguments: an `Http2Stream` object representing
203+
the associated HTTP/2 stream, a boolean indicating whether or not the DATA
204+
frame completed the stream, and a non-negative integer indicating the number
205+
of padding bytes included in the data frame.
206+
207+
### Event: `'frame-sent'`
208+
209+
The `'frame-sent'` event is emitted whenever a compete HTTP/2 frame has been
210+
sent.
211+
212+
### Event: `'goaway'`
213+
214+
The `'goaway'` event is emitted when a GOAWAY frame is received.
215+
216+
### Event: `'rst-stream'`
217+
218+
The `'rst-stream'` event is emitted when a RST-STREAM frame is received.
219+
220+
### Property: `session.deflateDynamicTableSize` (Read-only)
221+
### Property: `session.effectiveLocalWindowSize` (Read-only)
222+
### Property: `session.effectiveRecvDataLength` (Read-only)
223+
### Property: `session.inflateDynamicTableSize` (Read-only)
224+
### Property: `session.lastProcStreamID` (Read-only)
225+
### Property: `session.localSettings` (Read-Write)
226+
### Property: `session.localWindowSize` (Read-Write)
227+
### Property: `session.nextStreamID` (Read-Write)
228+
### Property: `session.outboundQueueSize` (Read-only)
229+
### Property: `session.remoteSettings` (Read-only)
230+
### Property: `session.remoteWindowSize` (Read-only)
231+
### Property: `session.type` (Read-only)
232+
### Property: `session.wantRead` (Read-only)
233+
### Property: `session.wantWrite` (Read-only)
234+
235+
### Method: `session.consume(stream, size)`
236+
### Method: `session.consumeSession(size)`
237+
### Method: `session.createIdleStream(stream, parent, weight, exclusive)`
238+
### Method: `session.destroy()`
239+
### Method: `session.ping(buf)`
240+
### Method: `session.receiveData(data)`
241+
### Method: `session.sendData()`
242+
### Method: `session.sendWindowUpdate(increment)`
243+
### Method: `session.terminate(code)`
244+
245+
## HTTP2.Http2Stream
246+
247+
### Property: `stream.id` (Read-only)
248+
### Property: `stream.localWindowSize` (Read-Write)
249+
### Property: `stream.localClose` (Read-only)
250+
### Property: `stream.remoteClose` (Read-only)
251+
### Property: `stream.session` (Read-only)
252+
### Property: `stream.state` (Read-only)
253+
### Property: `stream.sumDependencyWeight` (Read-only)
254+
### Property: `stream.weight` (Read-only)
255+
256+
### Method: `stream.changeStreamPriority(parent, weight, exclusive)`
257+
### Method: `stream.consume(size)`
258+
### Method: `stream.sendContinue()`
259+
### Method: `stream.sendDataFrame(flags, provider)`
260+
### Method: `stream.sendPriority(paret weight, exclusive)`
261+
### Method: `stream.sendRstStream(code)`
262+
### Method: `stream.sendTrailers(trailers)`
263+
### Method: `stream.sendWindowUpdate(increment)`
264+
### Method: `stream.respond(headers, provider)`
265+
### Method: `stream.resumeData()`
266+
267+
## HTTP2.Http2Request : extends stream.Readable
268+
269+
### Property: `request.headers` (Read-only)
270+
### Property: `request.method` (Read-only)
271+
### Property: `request.authority` (Read-only)
272+
### Property: `request.scheme` (Read-only)
273+
### Property: `request.url` (Read-only)
274+
### Property: `request.httpVersion` (Read-only)
275+
### Property: `request.socket` (Read-only)
276+
### Property: `request.trailers` (Read-only)
277+
### Method: `request.setTimeout(msec, callback)`
278+
279+
## HTTP2.Http2Response : ends stream.Writable
280+
281+
### Property: `response.sendDate` (Read-Write)
282+
### Property: `response.socket` (Read-only)
283+
### Property: `response.finished` (Read-only)
284+
### Property: `response.headersSent` (Read-only)
285+
### Property: `response.pushSupported` (Read-only)
286+
### Property: `response.statusCode` (Read-Write)
287+
### Method: `response.setHeader(name, value)`
288+
### Method: `response.setTrailer(name, value)`
289+
### Method: `response.addHeaders(headers)`
290+
### Method: `response.addTrailers(headers)`
291+
### Method: `response.getHeader(name)`
292+
### Method: `response.getTrailer(name)`
293+
### Method: `response.removeHeader(name)`
294+
### Method: `response.removeTrailer(name)`
295+
### Method: `response.setTimeout(msec, callback)`
296+
### Method: `response.writeContinue()`
297+
### Method: `response.writeHeader(statusCode, headers)`
298+
### Method: `response.write()`
299+
### Method: `response.end()`
300+
### Method: `response.createPushResponse()`
301+
302+
## HTTP2.createServerSession(options)
303+
304+
* `options` {Object}
305+
* `maxDeflateDynamicTableSize` {Number}
306+
* `maxReservedRemoteStreams` {Number}
307+
* `maxSendHeaderBlockLength` {Number}
308+
* `noAutoPingAck` {Boolean}
309+
* `noAutoWindowUpdate` {Boolean}
310+
* `noHttpMessaging` {Boolean}
311+
* `noRecvClientMagic` {Boolean}
312+
* `peerMaxConcurrentStreams` {Number}
313+
314+
## HTTP2.createClientSession(options)
315+
316+
* `options` {Object}
317+
* `maxDeflateDynamicTableSize` {Number}
318+
* `maxReservedRemoteStreams` {Number}
319+
* `maxSendHeaderBlockLength` {Number}
320+
* `noAutoPingAck` {Boolean}
321+
* `noAutoWindowUpdate` {Boolean}
322+
* `noHttpMessaging` {Boolean}
323+
* `noRecvClientMagic` {Boolean}
324+
* `peerMaxConcurrentStreams` {Number}
325+
326+
## HTTP2.createServer(options, callback)
327+
328+
## HTTP2.createSecureServer(options, callback)
329+
330+
## HTTP2.createClient(options)

lib/http.js

100644100755
File mode changed.

lib/http2.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const core = require('internal/http2/core');
4+
//const compat = require('internal/http2/compat');
5+
6+
// Exports
7+
module.exports = {
8+
constants: core.constants,
9+
getDefaultSettings: core.getDefaultSettings,
10+
getPackedSettings: core.getPackedSettings,
11+
createServer: core.createServer,
12+
createSecureServer: core.createSecureServer,
13+
createServerSession: core.createServerSession,
14+
connect: core.connect,
15+
secureConnect: core.secureConnect
16+
};

0 commit comments

Comments
 (0)