At long last: The initial *experimental* implementation of HTTP/2.

This is an accumulation of the work that has been done in the nodejs/http2
repository, squashed down to a couple of commits. The original commit
history has been preserved in the nodejs/http2 repository.

This PR introduces the nghttp2 C library as a new dependency. This library
provides the majority of the HTTP/2 protocol implementation, with the rest
of the code here providing the mapping of the library into a usable JS API.

Within src, a handful of new node_http2_*.c and node_http2_*.h files are
introduced. These provide the internal mechanisms that interface with nghttp
and define the `process.binding('http2')` interface.

The JS API is defined within `internal/http2/*.js`.

There are two APIs provided: Core and Compat.

The Core API is HTTP/2 specific and is designed to be as minimal and as
efficient as possible.

The Compat API is intended to be as close to the existing HTTP/1 API as
possible, with some exceptions.

Tests, documentation and initial benchmarks are included.

The `http2` module is gated by a new `--expose-http2` command line flag.
When used, `require('http2')` will be exposed to users. Note that there
is an existing `http2` module on npm that would be impacted by the introduction
of this module, which is the main reason for gating this behind a flag.

When using `require('http2')` the first time, a process warning will be
emitted indicating that an experimental feature is being used.

To run the benchmarks, the `h2load` tool (part of the nghttp project) is
required: `./node benchmarks/http2/simple.js benchmarker=h2load`. Only
two benchmarks are currently available.

Additional configuration options to enable verbose debugging are provided:

```
$ ./configure --debug-http2 --debug-nghttp2
$ NODE_DEBUG=http2 ./node
```

The `--debug-http2` configuration option enables verbose debug statements
from the `src/node_http2_*` files. The `--debug-nghttp2` enables the nghttp
library's own verbose debug output. The `NODE_DEBUG=http2` enables JS-level
debug output.

The following illustrates as simple HTTP/2 server and client interaction:

(The HTTP/2 client and server support both plain text and TLS connections)

```jt client = http2.connect('http://localhost:80');
const req = client.request({ ':path': '/some/path' });
req.on('data', (chunk) => { /* do something with the data */ });
req.on('end', () => {
  client.destroy();
});

// Plain text (non-TLS server)
const server = http2.createServer();
server.on('stream', (stream, requestHeaders) => {
  stream.respond({ ':status': 200 });
  stream.write('hello ');
  stream.end('world');
});
server.listen(80);
```

```js
const http2 = require('http2');
const client = http2.connect('http://localhost');

```

Author: Anna Henningsen <anna@addaleax.net>
Author: Colin Ihrig <cjihrig@gmail.com>
Author: Daniel Bevenius <daniel.bevenius@gmail.com>
Author: James M Snell <jasnell@gmail.com>
Author: Jun Mukai
Author: Kelvin Jin
Author: Matteo Collina <matteo.collina@gmail.com>
Author: Robert Kowalski <rok@kowalski.gd>
Author: Santiago Gimeno <santiago.gimeno@gmail.com>
Author: Sebastiaan Deckers <sebdeckers83@gmail.com>
Author: Yosuke Furukawa <yosuke.furukawa@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/14239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2017-07-17 10:17:16 -07:00
Родитель 71a1876f6c
Коммит e71e71b513
35 изменённых файлов: 9061 добавлений и 7 удалений

22
configure поставляемый
Просмотреть файл

@ -64,6 +64,8 @@ shared_optgroup = optparse.OptionGroup(parser, "Shared libraries",
intl_optgroup = optparse.OptionGroup(parser, "Internationalization",
"Flags that lets you enable i18n features in Node.js as well as which "
"library you want to build against.")
http2_optgroup = optparse.OptionGroup(parser, "HTTP2",
"Flags that allows you to control HTTP2 features in Node.js")
# Options should be in alphabetical order but keep --prefix at the top,
# that's arguably the one people will be looking for most.
@ -397,6 +399,16 @@ intl_optgroup.add_option('--download-path',
parser.add_option_group(intl_optgroup)
http2_optgroup.add_option('--debug-http2',
action='store_true',
dest='debug_http2',
help='build with http2 debug statements on (default is false)')
http2_optgroup.add_option('--debug-nghttp2',
action='store_true',
dest='debug_nghttp2',
help='build nghttp2 with DEBUGBUILD (default is false)')
parser.add_option('--with-perfctr',
action='store_true',
dest='with_perfctr',
@ -898,6 +910,16 @@ def configure_node(o):
if options.enable_static:
o['variables']['node_target_type'] = 'static_library'
if options.debug_http2:
o['variables']['debug_http2'] = 1
else:
o['variables']['debug_http2'] = 'false'
if options.debug_nghttp2:
o['variables']['debug_nghttp2'] = 1
else:
o['variables']['debug_nghttp2'] = 'false'
o['variables']['node_no_browser_globals'] = b(options.no_browser_globals)
o['variables']['node_shared'] = b(options.shared)
node_module_version = getmoduleversion.get_version()

Просмотреть файл

@ -24,6 +24,7 @@
* [File System](fs.html)
* [Globals](globals.html)
* [HTTP](http.html)
* [HTTP/2](http2.html)
* [HTTPS](https.html)
* [Inspector](inspector.html)
* [Internationalization](intl.html)

Просмотреть файл

@ -170,6 +170,13 @@ added: v6.0.0
Silence all process warnings (including deprecations).
### `--expose-http2`
<!-- YAML
added: REPLACEME
-->
Enable the experimental `'http2'` module.
### `--napi-modules`
<!-- YAML
added: v8.0.0

Просмотреть файл

@ -624,6 +624,190 @@ Used for status codes outside the regular status code ranges (100-999).
Used when the `Trailer` header is set even though the transfer encoding does not
support that.
<a id="ERR_HTTP2_CONNECT_AUTHORITY"></a>
### ERR_HTTP2_CONNECT_AUTHORITY
For HTTP/2 requests using the `CONNECT` method, the `:authority` pseudo-header
is required.
<a id="ERR_HTTP2_CONNECT_PATH"></a>
### ERR_HTTP2_CONNECT_PATH
For HTTP/2 requests using the `CONNECT` method, the `:path` pseudo-header is
forbidden.
<a id="ERR_HTTP2_CONNECT_SCHEME"></a>
### ERR_HTTP2_CONNECT_SCHEME
The HTTP/2 requests using the `CONNECT` method, the `:scheme` pseudo-header is
forbidden.
<a id="ERR_HTTP2_ERROR"></a>
### ERR_HTTP2_ERROR
A non-specific HTTP/2 error has occurred.
<a id="ERR_HTTP2_FRAME_ERROR"></a>
### ERR_HTTP2_FRAME_ERROR
Used when a failure occurs sending an individual frame on the HTTP/2
session.
<a id="ERR_HTTP2_HEADERS_OBJECT"></a>
### ERR_HTTP2_HEADERS_OBJECT
Used when an HTTP/2 Headers Object is expected.
<a id="ERR_HTTP2_HEADERS_SENT"></a>
### ERR_HTTP2_HEADERS_SENT
Used when an attempt is made to send multiple response headers.
<a id="ERR_HTTP2_HEADER_SINGLE_VALUE"></a>
### ERR_HTTP2_HEADER_SINGLE_VALUE
Used when multiple values have been provided for an HTTP header field that
required to have only a single value.
<a id="ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND"></a>
### ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND
HTTP/2 Informational headers must only be sent *prior* to calling the
`Http2Stream.prototype.respond()` method.
<a id="ERR_HTTP2_INFO_STATUS_NOT_ALLOWED"></a>
### ERR_HTTP2_INFO_STATUS_NOT_ALLOWED
Informational HTTP status codes (`1xx`) may not be set as the response status
code on HTTP/2 responses.
<a id="ERR_HTTP2_INVALID_CONNECTION_HEADERS"></a>
### ERR_HTTP2_INVALID_CONNECTION_HEADERS
HTTP/1 connection specific headers are forbidden to be used in HTTP/2
requests and responses.
<a id="ERR_HTTP2_INVALID_HEADER_VALUE"></a>
### ERR_HTTP2_INVALID_HEADER_VALUE
Used to indicate that an invalid HTTP/2 header value has been specified.
<a id="ERR_HTTP2_INVALID_INFO_STATUS"></a>
### ERR_HTTP2_INVALID_INFO_STATUS
An invalid HTTP informational status code has been specified. Informational
status codes must be an integer between `100` and `199` (inclusive).
<a id="ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH"></a>
Input `Buffer` and `Uint8Array` instances passed to the
`http2.getUnpackedSettings()` API must have a length that is a multiple of
six.
<a id="ERR_HTTP2_INVALID_PSEUDOHEADER"></a>
### ERR_HTTP2_INVALID_PSEUDOHEADER
Only valid HTTP/2 pseudoheaders (`:status`, `:path`, `:authority`, `:scheme`,
and `:method`) may be used.
<a id="ERR_HTTP2_INVALID_SESSION"></a>
### ERR_HTTP2_INVALID_SESSION
Used when any action is performed on an `Http2Session` object that has already
been destroyed.
<a id="ERR_HTTP2_INVALID_SETTING_VALUE"></a>
### ERR_HTTP2_INVALID_SETTING_VALUE
An invalid value has been specified for an HTTP/2 setting.
<a id="ERR_HTTP2_INVALID_STREAM"></a>
### ERR_HTTP2_INVALID_STREAM
Used when an operation has been performed on a stream that has already been
destroyed.
<a id="ERR_HTTP2_MAX_PENDING_SETTINGS_ACK"></a>
### ERR_HTTP2_MAX_PENDING_SETTINGS_ACK
Whenever an HTTP/2 `SETTINGS` frame is sent to a connected peer, the peer is
required to send an acknowledgement that it has received and applied the new
SETTINGS. By default, a maximum number of un-acknowledged `SETTINGS` frame may
be sent at any given time. This error code is used when that limit has been
reached.
<a id="ERR_HTTP2_OUT_OF_STREAMS"></a>
### ERR_HTTP2_OUT_OF_STREAMS
Used when the maximum number of streams on a single HTTP/2 session have been
created.
<a id="ERR_HTTP2_PAYLOAD_FORBIDDEN"></a>
### ERR_HTTP2_PAYLOAD_FORBIDDEN
Used when a message payload is specified for an HTTP response code for which
a payload is forbidden.
<a id="ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED"></a>
### ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED
Used to indicate that an HTTP/2 pseudo-header has been used inappropriately.
Pseudo-headers are header key names that begin with the `:` prefix.
<a id="ERR_HTTP2_PUSH_DISABLED"></a>
### ERR_HTTP2_PUSH_DISABLED
Used when push streams have been disabled by the client but an attempt to
create a push stream is made.
<a id="ERR_HTTP2_SEND_FILE"></a>
### ERR_HTTP2_SEND_FILE
Used when an attempt is made to use the
`Http2Stream.prototype.responseWithFile()` API to send a non-regular file.
<a id="ERR_HTTP2_SOCKET_BOUND"></a>
### ERR_HTTP2_SOCKET_BOUND
Used when an attempt is made to connect a `Http2Session` object to a
`net.Socket` or `tls.TLSSocket` that has already been bound to another
`Http2Session` object.
<a id="ERR_HTTP2_STATUS_101"></a>
### ERR_HTTP2_STATUS_101
Use of the `101` Informational status code is forbidden in HTTP/2.
<a id="ERR_HTTP2_STATUS_INVALID"></a>
### ERR_HTTP2_STATUS_INVALID
An invalid HTTP status code has been specified. Status codes must be an integer
between `100` and `599` (inclusive).
<a id="ERR_HTTP2_STREAM_CLOSED"></a>
### ERR_HTTP2_STREAM_CLOSED
Used when an action has been performed on an HTTP/2 Stream that has already
been closed.
<a id="ERR_HTTP2_STREAM_ERROR"></a>
### ERR_HTTP2_STREAM_ERROR
Used when a non-zero error code has been specified in an RST_STREAM frame.
<a id="ERR_HTTP2_STREAM_SELF_DEPENDENCY"></a>
### ERR_HTTP2_STREAM_SELF_DEPENDENCY
When setting the priority for an HTTP/2 stream, the stream may be marked as
a dependency for a parent stream. This error code is used when an attempt is
made to mark a stream and dependent of itself.
<a id="ERR_HTTP2_UNSUPPORTED_PROTOCOL"></a>
### ERR_HTTP2_UNSUPPORTED_PROTOCOL
Used when `http2.connect()` is passed a URL that uses any protocol other than
`http:` or `https:`.
<a id="ERR_INDEX_OUT_OF_RANGE"></a>
### ERR_INDEX_OUT_OF_RANGE

1704
doc/api/http2.md Executable file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -41,6 +41,14 @@ benchmarker to be used should be specified by providing it as an argument:
`node benchmark/http/simple.js benchmarker=autocannon`
#### HTTP/2 Benchmark Requirements
To run the `http2` benchmarks, the `h2load` benchmarker must be used. The
`h2load` tool is a component of the `nghttp2` project and may be installed
from [nghttp.org][] or built from source.
`node benchmark/http2/simple.js benchmarker=autocannon`
### Benchmark Analysis Requirements
To analyze the results, `R` should be installed. Use one of the available
@ -423,3 +431,4 @@ Supported options keys are:
[wrk]: https://github.com/wg/wrk
[t-test]: https://en.wikipedia.org/wiki/Student%27s_t-test#Equal_or_unequal_sample_sizes.2C_unequal_variances
[git-for-windows]: http://git-scm.com/download/win
[nghttp2.org]: http://nghttp2.org

Просмотреть файл

@ -130,6 +130,10 @@ Emit pending deprecation warnings.
.BR \-\-no\-warnings
Silence all process warnings (including deprecations).
.TP
.BR \-\-expose\-http2
Enable the experimental `'http2'` module.
.TP
.BR \-\-napi\-modules
Enable loading native modules compiled with the ABI-stable Node.js API (N-API)

0
lib/_http_outgoing.js Executable file → Normal file
Просмотреть файл

27
lib/http2.js Normal file
Просмотреть файл

@ -0,0 +1,27 @@
'use strict';
process.emitWarning(
'The http2 module is an experimental API.',
'ExperimentalWarning', undefined,
'See https://github.com/nodejs/http2'
);
const {
constants,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
createServer,
createSecureServer,
connect
} = require('internal/http2/core');
module.exports = {
constants,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
createServer,
createSecureServer,
connect
};

7
lib/internal/bootstrap_node.js поставляемый
Просмотреть файл

@ -478,6 +478,11 @@
NativeModule._source = process.binding('natives');
NativeModule._cache = {};
const config = process.binding('config');
if (!config.exposeHTTP2)
delete NativeModule._source.http2;
NativeModule.require = function(id) {
if (id === 'native_module') {
return NativeModule;
@ -516,8 +521,6 @@
return NativeModule._source.hasOwnProperty(id);
};
const config = process.binding('config');
if (config.exposeInternals) {
NativeModule.nonInternalExists = NativeModule.exists;

Просмотреть файл

@ -118,6 +118,69 @@ E('ERR_HTTP_HEADERS_SENT',
E('ERR_HTTP_INVALID_STATUS_CODE', 'Invalid status code: %s');
E('ERR_HTTP_TRAILER_INVALID',
'Trailers are invalid with this transfer encoding');
E('ERR_HTTP_INVALID_CHAR', 'Invalid character in statusMessage.');
E('ERR_HTTP_INVALID_STATUS_CODE',
(originalStatusCode) => `Invalid status code: ${originalStatusCode}`);
E('ERR_HTTP2_CONNECT_AUTHORITY',
':authority header is required for CONNECT requests');
E('ERR_HTTP2_CONNECT_PATH',
'The :path header is forbidden for CONNECT requests');
E('ERR_HTTP2_CONNECT_SCHEME',
'The :scheme header is forbidden for CONNECT requests');
E('ERR_HTTP2_FRAME_ERROR',
(type, code, id) => {
let msg = `Error sending frame type ${type}`;
if (id !== undefined)
msg += ` for stream ${id}`;
msg += ` with code ${code}`;
return msg;
});
E('ERR_HTTP2_HEADER_REQUIRED',
(name) => `The ${name} header is required`);
E('ERR_HTTP2_HEADER_SINGLE_VALUE',
(name) => `Header field "${name}" must have only a single value`);
E('ERR_HTTP2_HEADERS_OBJECT', 'Headers must be an object');
E('ERR_HTTP2_HEADERS_SENT', 'Response has already been initiated.');
E('ERR_HTTP2_HEADERS_AFTER_RESPOND',
'Cannot specify additional headers after response initiated');
E('ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND',
'Cannot send informational headers after the HTTP message has been sent');
E('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED',
'Informational status codes cannot be used');
E('ERR_HTTP2_INVALID_CONNECTION_HEADERS',
'HTTP/1 Connection specific headers are forbidden');
E('ERR_HTTP2_INVALID_HEADER_VALUE', 'Value must not be undefined or null');
E('ERR_HTTP2_INVALID_INFO_STATUS',
(code) => `Invalid informational status code: ${code}`);
E('ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH',
'Packed settings length must be a multiple of six');
E('ERR_HTTP2_INVALID_PSEUDOHEADER',
(name) => `"${name}" is an invalid pseudoheader or is used incorrectly`);
E('ERR_HTTP2_INVALID_SESSION', 'The session has been destroyed');
E('ERR_HTTP2_INVALID_STREAM', 'The stream has been destroyed');
E('ERR_HTTP2_INVALID_SETTING_VALUE',
(name, value) => `Invalid value for setting "${name}": ${value}`);
E('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
(max) => `Maximum number of pending settings acknowledgements (${max})`);
E('ERR_HTTP2_PAYLOAD_FORBIDDEN',
(code) => `Responses with ${code} status must not have a payload`);
E('ERR_HTTP2_OUT_OF_STREAMS',
'No stream ID is available because maximum stream ID has been reached');
E('ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED', 'Cannot set HTTP/2 pseudo-headers');
E('ERR_HTTP2_PUSH_DISABLED', 'HTTP/2 client has disabled push streams');
E('ERR_HTTP2_SEND_FILE', 'Only regular files can be sent');
E('ERR_HTTP2_SOCKET_BOUND',
'The socket is already bound to an Http2Session');
E('ERR_HTTP2_STATUS_INVALID',
(code) => `Invalid status code: ${code}`);
E('ERR_HTTP2_STATUS_101',
'HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2');
E('ERR_HTTP2_STREAM_CLOSED', 'The stream is already closed');
E('ERR_HTTP2_STREAM_ERROR',
(code) => `Stream closed with error code ${code}`);
E('ERR_HTTP2_STREAM_SELF_DEPENDENCY', 'A stream cannot depend on itself');
E('ERR_HTTP2_UNSUPPORTED_PROTOCOL',
(protocol) => `protocol "${protocol}" is unsupported.`);
E('ERR_INDEX_OUT_OF_RANGE', 'Index out of range');
E('ERR_INVALID_ARG_TYPE', invalidArgType);
E('ERR_INVALID_ARRAY_LENGTH',
@ -173,6 +236,7 @@ E('ERR_SOCKET_BAD_TYPE',
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
E('ERR_SOCKET_CLOSED', 'Socket is closed');
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
E('ERR_OUTOFMEMORY', 'Out of memory');
E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode');

0
lib/internal/http.js Executable file → Normal file
Просмотреть файл

Просмотреть файл

@ -0,0 +1,570 @@
'use strict';
const Stream = require('stream');
const Readable = Stream.Readable;
const binding = process.binding('http2');
const constants = binding.constants;
const errors = require('internal/errors');
const kFinish = Symbol('finish');
const kBeginSend = Symbol('begin-send');
const kState = Symbol('state');
const kStream = Symbol('stream');
const kRequest = Symbol('request');
const kResponse = Symbol('response');
const kHeaders = Symbol('headers');
const kTrailers = Symbol('trailers');
let statusMessageWarned = false;
// Defines and implements an API compatibility layer on top of the core
// HTTP/2 implementation, intended to provide an interface that is as
// close as possible to the current require('http') API
function assertValidHeader(name, value) {
if (isPseudoHeader(name))
throw new errors.Error('ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED');
if (value === undefined || value === null)
throw new errors.TypeError('ERR_HTTP2_INVALID_HEADER_VALUE');
}
function isPseudoHeader(name) {
switch (name) {
case ':status':
return true;
case ':method':
return true;
case ':path':
return true;
case ':authority':
return true;
case ':scheme':
return true;
default:
return false;
}
}
function onStreamData(chunk) {
const request = this[kRequest];
if (!request.push(chunk))
this.pause();
}
function onStreamEnd() {
// Cause the request stream to end as well.
const request = this[kRequest];
request.push(null);
}
function onStreamError(error) {
const request = this[kRequest];
request.emit('error', error);
}
function onRequestPause() {
const stream = this[kStream];
stream.pause();
}
function onRequestResume() {
const stream = this[kStream];
stream.resume();
}
function onRequestDrain() {
if (this.isPaused())
this.resume();
}
function onStreamResponseDrain() {
const response = this[kResponse];
response.emit('drain');
}
function onStreamResponseError(error) {
const response = this[kResponse];
response.emit('error', error);
}
function onStreamClosedRequest() {
const req = this[kRequest];
req.push(null);
}
function onStreamClosedResponse() {
const res = this[kResponse];
res.writable = false;
res.emit('finish');
}
function onAborted(hadError, code) {
if ((this.writable) ||
(this._readableState && !this._readableState.ended)) {
this.emit('aborted', hadError, code);
}
}
class Http2ServerRequest extends Readable {
constructor(stream, headers, options) {
super(options);
this[kState] = {
statusCode: null,
closed: false,
closedCode: constants.NGHTTP2_NO_ERROR
};
this[kHeaders] = headers;
this[kStream] = stream;
stream[kRequest] = this;
// Pause the stream..
stream.pause();
stream.on('data', onStreamData);
stream.on('end', onStreamEnd);
stream.on('error', onStreamError);
stream.on('close', onStreamClosedRequest);
stream.on('aborted', onAborted.bind(this));
const onfinish = this[kFinish].bind(this);
stream.on('streamClosed', onfinish);
stream.on('finish', onfinish);
this.on('pause', onRequestPause);
this.on('resume', onRequestResume);
this.on('drain', onRequestDrain);
}
get closed() {
const state = this[kState];
return Boolean(state.closed);
}
get code() {
const state = this[kState];
return Number(state.closedCode);
}
get stream() {
return this[kStream];
}
get statusCode() {
return this[kState].statusCode;
}
get headers() {
return this[kHeaders];
}
get rawHeaders() {
const headers = this[kHeaders];
if (headers === undefined)
return [];
const tuples = Object.entries(headers);
const flattened = Array.prototype.concat.apply([], tuples);
return flattened.map(String);
}
get trailers() {
return this[kTrailers];
}
get httpVersionMajor() {
return 2;
}
get httpVersionMinor() {
return 0;
}
get httpVersion() {
return '2.0';
}
get socket() {
return this.stream.session.socket;
}
get connection() {
return this.socket;
}
_read(nread) {
const stream = this[kStream];
if (stream) {
stream.resume();
} else {
throw new errors.Error('ERR_HTTP2_STREAM_CLOSED');
}
}
get method() {
const headers = this[kHeaders];
if (headers === undefined)
return;
return headers[constants.HTTP2_HEADER_METHOD];
}
get authority() {
const headers = this[kHeaders];
if (headers === undefined)
return;
return headers[constants.HTTP2_HEADER_AUTHORITY];
}
get scheme() {
const headers = this[kHeaders];
if (headers === undefined)
return;
return headers[constants.HTTP2_HEADER_SCHEME];
}
get url() {
return this.path;
}
set url(url) {
this.path = url;
}
get path() {
const headers = this[kHeaders];
if (headers === undefined)
return;
return headers[constants.HTTP2_HEADER_PATH];
}
set path(path) {
let headers = this[kHeaders];
if (headers === undefined)
headers = this[kHeaders] = Object.create(null);
headers[constants.HTTP2_HEADER_PATH] = path;
}
setTimeout(msecs, callback) {
const stream = this[kStream];
if (stream === undefined) return;
stream.setTimeout(msecs, callback);
}
[kFinish](code) {
const state = this[kState];
if (state.closed)
return;
state.closedCode = code;
state.closed = true;
this.push(null);
this[kStream] = undefined;
}
}
class Http2ServerResponse extends Stream {
constructor(stream, options) {
super(options);
this[kState] = {
sendDate: true,
statusCode: constants.HTTP_STATUS_OK,
headerCount: 0,
trailerCount: 0,
closed: false,
closedCode: constants.NGHTTP2_NO_ERROR
};
this[kStream] = stream;
stream[kResponse] = this;
this.writable = true;
stream.on('drain', onStreamResponseDrain);
stream.on('error', onStreamResponseError);
stream.on('close', onStreamClosedResponse);
stream.on('aborted', onAborted.bind(this));
const onfinish = this[kFinish].bind(this);
stream.on('streamClosed', onfinish);
stream.on('finish', onfinish);
}
get finished() {
const stream = this[kStream];
return stream === undefined || stream._writableState.ended;
}
get closed() {
const state = this[kState];
return Boolean(state.closed);
}
get code() {
const state = this[kState];
return Number(state.closedCode);
}
get stream() {
return this[kStream];
}
get headersSent() {
const stream = this[kStream];
return stream.headersSent;
}
get sendDate() {
return Boolean(this[kState].sendDate);
}
set sendDate(bool) {
this[kState].sendDate = Boolean(bool);
}
get statusCode() {
return this[kState].statusCode;
}
set statusCode(code) {
const state = this[kState];
code |= 0;
if (code >= 100 && code < 200)
throw new errors.RangeError('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED');
if (code < 200 || code > 599)
throw new errors.RangeError('ERR_HTTP2_STATUS_INVALID', code);
state.statusCode = code;
}
addTrailers(headers) {
let trailers = this[kTrailers];
const keys = Object.keys(headers);
let key = '';
if (keys.length > 0)
return;
if (trailers === undefined)
trailers = this[kTrailers] = Object.create(null);
for (var i = 0; i < keys.length; i++) {
key = String(keys[i]).trim().toLowerCase();
const value = headers[key];
assertValidHeader(key, value);
trailers[key] = String(value);
}
}
getHeader(name) {
const headers = this[kHeaders];
if (headers === undefined)
return;
name = String(name).trim().toLowerCase();
return headers[name];
}
getHeaderNames() {
const headers = this[kHeaders];
if (headers === undefined)
return [];
return Object.keys(headers);
}
getHeaders() {
const headers = this[kHeaders];
return Object.assign({}, headers);
}
hasHeader(name) {
const headers = this[kHeaders];
if (headers === undefined)
return false;
name = String(name).trim().toLowerCase();
return Object.prototype.hasOwnProperty.call(headers, name);
}
removeHeader(name) {
const headers = this[kHeaders];
if (headers === undefined)
return;
name = String(name).trim().toLowerCase();
delete headers[name];
}
setHeader(name, value) {
name = String(name).trim().toLowerCase();
assertValidHeader(name, value);
let headers = this[kHeaders];
if (headers === undefined)
headers = this[kHeaders] = Object.create(null);
headers[name] = String(value);
}
flushHeaders() {
if (this[kStream].headersSent === false)
this[kBeginSend]();
}
writeHead(statusCode, statusMessage, headers) {
if (typeof statusMessage === 'string' && statusMessageWarned === false) {
process.emitWarning(
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
'UnsupportedWarning'
);
statusMessageWarned = true;
}
if (headers === undefined && typeof statusMessage === 'object') {
headers = statusMessage;
}
if (headers) {
const keys = Object.keys(headers);
let key = '';
for (var i = 0; i < keys.length; i++) {
key = keys[i];
this.setHeader(key, headers[key]);
}
}
this.statusCode = statusCode;
}
write(chunk, encoding, cb) {
const stream = this[kStream];
if (typeof encoding === 'function') {
cb = encoding;
encoding = 'utf8';
}
if (stream === undefined) {
const err = new errors.Error('ERR_HTTP2_STREAM_CLOSED');
if (cb)
process.nextTick(cb, err);
else
throw err;
return;
}
this[kBeginSend]();
return stream.write(chunk, encoding, cb);
}
end(chunk, encoding, cb) {
const stream = this[kStream];
if (typeof chunk === 'function') {
cb = chunk;
chunk = null;
encoding = 'utf8';
} else if (typeof encoding === 'function') {
cb = encoding;
encoding = 'utf8';
}
if (chunk !== null && chunk !== undefined) {
this.write(chunk, encoding);
}
if (typeof cb === 'function' && stream !== undefined) {
stream.once('finish', cb);
}
this[kBeginSend]({endStream: true});
if (stream !== undefined) {
stream.end();
}
}
destroy(err) {
const stream = this[kStream];
if (stream === undefined) {
// nothing to do, already closed
return;
}
stream.destroy(err);
}
setTimeout(msecs, callback) {
const stream = this[kStream];
if (stream === undefined) return;
stream.setTimeout(msecs, callback);
}
sendContinue(headers) {
this.sendInfo(100, headers);
}
sendInfo(code, headers) {
const stream = this[kStream];
if (stream.headersSent === true) {
throw new errors.Error('ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND');
}
if (headers && typeof headers !== 'object')
throw new errors.TypeError('ERR_HTTP2_HEADERS_OBJECT');
if (stream === undefined) return;
code |= 0;
if (code < 100 || code >= 200)
throw new errors.RangeError('ERR_HTTP2_INVALID_INFO_STATUS', code);
headers[constants.HTTP2_HEADER_STATUS] = code;
stream.respond(headers);
}
createPushResponse(headers, callback) {
const stream = this[kStream];
if (stream === undefined) {
throw new errors.Error('ERR_HTTP2_STREAM_CLOSED');
}
stream.pushStream(headers, {}, function(stream, headers, options) {
const response = new Http2ServerResponse(stream);
callback(null, response);
});
}
[kBeginSend](options) {
const stream = this[kStream];
if (stream !== undefined && stream.headersSent === false) {
const state = this[kState];
const headers = this[kHeaders] || Object.create(null);
headers[constants.HTTP2_HEADER_STATUS] = state.statusCode;
if (stream.finished === true)
options.endStream = true;
if (stream.destroyed === false) {
stream.respond(headers, options);
}
}
}
[kFinish](code) {
const state = this[kState];
if (state.closed)
return;
state.closedCode = code;
state.closed = true;
this.end();
this[kStream] = undefined;
this.emit('finish');
}
}
function onServerStream(stream, headers, flags) {
const server = this;
const request = new Http2ServerRequest(stream, headers);
const response = new Http2ServerResponse(stream);
// Check for the CONNECT method
const method = headers[constants.HTTP2_HEADER_METHOD];
if (method === 'CONNECT') {
if (!server.emit('connect', request, response)) {
response.statusCode = constants.HTTP_STATUS_METHOD_NOT_ALLOWED;
response.end();
}
return;
}
// Check for Expectations
if (headers.expect !== undefined) {
if (headers.expect === '100-continue') {
if (server.listenerCount('checkContinue')) {
server.emit('checkContinue', request, response);
} else {
response.sendContinue();
server.emit('request', request, response);
}
} else if (server.listenerCount('checkExpectation')) {
server.emit('checkExpectation', request, response);
} else {
response.statusCode = constants.HTTP_STATUS_EXPECTATION_FAILED;
response.end();
}
return;
}
server.emit('request', request, response);
}
module.exports = { onServerStream };

2392
lib/internal/http2/core.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

513
lib/internal/http2/util.js Normal file
Просмотреть файл

@ -0,0 +1,513 @@
'use strict';
const binding = process.binding('http2');
const errors = require('internal/errors');
const {
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_COOKIE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_LOCATION,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_USER_AGENT,
HTTP2_HEADER_CONNECTION,
HTTP2_HEADER_UPGRADE,
HTTP2_HEADER_HTTP2_SETTINGS,
HTTP2_HEADER_TE,
HTTP2_HEADER_TRANSFER_ENCODING,
HTTP2_HEADER_HOST,
HTTP2_HEADER_KEEP_ALIVE,
HTTP2_HEADER_PROXY_CONNECTION,
HTTP2_METHOD_DELETE,
HTTP2_METHOD_GET,
HTTP2_METHOD_HEAD
} = binding.constants;
// This set is defined strictly by the HTTP/2 specification. Only
// :-prefixed headers defined by that specification may be added to
// this set.
const kValidPseudoHeaders = new Set([
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH
]);
// This set contains headers that are permitted to have only a single
// value. Multiple instances must not be specified.
const kSingleValueHeaders = new Set([
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_LOCATION,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_USER_AGENT
]);
// The HTTP methods in this set are specifically defined as assigning no
// meaning to the request payload. By default, unless the user explicitly
// overrides the endStream option on the request method, the endStream
// option will be defaulted to true when these methods are used.
const kNoPayloadMethods = new Set([
HTTP2_METHOD_DELETE,
HTTP2_METHOD_GET,
HTTP2_METHOD_HEAD
]);
// The following ArrayBuffer instances are used to share memory more efficiently
// with the native binding side for a number of methods. These are not intended
// to be used directly by users in any way. The ArrayBuffers are created on
// the native side with values that are filled in on demand, the js code then
// reads those values out. The set of IDX constants that follow identify the
// relevant data positions within these buffers.
const settingsBuffer = new Uint32Array(binding.settingsArrayBuffer);
const optionsBuffer = new Uint32Array(binding.optionsArrayBuffer);
// Note that Float64Array is used here because there is no Int64Array available
// and these deal with numbers that can be beyond the range of Uint32 and Int32.
// The values set on the native side will always be integers. This is not a
// unique example of this, this pattern can be found in use in other parts of
// Node.js core as a performance optimization.
const sessionState = new Float64Array(binding.sessionStateArrayBuffer);
const streamState = new Float64Array(binding.streamStateArrayBuffer);
const IDX_SETTINGS_HEADER_TABLE_SIZE = 0;
const IDX_SETTINGS_ENABLE_PUSH = 1;
const IDX_SETTINGS_INITIAL_WINDOW_SIZE = 2;
const IDX_SETTINGS_MAX_FRAME_SIZE = 3;
const IDX_SETTINGS_MAX_CONCURRENT_STREAMS = 4;
const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;
const IDX_SETTINGS_FLAGS = 6;
const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;
const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;
const IDX_SESSION_STATE_NEXT_STREAM_ID = 2;
const IDX_SESSION_STATE_LOCAL_WINDOW_SIZE = 3;
const IDX_SESSION_STATE_LAST_PROC_STREAM_ID = 4;
const IDX_SESSION_STATE_REMOTE_WINDOW_SIZE = 5;
const IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE = 6;
const IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE = 7;
const IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE = 8;
const IDX_STREAM_STATE = 0;
const IDX_STREAM_STATE_WEIGHT = 1;
const IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT = 2;
const IDX_STREAM_STATE_LOCAL_CLOSE = 3;
const IDX_STREAM_STATE_REMOTE_CLOSE = 4;
const IDX_STREAM_STATE_LOCAL_WINDOW_SIZE = 5;
const IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 0;
const IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS = 1;
const IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH = 2;
const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
const IDX_OPTIONS_PADDING_STRATEGY = 4;
const IDX_OPTIONS_FLAGS = 5;
function updateOptionsBuffer(options) {
var flags = 0;
if (typeof options.maxDeflateDynamicTableSize === 'number') {
flags |= (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE);
optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE] =
options.maxDeflateDynamicTableSize;
}
if (typeof options.maxReservedRemoteStreams === 'number') {
flags |= (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS);
optionsBuffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS] =
options.maxReservedRemoteStreams;
}
if (typeof options.maxSendHeaderBlockLength === 'number') {
flags |= (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH);
optionsBuffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH] =
options.maxSendHeaderBlockLength;
}
if (typeof options.peerMaxConcurrentStreams === 'number') {
flags |= (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS);
optionsBuffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS] =
options.peerMaxConcurrentStreams;
}
if (typeof options.paddingStrategy === 'number') {
flags |= (1 << IDX_OPTIONS_PADDING_STRATEGY);
optionsBuffer[IDX_OPTIONS_PADDING_STRATEGY] =
options.paddingStrategy;
}
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
binding.refreshDefaultSettings();
const holder = Object.create(null);
const flags = settingsBuffer[IDX_SETTINGS_FLAGS];
if ((flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) ===
(1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
holder.headerTableSize =
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
}
if ((flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) ===
(1 << IDX_SETTINGS_ENABLE_PUSH)) {
holder.enablePush =
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] === 1;
}
if ((flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) ===
(1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
holder.initialWindowSize =
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
}
if ((flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) ===
(1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
holder.maxFrameSize =
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE];
}
if ((flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) ===
(1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
console.log('setting it');
holder.maxConcurrentStreams =
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
}
if ((flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) ===
(1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
holder.maxHeaderListSize =
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
}
return holder;
}
// remote is a boolean. true to fetch remote settings, false to fetch local.
// this is only called internally
function getSettings(session, remote) {
const holder = Object.create(null);
if (remote)
binding.refreshRemoteSettings(session);
else
binding.refreshLocalSettings(session);
holder.headerTableSize =
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
holder.enablePush =
!!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH];
holder.initialWindowSize =
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
holder.maxFrameSize =
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE];
holder.maxConcurrentStreams =
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
holder.maxHeaderListSize =
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
return holder;
}
function updateSettingsBuffer(settings) {
var flags = 0;
if (typeof settings.headerTableSize === 'number') {
flags |= (1 << IDX_SETTINGS_HEADER_TABLE_SIZE);
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
settings.headerTableSize;
}
if (typeof settings.maxConcurrentStreams === 'number') {
flags |= (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS);
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
settings.maxConcurrentStreams;
}
if (typeof settings.initialWindowSize === 'number') {
flags |= (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE);
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
settings.initialWindowSize;
}
if (typeof settings.maxFrameSize === 'number') {
flags |= (1 << IDX_SETTINGS_MAX_FRAME_SIZE);
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
settings.maxFrameSize;
}
if (typeof settings.maxHeaderListSize === 'number') {
flags |= (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
settings.maxHeaderListSize;
}
if (typeof settings.enablePush === 'boolean') {
flags |= (1 << IDX_SETTINGS_ENABLE_PUSH);
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = Number(settings.enablePush);
}
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
}
function getSessionState(session) {
const holder = Object.create(null);
binding.refreshSessionState(session);
holder.effectiveLocalWindowSize =
sessionState[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE];
holder.effectiveRecvDataLength =
sessionState[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH];
holder.nextStreamID =
sessionState[IDX_SESSION_STATE_NEXT_STREAM_ID];
holder.localWindowSize =
sessionState[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE];
holder.lastProcStreamID =
sessionState[IDX_SESSION_STATE_LAST_PROC_STREAM_ID];
holder.remoteWindowSize =
sessionState[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE];
holder.outboundQueueSize =
sessionState[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE];
holder.deflateDynamicTableSize =
sessionState[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE];
holder.inflateDynamicTableSize =
sessionState[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE];
return holder;
}
function getStreamState(session, stream) {
const holder = Object.create(null);
binding.refreshStreamState(session, stream);
holder.state =
streamState[IDX_STREAM_STATE];
holder.weight =
streamState[IDX_STREAM_STATE_WEIGHT];
holder.sumDependencyWeight =
streamState[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT];
holder.localClose =
streamState[IDX_STREAM_STATE_LOCAL_CLOSE];
holder.remoteClose =
streamState[IDX_STREAM_STATE_REMOTE_CLOSE];
holder.localWindowSize =
streamState[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE];
return holder;
}
function isIllegalConnectionSpecificHeader(name, value) {
switch (name) {
case HTTP2_HEADER_CONNECTION:
case HTTP2_HEADER_UPGRADE:
case HTTP2_HEADER_HOST:
case HTTP2_HEADER_HTTP2_SETTINGS:
case HTTP2_HEADER_KEEP_ALIVE:
case HTTP2_HEADER_PROXY_CONNECTION:
case HTTP2_HEADER_TRANSFER_ENCODING:
return true;
case HTTP2_HEADER_TE:
const val = Array.isArray(value) ? value.join(', ') : value;
return val !== 'trailers';
default:
return false;
}
}
function assertValidPseudoHeader(key) {
if (!kValidPseudoHeaders.has(key)) {
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
Error.captureStackTrace(err, assertValidPseudoHeader);
return err;
}
}
function assertValidPseudoHeaderResponse(key) {
if (key !== ':status') {
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
Error.captureStackTrace(err, assertValidPseudoHeaderResponse);
return err;
}
}
function assertValidPseudoHeaderTrailer(key) {
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
Error.captureStackTrace(err, assertValidPseudoHeaderTrailer);
return err;
}
function mapToHeaders(map,
assertValuePseudoHeader = assertValidPseudoHeader) {
const ret = [];
const keys = Object.keys(map);
const singles = new Set();
for (var i = 0; i < keys.length; i++) {
let key = keys[i];
let value = map[key];
let val;
if (typeof key === 'symbol' || value === undefined || !key)
continue;
key = String(key).toLowerCase();
const isArray = Array.isArray(value);
if (isArray) {
switch (value.length) {
case 0:
continue;
case 1:
value = String(value[0]);
break;
default:
if (kSingleValueHeaders.has(key))
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
}
}
if (key[0] === ':') {
const err = assertValuePseudoHeader(key);
if (err !== undefined)
return err;
ret.unshift([key, String(value)]);
} else {
if (kSingleValueHeaders.has(key)) {
if (singles.has(key))
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
singles.add(key);
}
if (isIllegalConnectionSpecificHeader(key, value)) {
return new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS');
}
if (isArray) {
for (var k = 0; k < value.length; k++) {
val = String(value[k]);
ret.push([key, val]);
}
} else {
val = String(value);
ret.push([key, val]);
}
}
}
return ret;
}
class NghttpError extends Error {
constructor(ret) {
super(binding.nghttp2ErrorString(ret));
this.code = 'ERR_HTTP2_ERROR';
this.name = 'Error [ERR_HTTP2_ERROR]';
this.errno = ret;
}
}
function assertIsObject(value, name, types) {
if (value !== undefined &&
(value === null ||
typeof value !== 'object' ||
Array.isArray(value))) {
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE',
name, types || 'object');
Error.captureStackTrace(err, assertIsObject);
throw err;
}
}
function assertWithinRange(name, value, min = 0, max = Infinity) {
if (value !== undefined &&
(typeof value !== 'number' || value < min || value > max)) {
const err = new errors.RangeError('ERR_HTTP2_INVALID_SETTING_VALUE',
name, value);
err.min = min;
err.max = max;
err.actual = value;
Error.captureStackTrace(err, assertWithinRange);
throw err;
}
}
function toHeaderObject(headers) {
const obj = Object.create(null);
for (var n = 0; n < headers.length; n = n + 2) {
var name = headers[n];
var value = headers[n + 1];
if (name === HTTP2_HEADER_STATUS)
value |= 0;
var existing = obj[name];
if (existing === undefined) {
obj[name] = value;
} else if (!kSingleValueHeaders.has(name)) {
if (name === HTTP2_HEADER_COOKIE) {
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
// "...If there are multiple Cookie header fields after decompression,
// these MUST be concatenated into a single octet string using the
// two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") before
// being passed into a non-HTTP/2 context."
obj[name] = `${existing}; ${value}`;
} else {
if (Array.isArray(existing))
existing.push(value);
else
obj[name] = [existing, value];
}
}
}
return obj;
}
function isPayloadMeaningless(method) {
return kNoPayloadMethods.has(method);
}
module.exports = {
assertIsObject,
assertValidPseudoHeaderResponse,
assertValidPseudoHeaderTrailer,
assertWithinRange,
getDefaultSettings,
getSessionState,
getSettings,
getStreamState,
isPayloadMeaningless,
mapToHeaders,
NghttpError,
toHeaderObject,
updateOptionsBuffer,
updateSettingsBuffer
};

Просмотреть файл

@ -78,11 +78,15 @@ function stripShebang(content) {
const builtinLibs = [
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto',
'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', 'os',
'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'
];
const { exposeHTTP2 } = process.binding('config');
if (exposeHTTP2)
builtinLibs.push('http2');
function addBuiltinLibsToObject(object) {
// Make built-in modules available directly (loaded lazily).
builtinLibs.forEach((name) => {

Просмотреть файл

@ -37,6 +37,7 @@
'lib/events.js',
'lib/fs.js',
'lib/http.js',
'lib/http2.js',
'lib/_http_agent.js',
'lib/_http_client.js',
'lib/_http_common.js',
@ -103,6 +104,9 @@
'lib/internal/test/unicode.js',
'lib/internal/url.js',
'lib/internal/util.js',
'lib/internal/http2/core.js',
'lib/internal/http2/compat.js',
'lib/internal/http2/util.js',
'lib/internal/v8_prof_polyfill.js',
'lib/internal/v8_prof_processor.js',
'lib/internal/streams/lazy_transform.js',
@ -146,6 +150,7 @@
'dependencies': [
'node_js2c#host',
'deps/nghttp2/nghttp2.gyp:nghttp2'
],
'includes': [
@ -156,7 +161,8 @@
'src',
'tools/msvs/genfiles',
'deps/uv/src/ares',
'<(SHARED_INTERMEDIATE_DIR)',
'<(SHARED_INTERMEDIATE_DIR)', # for node_natives.h
'deps/nghttp2/lib/includes'
],
'sources': [
@ -178,6 +184,8 @@
'src/node_contextify.cc',
'src/node_debug_options.cc',
'src/node_file.cc',
'src/node_http2_core.cc',
'src/node_http2.cc',
'src/node_http_parser.cc',
'src/node_main.cc',
'src/node_os.cc',
@ -220,9 +228,12 @@
'src/handle_wrap.h',
'src/js_stream.h',
'src/node.h',
'src/node_http2_core.h',
'src/node_http2_core-inl.h',
'src/node_buffer.h',
'src/node_constants.h',
'src/node_debug_options.h',
'src/node_http2.h',
'src/node_internals.h',
'src/node_javascript.h',
'src/node_mutex.h',
@ -265,6 +276,8 @@
'NODE_WANT_INTERNALS=1',
# Warn when using deprecated V8 APIs.
'V8_DEPRECATION_WARNINGS=1',
# We're using the nghttp2 static lib
'NGHTTP2_STATICLIB'
],
},
{

Просмотреть файл

@ -52,6 +52,10 @@
'NODE_RELEASE_URLBASE="<(node_release_urlbase)"',
]
}],
[
'debug_http2==1', {
'defines': [ 'NODE_DEBUG_HTTP2=1' ]
}],
[ 'v8_enable_i18n_support==1', {
'defines': [ 'NODE_HAVE_I18N_SUPPORT=1' ],
'dependencies': [

Просмотреть файл

@ -40,6 +40,8 @@ namespace node {
V(FSREQWRAP) \
V(GETADDRINFOREQWRAP) \
V(GETNAMEINFOREQWRAP) \
V(HTTP2SESSION) \
V(HTTP2SESSIONSHUTDOWNWRAP) \
V(HTTPPARSER) \
V(JSSTREAM) \
V(PIPECONNECTWRAP) \

Просмотреть файл

@ -302,6 +302,7 @@ inline Environment::Environment(IsolateData* isolate_data,
#endif
handle_cleanup_waiting_(0),
http_parser_buffer_(nullptr),
http2_socket_buffer_(nullptr),
fs_stats_field_array_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
@ -328,6 +329,12 @@ inline Environment::~Environment() {
delete[] heap_statistics_buffer_;
delete[] heap_space_statistics_buffer_;
delete[] http_parser_buffer_;
delete[] http2_socket_buffer_;
delete[] http2_settings_buffer_;
delete[] http2_options_buffer_;
delete[] http2_session_state_buffer_;
delete[] http2_stream_state_buffer_;
delete[] http2_padding_buffer_;
}
inline v8::Isolate* Environment::isolate() const {
@ -468,6 +475,55 @@ inline void Environment::set_heap_space_statistics_buffer(double* pointer) {
heap_space_statistics_buffer_ = pointer;
}
inline uint32_t* Environment::http2_settings_buffer() const {
CHECK_NE(http2_settings_buffer_, nullptr);
return http2_settings_buffer_;
}
inline void Environment::set_http2_settings_buffer(uint32_t* pointer) {
CHECK_EQ(http2_settings_buffer_, nullptr); // Should be set only once
http2_settings_buffer_ = pointer;
}
inline uint32_t* Environment::http2_options_buffer() const {
CHECK_NE(http2_options_buffer_, nullptr);
return http2_options_buffer_;
}
inline void Environment::set_http2_options_buffer(uint32_t* pointer) {
CHECK_EQ(http2_options_buffer_, nullptr); // Should be set only once
http2_options_buffer_ = pointer;
}
inline double* Environment::http2_session_state_buffer() const {
CHECK_NE(http2_session_state_buffer_, nullptr);
return http2_session_state_buffer_;
}
inline void Environment::set_http2_session_state_buffer(double* pointer) {
CHECK_EQ(http2_session_state_buffer_, nullptr);
http2_session_state_buffer_ = pointer;
}
inline double* Environment::http2_stream_state_buffer() const {
CHECK_NE(http2_stream_state_buffer_, nullptr);
return http2_stream_state_buffer_;
}
inline void Environment::set_http2_stream_state_buffer(double* pointer) {
CHECK_EQ(http2_stream_state_buffer_, nullptr);
http2_stream_state_buffer_ = pointer;
}
inline uint32_t* Environment::http2_padding_buffer() const {
CHECK_NE(http2_padding_buffer_, nullptr);
return http2_padding_buffer_;
}
inline void Environment::set_http2_padding_buffer(uint32_t* pointer) {
CHECK_EQ(http2_padding_buffer_, nullptr);
http2_padding_buffer_ = pointer;
}
inline char* Environment::http_parser_buffer() const {
return http_parser_buffer_;
@ -487,6 +543,15 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
fs_stats_field_array_ = fields;
}
inline char* Environment::http2_socket_buffer() const {
return http2_socket_buffer_;
}
inline void Environment::set_http2_socket_buffer(char* buffer) {
CHECK_EQ(http2_socket_buffer_, nullptr); // Should be set only once.
http2_socket_buffer_ = buffer;
}
inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}

Просмотреть файл

@ -104,6 +104,7 @@ namespace node {
V(configurable_string, "configurable") \
V(cwd_string, "cwd") \
V(dest_string, "dest") \
V(destroy_string, "destroy") \
V(detached_string, "detached") \
V(disposed_string, "_disposed") \
V(dns_a_string, "A") \
@ -117,11 +118,13 @@ namespace node {
V(dns_srv_string, "SRV") \
V(dns_txt_string, "TXT") \
V(domain_string, "domain") \
V(emit_string, "emit") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \
V(enumerable_string, "enumerable") \
V(idle_string, "idle") \
V(irq_string, "irq") \
V(enablepush_string, "enablePush") \
V(encoding_string, "encoding") \
V(enter_string, "enter") \
V(entries_string, "entries") \
@ -148,8 +151,11 @@ namespace node {
V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \
V(gid_string, "gid") \
V(handle_string, "handle") \
V(heap_total_string, "heapTotal") \
V(heap_used_string, "heapUsed") \
V(homedir_string, "homedir") \
V(hostmaster_string, "hostmaster") \
V(id_string, "id") \
V(ignore_string, "ignore") \
V(immediate_callback_string, "_immediateCallback") \
V(infoaccess_string, "infoAccess") \
@ -174,6 +180,7 @@ namespace node {
V(netmask_string, "netmask") \
V(nice_string, "nice") \
V(nsname_string, "nsname") \
V(nexttick_string, "nextTick") \
V(ocsp_request_string, "OCSPRequest") \
V(onchange_string, "onchange") \
V(onclienthello_string, "onclienthello") \
@ -182,19 +189,27 @@ namespace node {
V(ondone_string, "ondone") \
V(onerror_string, "onerror") \
V(onexit_string, "onexit") \
V(onframeerror_string, "onframeerror") \
V(ongetpadding_string, "ongetpadding") \
V(onhandshakedone_string, "onhandshakedone") \
V(onhandshakestart_string, "onhandshakestart") \
V(onheaders_string, "onheaders") \
V(onmessage_string, "onmessage") \
V(onnewsession_string, "onnewsession") \
V(onnewsessiondone_string, "onnewsessiondone") \
V(onocspresponse_string, "onocspresponse") \
V(ongoawaydata_string, "ongoawaydata") \
V(onpriority_string, "onpriority") \
V(onread_string, "onread") \
V(onreadstart_string, "onreadstart") \
V(onreadstop_string, "onreadstop") \
V(onselect_string, "onselect") \
V(onsettings_string, "onsettings") \
V(onshutdown_string, "onshutdown") \
V(onsignal_string, "onsignal") \
V(onstop_string, "onstop") \
V(onstreamclose_string, "onstreamclose") \
V(ontrailers_string, "ontrailers") \
V(onwrite_string, "onwrite") \
V(output_string, "output") \
V(order_string, "order") \
@ -234,6 +249,7 @@ namespace node {
V(stack_string, "stack") \
V(status_string, "status") \
V(stdio_string, "stdio") \
V(stream_string, "stream") \
V(subject_string, "subject") \
V(subjectaltname_string, "subjectaltname") \
V(sys_string, "sys") \
@ -262,7 +278,7 @@ namespace node {
V(write_host_object_string, "_writeHostObject") \
V(write_queue_size_string, "writeQueueSize") \
V(x_forwarded_string, "x-forwarded-for") \
V(zero_return_string, "ZERO_RETURN") \
V(zero_return_string, "ZERO_RETURN")
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
V(as_external, v8::External) \
@ -579,8 +595,25 @@ class Environment {
inline double* heap_space_statistics_buffer() const;
inline void set_heap_space_statistics_buffer(double* pointer);
inline uint32_t* http2_settings_buffer() const;
inline void set_http2_settings_buffer(uint32_t* pointer);
inline uint32_t* http2_options_buffer() const;
inline void set_http2_options_buffer(uint32_t* pointer);
inline double* http2_session_state_buffer() const;
inline void set_http2_session_state_buffer(double* pointer);
inline double* http2_stream_state_buffer() const;
inline void set_http2_stream_state_buffer(double* pointer);
inline uint32_t* http2_padding_buffer() const;
inline void set_http2_padding_buffer(uint32_t* pointer);
inline char* http_parser_buffer() const;
inline void set_http_parser_buffer(char* buffer);
inline char* http2_socket_buffer() const;
inline void set_http2_socket_buffer(char* buffer);
inline double* fs_stats_field_array() const;
inline void set_fs_stats_field_array(double* fields);
@ -686,8 +719,14 @@ class Environment {
double* heap_statistics_buffer_ = nullptr;
double* heap_space_statistics_buffer_ = nullptr;
uint32_t* http2_settings_buffer_ = nullptr;
uint32_t* http2_options_buffer_ = nullptr;
double* http2_session_state_buffer_ = nullptr;
double* http2_stream_state_buffer_ = nullptr;
uint32_t* http2_padding_buffer_ = nullptr;
char* http_parser_buffer_;
char* http2_socket_buffer_;
double* fs_stats_field_array_;

92
src/freelist.h Normal file
Просмотреть файл

@ -0,0 +1,92 @@
#ifndef SRC_FREELIST_H_
#define SRC_FREELIST_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "util.h"
namespace node {
struct DefaultFreelistTraits;
template <typename T,
size_t kMaximumLength,
typename FreelistTraits = DefaultFreelistTraits>
class Freelist {
public:
typedef struct list_item {
T* item = nullptr;
list_item* next = nullptr;
} list_item;
Freelist() {}
~Freelist() {
while (head_ != nullptr) {
list_item* item = head_;
head_ = item->next;
FreelistTraits::Free(item->item);
free(item);
}
}
void push(T* item) {
if (size_ > kMaximumLength) {
FreelistTraits::Free(item);
} else {
size_++;
FreelistTraits::Reset(item);
list_item* li = Calloc<list_item>(1);
li->item = item;
if (head_ == nullptr) {
head_ = li;
tail_ = li;
} else {
tail_->next = li;
tail_ = li;
}
}
}
T* pop() {
if (head_ != nullptr) {
size_--;
list_item* cur = head_;
T* item = cur->item;
head_ = cur->next;
free(cur);
return item;
} else {
return FreelistTraits::template Alloc<T>();
}
}
private:
size_t size_ = 0;
list_item* head_ = nullptr;
list_item* tail_ = nullptr;
};
struct DefaultFreelistTraits {
template <typename T>
static T* Alloc() {
return ::new (Malloc<T>(1)) T();
}
template <typename T>
static void Free(T* item) {
item->~T();
free(item);
}
template <typename T>
static void Reset(T* item) {
item->~T();
::new (item) T();
}
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_FREELIST_H_

Просмотреть файл

@ -59,6 +59,7 @@
#include "env-inl.h"
#include "handle_wrap.h"
#include "http_parser.h"
#include "nghttp2/nghttp2ver.h"
#include "req-wrap.h"
#include "req-wrap-inl.h"
#include "string_bytes.h"
@ -232,6 +233,9 @@ std::string config_warning_file; // NOLINT(runtime/string)
// that is used by lib/internal/bootstrap_node.js
bool config_expose_internals = false;
// Set in node.cc by ParseArgs when --expose-http2 is used.
bool config_expose_http2 = false;
bool v8_initialized = false;
bool linux_at_secure = false;
@ -3210,6 +3214,10 @@ void SetupProcessObject(Environment* env,
"modules",
FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version));
READONLY_PROPERTY(versions,
"nghttp2",
FIXED_ONE_BYTE_STRING(env->isolate(), NGHTTP2_VERSION));
// process._promiseRejectEvent
Local<Object> promiseRejectEvent = Object::New(env->isolate());
READONLY_DONT_ENUM_PROPERTY(process,
@ -3648,6 +3656,7 @@ static void PrintHelp() {
" --abort-on-uncaught-exception\n"
" aborting instead of exiting causes a\n"
" core file to be generated for analysis\n"
" --expose-http2 enable experimental HTTP2 support\n"
" --trace-warnings show stack traces on process warnings\n"
" --redirect-warnings=file\n"
" write warnings to file instead of\n"
@ -3768,6 +3777,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
"--throw-deprecation",
"--no-warnings",
"--napi-modules",
"--expose-http2",
"--trace-warnings",
"--redirect-warnings",
"--trace-sync-io",
@ -3965,6 +3975,9 @@ static void ParseArgs(int* argc,
} else if (strcmp(arg, "--expose-internals") == 0 ||
strcmp(arg, "--expose_internals") == 0) {
config_expose_internals = true;
} else if (strcmp(arg, "--expose-http2") == 0 ||
strcmp(arg, "--expose_http2") == 0) {
config_expose_http2 = true;
} else if (strcmp(arg, "-") == 0) {
break;
} else if (strcmp(arg, "--") == 0) {

Просмотреть файл

@ -253,6 +253,25 @@ NODE_EXTERN void RunAtExit(Environment* env);
} \
while (0)
#define NODE_DEFINE_HIDDEN_CONSTANT(target, constant) \
do { \
v8::Isolate* isolate = target->GetIsolate(); \
v8::Local<v8::Context> context = isolate->GetCurrentContext(); \
v8::Local<v8::String> constant_name = \
v8::String::NewFromUtf8(isolate, #constant); \
v8::Local<v8::Number> constant_value = \
v8::Number::New(isolate, static_cast<double>(constant)); \
v8::PropertyAttribute constant_attributes = \
static_cast<v8::PropertyAttribute>(v8::ReadOnly | \
v8::DontDelete | \
v8::DontEnum); \
(target)->DefineOwnProperty(context, \
constant_name, \
constant_value, \
constant_attributes).FromJust(); \
} \
while (0)
// Used to be a macro, hence the uppercase name.
inline void NODE_SET_METHOD(v8::Local<v8::Template> recv,
const char* name,

Просмотреть файл

@ -88,6 +88,9 @@ static void InitConfig(Local<Object> target,
if (config_expose_internals)
READONLY_BOOLEAN_PROPERTY("exposeInternals");
if (config_expose_http2)
READONLY_BOOLEAN_PROPERTY("exposeHTTP2");
} // InitConfig
} // namespace node

Просмотреть файл

@ -357,7 +357,6 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
return max;
}
void NodeBIO::Write(const char* data, size_t size) {
size_t offset = 0;
size_t left = size;

1326
src/node_http2.cc Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

572
src/node_http2.h Normal file
Просмотреть файл

@ -0,0 +1,572 @@
#ifndef SRC_NODE_HTTP2_H_
#define SRC_NODE_HTTP2_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_http2_core-inl.h"
#include "stream_base-inl.h"
#include "string_bytes.h"
namespace node {
namespace http2 {
using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Isolate;
using v8::MaybeLocal;
#define HTTP_KNOWN_METHODS(V) \
V(ACL, "ACL") \
V(BASELINE_CONTROL, "BASELINE-CONTROL") \
V(BIND, "BIND") \
V(CHECKIN, "CHECKIN") \
V(CHECKOUT, "CHECKOUT") \
V(CONNECT, "CONNECT") \
V(COPY, "COPY") \
V(DELETE, "DELETE") \
V(GET, "GET") \
V(HEAD, "HEAD") \
V(LABEL, "LABEL") \
V(LINK, "LINK") \
V(LOCK, "LOCK") \
V(MERGE, "MERGE") \
V(MKACTIVITY, "MKACTIVITY") \
V(MKCALENDAR, "MKCALENDAR") \
V(MKCOL, "MKCOL") \
V(MKREDIRECTREF, "MKREDIRECTREF") \
V(MKWORKSPACE, "MKWORKSPACE") \
V(MOVE, "MOVE") \
V(OPTIONS, "OPTIONS") \
V(ORDERPATCH, "ORDERPATCH") \
V(PATCH, "PATCH") \
V(POST, "POST") \
V(PRI, "PRI") \
V(PROPFIND, "PROPFIND") \
V(PROPPATCH, "PROPPATCH") \
V(PUT, "PUT") \
V(REBIND, "REBIND") \
V(REPORT, "REPORT") \
V(SEARCH, "SEARCH") \
V(TRACE, "TRACE") \
V(UNBIND, "UNBIND") \
V(UNCHECKOUT, "UNCHECKOUT") \
V(UNLINK, "UNLINK") \
V(UNLOCK, "UNLOCK") \
V(UPDATE, "UPDATE") \
V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \
V(VERSION_CONTROL, "VERSION-CONTROL")
#define HTTP_KNOWN_HEADERS(V) \
V(STATUS, ":status") \
V(METHOD, ":method") \
V(AUTHORITY, ":authority") \
V(SCHEME, ":scheme") \
V(PATH, ":path") \
V(ACCEPT_CHARSET, "accept-charset") \
V(ACCEPT_ENCODING, "accept-encoding") \
V(ACCEPT_LANGUAGE, "accept-language") \
V(ACCEPT_RANGES, "accept-ranges") \
V(ACCEPT, "accept") \
V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \
V(AGE, "age") \
V(ALLOW, "allow") \
V(AUTHORIZATION, "authorization") \
V(CACHE_CONTROL, "cache-control") \
V(CONNECTION, "connection") \
V(CONTENT_DISPOSITION, "content-disposition") \
V(CONTENT_ENCODING, "content-encoding") \
V(CONTENT_LANGUAGE, "content-language") \
V(CONTENT_LENGTH, "content-length") \
V(CONTENT_LOCATION, "content-location") \
V(CONTENT_MD5, "content-md5") \
V(CONTENT_RANGE, "content-range") \
V(CONTENT_TYPE, "content-type") \
V(COOKIE, "cookie") \
V(DATE, "date") \
V(ETAG, "etag") \
V(EXPECT, "expect") \
V(EXPIRES, "expires") \
V(FROM, "from") \
V(HOST, "host") \
V(IF_MATCH, "if-match") \
V(IF_MODIFIED_SINCE, "if-modified-since") \
V(IF_NONE_MATCH, "if-none-match") \
V(IF_RANGE, "if-range") \
V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \
V(LAST_MODIFIED, "last-modified") \
V(LINK, "link") \
V(LOCATION, "location") \
V(MAX_FORWARDS, "max-forwards") \
V(PREFER, "prefer") \
V(PROXY_AUTHENTICATE, "proxy-authenticate") \
V(PROXY_AUTHORIZATION, "proxy-authorization") \
V(RANGE, "range") \
V(REFERER, "referer") \
V(REFRESH, "refresh") \
V(RETRY_AFTER, "retry-after") \
V(SERVER, "server") \
V(SET_COOKIE, "set-cookie") \
V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \
V(TRANSFER_ENCODING, "transfer-encoding") \
V(TE, "te") \
V(UPGRADE, "upgrade") \
V(USER_AGENT, "user-agent") \
V(VARY, "vary") \
V(VIA, "via") \
V(WWW_AUTHENTICATE, "www-authenticate") \
V(HTTP2_SETTINGS, "http2-settings") \
V(KEEP_ALIVE, "keep-alive") \
V(PROXY_CONNECTION, "proxy-connection")
enum http_known_headers {
HTTP_KNOWN_HEADER_MIN,
#define V(name, value) HTTP_HEADER_##name,
HTTP_KNOWN_HEADERS(V)
#undef V
HTTP_KNOWN_HEADER_MAX
};
#define HTTP_STATUS_CODES(V) \
V(CONTINUE, 100) \
V(SWITCHING_PROTOCOLS, 101) \
V(PROCESSING, 102) \
V(OK, 200) \
V(CREATED, 201) \
V(ACCEPTED, 202) \
V(NON_AUTHORITATIVE_INFORMATION, 203) \
V(NO_CONTENT, 204) \
V(RESET_CONTENT, 205) \
V(PARTIAL_CONTENT, 206) \
V(MULTI_STATUS, 207) \
V(ALREADY_REPORTED, 208) \
V(IM_USED, 226) \
V(MULTIPLE_CHOICES, 300) \
V(MOVED_PERMANENTLY, 301) \
V(FOUND, 302) \
V(SEE_OTHER, 303) \
V(NOT_MODIFIED, 304) \
V(USE_PROXY, 305) \
V(TEMPORARY_REDIRECT, 307) \
V(PERMANENT_REDIRECT, 308) \
V(BAD_REQUEST, 400) \
V(UNAUTHORIZED, 401) \
V(PAYMENT_REQUIRED, 402) \
V(FORBIDDEN, 403) \
V(NOT_FOUND, 404) \
V(METHOD_NOT_ALLOWED, 405) \
V(NOT_ACCEPTABLE, 406) \
V(PROXY_AUTHENTICATION_REQUIRED, 407) \
V(REQUEST_TIMEOUT, 408) \
V(CONFLICT, 409) \
V(GONE, 410) \
V(LENGTH_REQUIRED, 411) \
V(PRECONDITION_FAILED, 412) \
V(PAYLOAD_TOO_LARGE, 413) \
V(URI_TOO_LONG, 414) \
V(UNSUPPORTED_MEDIA_TYPE, 415) \
V(RANGE_NOT_SATISFIABLE, 416) \
V(EXPECTATION_FAILED, 417) \
V(TEAPOT, 418) \
V(MISDIRECTED_REQUEST, 421) \
V(UNPROCESSABLE_ENTITY, 422) \
V(LOCKED, 423) \
V(FAILED_DEPENDENCY, 424) \
V(UNORDERED_COLLECTION, 425) \
V(UPGRADE_REQUIRED, 426) \
V(PRECONDITION_REQUIRED, 428) \
V(TOO_MANY_REQUESTS, 429) \
V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \
V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \
V(INTERNAL_SERVER_ERROR, 500) \
V(NOT_IMPLEMENTED, 501) \
V(BAD_GATEWAY, 502) \
V(SERVICE_UNAVAILABLE, 503) \
V(GATEWAY_TIMEOUT, 504) \
V(HTTP_VERSION_NOT_SUPPORTED, 505) \
V(VARIANT_ALSO_NEGOTIATES, 506) \
V(INSUFFICIENT_STORAGE, 507) \
V(LOOP_DETECTED, 508) \
V(BANDWIDTH_LIMIT_EXCEEDED, 509) \
V(NOT_EXTENDED, 510) \
V(NETWORK_AUTHENTICATION_REQUIRED, 511)
enum http_status_codes {
#define V(name, code) HTTP_STATUS_##name = code,
HTTP_STATUS_CODES(V)
#undef V
};
enum padding_strategy_type {
// No padding strategy
PADDING_STRATEGY_NONE,
// Padding will ensure all data frames are maxFrameSize
PADDING_STRATEGY_MAX,
// Padding will be determined via JS callback
PADDING_STRATEGY_CALLBACK
};
#define NGHTTP2_ERROR_CODES(V) \
V(NGHTTP2_ERR_INVALID_ARGUMENT) \
V(NGHTTP2_ERR_BUFFER_ERROR) \
V(NGHTTP2_ERR_UNSUPPORTED_VERSION) \
V(NGHTTP2_ERR_WOULDBLOCK) \
V(NGHTTP2_ERR_PROTO) \
V(NGHTTP2_ERR_INVALID_FRAME) \
V(NGHTTP2_ERR_EOF) \
V(NGHTTP2_ERR_DEFERRED) \
V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \
V(NGHTTP2_ERR_STREAM_CLOSED) \
V(NGHTTP2_ERR_STREAM_CLOSING) \
V(NGHTTP2_ERR_STREAM_SHUT_WR) \
V(NGHTTP2_ERR_INVALID_STREAM_ID) \
V(NGHTTP2_ERR_INVALID_STREAM_STATE) \
V(NGHTTP2_ERR_DEFERRED_DATA_EXIST) \
V(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED) \
V(NGHTTP2_ERR_GOAWAY_ALREADY_SENT) \
V(NGHTTP2_ERR_INVALID_HEADER_BLOCK) \
V(NGHTTP2_ERR_INVALID_STATE) \
V(NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) \
V(NGHTTP2_ERR_FRAME_SIZE_ERROR) \
V(NGHTTP2_ERR_HEADER_COMP) \
V(NGHTTP2_ERR_FLOW_CONTROL) \
V(NGHTTP2_ERR_INSUFF_BUFSIZE) \
V(NGHTTP2_ERR_PAUSE) \
V(NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS) \
V(NGHTTP2_ERR_PUSH_DISABLED) \
V(NGHTTP2_ERR_DATA_EXIST) \
V(NGHTTP2_ERR_SESSION_CLOSING) \
V(NGHTTP2_ERR_HTTP_HEADER) \
V(NGHTTP2_ERR_HTTP_MESSAGING) \
V(NGHTTP2_ERR_REFUSED_STREAM) \
V(NGHTTP2_ERR_INTERNAL) \
V(NGHTTP2_ERR_CANCEL) \
V(NGHTTP2_ERR_FATAL) \
V(NGHTTP2_ERR_NOMEM) \
V(NGHTTP2_ERR_CALLBACK_FAILURE) \
V(NGHTTP2_ERR_BAD_CLIENT_MAGIC) \
V(NGHTTP2_ERR_FLOODED)
const char* nghttp2_errname(int rv) {
switch (rv) {
#define V(code) case code: return #code;
NGHTTP2_ERROR_CODES(V)
#undef V
default:
return "NGHTTP2_UNKNOWN_ERROR";
}
}
#define DEFAULT_SETTINGS_HEADER_TABLE_SIZE 4096
#define DEFAULT_SETTINGS_ENABLE_PUSH 1
#define DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE 65535
#define DEFAULT_SETTINGS_MAX_FRAME_SIZE 16384
#define MAX_MAX_FRAME_SIZE 16777215
#define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE
#define MAX_INITIAL_WINDOW_SIZE 2147483647
class Http2Options {
public:
explicit Http2Options(Environment* env);
~Http2Options() {
nghttp2_option_del(options_);
}
nghttp2_option* operator*() {
return options_;
}
void SetPaddingStrategy(uint32_t val) {
CHECK_LE(val, PADDING_STRATEGY_CALLBACK);
padding_strategy_ = static_cast<padding_strategy_type>(val);
}
void SetMaxDeflateDynamicTableSize(size_t val) {
nghttp2_option_set_max_deflate_dynamic_table_size(options_, val);
}
void SetMaxReservedRemoteStreams(uint32_t val) {
nghttp2_option_set_max_reserved_remote_streams(options_, val);
}
void SetMaxSendHeaderBlockLength(size_t val) {
nghttp2_option_set_max_send_header_block_length(options_, val);
}
void SetPeerMaxConcurrentStreams(uint32_t val) {
nghttp2_option_set_peer_max_concurrent_streams(options_, val);
}
padding_strategy_type GetPaddingStrategy() {
return padding_strategy_;
}
private:
nghttp2_option* options_;
padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE;
};
static const size_t kAllocBufferSize = 64 * 1024;
////
typedef uint32_t(*get_setting)(nghttp2_session* session,
nghttp2_settings_id id);
class Http2Session : public AsyncWrap,
public StreamBase,
public Nghttp2Session {
public:
Http2Session(Environment* env,
Local<Object> wrap,
nghttp2_session_type type) :
AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
StreamBase(env) {
Wrap(object(), this);
Http2Options opts(env);
padding_strategy_ = opts.GetPaddingStrategy();
Init(env->event_loop(), type, *opts);
stream_buf_.AllocateSufficientStorage(kAllocBufferSize);
}
~Http2Session() override {
CHECK_EQ(false, persistent().IsEmpty());
ClearWrap(object());
persistent().Reset();
CHECK_EQ(true, persistent().IsEmpty());
}
static void OnStreamAllocImpl(size_t suggested_size,
uv_buf_t* buf,
void* ctx);
static void OnStreamReadImpl(ssize_t nread,
const uv_buf_t* bufs,
uv_handle_type pending,
void* ctx);
protected:
void OnFreeSession() override;
ssize_t OnMaxFrameSizePadding(size_t frameLength,
size_t maxPayloadLen);
ssize_t OnCallbackPadding(size_t frame,
size_t maxPayloadLen);
bool HasGetPaddingCallback() override {
return padding_strategy_ == PADDING_STRATEGY_MAX ||
padding_strategy_ == PADDING_STRATEGY_CALLBACK;
}
ssize_t GetPadding(size_t frameLength, size_t maxPayloadLen) override {
if (padding_strategy_ == PADDING_STRATEGY_MAX) {
return OnMaxFrameSizePadding(frameLength, maxPayloadLen);
}
CHECK_EQ(padding_strategy_, PADDING_STRATEGY_CALLBACK);
return OnCallbackPadding(frameLength, maxPayloadLen);
}
void OnHeaders(Nghttp2Stream* stream,
nghttp2_header_list* headers,
nghttp2_headers_category cat,
uint8_t flags) override;
void OnStreamClose(int32_t id, uint32_t code) override;
void Send(uv_buf_t* bufs, size_t total) override;
void OnDataChunk(Nghttp2Stream* stream, nghttp2_data_chunk_t* chunk) override;
void OnSettings(bool ack) override;
void OnPriority(int32_t stream,
int32_t parent,
int32_t weight,
int8_t exclusive) override;
void OnGoAway(int32_t lastStreamID,
uint32_t errorCode,
uint8_t* data,
size_t length) override;
void OnFrameError(int32_t id, uint8_t type, int error_code) override;
void OnTrailers(Nghttp2Stream* stream,
MaybeStackBuffer<nghttp2_nv>* trailers) override;
void AllocateSend(size_t recommended, uv_buf_t* buf) override;
int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count,
uv_stream_t* send_handle) override;
AsyncWrap* GetAsyncWrap() override {
return static_cast<AsyncWrap*>(this);
}
void* Cast() override {
return reinterpret_cast<void*>(this);
}
// Required for StreamBase
bool IsAlive() override {
return true;
}
// Required for StreamBase
bool IsClosing() override {
return false;
}
// Required for StreamBase
int ReadStart() override { return 0; }
// Required for StreamBase
int ReadStop() override { return 0; }
// Required for StreamBase
int DoShutdown(ShutdownWrap* req_wrap) override {
return 0;
}
public:
void Consume(Local<External> external);
void Unconsume();
static void New(const FunctionCallbackInfo<Value>& args);
static void Consume(const FunctionCallbackInfo<Value>& args);
static void Unconsume(const FunctionCallbackInfo<Value>& args);
static void Destroy(const FunctionCallbackInfo<Value>& args);
static void SubmitSettings(const FunctionCallbackInfo<Value>& args);
static void SubmitRstStream(const FunctionCallbackInfo<Value>& args);
static void SubmitResponse(const FunctionCallbackInfo<Value>& args);
static void SubmitFile(const FunctionCallbackInfo<Value>& args);
static void SubmitRequest(const FunctionCallbackInfo<Value>& args);
static void SubmitPushPromise(const FunctionCallbackInfo<Value>& args);
static void SubmitPriority(const FunctionCallbackInfo<Value>& args);
static void SendHeaders(const FunctionCallbackInfo<Value>& args);
static void ShutdownStream(const FunctionCallbackInfo<Value>& args);
static void StreamWrite(const FunctionCallbackInfo<Value>& args);
static void StreamReadStart(const FunctionCallbackInfo<Value>& args);
static void StreamReadStop(const FunctionCallbackInfo<Value>& args);
static void SetNextStreamID(const FunctionCallbackInfo<Value>& args);
static void SendShutdownNotice(const FunctionCallbackInfo<Value>& args);
static void SubmitGoaway(const FunctionCallbackInfo<Value>& args);
static void DestroyStream(const FunctionCallbackInfo<Value>& args);
template <get_setting fn>
static void GetSettings(const FunctionCallbackInfo<Value>& args);
size_t self_size() const override {
return sizeof(*this);
}
char* stream_alloc() {
return *stream_buf_;
}
private:
StreamBase* stream_;
StreamResource::Callback<StreamResource::AllocCb> prev_alloc_cb_;
StreamResource::Callback<StreamResource::ReadCb> prev_read_cb_;
padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE;
MaybeStackBuffer<char, kAllocBufferSize> stream_buf_;
};
class ExternalHeader :
public String::ExternalOneByteStringResource {
public:
explicit ExternalHeader(nghttp2_rcbuf* buf)
: buf_(buf), vec_(nghttp2_rcbuf_get_buf(buf)) {
}
~ExternalHeader() override {
nghttp2_rcbuf_decref(buf_);
buf_ = nullptr;
}
const char* data() const override {
return const_cast<const char*>(reinterpret_cast<char*>(vec_.base));
}
size_t length() const override {
return vec_.len;
}
static Local<String> New(Isolate* isolate, nghttp2_rcbuf* buf) {
EscapableHandleScope scope(isolate);
nghttp2_vec vec = nghttp2_rcbuf_get_buf(buf);
if (vec.len == 0) {
nghttp2_rcbuf_decref(buf);
return scope.Escape(String::Empty(isolate));
}
ExternalHeader* h_str = new ExternalHeader(buf);
MaybeLocal<String> str = String::NewExternalOneByte(isolate, h_str);
isolate->AdjustAmountOfExternalAllocatedMemory(vec.len);
if (str.IsEmpty()) {
delete h_str;
return scope.Escape(String::Empty(isolate));
}
return scope.Escape(str.ToLocalChecked());
}
private:
nghttp2_rcbuf* buf_;
nghttp2_vec vec_;
};
class Headers {
public:
Headers(Isolate* isolate, Local<Context> context, Local<Array> headers) {
headers_.AllocateSufficientStorage(headers->Length());
Local<Value> item;
Local<Array> header;
for (size_t n = 0; n < headers->Length(); n++) {
item = headers->Get(context, n).ToLocalChecked();
CHECK(item->IsArray());
header = item.As<Array>();
Local<Value> key = header->Get(context, 0).ToLocalChecked();
Local<Value> value = header->Get(context, 1).ToLocalChecked();
CHECK(key->IsString());
CHECK(value->IsString());
size_t keylen = StringBytes::StorageSize(isolate, key, ASCII);
size_t valuelen = StringBytes::StorageSize(isolate, value, ASCII);
headers_[n].flags = NGHTTP2_NV_FLAG_NONE;
Local<Value> flag = header->Get(context, 2).ToLocalChecked();
if (flag->BooleanValue(context).ToChecked())
headers_[n].flags |= NGHTTP2_NV_FLAG_NO_INDEX;
uint8_t* buf = Malloc<uint8_t>(keylen + valuelen);
headers_[n].name = buf;
headers_[n].value = buf + keylen;
headers_[n].namelen =
StringBytes::Write(isolate,
reinterpret_cast<char*>(headers_[n].name),
keylen, key, ASCII);
headers_[n].valuelen =
StringBytes::Write(isolate,
reinterpret_cast<char*>(headers_[n].value),
valuelen, value, ASCII);
}
}
~Headers() {
for (size_t n = 0; n < headers_.length(); n++)
free(headers_[n].name);
}
nghttp2_nv* operator*() {
return *headers_;
}
size_t length() const {
return headers_.length();
}
private:
MaybeStackBuffer<nghttp2_nv> headers_;
};
} // namespace http2
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_HTTP2_H_

590
src/node_http2_core-inl.h Normal file
Просмотреть файл

@ -0,0 +1,590 @@
#ifndef SRC_NODE_HTTP2_CORE_INL_H_
#define SRC_NODE_HTTP2_CORE_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_http2_core.h"
#include "node_internals.h" // arraysize
#include "freelist.h"
namespace node {
namespace http2 {
#define FREELIST_MAX 1024
#define LINKED_LIST_ADD(list, item) \
do { \
if (list ## _tail_ == nullptr) { \
list ## _head_ = item; \
list ## _tail_ = item; \
} else { \
list ## _tail_->next = item; \
list ## _tail_ = item; \
} \
} while (0);
extern Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
data_chunk_free_list;
extern Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
extern Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
extern Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
data_chunks_free_list;
// See: https://nghttp2.org/documentation/nghttp2_submit_shutdown_notice.html
inline void Nghttp2Session::SubmitShutdownNotice() {
DEBUG_HTTP2("Nghttp2Session %d: submitting shutdown notice\n", session_type_);
nghttp2_submit_shutdown_notice(session_);
}
// Sends a SETTINGS frame on the current session
// Note that this *should* send a SETTINGS frame even if niv == 0 and there
// are no settings entries to send.
inline int Nghttp2Session::SubmitSettings(const nghttp2_settings_entry iv[],
size_t niv) {
DEBUG_HTTP2("Nghttp2Session %d: submitting settings, count: %d\n",
session_type_, niv);
return nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv);
}
// Returns the Nghttp2Stream associated with the given id, or nullptr if none
inline Nghttp2Stream* Nghttp2Session::FindStream(int32_t id) {
auto s = streams_.find(id);
if (s != streams_.end()) {
DEBUG_HTTP2("Nghttp2Session %d: stream %d found\n", session_type_, id);
return s->second;
} else {
DEBUG_HTTP2("Nghttp2Session %d: stream %d not found\n", session_type_, id);
return nullptr;
}
}
// Flushes any received queued chunks of data out to the JS layer
inline void Nghttp2Stream::FlushDataChunks(bool done) {
while (data_chunks_head_ != nullptr) {
DEBUG_HTTP2("Nghttp2Stream %d: emitting data chunk\n", id_);
nghttp2_data_chunk_t* item = data_chunks_head_;
data_chunks_head_ = item->next;
// item will be passed to the Buffer instance and freed on gc
session_->OnDataChunk(this, item);
}
data_chunks_tail_ = nullptr;
if (done)
session_->OnDataChunk(this, nullptr);
}
// Passes all of the the chunks for a data frame out to the JS layer
// The chunks are collected as the frame is being processed and sent out
// to the JS side only when the frame is fully processed.
inline void Nghttp2Session::HandleDataFrame(const nghttp2_frame* frame) {
int32_t id = frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling data frame for stream %d\n",
session_type_, id);
Nghttp2Stream* stream = this->FindStream(id);
// If the stream does not exist, something really bad happened
CHECK_NE(stream, nullptr);
bool done = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) ==
NGHTTP2_FLAG_END_STREAM;
stream->FlushDataChunks(done);
}
// Passes all of the collected headers for a HEADERS frame out to the JS layer.
// The headers are collected as the frame is being processed and sent out
// to the JS side only when the frame is fully processed.
inline void Nghttp2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id : frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling headers frame for stream %d\n",
session_type_, id);
Nghttp2Stream* stream = FindStream(id);
// If the stream does not exist, something really bad happened
CHECK_NE(stream, nullptr);
OnHeaders(stream,
stream->headers(),
stream->headers_category(),
frame->hd.flags);
stream->FreeHeaders();
}
// Notifies the JS layer that a PRIORITY frame has been received
inline void Nghttp2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
nghttp2_priority priority_frame = frame->priority;
int32_t id = frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: handling priority frame for stream %d\n",
session_type_, id);
// Ignore the priority frame if stream ID is <= 0
// This actually should never happen because nghttp2 should treat this as
// an error condition that terminates the session.
if (id > 0) {
nghttp2_priority_spec spec = priority_frame.pri_spec;
OnPriority(id, spec.stream_id, spec.weight, spec.exclusive);
}
}
// Notifies the JS layer that a GOAWAY frame has been received
inline void Nghttp2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
nghttp2_goaway goaway_frame = frame->goaway;
DEBUG_HTTP2("Nghttp2Session %d: handling goaway frame\n", session_type_);
OnGoAway(goaway_frame.last_stream_id,
goaway_frame.error_code,
goaway_frame.opaque_data,
goaway_frame.opaque_data_len);
}
// Prompts nghttp2 to flush the queue of pending data frames
inline void Nghttp2Session::SendPendingData() {
const uint8_t* data;
ssize_t len = 0;
size_t ncopy = 0;
uv_buf_t buf;
AllocateSend(SEND_BUFFER_RECOMMENDED_SIZE, &buf);
while (nghttp2_session_want_write(session_)) {
len = nghttp2_session_mem_send(session_, &data);
CHECK_GE(len, 0); // If this is less than zero, we're out of memory
// While len is greater than 0, send a chunk
while (len > 0) {
ncopy = len;
if (ncopy > buf.len)
ncopy = buf.len;
memcpy(buf.base, data, ncopy);
Send(&buf, ncopy);
len -= ncopy;
CHECK_GE(len, 0); // This should never be less than zero
}
}
}
// Initialize the Nghttp2Session handle by creating and
// assigning the Nghttp2Session instance and associated
// uv_loop_t.
inline int Nghttp2Session::Init(uv_loop_t* loop,
const nghttp2_session_type type,
nghttp2_option* options,
nghttp2_mem* mem) {
DEBUG_HTTP2("Nghttp2Session %d: initializing session\n", type);
loop_ = loop;
session_type_ = type;
int ret = 0;
nghttp2_session_callbacks* callbacks
= callback_struct_saved[HasGetPaddingCallback() ? 1 : 0].callbacks;
nghttp2_option* opts;
if (options != nullptr) {
opts = options;
} else {
nghttp2_option_new(&opts);
}
switch (type) {
case NGHTTP2_SESSION_SERVER:
ret = nghttp2_session_server_new3(&session_,
callbacks,
this,
opts,
mem);
break;
case NGHTTP2_SESSION_CLIENT:
ret = nghttp2_session_client_new3(&session_,
callbacks,
this,
opts,
mem);
break;
}
if (opts != options) {
nghttp2_option_del(opts);
}
// For every node::Http2Session instance, there is a uv_prep_t handle
// whose callback is triggered on every tick of the event loop. When
// run, nghttp2 is prompted to send any queued data it may have stored.
uv_prepare_init(loop_, &prep_);
uv_prepare_start(&prep_, [](uv_prepare_t* t) {
Nghttp2Session* session = ContainerOf(&Nghttp2Session::prep_, t);
session->SendPendingData();
});
// uv_unref(reinterpret_cast<uv_handle_t*>(&prep_));
return ret;
}
inline int Nghttp2Session::Free() {
assert(session_ != nullptr);
DEBUG_HTTP2("Nghttp2Session %d: freeing session\n", session_type_);
// Stop the loop
uv_prepare_stop(&prep_);
auto PrepClose = [](uv_handle_t* handle) {
Nghttp2Session* session =
ContainerOf(&Nghttp2Session::prep_,
reinterpret_cast<uv_prepare_t*>(handle));
session->OnFreeSession();
DEBUG_HTTP2("Nghttp2Session %d: session is free\n",
session->session_type_);
};
uv_close(reinterpret_cast<uv_handle_t*>(&prep_), PrepClose);
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
nghttp2_session_del(session_);
session_ = nullptr;
loop_ = nullptr;
return 1;
}
// Write data received from the socket to the underlying nghttp2_session.
inline ssize_t Nghttp2Session::Write(const uv_buf_t* bufs, unsigned int nbufs) {
size_t total = 0;
for (unsigned int n = 0; n < nbufs; n++) {
ssize_t ret =
nghttp2_session_mem_recv(session_,
reinterpret_cast<uint8_t*>(bufs[n].base),
bufs[n].len);
if (ret < 0) {
return ret;
} else {
total += ret;
}
}
SendPendingData();
return total;
}
inline void Nghttp2Session::AddStream(Nghttp2Stream* stream) {
streams_[stream->id()] = stream;
}
// Removes a stream instance from this session
inline void Nghttp2Session::RemoveStream(int32_t id) {
streams_.erase(id);
}
// Implementation for Nghttp2Stream functions
inline Nghttp2Stream* Nghttp2Stream::Init(
int32_t id,
Nghttp2Session* session,
nghttp2_headers_category category) {
DEBUG_HTTP2("Nghttp2Stream %d: initializing stream\n", id);
Nghttp2Stream* stream = stream_free_list.pop();
stream->ResetState(id, session, category);
session->AddStream(stream);
return stream;
}
// Resets the state of the stream instance to defaults
inline void Nghttp2Stream::ResetState(
int32_t id,
Nghttp2Session* session,
nghttp2_headers_category category) {
DEBUG_HTTP2("Nghttp2Stream %d: resetting stream state\n", id);
session_ = session;
queue_head_ = nullptr;
queue_tail_ = nullptr;
data_chunks_head_ = nullptr;
data_chunks_tail_ = nullptr;
current_headers_head_ = nullptr;
current_headers_tail_ = nullptr;
current_headers_category_ = category;
flags_ = NGHTTP2_STREAM_FLAG_NONE;
id_ = id;
code_ = NGHTTP2_NO_ERROR;
prev_local_window_size_ = 65535;
queue_head_index_ = 0;
queue_head_offset_ = 0;
}
inline void Nghttp2Stream::Destroy() {
DEBUG_HTTP2("Nghttp2Stream %d: destroying stream\n", id_);
// Do nothing if this stream instance is already destroyed
if (IsDestroyed() || IsDestroying())
return;
flags_ |= NGHTTP2_STREAM_DESTROYING;
Nghttp2Session* session = this->session_;
if (session != nullptr) {
// Remove this stream from the associated session
session_->RemoveStream(this->id());
session_ = nullptr;
}
// Free any remaining incoming data chunks.
while (data_chunks_head_ != nullptr) {
nghttp2_data_chunk_t* chunk = data_chunks_head_;
data_chunks_head_ = chunk->next;
delete[] chunk->buf.base;
data_chunk_free_list.push(chunk);
}
data_chunks_tail_ = nullptr;
// Free any remaining outgoing data chunks.
while (queue_head_ != nullptr) {
nghttp2_stream_write_queue* head = queue_head_;
queue_head_ = head->next;
head->cb(head->req, UV_ECANCELED);
delete head;
}
queue_tail_ = nullptr;
// Free any remaining headers
FreeHeaders();
// Return this stream instance to the freelist
stream_free_list.push(this);
}
inline void Nghttp2Stream::FreeHeaders() {
DEBUG_HTTP2("Nghttp2Stream %d: freeing headers\n", id_);
while (current_headers_head_ != nullptr) {
DEBUG_HTTP2("Nghttp2Stream %d: freeing header item\n", id_);
nghttp2_header_list* item = current_headers_head_;
current_headers_head_ = item->next;
header_free_list.push(item);
}
current_headers_tail_ = nullptr;
}
// Submit informational headers for a stream.
inline int Nghttp2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
DEBUG_HTTP2("Nghttp2Stream %d: sending informational headers, count: %d\n",
id_, len);
CHECK_GT(len, 0);
return nghttp2_submit_headers(session_->session(),
NGHTTP2_FLAG_NONE,
id_, nullptr,
nva, len, nullptr);
}
inline int Nghttp2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
bool silent) {
DEBUG_HTTP2("Nghttp2Stream %d: sending priority spec\n", id_);
return silent ?
nghttp2_session_change_stream_priority(session_->session(),
id_, prispec) :
nghttp2_submit_priority(session_->session(),
NGHTTP2_FLAG_NONE,
id_, prispec);
}
// Submit an RST_STREAM frame
inline int Nghttp2Stream::SubmitRstStream(const uint32_t code) {
DEBUG_HTTP2("Nghttp2Stream %d: sending rst-stream, code: %d\n", id_, code);
session_->SendPendingData();
return nghttp2_submit_rst_stream(session_->session(),
NGHTTP2_FLAG_NONE,
id_,
code);
}
// Submit a push promise.
inline int32_t Nghttp2Stream::SubmitPushPromise(
nghttp2_nv* nva,
size_t len,
Nghttp2Stream** assigned,
bool emptyPayload) {
CHECK_GT(len, 0);
DEBUG_HTTP2("Nghttp2Stream %d: sending push promise\n", id_);
int32_t ret = nghttp2_submit_push_promise(session_->session(),
NGHTTP2_FLAG_NONE,
id_, nva, len,
nullptr);
if (ret > 0) {
auto stream = Nghttp2Stream::Init(ret, session_);
if (emptyPayload) stream->Shutdown();
if (assigned != nullptr) *assigned = stream;
}
return ret;
}
// Initiate a response. If the nghttp2_stream is still writable by
// the time this is called, then an nghttp2_data_provider will be
// initialized, causing at least one (possibly empty) data frame to
// be sent.
inline int Nghttp2Stream::SubmitResponse(nghttp2_nv* nva,
size_t len,
bool emptyPayload) {
CHECK_GT(len, 0);
DEBUG_HTTP2("Nghttp2Stream %d: submitting response\n", id_);
nghttp2_data_provider* provider = nullptr;
nghttp2_data_provider prov;
prov.source.ptr = this;
prov.read_callback = Nghttp2Session::OnStreamRead;
if (!emptyPayload && IsWritable())
provider = &prov;
return nghttp2_submit_response(session_->session(), id_,
nva, len, provider);
}
// Initiate a response that contains data read from a file descriptor.
inline int Nghttp2Stream::SubmitFile(int fd, nghttp2_nv* nva, size_t len) {
CHECK_GT(len, 0);
CHECK_GT(fd, 0);
DEBUG_HTTP2("Nghttp2Stream %d: submitting file\n", id_);
nghttp2_data_provider prov;
prov.source.ptr = this;
prov.source.fd = fd;
prov.read_callback = Nghttp2Session::OnStreamReadFD;
return nghttp2_submit_response(session_->session(), id_,
nva, len, &prov);
}
// Initiate a request. If writable is true (the default), then
// an nghttp2_data_provider will be initialized, causing at
// least one (possibly empty) data frame to to be sent.
inline int32_t Nghttp2Session::SubmitRequest(
nghttp2_priority_spec* prispec,
nghttp2_nv* nva,
size_t len,
Nghttp2Stream** assigned,
bool emptyPayload) {
CHECK_GT(len, 0);
DEBUG_HTTP2("Nghttp2Session: submitting request\n");
nghttp2_data_provider* provider = nullptr;
nghttp2_data_provider prov;
prov.source.ptr = this;
prov.read_callback = OnStreamRead;
if (!emptyPayload)
provider = &prov;
int32_t ret = nghttp2_submit_request(session_,
prispec, nva, len,
provider, nullptr);
// Assign the Nghttp2Stream handle
if (ret > 0) {
Nghttp2Stream* stream = Nghttp2Stream::Init(ret, this);
if (emptyPayload) stream->Shutdown();
if (assigned != nullptr) *assigned = stream;
}
return ret;
}
// Queue the given set of uv_but_t handles for writing to an
// nghttp2_stream. The callback will be invoked once the chunks
// of data have been flushed to the underlying nghttp2_session.
// Note that this does *not* mean that the data has been flushed
// to the socket yet.
inline int Nghttp2Stream::Write(nghttp2_stream_write_t* req,
const uv_buf_t bufs[],
unsigned int nbufs,
nghttp2_stream_write_cb cb) {
if (!IsWritable()) {
if (cb != nullptr)
cb(req, UV_EOF);
return 0;
}
DEBUG_HTTP2("Nghttp2Stream %d: queuing buffers to send, count: %d\n",
id_, nbufs);
nghttp2_stream_write_queue* item = new nghttp2_stream_write_queue;
item->cb = cb;
item->req = req;
item->nbufs = nbufs;
item->bufs.AllocateSufficientStorage(nbufs);
req->handle = this;
req->item = item;
memcpy(*(item->bufs), bufs, nbufs * sizeof(*bufs));
if (queue_head_ == nullptr) {
queue_head_ = item;
queue_tail_ = item;
} else {
queue_tail_->next = item;
queue_tail_ = item;
}
nghttp2_session_resume_data(session_->session(), id_);
return 0;
}
inline void Nghttp2Stream::ReadStart() {
// Has no effect if IsReading() is true.
if (IsReading())
return;
DEBUG_HTTP2("Nghttp2Stream %d: start reading\n", id_);
if (IsPaused()) {
// If handle->reading is less than zero, read_start had never previously
// been called. If handle->reading is zero, reading had started and read
// stop had been previously called, meaning that the flow control window
// has been explicitly set to zero. Reset the flow control window now to
// restart the flow of data.
nghttp2_session_set_local_window_size(session_->session(),
NGHTTP2_FLAG_NONE,
id_,
prev_local_window_size_);
}
flags_ |= NGHTTP2_STREAM_READ_START;
flags_ &= ~NGHTTP2_STREAM_READ_PAUSED;
// Flush any queued data chunks immediately out to the JS layer
FlushDataChunks();
}
inline void Nghttp2Stream::ReadStop() {
DEBUG_HTTP2("Nghttp2Stream %d: stop reading\n", id_);
// Has no effect if IsReading() is false, which will happen if we either
// have not started reading yet at all (NGHTTP2_STREAM_READ_START is not
// set) or if we're already paused (NGHTTP2_STREAM_READ_PAUSED is set.
if (!IsReading())
return;
flags_ |= NGHTTP2_STREAM_READ_PAUSED;
// When not reading, explicitly set the local window size to 0 so that
// the peer does not keep sending data that has to be buffered
int32_t ret =
nghttp2_session_get_stream_local_window_size(session_->session(), id_);
if (ret >= 0)
prev_local_window_size_ = ret;
nghttp2_session_set_local_window_size(session_->session(),
NGHTTP2_FLAG_NONE,
id_, 0);
}
nghttp2_data_chunks_t::~nghttp2_data_chunks_t() {
for (unsigned int n = 0; n < nbufs; n++) {
free(buf[n].base);
}
}
Nghttp2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, OnBeginHeadersCallback);
nghttp2_session_callbacks_set_on_header_callback2(
callbacks, OnHeaderCallback);
nghttp2_session_callbacks_set_on_frame_recv_callback(
callbacks, OnFrameReceive);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, OnStreamClose);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, OnDataChunkReceived);
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, OnFrameNotSent);
// nghttp2_session_callbacks_set_on_invalid_frame_recv(
// callbacks, OnInvalidFrameReceived);
#ifdef NODE_DEBUG_HTTP2
nghttp2_session_callbacks_set_error_callback(
callbacks, OnNghttpError);
#endif
if (kHasGetPaddingCallback) {
nghttp2_session_callbacks_set_select_padding_callback(
callbacks, OnSelectPadding);
}
}
Nghttp2Session::Callbacks::~Callbacks() {
nghttp2_session_callbacks_del(callbacks);
}
} // namespace http2
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_HTTP2_CORE_INL_H_

326
src/node_http2_core.cc Normal file
Просмотреть файл

@ -0,0 +1,326 @@
#include "node_http2_core-inl.h"
namespace node {
namespace http2 {
#ifdef NODE_DEBUG_HTTP2
int Nghttp2Session::OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: Error '%.*s'\n",
handle->session_type_, len, message);
return 0;
}
#endif
// nghttp2 calls this at the beginning a new HEADERS or PUSH_PROMISE frame.
// We use it to ensure that an Nghttp2Stream instance is allocated to store
// the state.
int Nghttp2Session::OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
DEBUG_HTTP2("Nghttp2Session %d: beginning headers for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
if (stream == nullptr) {
Nghttp2Stream::Init(id, handle, frame->headers.cat);
} else {
stream->StartHeaders(frame->headers.cat);
}
return 0;
}
// nghttp2 calls this once for every header name-value pair in a HEADERS
// or PUSH_PROMISE block. CONTINUATION frames are handled automatically
// and transparently so we do not need to worry about those at all.
int Nghttp2Session::OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf *name,
nghttp2_rcbuf *value,
uint8_t flags,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
frame->push_promise.promised_stream_id :
frame->hd.stream_id;
Nghttp2Stream* stream = handle->FindStream(id);
nghttp2_header_list* header = header_free_list.pop();
header->name = name;
header->value = value;
nghttp2_rcbuf_incref(name);
nghttp2_rcbuf_incref(value);
LINKED_LIST_ADD(stream->current_headers, header);
return 0;
}
// When nghttp2 has completely processed a frame, it calls OnFrameReceive.
// It is our responsibility to delegate out from there. We can ignore most
// control frames since nghttp2 will handle those for us.
int Nghttp2Session::OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: complete frame received: type: %d\n",
handle->session_type_, frame->hd.type);
bool ack;
switch (frame->hd.type) {
case NGHTTP2_DATA:
handle->HandleDataFrame(frame);
break;
case NGHTTP2_PUSH_PROMISE:
case NGHTTP2_HEADERS:
handle->HandleHeadersFrame(frame);
break;
case NGHTTP2_SETTINGS:
ack = (frame->hd.flags & NGHTTP2_FLAG_ACK) == NGHTTP2_FLAG_ACK;
handle->OnSettings(ack);
break;
case NGHTTP2_PRIORITY:
handle->HandlePriorityFrame(frame);
break;
case NGHTTP2_GOAWAY:
handle->HandleGoawayFrame(frame);
break;
default:
break;
}
return 0;
}
int Nghttp2Session::OnFrameNotSent(nghttp2_session* session,
const nghttp2_frame* frame,
int error_code,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: frame type %d was not sent, code: %d\n",
handle->session_type_, frame->hd.type, error_code);
// Do not report if the frame was not sent due to the session closing
if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
error_code != NGHTTP2_ERR_STREAM_CLOSED &&
error_code != NGHTTP2_ERR_STREAM_CLOSING)
handle->OnFrameError(frame->hd.stream_id, frame->hd.type, error_code);
return 0;
}
// Called when nghttp2 closes a stream, either in response to an RST_STREAM
// frame or the stream closing naturally on it's own
int Nghttp2Session::OnStreamClose(nghttp2_session *session,
int32_t id,
uint32_t code,
void *user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: stream %d closed, code: %d\n",
handle->session_type_, id, code);
Nghttp2Stream* stream = handle->FindStream(id);
// Intentionally ignore the callback if the stream does not exist
if (stream != nullptr)
stream->Close(code);
return 0;
}
// Called by nghttp2 multiple times while processing a DATA frame
int Nghttp2Session::OnDataChunkReceived(nghttp2_session *session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void *user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: buffering data chunk for stream %d, size: "
"%d, flags: %d\n", handle->session_type_, id, len, flags);
Nghttp2Stream* stream = handle->FindStream(id);
nghttp2_data_chunk_t* chunk = data_chunk_free_list.pop();
chunk->buf = uv_buf_init(new char[len], len);
memcpy(chunk->buf.base, data, len);
if (stream->data_chunks_tail_ == nullptr) {
stream->data_chunks_head_ =
stream->data_chunks_tail_ = chunk;
} else {
stream->data_chunks_tail_->next = chunk;
stream->data_chunks_tail_ = chunk;
}
return 0;
}
// Called by nghttp2 when it needs to determine how much padding to apply
// to a DATA or HEADERS frame
ssize_t Nghttp2Session::OnSelectPadding(nghttp2_session* session,
const nghttp2_frame* frame,
size_t maxPayloadLen,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
assert(handle->HasGetPaddingCallback());
ssize_t padding = handle->GetPadding(frame->hd.length, maxPayloadLen);
DEBUG_HTTP2("Nghttp2Session %d: using padding, size: %d\n",
handle->session_type_, padding);
return padding;
}
// Called by nghttp2 to collect the data while a file response is sent.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: reading outbound file data for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
int fd = source->fd;
int64_t offset = stream->fd_offset_;
ssize_t numchars;
uv_buf_t data;
data.base = reinterpret_cast<char*>(buf);
data.len = length;
uv_fs_t read_req;
numchars = uv_fs_read(handle->loop_,
&read_req,
fd, &data, 1,
offset, nullptr);
uv_fs_req_cleanup(&read_req);
// Close the stream with an error if reading fails
if (numchars < 0)
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
// Update the read offset for the next read
stream->fd_offset_ += numchars;
// if numchars < length, assume that we are done.
if (static_cast<size_t>(numchars) < length) {
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
handle->session_type_, id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
// Sending trailers is not permitted with this provider.
}
return numchars;
}
// Called by nghttp2 to collect the data to pack within a DATA frame.
// The buf is the DATA frame buffer that needs to be filled with at most
// length bytes. flags is used to control what nghttp2 does next.
ssize_t Nghttp2Session::OnStreamRead(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data) {
Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
DEBUG_HTTP2("Nghttp2Session %d: reading outbound data for stream %d\n",
handle->session_type_, id);
Nghttp2Stream* stream = handle->FindStream(id);
size_t remaining = length;
size_t offset = 0;
// While there is data in the queue, copy data into buf until it is full.
// There may be data left over, which will be sent the next time nghttp
// calls this callback.
while (stream->queue_head_ != nullptr) {
DEBUG_HTTP2("Nghttp2Session %d: processing outbound data chunk\n",
handle->session_type_);
nghttp2_stream_write_queue* head = stream->queue_head_;
while (stream->queue_head_index_ < head->nbufs) {
if (remaining == 0) {
goto end;
}
unsigned int n = stream->queue_head_index_;
// len is the number of bytes in head->bufs[n] that are yet to be written
size_t len = head->bufs[n].len - stream->queue_head_offset_;
size_t bytes_to_write = len < remaining ? len : remaining;
memcpy(buf + offset,
head->bufs[n].base + stream->queue_head_offset_,
bytes_to_write);
offset += bytes_to_write;
remaining -= bytes_to_write;
if (bytes_to_write < len) {
stream->queue_head_offset_ += bytes_to_write;
} else {
stream->queue_head_index_++;
stream->queue_head_offset_ = 0;
}
}
stream->queue_head_offset_ = 0;
stream->queue_head_index_ = 0;
stream->queue_head_ = head->next;
head->cb(head->req, 0);
delete head;
}
stream->queue_tail_ = nullptr;
end:
// If we are no longer writable and there is no more data in the queue,
// then we need to set the NGHTTP2_DATA_FLAG_EOF flag.
// If we are still writable but there is not yet any data to send, set the
// NGHTTP2_ERR_DEFERRED flag. This will put the stream into a pending state
// that will wait for data to become available.
// If neither of these flags are set, then nghttp2 will call this callback
// again to get the data for the next DATA frame.
int writable = stream->queue_head_ != nullptr || stream->IsWritable();
if (offset == 0 && writable && stream->queue_head_ == nullptr) {
DEBUG_HTTP2("Nghttp2Session %d: deferring stream %d\n",
handle->session_type_, id);
return NGHTTP2_ERR_DEFERRED;
}
if (!writable) {
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
handle->session_type_, id);
*flags |= NGHTTP2_DATA_FLAG_EOF;
// Only when we are done sending the last chunk of data do we check for
// any trailing headers that are to be sent. This is the only opportunity
// we have to make this check. If there are trailers, then the
// NGHTTP2_DATA_FLAG_NO_END_STREAM flag must be set.
MaybeStackBuffer<nghttp2_nv> trailers;
handle->OnTrailers(stream, &trailers);
if (trailers.length() > 0) {
DEBUG_HTTP2("Nghttp2Session %d: sending trailers for stream %d, "
"count: %d\n", handle->session_type_, id, trailers.length());
*flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
nghttp2_submit_trailer(session,
stream->id(),
*trailers,
trailers.length());
}
for (size_t n = 0; n < trailers.length(); n++) {
free(trailers[n].name);
free(trailers[n].value);
}
}
assert(offset <= length);
return offset;
}
Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
data_chunk_free_list;
Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
data_chunks_free_list;
Nghttp2Session::Callbacks Nghttp2Session::callback_struct_saved[2] = {
Callbacks(false),
Callbacks(true)
};
} // namespace http2
} // namespace node

465
src/node_http2_core.h Normal file
Просмотреть файл

@ -0,0 +1,465 @@
#ifndef SRC_NODE_HTTP2_CORE_H_
#define SRC_NODE_HTTP2_CORE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "util.h"
#include "util-inl.h"
#include "uv.h"
#include "nghttp2/nghttp2.h"
#include <stdio.h>
#include <unordered_map>
namespace node {
namespace http2 {
#ifdef NODE_DEBUG_HTTP2
// Adapted from nghttp2 own debug printer
static inline void _debug_vfprintf(const char *fmt, va_list args) {
vfprintf(stderr, fmt, args);
}
void inline debug_vfprintf(const char *format, ...) {
va_list args;
va_start(args, format);
_debug_vfprintf(format, args);
va_end(args);
}
#define DEBUG_HTTP2(...) debug_vfprintf(__VA_ARGS__);
#else
#define DEBUG_HTTP2(...) \
do { \
} while (0)
#endif
class Nghttp2Session;
class Nghttp2Stream;
struct nghttp2_stream_write_t;
struct nghttp2_data_chunk_t;
struct nghttp2_data_chunks_t;
#define MAX_BUFFER_COUNT 10
#define SEND_BUFFER_RECOMMENDED_SIZE 4096
enum nghttp2_session_type {
NGHTTP2_SESSION_SERVER,
NGHTTP2_SESSION_CLIENT
};
enum nghttp2_shutdown_flags {
NGHTTP2_SHUTDOWN_FLAG_GRACEFUL
};
enum nghttp2_stream_flags {
NGHTTP2_STREAM_FLAG_NONE = 0x0,
// Writable side has ended
NGHTTP2_STREAM_FLAG_SHUT = 0x1,
// Reading has started
NGHTTP2_STREAM_READ_START = 0x2,
// Reading is paused
NGHTTP2_STREAM_READ_PAUSED = 0x4,
// Stream is closed
NGHTTP2_STREAM_CLOSED = 0x8,
// Stream is destroyed
NGHTTP2_STREAM_DESTROYED = 0x10,
// Stream is being destroyed
NGHTTP2_STREAM_DESTROYING = 0x20
};
// Callbacks
typedef void (*nghttp2_stream_write_cb)(
nghttp2_stream_write_t* req,
int status);
struct nghttp2_stream_write_queue {
unsigned int nbufs = 0;
nghttp2_stream_write_t* req = nullptr;
nghttp2_stream_write_cb cb = nullptr;
nghttp2_stream_write_queue* next = nullptr;
MaybeStackBuffer<uv_buf_t, MAX_BUFFER_COUNT> bufs;
};
struct nghttp2_header_list {
nghttp2_rcbuf* name = nullptr;
nghttp2_rcbuf* value = nullptr;
nghttp2_header_list* next = nullptr;
};
// Handle Types
class Nghttp2Session {
public:
// Initializes the session instance
inline int Init(
uv_loop_t*,
const nghttp2_session_type type = NGHTTP2_SESSION_SERVER,
nghttp2_option* options = nullptr,
nghttp2_mem* mem = nullptr);
// Frees this session instance
inline int Free();
// Returns the pointer to the identified stream, or nullptr if
// the stream does not exist
inline Nghttp2Stream* FindStream(int32_t id);
// Submits a new request. If the request is a success, assigned
// will be a pointer to the Nghttp2Stream instance assigned.
// This only works if the session is a client session.
inline int32_t SubmitRequest(
nghttp2_priority_spec* prispec,
nghttp2_nv* nva,
size_t len,
Nghttp2Stream** assigned = nullptr,
bool emptyPayload = true);
// Submits a notice to the connected peer that the session is in the
// process of shutting down.
inline void SubmitShutdownNotice();
// Submits a SETTINGS frame to the connected peer.
inline int SubmitSettings(const nghttp2_settings_entry iv[], size_t niv);
// Write data to the session
inline ssize_t Write(const uv_buf_t* bufs, unsigned int nbufs);
// Returns the nghttp2 library session
inline nghttp2_session* session() { return session_; }
protected:
// Adds a stream instance to this session
inline void AddStream(Nghttp2Stream* stream);
// Removes a stream instance from this session
inline void RemoveStream(int32_t id);
virtual void Send(uv_buf_t* buf,
size_t length) {}
virtual void OnHeaders(Nghttp2Stream* stream,
nghttp2_header_list* headers,
nghttp2_headers_category cat,
uint8_t flags) {}
virtual void OnStreamClose(int32_t id, uint32_t code) {}
virtual void OnDataChunk(Nghttp2Stream* stream,
nghttp2_data_chunk_t* chunk) {}
virtual void OnSettings(bool ack) {}
virtual void OnPriority(int32_t id,
int32_t parent,
int32_t weight,
int8_t exclusive) {}
virtual void OnGoAway(int32_t lastStreamID,
uint32_t errorCode,
uint8_t* data,
size_t length) {}
virtual void OnFrameError(int32_t id,
uint8_t type,
int error_code) {}
virtual ssize_t GetPadding(size_t frameLength,
size_t maxFrameLength) { return 0; }
virtual void OnTrailers(Nghttp2Stream* stream,
MaybeStackBuffer<nghttp2_nv>* nva) {}
virtual void OnFreeSession() {}
virtual void AllocateSend(size_t suggested_size, uv_buf_t* buf) = 0;
virtual bool HasGetPaddingCallback() { return false; }
private:
inline void SendPendingData();
inline void HandleHeadersFrame(const nghttp2_frame* frame);
inline void HandlePriorityFrame(const nghttp2_frame* frame);
inline void HandleDataFrame(const nghttp2_frame* frame);
inline void HandleGoawayFrame(const nghttp2_frame* frame);
/* callbacks for nghttp2 */
#ifdef NODE_DEBUG_HTTP2
static int OnNghttpError(nghttp2_session* session,
const char* message,
size_t len,
void* user_data);
#endif
static int OnBeginHeadersCallback(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static int OnHeaderCallback(nghttp2_session* session,
const nghttp2_frame* frame,
nghttp2_rcbuf* name,
nghttp2_rcbuf* value,
uint8_t flags,
void* user_data);
static int OnFrameReceive(nghttp2_session* session,
const nghttp2_frame* frame,
void* user_data);
static int OnFrameNotSent(nghttp2_session* session,
const nghttp2_frame* frame,
int error_code,
void* user_data);
static int OnStreamClose(nghttp2_session* session,
int32_t id,
uint32_t code,
void* user_data);
static int OnDataChunkReceived(nghttp2_session* session,
uint8_t flags,
int32_t id,
const uint8_t *data,
size_t len,
void* user_data);
static ssize_t OnStreamReadFD(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static ssize_t OnStreamRead(nghttp2_session* session,
int32_t id,
uint8_t* buf,
size_t length,
uint32_t* flags,
nghttp2_data_source* source,
void* user_data);
static ssize_t OnSelectPadding(nghttp2_session* session,
const nghttp2_frame* frame,
size_t maxPayloadLen,
void* user_data);
struct Callbacks {
inline explicit Callbacks(bool kHasGetPaddingCallback);
inline ~Callbacks();
nghttp2_session_callbacks* callbacks;
};
/* Use callback_struct_saved[kHasGetPaddingCallback ? 1 : 0] */
static Callbacks callback_struct_saved[2];
nghttp2_session* session_;
uv_loop_t* loop_;
uv_prepare_t prep_;
nghttp2_session_type session_type_;
std::unordered_map<int32_t, Nghttp2Stream*> streams_;
friend class Nghttp2Stream;
};
class Nghttp2Stream {
public:
static inline Nghttp2Stream* Init(
int32_t id,
Nghttp2Session* session,
nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS);
inline ~Nghttp2Stream() {
CHECK_EQ(session_, nullptr);
CHECK_EQ(queue_head_, nullptr);
CHECK_EQ(queue_tail_, nullptr);
CHECK_EQ(data_chunks_head_, nullptr);
CHECK_EQ(data_chunks_tail_, nullptr);
CHECK_EQ(current_headers_head_, nullptr);
CHECK_EQ(current_headers_tail_, nullptr);
DEBUG_HTTP2("Nghttp2Stream %d: freed\n", id_);
}
inline void FlushDataChunks(bool done = false);
// Resets the state of the stream instance to defaults
inline void ResetState(
int32_t id,
Nghttp2Session* session,
nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS);
// Destroy this stream instance and free all held memory.
// Note that this will free queued outbound and inbound
// data chunks and inbound headers, so it's important not
// to call this until those are fully consumed.
//
// Also note: this does not actually destroy the instance.
// instead, it frees the held memory, removes the stream
// from the parent session, and returns the instance to
// the FreeList so that it can be reused.
inline void Destroy();
// Returns true if this stream has been destroyed
inline bool IsDestroyed() const {
return (flags_ & NGHTTP2_STREAM_DESTROYED) == NGHTTP2_STREAM_DESTROYED;
}
inline bool IsDestroying() const {
return (flags_ & NGHTTP2_STREAM_DESTROYING) == NGHTTP2_STREAM_DESTROYING;
}
// Queue outbound chunks of data to be sent on this stream
inline int Write(
nghttp2_stream_write_t* req,
const uv_buf_t bufs[],
unsigned int nbufs,
nghttp2_stream_write_cb cb);
// Initiate a response on this stream.
inline int SubmitResponse(nghttp2_nv* nva,
size_t len,
bool emptyPayload = false);
// Send data read from a file descriptor as the response on this stream.
inline int SubmitFile(int fd, nghttp2_nv* nva, size_t len);
// Submit informational headers for this stream
inline int SubmitInfo(nghttp2_nv* nva, size_t len);
// Submit a PRIORITY frame for this stream
inline int SubmitPriority(nghttp2_priority_spec* prispec,
bool silent = false);
// Submits an RST_STREAM frame using the given code
inline int SubmitRstStream(const uint32_t code);
// Submits a PUSH_PROMISE frame with this stream as the parent.
inline int SubmitPushPromise(
nghttp2_nv* nva,
size_t len,
Nghttp2Stream** assigned = nullptr,
bool writable = true);
// Marks the Writable side of the stream as being shutdown
inline void Shutdown() {
flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
nghttp2_session_resume_data(session_->session(), id_);
}
// Returns true if this stream is writable.
inline bool IsWritable() const {
return (flags_ & NGHTTP2_STREAM_FLAG_SHUT) == 0;
}
// Start Reading. If there are queued data chunks, they are pushed into
// the session to be emitted at the JS side
inline void ReadStart();
// Stop/Pause Reading.
inline void ReadStop();
// Returns true if reading is paused
inline bool IsPaused() const {
return (flags_ & NGHTTP2_STREAM_READ_PAUSED) == NGHTTP2_STREAM_READ_PAUSED;
}
// Returns true if this stream is in the reading state, which occurs when
// the NGHTTP2_STREAM_READ_START flag has been set and the
// NGHTTP2_STREAM_READ_PAUSED flag is *not* set.
inline bool IsReading() const {
return ((flags_ & NGHTTP2_STREAM_READ_START) == NGHTTP2_STREAM_READ_START)
&& ((flags_ & NGHTTP2_STREAM_READ_PAUSED) == 0);
}
inline void Close(int32_t code) {
DEBUG_HTTP2("Nghttp2Stream %d: closing with code %d\n", id_, code);
flags_ |= NGHTTP2_STREAM_CLOSED;
code_ = code;
session_->OnStreamClose(id_, code);
DEBUG_HTTP2("Nghttp2Stream %d: closed\n", id_);
}
// Returns true if this stream has been closed either by receiving or
// sending an RST_STREAM frame.
inline bool IsClosed() const {
return (flags_ & NGHTTP2_STREAM_CLOSED) == NGHTTP2_STREAM_CLOSED;
}
// Returns the RST_STREAM code used to close this stream
inline int32_t code() const {
return code_;
}
// Returns the stream identifier for this stream
inline int32_t id() const {
return id_;
}
inline nghttp2_header_list* headers() const {
return current_headers_head_;
}
inline nghttp2_headers_category headers_category() const {
return current_headers_category_;
}
inline void FreeHeaders();
void StartHeaders(nghttp2_headers_category category) {
DEBUG_HTTP2("Nghttp2Stream %d: starting headers, category: %d\n",
id_, category);
// We shouldn't be in the middle of a headers block already.
// Something bad happened if this fails
CHECK_EQ(current_headers_head_, nullptr);
CHECK_EQ(current_headers_tail_, nullptr);
current_headers_category_ = category;
}
private:
// The Parent HTTP/2 Session
Nghttp2Session* session_ = nullptr;
// The Stream Identifier
int32_t id_ = 0;
// Internal state flags
int flags_ = 0;
// Outbound Data... This is the data written by the JS layer that is
// waiting to be written out to the socket.
nghttp2_stream_write_queue* queue_head_ = nullptr;
nghttp2_stream_write_queue* queue_tail_ = nullptr;
unsigned int queue_head_index_ = 0;
size_t queue_head_offset_ = 0;
size_t fd_offset_ = 0;
// The Current Headers block... As headers are received for this stream,
// they are temporarily stored here until the OnFrameReceived is called
// signalling the end of the HEADERS frame
nghttp2_header_list* current_headers_head_ = nullptr;
nghttp2_header_list* current_headers_tail_ = nullptr;
nghttp2_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS;
// Inbound Data... This is the data received via DATA frames for this stream.
nghttp2_data_chunk_t* data_chunks_head_ = nullptr;
nghttp2_data_chunk_t* data_chunks_tail_ = nullptr;
// The RST_STREAM code used to close this stream
int32_t code_ = NGHTTP2_NO_ERROR;
int32_t prev_local_window_size_ = 65535;
friend class Nghttp2Session;
};
struct nghttp2_stream_write_t {
void* data;
int status;
Nghttp2Stream* handle;
nghttp2_stream_write_queue* item;
};
struct nghttp2_data_chunk_t {
uv_buf_t buf;
nghttp2_data_chunk_t* next = nullptr;
};
struct nghttp2_data_chunks_t {
unsigned int nbufs = 0;
uv_buf_t buf[MAX_BUFFER_COUNT];
inline ~nghttp2_data_chunks_t();
};
} // namespace http2
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_HTTP2_CORE_H_

Просмотреть файл

@ -83,6 +83,9 @@ extern std::string openssl_config;
// that is used by lib/module.js
extern bool config_preserve_symlinks;
// Set in node.cc by ParseArgs when --expose-http2 is used.
extern bool config_expose_http2;
// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is
// used.
// Used in node_config.cc to set a constant on process.binding('config')

Просмотреть файл

@ -408,6 +408,7 @@ void StreamBase::AfterWrite(WriteWrap* req_wrap, int status) {
// Unref handle property
Local<Object> req_wrap_obj = req_wrap->object();
req_wrap_obj->Delete(env->context(), env->handle_string()).FromJust();
wrap->OnAfterWrite(req_wrap);
Local<Value> argv[] = {

Просмотреть файл

@ -89,6 +89,17 @@ class WriteWrap: public ReqWrap<uv_write_t>,
static const size_t kAlignSize = 16;
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* wrap,
DoneCb cb)
: ReqWrap(env, obj, AsyncWrap::PROVIDER_WRITEWRAP),
StreamReq<WriteWrap>(cb),
wrap_(wrap),
storage_size_(0) {
Wrap(obj, this);
}
protected:
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,

Просмотреть файл

@ -48,6 +48,8 @@ set js_test_suites=async-hooks inspector known_issues message parallel sequentia
set v8_test_options=
set v8_build_options=
set "common_test_suites=%js_test_suites% doctool addons addons-napi&set build_addons=1&set build_addons_napi=1"
set http2_debug=
set nghttp2_debug=
:next-arg
if "%1"=="" goto args-done
@ -107,6 +109,8 @@ if /i "%1"=="enable-vtune" set enable_vtune_arg=1&goto arg-ok
if /i "%1"=="dll" set dll=1&goto arg-ok
if /i "%1"=="static" set enable_static=1&goto arg-ok
if /i "%1"=="no-NODE-OPTIONS" set no_NODE_OPTIONS=1&goto arg-ok
if /i "%1"=="debug-http2" set debug_http2=1&goto arg-ok
if /i "%1"=="debug-nghttp2" set debug_nghttp2=1&goto arg-ok
echo Error: invalid command line option `%1`.
exit /b 1
@ -144,6 +148,9 @@ if defined dll set configure_flags=%configure_flags% --shared
if defined enable_static set configure_flags=%configure_flags% --enable-static
if defined no_NODE_OPTIONS set configure_flags=%configure_flags% --without-node-options
REM if defined debug_http2 set configure_flags=%configure_flags% --debug-http2
REM if defined debug_nghttp2 set configure_flags=%configure_flags% --debug-nghttp2
if "%i18n_arg%"=="full-icu" set configure_flags=%configure_flags% --with-intl=full-icu
if "%i18n_arg%"=="small-icu" set configure_flags=%configure_flags% --with-intl=small-icu
if "%i18n_arg%"=="intl-none" set configure_flags=%configure_flags% --with-intl=none