http2: add range support for respondWith{File|FD}
* respondWithFD now supports optional statCheck * respondWithFD and respondWithFile both support offset/length for range requests * Fix linting nits following most recent update 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:
parent
953458f645
commit
d6a774b1bd
@ -998,13 +998,17 @@ server.on('stream', (stream) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### http2stream.respondWithFD(fd[, headers])
|
#### http2stream.respondWithFD(fd[, headers[, options]])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `fd` {number} A readable file descriptor
|
* `fd` {number} A readable file descriptor
|
||||||
* `headers` {[Headers Object][]}
|
* `headers` {[Headers Object][]}
|
||||||
|
* `options` {Object}
|
||||||
|
* `statCheck` {Function}
|
||||||
|
* `offset` {number} The offset position at which to begin reading
|
||||||
|
* `length` {number} The amount of data from the fd to send
|
||||||
|
|
||||||
Initiates a response whose data is read from the given file descriptor. No
|
Initiates a response whose data is read from the given file descriptor. No
|
||||||
validation is performed on the given file descriptor. If an error occurs while
|
validation is performed on the given file descriptor. If an error occurs while
|
||||||
@ -1034,6 +1038,16 @@ server.on('stream', (stream) => {
|
|||||||
server.on('close', () => fs.closeSync(fd));
|
server.on('close', () => fs.closeSync(fd));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The optional `options.statCheck` function may be specified to give user code
|
||||||
|
an opportunity to set additional content headers based on the `fs.Stat` details
|
||||||
|
of the given fd. If the `statCheck` function is provided, the
|
||||||
|
`http2stream.respondWithFD()` method will perform an `fs.fstat()` call to
|
||||||
|
collect details on the provided file descriptor.
|
||||||
|
|
||||||
|
The `offset` and `length` options may be used to limit the response to a
|
||||||
|
specific range subset. This can be used, for instance, to support HTTP Range
|
||||||
|
requests.
|
||||||
|
|
||||||
#### http2stream.respondWithFile(path[, headers[, options]])
|
#### http2stream.respondWithFile(path[, headers[, options]])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
@ -1043,6 +1057,8 @@ added: REPLACEME
|
|||||||
* `headers` {[Headers Object][]}
|
* `headers` {[Headers Object][]}
|
||||||
* `options` {Object}
|
* `options` {Object}
|
||||||
* `statCheck` {Function}
|
* `statCheck` {Function}
|
||||||
|
* `offset` {number} The offset position at which to begin reading
|
||||||
|
* `length` {number} The amount of data from the fd to send
|
||||||
|
|
||||||
Sends a regular file as the response. The `path` must specify a regular file
|
Sends a regular file as the response. The `path` must specify a regular file
|
||||||
or an `'error'` event will be emitted on the `Http2Stream` object.
|
or an `'error'` event will be emitted on the `Http2Stream` object.
|
||||||
@ -1096,6 +1112,10 @@ server.on('stream', (stream) => {
|
|||||||
|
|
||||||
The `content-length` header field will be automatically set.
|
The `content-length` header field will be automatically set.
|
||||||
|
|
||||||
|
The `offset` and `length` options may be used to limit the response to a
|
||||||
|
specific range subset. This can be used, for instance, to support HTTP Range
|
||||||
|
requests.
|
||||||
|
|
||||||
### Class: Http2Server
|
### Class: Http2Server
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
|
@ -452,7 +452,7 @@ class Http2ServerResponse extends Stream {
|
|||||||
stream.once('finish', cb);
|
stream.once('finish', cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
this[kBeginSend]({endStream: true});
|
this[kBeginSend]({ endStream: true });
|
||||||
|
|
||||||
if (stream !== undefined) {
|
if (stream !== undefined) {
|
||||||
stream.end();
|
stream.end();
|
||||||
|
@ -1541,7 +1541,7 @@ function processHeaders(headers) {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRespondWithFD(fd, headers) {
|
function processRespondWithFD(fd, headers, offset = 0, length = -1) {
|
||||||
const session = this[kSession];
|
const session = this[kSession];
|
||||||
const state = this[kState];
|
const state = this[kState];
|
||||||
state.headersSent = true;
|
state.headersSent = true;
|
||||||
@ -1551,7 +1551,7 @@ function processRespondWithFD(fd, headers) {
|
|||||||
|
|
||||||
const handle = session[kHandle];
|
const handle = session[kHandle];
|
||||||
const ret =
|
const ret =
|
||||||
handle.submitFile(this[kID], fd, headers);
|
handle.submitFile(this[kID], fd, headers, offset, length);
|
||||||
let err;
|
let err;
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
case NGHTTP2_ERR_NOMEM:
|
case NGHTTP2_ERR_NOMEM:
|
||||||
@ -1575,26 +1575,71 @@ function doSendFD(session, options, fd, headers, err, stat) {
|
|||||||
process.nextTick(() => this.emit('error', err));
|
process.nextTick(() => this.emit('error', err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!stat.isFile()) {
|
|
||||||
err = new errors.Error('ERR_HTTP2_SEND_FILE');
|
|
||||||
process.nextTick(() => this.emit('error', err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the content-length by default
|
const statOptions = {
|
||||||
headers[HTTP2_HEADER_CONTENT_LENGTH] = stat.size;
|
offset: options.offset !== undefined ? options.offset : 0,
|
||||||
|
length: options.length !== undefined ? options.length : -1
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof options.statCheck === 'function' &&
|
if (typeof options.statCheck === 'function' &&
|
||||||
options.statCheck.call(this, stat, headers) === false) {
|
options.statCheck.call(this, stat, headers, statOptions) === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers,
|
const headersList = mapToHeaders(headers,
|
||||||
assertValidPseudoHeaderResponse);
|
assertValidPseudoHeaderResponse);
|
||||||
if (!Array.isArray(headersList)) {
|
if (!Array.isArray(headersList)) {
|
||||||
throw headersList;
|
process.nextTick(() => this.emit('error', headersList));
|
||||||
}
|
}
|
||||||
|
|
||||||
processRespondWithFD.call(this, fd, headersList);
|
processRespondWithFD.call(this, fd, headersList,
|
||||||
|
statOptions.offset,
|
||||||
|
statOptions.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSendFileFD(session, options, fd, headers, err, stat) {
|
||||||
|
if (this.destroyed || session.destroyed) {
|
||||||
|
abort(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
process.nextTick(() => this.emit('error', err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!stat.isFile()) {
|
||||||
|
err = new errors.Error('ERR_HTTP2_SEND_FILE');
|
||||||
|
process.nextTick(() => this.emit('error', err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statOptions = {
|
||||||
|
offset: options.offset !== undefined ? options.offset : 0,
|
||||||
|
length: options.length !== undefined ? options.length : -1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the content-length by default
|
||||||
|
if (typeof options.statCheck === 'function' &&
|
||||||
|
options.statCheck.call(this, stat, headers) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statOptions.length =
|
||||||
|
statOptions.length < 0 ? stat.size - (+statOptions.offset) :
|
||||||
|
Math.min(stat.size - (+statOptions.offset),
|
||||||
|
statOptions.length);
|
||||||
|
|
||||||
|
if (headers[HTTP2_HEADER_CONTENT_LENGTH] === undefined)
|
||||||
|
headers[HTTP2_HEADER_CONTENT_LENGTH] = statOptions.length;
|
||||||
|
|
||||||
|
const headersList = mapToHeaders(headers,
|
||||||
|
assertValidPseudoHeaderResponse);
|
||||||
|
if (!Array.isArray(headersList)) {
|
||||||
|
process.nextTick(() => this.emit('error', headersList));
|
||||||
|
}
|
||||||
|
|
||||||
|
processRespondWithFD.call(this, fd, headersList,
|
||||||
|
options.offset,
|
||||||
|
options.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterOpen(session, options, headers, err, fd) {
|
function afterOpen(session, options, headers, err, fd) {
|
||||||
@ -1609,7 +1654,7 @@ function afterOpen(session, options, headers, err, fd) {
|
|||||||
}
|
}
|
||||||
state.fd = fd;
|
state.fd = fd;
|
||||||
|
|
||||||
fs.fstat(fd, doSendFD.bind(this, session, options, fd, headers));
|
fs.fstat(fd, doSendFileFD.bind(this, session, options, fd, headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1786,12 +1831,12 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a response using an open FD. Note that there are fewer
|
// Initiate a response using an open FD. Note that there are fewer
|
||||||
// protections with this approach. For one, the fd is not validated.
|
// protections with this approach. For one, the fd is not validated by
|
||||||
// In respondWithFile, the file is checked to make sure it is a
|
// default. In respondWithFile, the file is checked to make sure it is a
|
||||||
// regular file, here the fd is passed directly. If the underlying
|
// regular file, here the fd is passed directly. If the underlying
|
||||||
// mechanism is not able to read from the fd, then the stream will be
|
// mechanism is not able to read from the fd, then the stream will be
|
||||||
// reset with an error code.
|
// reset with an error code.
|
||||||
respondWithFD(fd, headers) {
|
respondWithFD(fd, headers, options) {
|
||||||
const session = this[kSession];
|
const session = this[kSession];
|
||||||
if (this.destroyed)
|
if (this.destroyed)
|
||||||
throw new errors.Error('ERR_HTTP2_INVALID_STREAM');
|
throw new errors.Error('ERR_HTTP2_INVALID_STREAM');
|
||||||
@ -1803,6 +1848,26 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
if (state.headersSent)
|
if (state.headersSent)
|
||||||
throw new errors.Error('ERR_HTTP2_HEADERS_SENT');
|
throw new errors.Error('ERR_HTTP2_HEADERS_SENT');
|
||||||
|
|
||||||
|
assertIsObject(options, 'options');
|
||||||
|
options = Object.assign(Object.create(null), options);
|
||||||
|
|
||||||
|
if (options.offset !== undefined && typeof options.offset !== 'number')
|
||||||
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
'offset',
|
||||||
|
options.offset);
|
||||||
|
|
||||||
|
if (options.length !== undefined && typeof options.length !== 'number')
|
||||||
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
'length',
|
||||||
|
options.length);
|
||||||
|
|
||||||
|
if (options.statCheck !== undefined &&
|
||||||
|
typeof options.statCheck !== 'function') {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
'statCheck',
|
||||||
|
options.statCheck);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof fd !== 'number')
|
if (typeof fd !== 'number')
|
||||||
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
|
||||||
'fd', 'number');
|
'fd', 'number');
|
||||||
@ -1816,13 +1881,20 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
throw new errors.Error('ERR_HTTP2_PAYLOAD_FORBIDDEN', statusCode);
|
throw new errors.Error('ERR_HTTP2_PAYLOAD_FORBIDDEN', statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.statCheck !== undefined) {
|
||||||
|
fs.fstat(fd, doSendFD.bind(this, session, options, fd, headers));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers,
|
const headersList = mapToHeaders(headers,
|
||||||
assertValidPseudoHeaderResponse);
|
assertValidPseudoHeaderResponse);
|
||||||
if (!Array.isArray(headersList)) {
|
if (!Array.isArray(headersList)) {
|
||||||
throw headersList;
|
process.nextTick(() => this.emit('error', headersList));
|
||||||
}
|
}
|
||||||
|
|
||||||
processRespondWithFD.call(this, fd, headersList);
|
processRespondWithFD.call(this, fd, headersList,
|
||||||
|
options.offset,
|
||||||
|
options.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a file response on this Http2Stream. The path is passed to
|
// Initiate a file response on this Http2Stream. The path is passed to
|
||||||
@ -1847,6 +1919,16 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
assertIsObject(options, 'options');
|
assertIsObject(options, 'options');
|
||||||
options = Object.assign(Object.create(null), options);
|
options = Object.assign(Object.create(null), options);
|
||||||
|
|
||||||
|
if (options.offset !== undefined && typeof options.offset !== 'number')
|
||||||
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
'offset',
|
||||||
|
options.offset);
|
||||||
|
|
||||||
|
if (options.length !== undefined && typeof options.length !== 'number')
|
||||||
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
'length',
|
||||||
|
options.length);
|
||||||
|
|
||||||
if (options.statCheck !== undefined &&
|
if (options.statCheck !== undefined &&
|
||||||
typeof options.statCheck !== 'function') {
|
typeof options.statCheck !== 'function') {
|
||||||
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
|
||||||
|
@ -604,7 +604,9 @@ void Http2Session::SubmitResponse(const FunctionCallbackInfo<Value>& args) {
|
|||||||
void Http2Session::SubmitFile(const FunctionCallbackInfo<Value>& args) {
|
void Http2Session::SubmitFile(const FunctionCallbackInfo<Value>& args) {
|
||||||
CHECK(args[0]->IsNumber()); // Stream ID
|
CHECK(args[0]->IsNumber()); // Stream ID
|
||||||
CHECK(args[1]->IsNumber()); // File Descriptor
|
CHECK(args[1]->IsNumber()); // File Descriptor
|
||||||
CHECK(args[2]->IsArray()); // Headers
|
CHECK(args[2]->IsArray()); // Headers
|
||||||
|
CHECK(args[3]->IsNumber()); // Offset
|
||||||
|
CHECK(args[4]->IsNumber()); // Length
|
||||||
|
|
||||||
Http2Session* session;
|
Http2Session* session;
|
||||||
Nghttp2Stream* stream;
|
Nghttp2Stream* stream;
|
||||||
@ -618,6 +620,11 @@ void Http2Session::SubmitFile(const FunctionCallbackInfo<Value>& args) {
|
|||||||
int fd = args[1]->Int32Value(context).ToChecked();
|
int fd = args[1]->Int32Value(context).ToChecked();
|
||||||
Local<Array> headers = args[2].As<Array>();
|
Local<Array> headers = args[2].As<Array>();
|
||||||
|
|
||||||
|
int64_t offset = args[3]->IntegerValue(context).ToChecked();
|
||||||
|
int64_t length = args[4]->IntegerValue(context).ToChecked();
|
||||||
|
|
||||||
|
CHECK_GE(offset, 0);
|
||||||
|
|
||||||
DEBUG_HTTP2("Http2Session: submitting file %d for stream %d: headers: %d, "
|
DEBUG_HTTP2("Http2Session: submitting file %d for stream %d: headers: %d, "
|
||||||
"end-stream: %d\n", fd, id, headers->Length());
|
"end-stream: %d\n", fd, id, headers->Length());
|
||||||
|
|
||||||
@ -627,7 +634,8 @@ void Http2Session::SubmitFile(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
Headers list(isolate, context, headers);
|
Headers list(isolate, context, headers);
|
||||||
|
|
||||||
args.GetReturnValue().Set(stream->SubmitFile(fd, *list, list.length()));
|
args.GetReturnValue().Set(stream->SubmitFile(fd, *list, list.length(),
|
||||||
|
offset, length));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Session::SendHeaders(const FunctionCallbackInfo<Value>& args) {
|
void Http2Session::SendHeaders(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
@ -429,7 +429,10 @@ inline int Nghttp2Stream::SubmitResponse(nghttp2_nv* nva,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a response that contains data read from a file descriptor.
|
// Initiate a response that contains data read from a file descriptor.
|
||||||
inline int Nghttp2Stream::SubmitFile(int fd, nghttp2_nv* nva, size_t len) {
|
inline int Nghttp2Stream::SubmitFile(int fd,
|
||||||
|
nghttp2_nv* nva, size_t len,
|
||||||
|
int64_t offset,
|
||||||
|
int64_t length) {
|
||||||
CHECK_GT(len, 0);
|
CHECK_GT(len, 0);
|
||||||
CHECK_GT(fd, 0);
|
CHECK_GT(fd, 0);
|
||||||
DEBUG_HTTP2("Nghttp2Stream %d: submitting file\n", id_);
|
DEBUG_HTTP2("Nghttp2Stream %d: submitting file\n", id_);
|
||||||
@ -438,6 +441,9 @@ inline int Nghttp2Stream::SubmitFile(int fd, nghttp2_nv* nva, size_t len) {
|
|||||||
prov.source.fd = fd;
|
prov.source.fd = fd;
|
||||||
prov.read_callback = Nghttp2Session::OnStreamReadFD;
|
prov.read_callback = Nghttp2Session::OnStreamReadFD;
|
||||||
|
|
||||||
|
if (offset > 0) fd_offset_ = offset;
|
||||||
|
if (length > -1) fd_length_ = length;
|
||||||
|
|
||||||
return nghttp2_submit_response(session_->session(), id_,
|
return nghttp2_submit_response(session_->session(), id_,
|
||||||
nva, len, &prov);
|
nva, len, &prov);
|
||||||
}
|
}
|
||||||
|
@ -180,18 +180,25 @@ ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session* session,
|
|||||||
|
|
||||||
int fd = source->fd;
|
int fd = source->fd;
|
||||||
int64_t offset = stream->fd_offset_;
|
int64_t offset = stream->fd_offset_;
|
||||||
ssize_t numchars;
|
ssize_t numchars = 0;
|
||||||
|
|
||||||
|
if (stream->fd_length_ >= 0 &&
|
||||||
|
stream->fd_length_ < static_cast<int64_t>(length))
|
||||||
|
length = stream->fd_length_;
|
||||||
|
|
||||||
uv_buf_t data;
|
uv_buf_t data;
|
||||||
data.base = reinterpret_cast<char*>(buf);
|
data.base = reinterpret_cast<char*>(buf);
|
||||||
data.len = length;
|
data.len = length;
|
||||||
|
|
||||||
uv_fs_t read_req;
|
uv_fs_t read_req;
|
||||||
numchars = uv_fs_read(handle->loop_,
|
|
||||||
&read_req,
|
if (length > 0) {
|
||||||
fd, &data, 1,
|
numchars = uv_fs_read(handle->loop_,
|
||||||
offset, nullptr);
|
&read_req,
|
||||||
uv_fs_req_cleanup(&read_req);
|
fd, &data, 1,
|
||||||
|
offset, nullptr);
|
||||||
|
uv_fs_req_cleanup(&read_req);
|
||||||
|
}
|
||||||
|
|
||||||
// Close the stream with an error if reading fails
|
// Close the stream with an error if reading fails
|
||||||
if (numchars < 0)
|
if (numchars < 0)
|
||||||
@ -199,9 +206,10 @@ ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session* session,
|
|||||||
|
|
||||||
// Update the read offset for the next read
|
// Update the read offset for the next read
|
||||||
stream->fd_offset_ += numchars;
|
stream->fd_offset_ += numchars;
|
||||||
|
stream->fd_length_ -= numchars;
|
||||||
|
|
||||||
// if numchars < length, assume that we are done.
|
// if numchars < length, assume that we are done.
|
||||||
if (static_cast<size_t>(numchars) < length) {
|
if (static_cast<size_t>(numchars) < length || length <= 0) {
|
||||||
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
|
DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
|
||||||
handle->session_type_, id);
|
handle->session_type_, id);
|
||||||
*flags |= NGHTTP2_DATA_FLAG_EOF;
|
*flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||||
|
@ -310,7 +310,10 @@ class Nghttp2Stream {
|
|||||||
bool emptyPayload = false);
|
bool emptyPayload = false);
|
||||||
|
|
||||||
// Send data read from a file descriptor as the response on this stream.
|
// Send data read from a file descriptor as the response on this stream.
|
||||||
inline int SubmitFile(int fd, nghttp2_nv* nva, size_t len);
|
inline int SubmitFile(int fd,
|
||||||
|
nghttp2_nv* nva, size_t len,
|
||||||
|
int64_t offset,
|
||||||
|
int64_t length);
|
||||||
|
|
||||||
// Submit informational headers for this stream
|
// Submit informational headers for this stream
|
||||||
inline int SubmitInfo(nghttp2_nv* nva, size_t len);
|
inline int SubmitInfo(nghttp2_nv* nva, size_t len);
|
||||||
@ -420,7 +423,8 @@ class Nghttp2Stream {
|
|||||||
nghttp2_stream_write_queue* queue_tail_ = nullptr;
|
nghttp2_stream_write_queue* queue_tail_ = nullptr;
|
||||||
unsigned int queue_head_index_ = 0;
|
unsigned int queue_head_index_ = 0;
|
||||||
size_t queue_head_offset_ = 0;
|
size_t queue_head_offset_ = 0;
|
||||||
size_t fd_offset_ = 0;
|
int64_t fd_offset_ = 0;
|
||||||
|
int64_t fd_length_ = -1;
|
||||||
|
|
||||||
// The Current Headers block... As headers are received for this stream,
|
// The Current Headers block... As headers are received for this stream,
|
||||||
// they are temporarily stored here until the OnFrameReceived is called
|
// they are temporarily stored here until the OnFrameReceived is called
|
||||||
|
@ -24,28 +24,28 @@ server.on('listening', common.mustCall(() => {
|
|||||||
|
|
||||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
assert.throws(() => client.settings({headerTableSize: -1}),
|
assert.throws(() => client.settings({ headerTableSize: -1 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({headerTableSize: 2 ** 32}),
|
assert.throws(() => client.settings({ headerTableSize: 2 ** 32 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({initialWindowSize: -1}),
|
assert.throws(() => client.settings({ initialWindowSize: -1 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({initialWindowSize: 2 ** 32}),
|
assert.throws(() => client.settings({ initialWindowSize: 2 ** 32 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxFrameSize: 1}),
|
assert.throws(() => client.settings({ maxFrameSize: 1 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxFrameSize: 2 ** 24}),
|
assert.throws(() => client.settings({ maxFrameSize: 2 ** 24 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxConcurrentStreams: -1}),
|
assert.throws(() => client.settings({ maxConcurrentStreams: -1 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxConcurrentStreams: 2 ** 31}),
|
assert.throws(() => client.settings({ maxConcurrentStreams: 2 ** 31 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxHeaderListSize: -1}),
|
assert.throws(() => client.settings({ maxHeaderListSize: -1 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
assert.throws(() => client.settings({maxHeaderListSize: 2 ** 32}),
|
assert.throws(() => client.settings({ maxHeaderListSize: 2 ** 32 }),
|
||||||
RangeError);
|
RangeError);
|
||||||
['a', 1, 0, null, {}].forEach((i) => {
|
['a', 1, 0, null, {}].forEach((i) => {
|
||||||
assert.throws(() => client.settings({enablePush: i}), TypeError);
|
assert.throws(() => client.settings({ enablePush: i }), TypeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.settings({ maxFrameSize: 1234567 });
|
client.settings({ maxFrameSize: 1234567 });
|
||||||
|
@ -15,7 +15,7 @@ server.on('listening', common.mustCall(() => {
|
|||||||
|
|
||||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
client.shutdown({graceful: true}, common.mustCall(() => {
|
client.shutdown({ graceful: true }, common.mustCall(() => {
|
||||||
server.close();
|
server.close();
|
||||||
client.destroy();
|
client.destroy();
|
||||||
}));
|
}));
|
||||||
|
@ -13,7 +13,7 @@ server.listen(0, common.mustCall(function() {
|
|||||||
server.once('request', common.mustCall(function(request, response) {
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
response.flushHeaders();
|
response.flushHeaders();
|
||||||
response.flushHeaders(); // Idempotent
|
response.flushHeaders(); // Idempotent
|
||||||
response.writeHead(400, {'foo-bar': 'abc123'}); // Ignored
|
response.writeHead(400, { 'foo-bar': 'abc123' }); // Ignored
|
||||||
|
|
||||||
response.on('finish', common.mustCall(function() {
|
response.on('finish', common.mustCall(function() {
|
||||||
server.close();
|
server.close();
|
||||||
|
@ -53,7 +53,7 @@ server.listen(0, common.mustCall(function() {
|
|||||||
response.setHeader(real, expectedValue);
|
response.setHeader(real, expectedValue);
|
||||||
const expectedHeaderNames = [real];
|
const expectedHeaderNames = [real];
|
||||||
assert.deepStrictEqual(response.getHeaderNames(), expectedHeaderNames);
|
assert.deepStrictEqual(response.getHeaderNames(), expectedHeaderNames);
|
||||||
const expectedHeaders = {[real]: expectedValue};
|
const expectedHeaders = { [real]: expectedValue };
|
||||||
assert.deepStrictEqual(response.getHeaders(), expectedHeaders);
|
assert.deepStrictEqual(response.getHeaders(), expectedHeaders);
|
||||||
|
|
||||||
response.getHeaders()[fake] = fake;
|
response.getHeaders()[fake] = fake;
|
||||||
|
@ -8,7 +8,7 @@ const h2 = require('http2');
|
|||||||
// Http2ServerResponse.writeHead should accept an optional status message
|
// Http2ServerResponse.writeHead should accept an optional status message
|
||||||
|
|
||||||
const unsupportedWarned = common.mustCall(1);
|
const unsupportedWarned = common.mustCall(1);
|
||||||
process.on('warning', ({name, message}) => {
|
process.on('warning', ({ name, message }) => {
|
||||||
const expectedMessage =
|
const expectedMessage =
|
||||||
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)';
|
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)';
|
||||||
if (name === 'UnsupportedWarning' && message === expectedMessage)
|
if (name === 'UnsupportedWarning' && message === expectedMessage)
|
||||||
@ -21,7 +21,7 @@ server.listen(0, common.mustCall(function() {
|
|||||||
server.once('request', common.mustCall(function(request, response) {
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
const statusCode = 200;
|
const statusCode = 200;
|
||||||
const statusMessage = 'OK';
|
const statusMessage = 'OK';
|
||||||
const headers = {'foo-bar': 'abc123'};
|
const headers = { 'foo-bar': 'abc123' };
|
||||||
response.writeHead(statusCode, statusMessage, headers);
|
response.writeHead(statusCode, statusMessage, headers);
|
||||||
|
|
||||||
response.on('finish', common.mustCall(function() {
|
response.on('finish', common.mustCall(function() {
|
||||||
|
@ -13,7 +13,7 @@ server.listen(0, common.mustCall(function() {
|
|||||||
server.once('request', common.mustCall(function(request, response) {
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
response.setHeader('foo-bar', 'def456');
|
response.setHeader('foo-bar', 'def456');
|
||||||
response.writeHead(500);
|
response.writeHead(500);
|
||||||
response.writeHead(418, {'foo-bar': 'abc123'}); // Override
|
response.writeHead(418, { 'foo-bar': 'abc123' }); // Override
|
||||||
|
|
||||||
response.on('finish', common.mustCall(function() {
|
response.on('finish', common.mustCall(function() {
|
||||||
assert.doesNotThrow(() => { response.writeHead(300); });
|
assert.doesNotThrow(() => { response.writeHead(300); });
|
||||||
|
@ -21,8 +21,8 @@ const URL = url.URL;
|
|||||||
[`http://localhost:${port}`],
|
[`http://localhost:${port}`],
|
||||||
[new URL(`http://localhost:${port}`)],
|
[new URL(`http://localhost:${port}`)],
|
||||||
[url.parse(`http://localhost:${port}`)],
|
[url.parse(`http://localhost:${port}`)],
|
||||||
[{port: port}, {protocol: 'http:'}],
|
[{ port: port }, { protocol: 'http:' }],
|
||||||
[{port: port, hostname: '127.0.0.1'}, {protocol: 'http:'}]
|
[{ port: port, hostname: '127.0.0.1' }, { protocol: 'http:' }]
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = items.length;
|
let count = items.length;
|
||||||
@ -41,7 +41,7 @@ const URL = url.URL;
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Will fail because protocol does not match the server.
|
// Will fail because protocol does not match the server.
|
||||||
h2.connect({port: port, protocol: 'https:'})
|
h2.connect({ port: port, protocol: 'https:' })
|
||||||
.on('socketError', common.mustCall());
|
.on('socketError', common.mustCall());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -60,14 +60,14 @@ const URL = url.URL;
|
|||||||
server.on('listening', common.mustCall(function() {
|
server.on('listening', common.mustCall(function() {
|
||||||
const port = this.address().port;
|
const port = this.address().port;
|
||||||
|
|
||||||
const opts = {rejectUnauthorized: false};
|
const opts = { rejectUnauthorized: false };
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
[`https://localhost:${port}`, opts],
|
[`https://localhost:${port}`, opts],
|
||||||
[new URL(`https://localhost:${port}`), opts],
|
[new URL(`https://localhost:${port}`), opts],
|
||||||
[url.parse(`https://localhost:${port}`), opts],
|
[url.parse(`https://localhost:${port}`), opts],
|
||||||
[{port: port, protocol: 'https:'}, opts],
|
[{ port: port, protocol: 'https:' }, opts],
|
||||||
[{port: port, hostname: '127.0.0.1', protocol: 'https:'}, opts]
|
[{ port: port, hostname: '127.0.0.1', protocol: 'https:' }, opts]
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = items.length;
|
let count = items.length;
|
||||||
|
@ -27,7 +27,7 @@ function onStream(stream, headers) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifySecureSession(key, cert, ca, opts) {
|
function verifySecureSession(key, cert, ca, opts) {
|
||||||
const server = h2.createSecureServer({cert, key});
|
const server = h2.createSecureServer({ cert, key });
|
||||||
server.on('stream', common.mustCall(onStream));
|
server.on('stream', common.mustCall(onStream));
|
||||||
server.listen(0);
|
server.listen(0);
|
||||||
server.on('listening', common.mustCall(function() {
|
server.on('listening', common.mustCall(function() {
|
||||||
@ -35,7 +35,7 @@ function verifySecureSession(key, cert, ca, opts) {
|
|||||||
if (!opts) {
|
if (!opts) {
|
||||||
opts = {};
|
opts = {};
|
||||||
}
|
}
|
||||||
opts.secureContext = tls.createSecureContext({ca});
|
opts.secureContext = tls.createSecureContext({ ca });
|
||||||
const client = h2.connect(`https://localhost:${this.address().port}`, opts, function() {
|
const client = h2.connect(`https://localhost:${this.address().port}`, opts, function() {
|
||||||
const req = client.request(headers);
|
const req = client.request(headers);
|
||||||
|
|
||||||
@ -72,4 +72,4 @@ verifySecureSession(
|
|||||||
loadKey('agent1-key.pem'),
|
loadKey('agent1-key.pem'),
|
||||||
loadKey('agent1-cert.pem'),
|
loadKey('agent1-cert.pem'),
|
||||||
loadKey('ca1-cert.pem'),
|
loadKey('ca1-cert.pem'),
|
||||||
{servername: 'agent1'});
|
{ servername: 'agent1' });
|
||||||
|
@ -122,7 +122,7 @@ assert.doesNotThrow(() => http2.getPackedSettings({ enablePush: false }));
|
|||||||
const packed = Buffer.from([0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF]);
|
const packed = Buffer.from([0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF]);
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
http2.getUnpackedSettings(packed, {validate: true});
|
http2.getUnpackedSettings(packed, { validate: true });
|
||||||
}, common.expectsError({
|
}, common.expectsError({
|
||||||
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
|
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
|
||||||
type: RangeError,
|
type: RangeError,
|
||||||
|
@ -56,7 +56,7 @@ server.on('listening', common.mustCall(() => {
|
|||||||
|
|
||||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
const req = client.request({ ':path': '/'});
|
const req = client.request({ ':path': '/' });
|
||||||
|
|
||||||
// The additionalHeaders method does not exist on client stream
|
// The additionalHeaders method does not exist on client stream
|
||||||
assert.strictEqual(req.additionalHeaders, undefined);
|
assert.strictEqual(req.additionalHeaders, undefined);
|
||||||
|
@ -13,7 +13,7 @@ const {
|
|||||||
} = h2.constants;
|
} = h2.constants;
|
||||||
|
|
||||||
// Only allow one stream to be open at a time
|
// Only allow one stream to be open at a time
|
||||||
const server = h2.createServer({ settings: { maxConcurrentStreams: 1 }});
|
const server = h2.createServer({ settings: { maxConcurrentStreams: 1 } });
|
||||||
|
|
||||||
// The stream handler must be called only once
|
// The stream handler must be called only once
|
||||||
server.on('stream', common.mustCall((stream) => {
|
server.on('stream', common.mustCall((stream) => {
|
||||||
|
@ -18,7 +18,7 @@ function onStream(stream, headers, flags) {
|
|||||||
':method',
|
':method',
|
||||||
':scheme'
|
':scheme'
|
||||||
].forEach((i) => {
|
].forEach((i) => {
|
||||||
assert.throws(() => stream.respond({[i]: '/'}),
|
assert.throws(() => stream.respond({ [i]: '/' }),
|
||||||
common.expectsError({
|
common.expectsError({
|
||||||
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
|
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
|
||||||
}));
|
}));
|
||||||
|
@ -47,7 +47,7 @@ server.listen(0, common.mustCall(() => {
|
|||||||
|
|
||||||
// Request 3 will fail because nghttp2 does not allow the content-length
|
// Request 3 will fail because nghttp2 does not allow the content-length
|
||||||
// header to be set for non-payload bearing requests...
|
// header to be set for non-payload bearing requests...
|
||||||
const req3 = client.request({ 'content-length': 1});
|
const req3 = client.request({ 'content-length': 1 });
|
||||||
req3.resume();
|
req3.resume();
|
||||||
req3.on('end', common.mustCall(maybeClose));
|
req3.on('end', common.mustCall(maybeClose));
|
||||||
req3.on('error', common.expectsError({
|
req3.on('error', common.expectsError({
|
||||||
|
@ -30,7 +30,7 @@ server.listen(0, common.mustCall(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doRequest() {
|
function doRequest() {
|
||||||
const req = client.request({ ':method': 'POST '});
|
const req = client.request({ ':method': 'POST ' });
|
||||||
|
|
||||||
let data = '';
|
let data = '';
|
||||||
req.setEncoding('utf8');
|
req.setEncoding('utf8');
|
||||||
|
@ -37,7 +37,7 @@ server.on('priority', common.mustCall(onPriority));
|
|||||||
server.on('listening', common.mustCall(() => {
|
server.on('listening', common.mustCall(() => {
|
||||||
|
|
||||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||||
const req = client.request({ ':path': '/'});
|
const req = client.request({ ':path': '/' });
|
||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
req.priority({
|
req.priority({
|
||||||
|
94
test/parallel/test-http2-respond-file-fd-range.js
Normal file
94
test/parallel/test-http2-respond-file-fd-range.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Tests the ability to minimally request a byte range with respondWithFD
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const http2 = require('http2');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const {
|
||||||
|
HTTP2_HEADER_CONTENT_TYPE,
|
||||||
|
HTTP2_HEADER_CONTENT_LENGTH
|
||||||
|
} = http2.constants;
|
||||||
|
|
||||||
|
const fname = path.resolve(common.fixturesDir, 'printA.js');
|
||||||
|
const data = fs.readFileSync(fname);
|
||||||
|
const fd = fs.openSync(fname, 'r');
|
||||||
|
|
||||||
|
// Note: this is not anywhere close to a proper implementation of the range
|
||||||
|
// header.
|
||||||
|
function getOffsetLength(range) {
|
||||||
|
if (range === undefined)
|
||||||
|
return [0, -1];
|
||||||
|
const r = /bytes=(\d+)-(\d+)/.exec(range);
|
||||||
|
return [+r[1], +r[2] - +r[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', (stream, headers) => {
|
||||||
|
|
||||||
|
const [ offset, length ] = getOffsetLength(headers.range);
|
||||||
|
|
||||||
|
stream.respondWithFD(fd, {
|
||||||
|
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
|
||||||
|
}, {
|
||||||
|
statCheck: common.mustCall((stat, headers, options) => {
|
||||||
|
assert.strictEqual(options.length, length);
|
||||||
|
assert.strictEqual(options.offset, offset);
|
||||||
|
headers[HTTP2_HEADER_CONTENT_LENGTH] =
|
||||||
|
Math.min(options.length, stat.size - offset);
|
||||||
|
}),
|
||||||
|
offset: offset,
|
||||||
|
length: length
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.on('close', common.mustCall(() => fs.closeSync(fd)));
|
||||||
|
server.listen(0, () => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
|
let remaining = 2;
|
||||||
|
function maybeClose() {
|
||||||
|
if (--remaining === 0) {
|
||||||
|
client.destroy();
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const req = client.request({ range: 'bytes=8-11' });
|
||||||
|
|
||||||
|
req.on('response', common.mustCall((headers) => {
|
||||||
|
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
|
||||||
|
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], 3);
|
||||||
|
}));
|
||||||
|
req.setEncoding('utf8');
|
||||||
|
let check = '';
|
||||||
|
req.on('data', (chunk) => check += chunk);
|
||||||
|
req.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(check, data.toString('utf8', 8, 11));
|
||||||
|
}));
|
||||||
|
req.on('streamClosed', common.mustCall(maybeClose));
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const req = client.request({ range: 'bytes=8-28' });
|
||||||
|
|
||||||
|
req.on('response', common.mustCall((headers) => {
|
||||||
|
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
|
||||||
|
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], 9);
|
||||||
|
}));
|
||||||
|
req.setEncoding('utf8');
|
||||||
|
let check = '';
|
||||||
|
req.on('data', (chunk) => check += chunk);
|
||||||
|
req.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(check, data.toString('utf8', 8, 28));
|
||||||
|
}));
|
||||||
|
req.on('streamClosed', common.mustCall(maybeClose));
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
52
test/parallel/test-http2-respond-file-range.js
Normal file
52
test/parallel/test-http2-respond-file-range.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const http2 = require('http2');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const {
|
||||||
|
HTTP2_HEADER_CONTENT_TYPE,
|
||||||
|
HTTP2_HEADER_CONTENT_LENGTH,
|
||||||
|
HTTP2_HEADER_LAST_MODIFIED
|
||||||
|
} = http2.constants;
|
||||||
|
|
||||||
|
const fname = path.resolve(common.fixturesDir, 'printA.js');
|
||||||
|
const data = fs.readFileSync(fname);
|
||||||
|
const stat = fs.statSync(fname);
|
||||||
|
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', (stream) => {
|
||||||
|
stream.respondWithFile(fname, {
|
||||||
|
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain'
|
||||||
|
}, {
|
||||||
|
statCheck: common.mustCall((stat, headers) => {
|
||||||
|
headers[HTTP2_HEADER_LAST_MODIFIED] = stat.mtime.toUTCString();
|
||||||
|
}),
|
||||||
|
offset: 8,
|
||||||
|
length: 3
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.listen(0, () => {
|
||||||
|
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
const req = client.request();
|
||||||
|
|
||||||
|
req.on('response', common.mustCall((headers) => {
|
||||||
|
assert.strictEqual(headers[HTTP2_HEADER_CONTENT_TYPE], 'text/plain');
|
||||||
|
assert.strictEqual(+headers[HTTP2_HEADER_CONTENT_LENGTH], 3);
|
||||||
|
assert.strictEqual(headers[HTTP2_HEADER_LAST_MODIFIED],
|
||||||
|
stat.mtime.toUTCString());
|
||||||
|
}));
|
||||||
|
req.setEncoding('utf8');
|
||||||
|
let check = '';
|
||||||
|
req.on('data', (chunk) => check += chunk);
|
||||||
|
req.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(check, data.toString('utf8', 8, 11));
|
||||||
|
client.destroy();
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
req.end();
|
||||||
|
});
|
@ -25,7 +25,7 @@ const key = loadKey('agent8-key.pem');
|
|||||||
const cert = loadKey('agent8-cert.pem');
|
const cert = loadKey('agent8-cert.pem');
|
||||||
const ca = loadKey('fake-startcom-root-cert.pem');
|
const ca = loadKey('fake-startcom-root-cert.pem');
|
||||||
|
|
||||||
const server = http2.createSecureServer({key, cert});
|
const server = http2.createSecureServer({ key, cert });
|
||||||
|
|
||||||
server.on('stream', (stream, headers) => {
|
server.on('stream', (stream, headers) => {
|
||||||
const name = headers[HTTP2_HEADER_PATH].slice(1);
|
const name = headers[HTTP2_HEADER_PATH].slice(1);
|
||||||
@ -44,7 +44,7 @@ server.on('stream', (stream, headers) => {
|
|||||||
|
|
||||||
server.listen(8000, () => {
|
server.listen(8000, () => {
|
||||||
|
|
||||||
const secureContext = tls.createSecureContext({ca});
|
const secureContext = tls.createSecureContext({ ca });
|
||||||
const client = http2.connect(`https://localhost:${server.address().port}`,
|
const client = http2.connect(`https://localhost:${server.address().port}`,
|
||||||
{ secureContext });
|
{ secureContext });
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ server.on('stream', common.mustCall((stream) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
server.listen(0, common.mustCall(() => {
|
server.listen(0, common.mustCall(() => {
|
||||||
const options = {settings: { enablePush: false }};
|
const options = { settings: { enablePush: false } };
|
||||||
const client = http2.connect(`http://localhost:${server.address().port}`,
|
const client = http2.connect(`http://localhost:${server.address().port}`,
|
||||||
options);
|
options);
|
||||||
const req = client.request({ ':path': '/' });
|
const req = client.request({ ':path': '/' });
|
||||||
|
@ -11,7 +11,7 @@ server.on('stream', common.mustCall(onStream));
|
|||||||
|
|
||||||
function onStream(stream, headers, flags) {
|
function onStream(stream, headers, flags) {
|
||||||
const session = stream.session;
|
const session = stream.session;
|
||||||
stream.session.shutdown({graceful: true}, common.mustCall(() => {
|
stream.session.shutdown({ graceful: true }, common.mustCall(() => {
|
||||||
session.destroy();
|
session.destroy();
|
||||||
}));
|
}));
|
||||||
stream.respond({});
|
stream.respond({});
|
||||||
|
@ -90,7 +90,7 @@ server.on('listening', common.mustCall(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
|
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
|
||||||
assert.throws(() => client.settings({enablePush: i}),
|
assert.throws(() => client.settings({ enablePush: i }),
|
||||||
common.expectsError({
|
common.expectsError({
|
||||||
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
|
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
|
||||||
type: TypeError
|
type: TypeError
|
||||||
|
@ -9,7 +9,7 @@ const server = h2.createServer();
|
|||||||
// we use the lower-level API here
|
// we use the lower-level API here
|
||||||
server.on('stream', common.mustCall((stream) => {
|
server.on('stream', common.mustCall((stream) => {
|
||||||
stream.setTimeout(1, common.mustCall(() => {
|
stream.setTimeout(1, common.mustCall(() => {
|
||||||
stream.respond({':status': 200});
|
stream.respond({ ':status': 200 });
|
||||||
stream.end('hello world');
|
stream.end('hello world');
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
@ -29,7 +29,7 @@ server.listen(0);
|
|||||||
|
|
||||||
server.on('listening', common.mustCall(function() {
|
server.on('listening', common.mustCall(function() {
|
||||||
const client = h2.connect(`http://localhost:${this.address().port}`);
|
const client = h2.connect(`http://localhost:${this.address().port}`);
|
||||||
const req = client.request({':path': '/'});
|
const req = client.request({ ':path': '/' });
|
||||||
req.on('data', common.mustCall());
|
req.on('data', common.mustCall());
|
||||||
req.on('trailers', common.mustCall((headers) => {
|
req.on('trailers', common.mustCall((headers) => {
|
||||||
assert.strictEqual(headers[trailerKey], trailerValue);
|
assert.strictEqual(headers[trailerKey], trailerValue);
|
||||||
|
@ -191,7 +191,7 @@ const {
|
|||||||
common.expectsError({
|
common.expectsError({
|
||||||
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
||||||
message: msg
|
message: msg
|
||||||
})(mapToHeaders({[name]: [1, 2, 3]}));
|
})(mapToHeaders({ [name]: [1, 2, 3] }));
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
@ -217,7 +217,7 @@ const {
|
|||||||
HTTP2_HEADER_VIA,
|
HTTP2_HEADER_VIA,
|
||||||
HTTP2_HEADER_WWW_AUTHENTICATE
|
HTTP2_HEADER_WWW_AUTHENTICATE
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
assert(!(mapToHeaders({[name]: [1, 2, 3]}) instanceof Error), name);
|
assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name);
|
||||||
});
|
});
|
||||||
|
|
||||||
const regex =
|
const regex =
|
||||||
@ -242,7 +242,7 @@ const regex =
|
|||||||
common.expectsError({
|
common.expectsError({
|
||||||
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
||||||
message: regex
|
message: regex
|
||||||
})(mapToHeaders({[name]: 'abc'}));
|
})(mapToHeaders({ [name]: 'abc' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(!(mapToHeaders({ te: 'trailers' }) instanceof Error));
|
assert(!(mapToHeaders({ te: 'trailers' }) instanceof Error));
|
||||||
|
@ -6,7 +6,7 @@ const assert = require('assert');
|
|||||||
const http2 = require('http2');
|
const http2 = require('http2');
|
||||||
|
|
||||||
const server = http2.createServer(function(request, response) {
|
const server = http2.createServer(function(request, response) {
|
||||||
response.writeHead(200, {'Content-Type': 'text/plain'});
|
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
response.write('1\n');
|
response.write('1\n');
|
||||||
response.write('');
|
response.write('');
|
||||||
response.write('2\n');
|
response.write('2\n');
|
||||||
|
@ -39,13 +39,17 @@ const server = tls.Server(options, common.mustCall((socket) => {
|
|||||||
|
|
||||||
server.listen(0, common.mustCall(() => {
|
server.listen(0, common.mustCall(() => {
|
||||||
const port = server.address().port;
|
const port = server.address().port;
|
||||||
|
const options = {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
port
|
||||||
|
};
|
||||||
const client =
|
const client =
|
||||||
tls.connect({rejectUnauthorized: false, port: port}, common.mustCall(() => {
|
tls.connect(options, common.mustCall(() => {
|
||||||
client.write('');
|
client.write('');
|
||||||
// Negotiation is still permitted for this first
|
// Negotiation is still permitted for this first
|
||||||
// attempt. This should succeed.
|
// attempt. This should succeed.
|
||||||
client.renegotiate(
|
client.renegotiate(
|
||||||
{rejectUnauthorized: false},
|
{ rejectUnauthorized: false },
|
||||||
common.mustCall(() => {
|
common.mustCall(() => {
|
||||||
// Once renegotiation completes, we write some
|
// Once renegotiation completes, we write some
|
||||||
// data to the socket, which triggers the on
|
// data to the socket, which triggers the on
|
||||||
@ -58,7 +62,7 @@ server.listen(0, common.mustCall(() => {
|
|||||||
// server will simply drop the connection after
|
// server will simply drop the connection after
|
||||||
// emitting the error.
|
// emitting the error.
|
||||||
client.renegotiate(
|
client.renegotiate(
|
||||||
{rejectUnauthorized: false},
|
{ rejectUnauthorized: false },
|
||||||
common.mustNotCall());
|
common.mustNotCall());
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user