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
|
||||
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
|
||||
const http2 = require('http2');
|
||||
@ -27,6 +28,7 @@ const http2 = require('http2');
|
||||
const server = http2.createServer();
|
||||
|
||||
server.on('stream', (stream, headers) => {
|
||||
// stream is a Duplex
|
||||
stream.respond({
|
||||
'content-type': 'text/html',
|
||||
':status': 200
|
||||
@ -44,6 +46,7 @@ const http2 = require('http2');
|
||||
|
||||
const client = http2.connect('http://localhost:80');
|
||||
|
||||
// req is a Duplex
|
||||
const req = client.request({ ':path': '/' });
|
||||
|
||||
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'
|
||||
<!-- YAML
|
||||
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'
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
@ -1314,7 +1339,8 @@ added: REPLACEME
|
||||
* `options` {Object}
|
||||
* `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
|
||||
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
|
||||
for deflating header fields. Defaults to 4Kib.
|
||||
* `maxSendHeaderBlockLength` {number} Sets the maximum allowed size for a
|
||||
@ -1701,16 +1727,755 @@ req.end('Jane');
|
||||
|
||||
## 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/1]: http.html
|
||||
[https]: https.html
|
||||
[`net.Socket`]: net.html
|
||||
[`tls.TLSSocket`]: tls.html
|
||||
[`tls.createServer()`]: tls.html#tls_tls_createserver_options_secureconnectionlistener
|
||||
[`ClientHttp2Stream`]: #http2_class_clienthttp2stream
|
||||
[Compatibility API]: #http2_compatibility_api
|
||||
[alpn-negotiation]: #http2_alpn_negotiation
|
||||
[`Duplex`]: stream.html#stream_class_stream_duplex
|
||||
[Headers Object]: #http2_headers_object
|
||||
[`Http2Stream`]: #http2_class_http2stream
|
||||
@ -1720,3 +2485,7 @@ TBD
|
||||
[Using options.selectPadding]: #http2_using_options_selectpadding
|
||||
[error code]: #error_codes
|
||||
[`'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) {
|
||||
stream.resume();
|
||||
} 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]();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (typeof statusMessage === 'string' && statusMessageWarned === false) {
|
||||
process.emitWarning(
|
||||
@ -411,6 +423,7 @@ class Http2ServerResponse extends Stream {
|
||||
}
|
||||
}
|
||||
this.statusCode = statusCode;
|
||||
// TODO mcollina this should probably call sendInfo
|
||||
}
|
||||
|
||||
write(chunk, encoding, cb) {
|
||||
@ -497,7 +510,8 @@ class Http2ServerResponse extends Stream {
|
||||
createPushResponse(headers, callback) {
|
||||
const stream = this[kStream];
|
||||
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) {
|
||||
const response = new Http2ServerResponse(stream);
|
||||
@ -529,6 +543,11 @@ class Http2ServerResponse extends Stream {
|
||||
this[kStream] = undefined;
|
||||
this.emit('finish');
|
||||
}
|
||||
|
||||
// added for parity with HTTP/1
|
||||
writeContinue() {
|
||||
// TODO mcollina this should probably be sendContinue
|
||||
}
|
||||
}
|
||||
|
||||
function onServerStream(stream, headers, flags) {
|
||||
|
@ -22,6 +22,16 @@ const server = h2.createServer((request, response) => {
|
||||
assert.strictEqual(push.stream.id % 2, 0);
|
||||
push.end(pushExpect);
|
||||
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