http2: introducing HTTP/2
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:
Родитель
71a1876f6c
Коммит
e71e71b513
|
@ -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
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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,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
|
||||
};
|
|
@ -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,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 };
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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) => {
|
||||
|
|
15
node.gyp
15
node.gyp
|
@ -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_;
|
||||
}
|
||||
|
|
41
src/env.h
41
src/env.h
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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_
|
13
src/node.cc
13
src/node.cc
|
@ -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) {
|
||||
|
|
19
src/node.h
19
src/node.h
|
@ -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;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче