diff options
author | Matteo Collina <hello@matteocollina.com> | 2017-07-24 18:34:17 +0100 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2017-08-04 12:56:50 -0700 |
commit | a4017736d213a580a8bdd1ac3184e74d29b87d67 (patch) | |
tree | 656ffd377232a6ab5624c720f5d0b7ec916dc3d9 /doc | |
parent | d6a774b1bd2c7ccbd783b06a95f6eeeb8dca210c (diff) | |
download | node-new-a4017736d213a580a8bdd1ac3184e74d29b87d67.tar.gz |
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>
Diffstat (limited to 'doc')
-rwxr-xr-x | doc/api/http2.md | 777 |
1 files changed, 773 insertions, 4 deletions
diff --git a/doc/api/http2.md b/doc/api/http2.md index cc7f3ea3b5..6f106c69c1 100755 --- a/doc/api/http2.md +++ b/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 |