http2: doc and fixes to the Compatibility API
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
d6a774b1bd
commit
a4017736d2
777
doc/api/http2.md
777
doc/api/http2.md
@ -16,9 +16,10 @@ in order to use the `'http2'` module.
|
|||||||
|
|
||||||
The Core API provides a low-level interface designed specifically around
|
The Core API provides a low-level interface designed specifically around
|
||||||
support for HTTP/2 protocol features. It is specifically *not* designed for
|
support for HTTP/2 protocol features. It is specifically *not* designed for
|
||||||
compatibility with the existing [HTTP/1][] module API.
|
compatibility with the existing [HTTP/1][] module API. However, the [Compatibility API][] is.
|
||||||
|
|
||||||
The following illustrates a simple, plain-text HTTP/2 server:
|
The following illustrates a simple, plain-text HTTP/2 server using the
|
||||||
|
Core API:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const http2 = require('http2');
|
const http2 = require('http2');
|
||||||
@ -27,6 +28,7 @@ const http2 = require('http2');
|
|||||||
const server = http2.createServer();
|
const server = http2.createServer();
|
||||||
|
|
||||||
server.on('stream', (stream, headers) => {
|
server.on('stream', (stream, headers) => {
|
||||||
|
// stream is a Duplex
|
||||||
stream.respond({
|
stream.respond({
|
||||||
'content-type': 'text/html',
|
'content-type': 'text/html',
|
||||||
':status': 200
|
':status': 200
|
||||||
@ -44,6 +46,7 @@ const http2 = require('http2');
|
|||||||
|
|
||||||
const client = http2.connect('http://localhost:80');
|
const client = http2.connect('http://localhost:80');
|
||||||
|
|
||||||
|
// req is a Duplex
|
||||||
const req = client.request({ ':path': '/' });
|
const req = client.request({ ':path': '/' });
|
||||||
|
|
||||||
req.on('response', (headers) => {
|
req.on('response', (headers) => {
|
||||||
@ -1171,6 +1174,17 @@ server.on('stream', (stream, headers, flags) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Event: 'request'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `request` {http2.Http2ServerRequest}
|
||||||
|
* `response` {http2.Http2ServerResponse}
|
||||||
|
|
||||||
|
Emitted each time there is a request. Note that there may be multiple requests
|
||||||
|
per session. See the [Compatibility API](compatiblity-api).
|
||||||
|
|
||||||
#### Event: 'timeout'
|
#### Event: 'timeout'
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
@ -1246,6 +1260,17 @@ server.on('stream', (stream, headers, flags) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Event: 'request'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `request` {http2.Http2ServerRequest}
|
||||||
|
* `response` {http2.Http2ServerResponse}
|
||||||
|
|
||||||
|
Emitted each time there is a request. Note that there may be multiple requests
|
||||||
|
per session. See the [Compatibility API](compatiblity-api).
|
||||||
|
|
||||||
#### Event: 'timeout'
|
#### Event: 'timeout'
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: REPLACEME
|
added: REPLACEME
|
||||||
@ -1314,7 +1339,8 @@ added: REPLACEME
|
|||||||
* `options` {Object}
|
* `options` {Object}
|
||||||
* `allowHTTP1` {boolean} Incoming client connections that do not support
|
* `allowHTTP1` {boolean} Incoming client connections that do not support
|
||||||
HTTP/2 will be downgraded to HTTP/1.x when set to `true`. The default value
|
HTTP/2 will be downgraded to HTTP/1.x when set to `true`. The default value
|
||||||
is `false`. See the [`'unknownProtocol'`][] event.
|
is `false`. See the [`'unknownProtocol'`][] event. See [ALPN
|
||||||
|
negotiation](#alpn-negotiation).
|
||||||
* `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size
|
* `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size
|
||||||
for deflating header fields. Defaults to 4Kib.
|
for deflating header fields. Defaults to 4Kib.
|
||||||
* `maxSendHeaderBlockLength` {number} Sets the maximum allowed size for a
|
* `maxSendHeaderBlockLength` {number} Sets the maximum allowed size for a
|
||||||
@ -1701,16 +1727,755 @@ req.end('Jane');
|
|||||||
|
|
||||||
## Compatibility API
|
## Compatibility API
|
||||||
|
|
||||||
TBD
|
The Compatibility API has the goal of providing a similar developer experience of
|
||||||
|
HTTP/1 when using HTTP/2, making it possible to develop applications
|
||||||
|
that supports both [HTTP/1](HTTP/1) and HTTP/2. This API targets only the **public
|
||||||
|
API** of the [HTTP/1](HTTP/1), however many modules uses internal
|
||||||
|
methods or state, and those _are not supported_ as it is a completely
|
||||||
|
different implementation.
|
||||||
|
|
||||||
|
The following example creates an HTTP/2 server using the compatibility
|
||||||
|
API:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http2 = require('http2');
|
||||||
|
const server = http2.createServer((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.setHeader('X-Foo', 'bar');
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('ok');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to create a mixed [HTTPs](https) and HTTP/2 server, refer to the
|
||||||
|
[ALPN negotiation](alpn-negotiation) section.
|
||||||
|
Upgrading from non-tls HTTP/1 servers is not supported.
|
||||||
|
|
||||||
|
The HTTP2 compatibility API is composed of [`Http2ServerRequest`]() and
|
||||||
|
[`Http2ServerResponse`](). They aim at API compatibility with HTTP/1, but
|
||||||
|
they do not hide the differences between the protocols. As an example,
|
||||||
|
the status message for HTTP codes is ignored.
|
||||||
|
|
||||||
|
### ALPN negotiation
|
||||||
|
|
||||||
|
ALPN negotiation allows to support both [HTTPs](https) and HTTP/2 over
|
||||||
|
the same socket. the `req` and `res` object could be either HTTP/1 or
|
||||||
|
HTTP/2, and an application **must** restrict itself to the public API of
|
||||||
|
[HTTP/1](), and detect if it is possible to use the more advanced
|
||||||
|
features of HTTP/2.
|
||||||
|
|
||||||
|
The following example creates a server that supports both protocols:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { createSecureServer } = require('http2');
|
||||||
|
const { readFileSync } = require('fs');
|
||||||
|
|
||||||
|
const cert = fs.readFileSync('./cert.pem');
|
||||||
|
const key = fs.readFileSync('./key.pem');
|
||||||
|
|
||||||
|
const server = createSecureServer(
|
||||||
|
{ cert, key, allowHTTP1: true },
|
||||||
|
onRequest
|
||||||
|
).listen(4443);
|
||||||
|
|
||||||
|
function onRequest(req, res) {
|
||||||
|
// detects if it is a HTTPs request or HTTP/2
|
||||||
|
const { socket: { alpnProtocol } } = request.httpVersion === '2.0' ?
|
||||||
|
request.stream.session : request;
|
||||||
|
response.writeHead(200, { 'content-type': 'application/json' });
|
||||||
|
response.end(JSON.stringify({
|
||||||
|
alpnProtocol,
|
||||||
|
httpVersion: request.httpVersion
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `'request'` event works identically on both [HTTPs](https) and
|
||||||
|
HTTP/2.
|
||||||
|
|
||||||
|
### Class: http2.Http2ServerRequest
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
A `Http2ServerRequest` object is created by [`http2.Server`][] or
|
||||||
|
[`http2.SecureServer`][] and passed as the first argument to the [`'request'`][] event. It may be used to access a request status,
|
||||||
|
headers and data.
|
||||||
|
|
||||||
|
It implements the [Readable Stream][] interface, as well as the
|
||||||
|
following additional events, methods, and properties.
|
||||||
|
|
||||||
|
#### Event: 'aborted'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
The `'aborted'` event is emitted whenever a `Http2ServerRequest` instance is
|
||||||
|
abnormally aborted in mid-communication.
|
||||||
|
|
||||||
|
*Note*: The `'aborted'` event will only be emitted if the
|
||||||
|
`Http2ServerRequest` writable side has not been ended.
|
||||||
|
|
||||||
|
#### Event: 'close'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Indicates that the underlying [`Http2Stream`]() was closed.
|
||||||
|
Just like `'end'`, this event occurs only once per response.
|
||||||
|
|
||||||
|
#### request.destroy([error])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `error` {Error}
|
||||||
|
|
||||||
|
Calls `destroy()` on the [Http2Stream]() that received the `ServerRequest`. If `error`
|
||||||
|
is provided, an `'error'` event is emitted and `error` is passed as an argument
|
||||||
|
to any listeners on the event.
|
||||||
|
|
||||||
|
It does nothing if the stream was already destroyed.
|
||||||
|
|
||||||
|
#### request.headers
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {Object}
|
||||||
|
|
||||||
|
The request/response headers object.
|
||||||
|
|
||||||
|
Key-value pairs of header names and values. Header names are lower-cased.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Prints something like:
|
||||||
|
//
|
||||||
|
// { 'user-agent': 'curl/7.22.0',
|
||||||
|
// host: '127.0.0.1:8000',
|
||||||
|
// accept: '*/*' }
|
||||||
|
console.log(request.headers);
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Headers Object][].
|
||||||
|
|
||||||
|
### request.httpVersion
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
In case of server request, the HTTP version sent by the client. In the case of
|
||||||
|
client response, the HTTP version of the connected-to server. Returns
|
||||||
|
`'2.0'`.
|
||||||
|
|
||||||
|
Also `message.httpVersionMajor` is the first integer and
|
||||||
|
`message.httpVersionMinor` is the second.
|
||||||
|
|
||||||
|
#### request.method
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
The request method as a string. Read only. Example:
|
||||||
|
`'GET'`, `'DELETE'`.
|
||||||
|
|
||||||
|
#### request.rawHeaders
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {Array}
|
||||||
|
|
||||||
|
The raw request/response headers list exactly as they were received.
|
||||||
|
|
||||||
|
Note that the keys and values are in the same list. It is *not* a
|
||||||
|
list of tuples. So, the even-numbered offsets are key values, and the
|
||||||
|
odd-numbered offsets are the associated values.
|
||||||
|
|
||||||
|
Header names are not lowercased, and duplicates are not merged.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Prints something like:
|
||||||
|
//
|
||||||
|
// [ 'user-agent',
|
||||||
|
// 'this is invalid because there can be only one',
|
||||||
|
// 'User-Agent',
|
||||||
|
// 'curl/7.22.0',
|
||||||
|
// 'Host',
|
||||||
|
// '127.0.0.1:8000',
|
||||||
|
// 'ACCEPT',
|
||||||
|
// '*/*' ]
|
||||||
|
console.log(request.rawHeaders);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### request.rawTrailers
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {Array}
|
||||||
|
|
||||||
|
The raw request/response trailer keys and values exactly as they were
|
||||||
|
received. Only populated at the `'end'` event.
|
||||||
|
|
||||||
|
#### request.setTimeout(msecs, callback)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `msecs` {number}
|
||||||
|
* `callback` {Function}
|
||||||
|
|
||||||
|
Calls `request.connection.setTimeout(msecs, callback)`.
|
||||||
|
|
||||||
|
Returns `request`.
|
||||||
|
|
||||||
|
#### request.socket
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {net.Socket}
|
||||||
|
|
||||||
|
The [`net.Socket`][] object associated with the connection.
|
||||||
|
|
||||||
|
With TLS support, use [`request.socket.getPeerCertificate()`][] to obtain the
|
||||||
|
client's authentication details.
|
||||||
|
|
||||||
|
*Note*: do not use this socket object to send or receive any data. All
|
||||||
|
data transfers are managed by HTTP/2 and data might be lost.
|
||||||
|
|
||||||
|
#### request.stream
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {http2.Http2Stream}
|
||||||
|
|
||||||
|
The [`Http2Stream`][] object backing the request.
|
||||||
|
|
||||||
|
#### request.trailers
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {Object}
|
||||||
|
|
||||||
|
The request/response trailers object. Only populated at the `'end'` event.
|
||||||
|
|
||||||
|
#### request.url
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
Request URL string. This contains only the URL that is
|
||||||
|
present in the actual HTTP request. If the request is:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
GET /status?name=ryan HTTP/1.1\r\n
|
||||||
|
Accept: text/plain\r\n
|
||||||
|
\r\n
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `request.url` will be:
|
||||||
|
|
||||||
|
<!-- eslint-disable semi -->
|
||||||
|
```js
|
||||||
|
'/status?name=ryan'
|
||||||
|
```
|
||||||
|
|
||||||
|
To parse the url into its parts `require('url').parse(request.url)`
|
||||||
|
can be used. Example:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
$ node
|
||||||
|
> require('url').parse('/status?name=ryan')
|
||||||
|
Url {
|
||||||
|
protocol: null,
|
||||||
|
slashes: null,
|
||||||
|
auth: null,
|
||||||
|
host: null,
|
||||||
|
port: null,
|
||||||
|
hostname: null,
|
||||||
|
hash: null,
|
||||||
|
search: '?name=ryan',
|
||||||
|
query: 'name=ryan',
|
||||||
|
pathname: '/status',
|
||||||
|
path: '/status?name=ryan',
|
||||||
|
href: '/status?name=ryan' }
|
||||||
|
```
|
||||||
|
|
||||||
|
To extract the parameters from the query string, the
|
||||||
|
`require('querystring').parse` function can be used, or
|
||||||
|
`true` can be passed as the second argument to `require('url').parse`.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
$ node
|
||||||
|
> require('url').parse('/status?name=ryan', true)
|
||||||
|
Url {
|
||||||
|
protocol: null,
|
||||||
|
slashes: null,
|
||||||
|
auth: null,
|
||||||
|
host: null,
|
||||||
|
port: null,
|
||||||
|
hostname: null,
|
||||||
|
hash: null,
|
||||||
|
search: '?name=ryan',
|
||||||
|
query: { name: 'ryan' },
|
||||||
|
pathname: '/status',
|
||||||
|
path: '/status?name=ryan',
|
||||||
|
href: '/status?name=ryan' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class: http2.Http2ServerResponse
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
This object is created internally by an HTTP server--not by the user. It is
|
||||||
|
passed as the second parameter to the [`'request'`][] event.
|
||||||
|
|
||||||
|
The response implements, but does not inherit from, the [Writable Stream][]
|
||||||
|
interface. This is an [`EventEmitter`][] with the following events:
|
||||||
|
|
||||||
|
### Event: 'close'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Indicates that the underlying [`Http2Stream`]() was terminated before
|
||||||
|
[`response.end()`][] was called or able to flush.
|
||||||
|
|
||||||
|
### Event: 'finish'
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Emitted when the response has been sent. More specifically, this event is
|
||||||
|
emitted when the last segment of the response headers and body have been
|
||||||
|
handed off to the HTTP/2 multiplexing for transmission over the network. It
|
||||||
|
does not imply that the client has received anything yet.
|
||||||
|
|
||||||
|
After this event, no more events will be emitted on the response object.
|
||||||
|
|
||||||
|
### response.addTrailers(headers)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `headers` {Object}
|
||||||
|
|
||||||
|
This method adds HTTP trailing headers (a header but at the end of the
|
||||||
|
message) to the response.
|
||||||
|
|
||||||
|
Attempting to set a header field name or value that contains invalid characters
|
||||||
|
will result in a [`TypeError`][] being thrown.
|
||||||
|
|
||||||
|
### response.connection
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {net.Socket}
|
||||||
|
|
||||||
|
See [`response.socket`][].
|
||||||
|
|
||||||
|
### response.end([data][, encoding][, callback])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `data` {string|Buffer}
|
||||||
|
* `encoding` {string}
|
||||||
|
* `callback` {Function}
|
||||||
|
|
||||||
|
This method signals to the server that all of the response headers and body
|
||||||
|
have been sent; that server should consider this message complete.
|
||||||
|
The method, `response.end()`, MUST be called on each response.
|
||||||
|
|
||||||
|
If `data` is specified, it is equivalent to calling
|
||||||
|
[`response.write(data, encoding)`][] followed by `response.end(callback)`.
|
||||||
|
|
||||||
|
If `callback` is specified, it will be called when the response stream
|
||||||
|
is finished.
|
||||||
|
|
||||||
|
### response.finished
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {boolean}
|
||||||
|
|
||||||
|
Boolean value that indicates whether the response has completed. Starts
|
||||||
|
as `false`. After [`response.end()`][] executes, the value will be `true`.
|
||||||
|
|
||||||
|
### response.getHeader(name)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `name` {string}
|
||||||
|
* Returns: {string}
|
||||||
|
|
||||||
|
Reads out a header that's already been queued but not sent to the client.
|
||||||
|
Note that the name is case insensitive.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const contentType = response.getHeader('content-type');
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.getHeaderNames()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {Array}
|
||||||
|
|
||||||
|
Returns an array containing the unique names of the current outgoing headers.
|
||||||
|
All header names are lowercase.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.setHeader('Foo', 'bar');
|
||||||
|
response.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']);
|
||||||
|
|
||||||
|
const headerNames = response.getHeaderNames();
|
||||||
|
// headerNames === ['foo', 'set-cookie']
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.getHeaders()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {Object}
|
||||||
|
|
||||||
|
Returns a shallow copy of the current outgoing headers. Since a shallow copy
|
||||||
|
is used, array values may be mutated without additional calls to various
|
||||||
|
header-related http module methods. The keys of the returned object are the
|
||||||
|
header names and the values are the respective header values. All header names
|
||||||
|
are lowercase.
|
||||||
|
|
||||||
|
*Note*: The object returned by the `response.getHeaders()` method _does not_
|
||||||
|
prototypically inherit from the JavaScript `Object`. This means that typical
|
||||||
|
`Object` methods such as `obj.toString()`, `obj.hasOwnProperty()`, and others
|
||||||
|
are not defined and *will not work*.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.setHeader('Foo', 'bar');
|
||||||
|
response.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']);
|
||||||
|
|
||||||
|
const headers = response.getHeaders();
|
||||||
|
// headers === { foo: 'bar', 'set-cookie': ['foo=bar', 'bar=baz'] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.hasHeader(name)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `name` {string}
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the header identified by `name` is currently set in the
|
||||||
|
outgoing headers. Note that the header name matching is case-insensitive.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const hasContentType = response.hasHeader('content-type');
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.headersSent
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {boolean}
|
||||||
|
|
||||||
|
Boolean (read-only). True if headers were sent, false otherwise.
|
||||||
|
|
||||||
|
### response.removeHeader(name)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `name` {string}
|
||||||
|
|
||||||
|
Removes a header that's queued for implicit sending.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.removeHeader('Content-Encoding');
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.sendDate
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {boolean}
|
||||||
|
|
||||||
|
When true, the Date header will be automatically generated and sent in
|
||||||
|
the response if it is not already present in the headers. Defaults to true.
|
||||||
|
|
||||||
|
This should only be disabled for testing; HTTP requires the Date header
|
||||||
|
in responses.
|
||||||
|
|
||||||
|
### response.setHeader(name, value)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `name` {string}
|
||||||
|
* `value` {string | string[]}
|
||||||
|
|
||||||
|
Sets a single header value for implicit headers. If this header already exists
|
||||||
|
in the to-be-sent headers, its value will be replaced. Use an array of strings
|
||||||
|
here to send multiple headers with the same name.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.setHeader('Content-Type', 'text/html');
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
|
||||||
|
```
|
||||||
|
|
||||||
|
Attempting to set a header field name or value that contains invalid characters
|
||||||
|
will result in a [`TypeError`][] being thrown.
|
||||||
|
|
||||||
|
When headers have been set with [`response.setHeader()`][], they will be merged with
|
||||||
|
any headers passed to [`response.writeHead()`][], with the headers passed to
|
||||||
|
[`response.writeHead()`][] given precedence.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// returns content-type = text/plain
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.setHeader('X-Foo', 'bar');
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('ok');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.setTimeout(msecs[, callback])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `msecs` {number}
|
||||||
|
* `callback` {Function}
|
||||||
|
|
||||||
|
Sets the [`Http2Stream`]()'s timeout value to `msecs`. If a callback is
|
||||||
|
provided, then it is added as a listener on the `'timeout'` event on
|
||||||
|
the response object.
|
||||||
|
|
||||||
|
If no `'timeout'` listener is added to the request, the response, or
|
||||||
|
the server, then [`Http2Stream`]()s are destroyed when they time out. If a handler is
|
||||||
|
assigned to the request, the response, or the server's `'timeout'` events,
|
||||||
|
timed out sockets must be handled explicitly.
|
||||||
|
|
||||||
|
Returns `response`.
|
||||||
|
|
||||||
|
### response.socket
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {net.Socket}
|
||||||
|
|
||||||
|
Reference to the underlying socket. Usually users will not want to access
|
||||||
|
this property. In particular, the socket will not emit `'readable'` events
|
||||||
|
because of how the protocol parser attaches to the socket. After
|
||||||
|
`response.end()`, the property is nulled. The `socket` may also be accessed
|
||||||
|
via `response.connection`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http = require('http');
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const ip = req.socket.remoteAddress;
|
||||||
|
const port = req.socket.remotePort;
|
||||||
|
res.end(`Your IP address is ${ip} and your source port is ${port}.`);
|
||||||
|
}).listen(3000);
|
||||||
|
```
|
||||||
|
|
||||||
|
### response.statusCode
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {number}
|
||||||
|
|
||||||
|
When using implicit headers (not calling [`response.writeHead()`][] explicitly),
|
||||||
|
this property controls the status code that will be sent to the client when
|
||||||
|
the headers get flushed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
response.statusCode = 404;
|
||||||
|
```
|
||||||
|
|
||||||
|
After response header was sent to the client, this property indicates the
|
||||||
|
status code which was sent out.
|
||||||
|
|
||||||
|
### response.statusMessage
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
Status message is not supported by HTTP/2 (RFC7540 8.1.2.4). It returns
|
||||||
|
an empty string.
|
||||||
|
|
||||||
|
#### response.stream
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {http2.Http2Stream}
|
||||||
|
|
||||||
|
The [`Http2Stream`][] object backing the response.
|
||||||
|
|
||||||
|
### response.write(chunk[, encoding][, callback])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `chunk` {string|Buffer}
|
||||||
|
* `encoding` {string}
|
||||||
|
* `callback` {Function}
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
If this method is called and [`response.writeHead()`][] has not been called,
|
||||||
|
it will switch to implicit header mode and flush the implicit headers.
|
||||||
|
|
||||||
|
This sends a chunk of the response body. This method may
|
||||||
|
be called multiple times to provide successive parts of the body.
|
||||||
|
|
||||||
|
Note that in the `http` module, the response body is omitted when the
|
||||||
|
request is a HEAD request. Similarly, the `204` and `304` responses
|
||||||
|
_must not_ include a message body.
|
||||||
|
|
||||||
|
`chunk` can be a string or a buffer. If `chunk` is a string,
|
||||||
|
the second parameter specifies how to encode it into a byte stream.
|
||||||
|
By default the `encoding` is `'utf8'`. `callback` will be called when this chunk
|
||||||
|
of data is flushed.
|
||||||
|
|
||||||
|
*Note*: This is the raw HTTP body and has nothing to do with
|
||||||
|
higher-level multi-part body encodings that may be used.
|
||||||
|
|
||||||
|
The first time [`response.write()`][] is called, it will send the buffered
|
||||||
|
header information and the first chunk of the body to the client. The second
|
||||||
|
time [`response.write()`][] is called, Node.js assumes data will be streamed,
|
||||||
|
and sends the new data separately. That is, the response is buffered up to the
|
||||||
|
first chunk of the body.
|
||||||
|
|
||||||
|
Returns `true` if the entire data was flushed successfully to the kernel
|
||||||
|
buffer. Returns `false` if all or part of the data was queued in user memory.
|
||||||
|
`'drain'` will be emitted when the buffer is free again.
|
||||||
|
|
||||||
|
### response.writeContinue()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Does nothing. Added for parity with [HTTP/1]().
|
||||||
|
|
||||||
|
### response.writeHead(statusCode[, statusMessage][, headers])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `statusCode` {number}
|
||||||
|
* `statusMessage` {string}
|
||||||
|
* `headers` {Object}
|
||||||
|
|
||||||
|
Sends a response header to the request. The status code is a 3-digit HTTP
|
||||||
|
status code, like `404`. The last argument, `headers`, are the response headers.
|
||||||
|
For compatibility with [HTTP/1](), one can give a human-readable `statusMessage` as the second argument, which will be silenty ignored and emit a warning.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const body = 'hello world';
|
||||||
|
response.writeHead(200, {
|
||||||
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
'Content-Type': 'text/plain' });
|
||||||
|
```
|
||||||
|
|
||||||
|
This method must only be called once on a message and it must
|
||||||
|
be called before [`response.end()`][] is called.
|
||||||
|
|
||||||
|
If [`response.write()`][] or [`response.end()`][] are called before calling
|
||||||
|
this, the implicit/mutable headers will be calculated and call this function.
|
||||||
|
|
||||||
|
When headers have been set with [`response.setHeader()`][], they will be merged with
|
||||||
|
any headers passed to [`response.writeHead()`][], with the headers passed to
|
||||||
|
[`response.writeHead()`][] given precedence.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// returns content-type = text/plain
|
||||||
|
const server = http2.createServer((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.setHeader('X-Foo', 'bar');
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('ok');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that Content-Length is given in bytes not characters. The above example
|
||||||
|
works because the string `'hello world'` contains only single byte characters.
|
||||||
|
If the body contains higher coded characters then `Buffer.byteLength()`
|
||||||
|
should be used to determine the number of bytes in a given encoding.
|
||||||
|
And Node.js does not check whether Content-Length and the length of the body
|
||||||
|
which has been transmitted are equal or not.
|
||||||
|
|
||||||
|
Attempting to set a header field name or value that contains invalid characters
|
||||||
|
will result in a [`TypeError`][] being thrown.
|
||||||
|
|
||||||
|
### response.createPushResponse(headers, callback)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Call [`stream.pushStream()`]() with the given headers, and wraps the
|
||||||
|
given newly created [`Http2Stream`] on `Http2ServerRespose`.
|
||||||
|
|
||||||
|
The callback will be called with an error with code `ERR_HTTP2_STREAM_CLOSED`
|
||||||
|
if the stream is closed.
|
||||||
|
|
||||||
[HTTP/2]: https://tools.ietf.org/html/rfc7540
|
[HTTP/2]: https://tools.ietf.org/html/rfc7540
|
||||||
[HTTP/1]: http.html
|
[HTTP/1]: http.html
|
||||||
|
[https]: https.html
|
||||||
[`net.Socket`]: net.html
|
[`net.Socket`]: net.html
|
||||||
[`tls.TLSSocket`]: tls.html
|
[`tls.TLSSocket`]: tls.html
|
||||||
[`tls.createServer()`]: tls.html#tls_tls_createserver_options_secureconnectionlistener
|
[`tls.createServer()`]: tls.html#tls_tls_createserver_options_secureconnectionlistener
|
||||||
[`ClientHttp2Stream`]: #http2_class_clienthttp2stream
|
[`ClientHttp2Stream`]: #http2_class_clienthttp2stream
|
||||||
[Compatibility API]: #http2_compatibility_api
|
[Compatibility API]: #http2_compatibility_api
|
||||||
|
[alpn-negotiation]: #http2_alpn_negotiation
|
||||||
[`Duplex`]: stream.html#stream_class_stream_duplex
|
[`Duplex`]: stream.html#stream_class_stream_duplex
|
||||||
[Headers Object]: #http2_headers_object
|
[Headers Object]: #http2_headers_object
|
||||||
[`Http2Stream`]: #http2_class_http2stream
|
[`Http2Stream`]: #http2_class_http2stream
|
||||||
@ -1720,3 +2485,7 @@ TBD
|
|||||||
[Using options.selectPadding]: #http2_using_options_selectpadding
|
[Using options.selectPadding]: #http2_using_options_selectpadding
|
||||||
[error code]: #error_codes
|
[error code]: #error_codes
|
||||||
[`'unknownProtocol'`]: #http2_event_unknownprotocol
|
[`'unknownProtocol'`]: #http2_event_unknownprotocol
|
||||||
|
[`'request'`]: #http2_event_request
|
||||||
|
[Readable Stream]: stream.html#stream_class_stream_readable
|
||||||
|
[`ServerRequest`]: #http2_class_server_request
|
||||||
|
[`stream.pushStream()`]: #http2_stream-pushstream
|
||||||
|
@ -192,7 +192,7 @@ class Http2ServerRequest extends Readable {
|
|||||||
if (stream) {
|
if (stream) {
|
||||||
stream.resume();
|
stream.resume();
|
||||||
} else {
|
} else {
|
||||||
throw new errors.Error('ERR_HTTP2_STREAM_CLOSED');
|
this.emit('error', new errors.Error('ERR_HTTP2_STREAM_CLOSED'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,6 +391,18 @@ class Http2ServerResponse extends Stream {
|
|||||||
this[kBeginSend]();
|
this[kBeginSend]();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get statusMessage() {
|
||||||
|
if (statusMessageWarned === false) {
|
||||||
|
process.emitWarning(
|
||||||
|
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
|
||||||
|
'UnsupportedWarning'
|
||||||
|
);
|
||||||
|
statusMessageWarned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
writeHead(statusCode, statusMessage, headers) {
|
writeHead(statusCode, statusMessage, headers) {
|
||||||
if (typeof statusMessage === 'string' && statusMessageWarned === false) {
|
if (typeof statusMessage === 'string' && statusMessageWarned === false) {
|
||||||
process.emitWarning(
|
process.emitWarning(
|
||||||
@ -411,6 +423,7 @@ class Http2ServerResponse extends Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.statusCode = statusCode;
|
this.statusCode = statusCode;
|
||||||
|
// TODO mcollina this should probably call sendInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
write(chunk, encoding, cb) {
|
write(chunk, encoding, cb) {
|
||||||
@ -497,7 +510,8 @@ class Http2ServerResponse extends Stream {
|
|||||||
createPushResponse(headers, callback) {
|
createPushResponse(headers, callback) {
|
||||||
const stream = this[kStream];
|
const stream = this[kStream];
|
||||||
if (stream === undefined) {
|
if (stream === undefined) {
|
||||||
throw new errors.Error('ERR_HTTP2_STREAM_CLOSED');
|
process.nextTick(callback, new errors.Error('ERR_HTTP2_STREAM_CLOSED'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
stream.pushStream(headers, {}, function(stream, headers, options) {
|
stream.pushStream(headers, {}, function(stream, headers, options) {
|
||||||
const response = new Http2ServerResponse(stream);
|
const response = new Http2ServerResponse(stream);
|
||||||
@ -529,6 +543,11 @@ class Http2ServerResponse extends Stream {
|
|||||||
this[kStream] = undefined;
|
this[kStream] = undefined;
|
||||||
this.emit('finish');
|
this.emit('finish');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// added for parity with HTTP/1
|
||||||
|
writeContinue() {
|
||||||
|
// TODO mcollina this should probably be sendContinue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onServerStream(stream, headers, flags) {
|
function onServerStream(stream, headers, flags) {
|
||||||
|
@ -22,6 +22,16 @@ const server = h2.createServer((request, response) => {
|
|||||||
assert.strictEqual(push.stream.id % 2, 0);
|
assert.strictEqual(push.stream.id % 2, 0);
|
||||||
push.end(pushExpect);
|
push.end(pushExpect);
|
||||||
response.end();
|
response.end();
|
||||||
|
|
||||||
|
// wait for a tick, so the stream is actually closed
|
||||||
|
setImmediate(function() {
|
||||||
|
response.createPushResponse({
|
||||||
|
':path': '/pushed',
|
||||||
|
':method': 'GET'
|
||||||
|
}, common.mustCall((error) => {
|
||||||
|
assert.strictEqual(error.code, 'ERR_HTTP2_STREAM_CLOSED');
|
||||||
|
}));
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const h2 = require('http2');
|
||||||
|
|
||||||
|
// Http2ServerResponse.statusMessage should warn
|
||||||
|
|
||||||
|
const unsupportedWarned = common.mustCall(1);
|
||||||
|
process.on('warning', ({ name, message }) => {
|
||||||
|
const expectedMessage =
|
||||||
|
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)';
|
||||||
|
if (name === 'UnsupportedWarning' && message === expectedMessage)
|
||||||
|
unsupportedWarned();
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = h2.createServer();
|
||||||
|
server.listen(0, common.mustCall(function() {
|
||||||
|
const port = server.address().port;
|
||||||
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
|
response.on('finish', common.mustCall(function() {
|
||||||
|
assert.strictEqual(response.statusMessage, '');
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
response.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
const client = h2.connect(url, common.mustCall(function() {
|
||||||
|
const headers = {
|
||||||
|
':path': '/',
|
||||||
|
':method': 'GET',
|
||||||
|
':scheme': 'http',
|
||||||
|
':authority': `localhost:${port}`
|
||||||
|
};
|
||||||
|
const request = client.request(headers);
|
||||||
|
request.on('response', common.mustCall(function(headers) {
|
||||||
|
assert.strictEqual(headers[':status'], 200);
|
||||||
|
}, 1));
|
||||||
|
request.on('end', common.mustCall(function() {
|
||||||
|
client.destroy();
|
||||||
|
}));
|
||||||
|
request.end();
|
||||||
|
request.resume();
|
||||||
|
}));
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user