зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1296280 (part 0a) - Update to node-http2 3.3.6 r=mcmanus
MozReview-Commit-ID: 6c5RrFFDak0 --HG-- extra : rebase_source : 4c4b9fe9546427e85db5c333657933f10b94da91
This commit is contained in:
Родитель
dcc44df7e1
Коммит
3ce8296e41
|
@ -2,3 +2,6 @@ node_modules
|
|||
.idea
|
||||
coverage
|
||||
doc
|
||||
.vscode/.browse*
|
||||
npm-debug.log
|
||||
typings
|
|
@ -1,5 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "iojs"
|
||||
- "0.12"
|
||||
|
||||
|
|
|
@ -1,6 +1,31 @@
|
|||
Version history
|
||||
===============
|
||||
|
||||
### 3.3.6 (2016-09-16) ###
|
||||
* We were not appropriately sending HPACK context updates when receiving SETTINGS_HEADER_TABLE_SIZE. This release fixes that bug.
|
||||
|
||||
### 3.3.5 (2016-09-06) ###
|
||||
* Fix issues with large DATA frames (https://github.com/molnarg/node-http2/issues/207)
|
||||
|
||||
### 3.3.4 (2016-04-22) ###
|
||||
* More PR bugfixes (https://github.com/molnarg/node-http2/issues?q=milestone%3Av3.3.4)
|
||||
|
||||
### 3.3.3 (2016-04-21) ###
|
||||
|
||||
* Bugfixes from pull requests (https://github.com/molnarg/node-http2/search?q=milestone%3Av3.3.3&type=Issues&utf8=%E2%9C%93)
|
||||
|
||||
### 3.3.2 (2016-01-11) ###
|
||||
|
||||
* Fix an incompatibility with Firefox (issue 167)
|
||||
|
||||
### 3.3.1 (2016-01-11) ###
|
||||
|
||||
* Fix some DoS bugs (issues 145, 146, 147, and 148)
|
||||
|
||||
### 3.3.0 (2016-01-10) ###
|
||||
|
||||
* Bugfix updates from pull requests
|
||||
|
||||
### 3.2.0 (2015-02-19) ###
|
||||
|
||||
* Update ALPN token to final RFC version (h2).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>, Google Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
@ -20,4 +20,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
node-http2
|
||||
==========
|
||||
|
||||
An HTTP/2 ([draft-ietf-httpbis-http2-16](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16))
|
||||
An HTTP/2 ([RFC 7540](http://tools.ietf.org/html/rfc7540))
|
||||
client and server implementation for node.js.
|
||||
|
||||
![Travis CI status](https://travis-ci.org/molnarg/node-http2.svg?branch=master)
|
||||
|
@ -41,8 +41,6 @@ require('http2').createServer(options, function(request, response) {
|
|||
### Using as a client ###
|
||||
|
||||
```javascript
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
require('http2').get('https://localhost:8080/', function(response) {
|
||||
response.pipe(process.stdout);
|
||||
});
|
||||
|
@ -74,10 +72,7 @@ For a server push example, see the source code of the example
|
|||
Status
|
||||
------
|
||||
|
||||
* ALPN is not yet supported in node.js (see
|
||||
[this issue](https://github.com/joyent/node/issues/5945)). For ALPN support, you will have to use
|
||||
[Shigeki Ohtsu's node.js fork](https://github.com/shigeki/node/tree/alpn_support) until this code
|
||||
gets merged upstream.
|
||||
* ALPN is only supported in node.js >= 5.0
|
||||
* Upgrade mechanism to start HTTP/2 over unencrypted channel is not implemented yet
|
||||
(issue [#4](https://github.com/molnarg/node-http2/issues/4))
|
||||
* Other minor features found in
|
||||
|
@ -145,7 +140,7 @@ $ HTTP2_LOG=info node ./example/server.js
|
|||
```
|
||||
|
||||
```bash
|
||||
$ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' >/dev/null
|
||||
$ HTTP2_LOG=info node ./example/client.js 'https://localhost:8080/server.js' >/dev/null
|
||||
```
|
||||
|
||||
Contributors
|
||||
|
@ -165,7 +160,7 @@ Code contributions are always welcome! People who contributed to node-http2 so f
|
|||
|
||||
Special thanks to Google for financing the development of this module as part of their [Summer of
|
||||
Code program](https://developers.google.com/open-source/soc/) (project: [HTTP/2 prototype server
|
||||
implementation](https://google-melange.appspot.com/gsoc/project/google/gsoc2013/molnarg/5001)), and
|
||||
implementation](https://google-melange.appspot.com/gsoc/project/details/google/gsoc2013/molnarg/5818821692620800)), and
|
||||
Nick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.
|
||||
|
||||
License
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var http2 = require('..');
|
||||
var urlParse = require('url').parse;
|
||||
|
||||
// Setting the global logger (optional)
|
||||
http2.globalAgent = new http2.Agent({
|
||||
rejectUnauthorized: true,
|
||||
log: require('../test/util').createLogger('client')
|
||||
});
|
||||
|
||||
// We use self signed certs in the example code so we ignore cert errors
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
// Sending the request
|
||||
var url = process.argv.pop();
|
||||
var request = process.env.HTTP2_PLAIN ? http2.raw.get(url) : http2.get(url);
|
||||
var options = urlParse(url);
|
||||
|
||||
// Optionally verify self-signed certificates.
|
||||
if (options.hostname == 'localhost') {
|
||||
options.key = fs.readFileSync(path.join(__dirname, '/localhost.key'));
|
||||
options.ca = fs.readFileSync(path.join(__dirname, '/localhost.crt'));
|
||||
}
|
||||
|
||||
var request = process.env.HTTP2_PLAIN ? http2.raw.get(options) : http2.get(options);
|
||||
|
||||
// Receiving the response
|
||||
request.on('response', function(response) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICIzCCAYwCCQCsvG34Az33qTANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJY
|
||||
WDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBh
|
||||
bnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTMwODAyMTMwODQzWhcNMTMw
|
||||
OTAxMTMwODQzWjBWMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5
|
||||
MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
|
||||
c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM8D4tgE1cdI9uLo4N9AL8Ck
|
||||
ogREH5LSm3SsRGFdUu5b2Nx63K/qwtTUbtUlISZBI+KESkwQXcf1ErwXUDnbTtk/
|
||||
VpLJ+gfIN18e9LAdiZgAMEWlitiLhR+D17w4NzHYOpWy1YzgOckukPy1ZfTH9e7j
|
||||
tEH9+7c4mpv7QMkFdw4hAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAP+ZFskjJtNxY
|
||||
c+5JfMjEgSHEIy+AJ5/vXIspNYKMb7l0gYDvmFm8QTKChKTYvJmepBrIdL7MjXCX
|
||||
SWiPz05ch99c84yOx5qVpcPd0y2fjO8xn2NCLfWdP7iSVYmpftwzjqFzPc4EkAny
|
||||
NOpbnw9iM4JXsZNFtPTvSp+8StPGWzU=
|
||||
MIICDTCCAXYCCQC7iiBVXeTv1DANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJI
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTETMBEGA1UEChMKbm9kZS1odHRwMjESMBAG
|
||||
A1UEAxMJbG9jYWxob3N0MB4XDTE0MTIwMjE4NDcwNFoXDTI0MTEyOTE4NDcwNFow
|
||||
SzELMAkGA1UEBhMCSFUxEzARBgNVBAgTClNvbWUtU3RhdGUxEzARBgNVBAoTCm5v
|
||||
ZGUtaHR0cDIxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB
|
||||
jQAwgYkCgYEA8As7rj7xdD+RuAmORju9NI+jtOScGgiAbfovaFyzTu0O0H9SCExi
|
||||
u6e2iXMRfzomTix/yjRvbdHEXfgONG1MnKUc0oC4GxHXshyMDEXq9LadgAmR/nDL
|
||||
UVT0eo7KqC21ufaca2nVS9qOdlSCE/p7IJdb2+BF1RmuC9pHpXvFW20CAwEAATAN
|
||||
BgkqhkiG9w0BAQUFAAOBgQDn8c/9ho9L08dOqEJ2WTBmv4dfRC3oTWR/0oIGsaXb
|
||||
RhQONy5CJv/ymPYE7nCFWTMaia+w8oFqMie/aNZ7VK6L+hafuUS93IjuTXVN++JP
|
||||
4948B0BBagvXGTwNtvm/1sZHLrXTkH1dbRUEF8M+KUSRUu2zJgm+e1bD8WTKQOIL
|
||||
NA==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDPA+LYBNXHSPbi6ODfQC/ApKIERB+S0pt0rERhXVLuW9jcetyv
|
||||
6sLU1G7VJSEmQSPihEpMEF3H9RK8F1A5207ZP1aSyfoHyDdfHvSwHYmYADBFpYrY
|
||||
i4Ufg9e8ODcx2DqVstWM4DnJLpD8tWX0x/Xu47RB/fu3OJqb+0DJBXcOIQIDAQAB
|
||||
AoGAHtRVVBZkP+l92w0TcCv+8JGUD06V5Se4Pwfopxde4mCLS0qA0zIDEe8REm0V
|
||||
Ir1Quss4xVsqnDzDLX/LUtJ2S1+seWcoLdDV/wSDiM2CLS7KauUazrTWHLNId/lu
|
||||
/VombYWK10uNiDZZJ8xwEaKt+ZptC2kK8/yi0aX0PrGhAIECQQDsD8A64BBrWCrb
|
||||
7PrJt04CAcM3uBUzS6ausiJKw9IEktnvcnsN9kZazcAW86WDFsXI5oPubmgHhQ/s
|
||||
m9iIrbMPAkEA4IAUWi5mVuWAyUIc9YbjJdnmvkAykSxr/vp/26RMSDmUAAUlYNNc
|
||||
HZbM1uVZsFForKza28Px01Ga728ZdhRrzwJBAIrwNlcwu9lCWm95Cp6hGfPKb8ki
|
||||
uq+nTiKyS8avfLQebtElE1JDamNViEK6AuemBqFZM7upFeefJKFBlO/VNHcCQCXN
|
||||
CyBALdU14aCBtFSXOMoXzaV9M8aD/084qKy4FmwW3de/BhMuo5UL3kPU7Gwm2QQy
|
||||
OsvES4S0ee0U/OmH+LsCQAnNdxNPgzJDTx7wOTFhHIBr4mtepLiaRXIdkLEsR9Kb
|
||||
vcK6BwUfomM29eGOXtUAU7sJ5xnyKkSuNN7fxIWjzPI=
|
||||
MIICXQIBAAKBgQDwCzuuPvF0P5G4CY5GO700j6O05JwaCIBt+i9oXLNO7Q7Qf1II
|
||||
TGK7p7aJcxF/OiZOLH/KNG9t0cRd+A40bUycpRzSgLgbEdeyHIwMRer0tp2ACZH+
|
||||
cMtRVPR6jsqoLbW59pxradVL2o52VIIT+nsgl1vb4EXVGa4L2kele8VbbQIDAQAB
|
||||
AoGAKKB+FVup2hb4PsG/RrvNphu5hWA721wdAIAbjfpCjtUocLlb1PO4sjIMfu7u
|
||||
wy3AVfLKHhsJ0Phz18OoA8+L65NMoMRsHOGaLEnGIJzJcnDLT5+uTFN5di0a1+UK
|
||||
BzB828rlHBNoQisogVCoKTYlCPJAZuI3trEzupWAV28XjTECQQD5LUEwYq4xr62L
|
||||
dEq5Qj/+c5paK/jrEBY83VZUmWzYsFgUwmpdku2ITRILQlOM33j6rk8krZZb93sb
|
||||
38ydmfwjAkEA9p30zyjOI9kKqTl9WdYNYtIXpyNGYa+Pga33o9pawTewiyS2uCYs
|
||||
wnQQV26bQ0YwQqLQhtIbo4fzCO6Ex0w7LwJBANHNbd8cp4kEX35U+3nDM3i+w477
|
||||
CUp6sA6tWrw+tqw4xuEr1T1WshOauP+r6AdsPkPsMo0yb7CdzxVoObPVbLsCQQCc
|
||||
sx0cjEb/TCeUAy186Z+zzN6umqFb7Jt4wLt7Z4EHCIWqw/c95zPFks3XYDZTdsOv
|
||||
c5igMdzR+c4ZPMUthWiNAkByx7If12G1Z/R2Y0vIB0WJq4BJnZCZ0mRR0oAmPoA+
|
||||
sZbmwctZ3IU+68Rgr4EAhrU04ygjF67IiNyXX0qqu3VH
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
|
@ -24,14 +24,26 @@ function onRequest(request, response) {
|
|||
|
||||
// Reading file from disk if it exists and is safe.
|
||||
else if ((filename.indexOf(__dirname) === 0) && fs.existsSync(filename) && fs.statSync(filename).isFile()) {
|
||||
response.writeHead('200');
|
||||
response.writeHead(200);
|
||||
var fileStream = fs.createReadStream(filename);
|
||||
fileStream.pipe(response);
|
||||
fileStream.on('finish',response.end);
|
||||
}
|
||||
|
||||
fs.createReadStream(filename).pipe(response);
|
||||
// Example for testing large (boundary-sized) frames.
|
||||
else if (request.url === "/largeframe") {
|
||||
response.writeHead(200);
|
||||
var body = 'a';
|
||||
for (var i = 0; i < 14; i++) {
|
||||
body += body;
|
||||
}
|
||||
body = body + 'a';
|
||||
response.end(body);
|
||||
}
|
||||
|
||||
// Otherwise responding with 404.
|
||||
else {
|
||||
response.writeHead('404');
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@
|
|||
// ------------------------------------
|
||||
//
|
||||
// - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation
|
||||
// see the [lib/endpoint.js](endpoint.html) file.
|
||||
// see [protocol/endpoint.js](protocol/endpoint.html).
|
||||
//
|
||||
// - **Class: http2.Server**
|
||||
// - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of
|
||||
// HTTP/2 was successful: the reference to the [Endpoint](endpoint.html) object tied to the
|
||||
// HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the
|
||||
// socket.
|
||||
//
|
||||
// - **http2.createServer(options, [requestListener])**: additional option:
|
||||
// - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object
|
||||
// - **plain**: if `true`, the server will accept HTTP/2 connections over plain TCP instead of
|
||||
// TLS
|
||||
//
|
||||
// - **Class: http2.ServerResponse**
|
||||
// - **response.push(options)**: initiates a server push. `options` describes the 'imaginary'
|
||||
|
@ -33,15 +31,17 @@
|
|||
// - **new Agent(options)**: additional option:
|
||||
// - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object
|
||||
// - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests.
|
||||
// - **agent.endpoints**: contains [Endpoint](endpoint.html) objects for HTTP/2 connections.
|
||||
// - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections.
|
||||
//
|
||||
// - **http2.request(options, [callback])**: additional option:
|
||||
// - **plain**: if `true`, the client will not try to build a TLS tunnel, instead it will use
|
||||
// the raw TCP stream for HTTP/2
|
||||
// - **http2.request(options, [callback])**:
|
||||
// - similar to http.request
|
||||
//
|
||||
// - **http2.get(options, [callback])**:
|
||||
// - similar to http.get
|
||||
//
|
||||
// - **Class: http2.ClientRequest**
|
||||
// - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference
|
||||
// to the associated [HTTP/2 Stream](stream.html) object (and not to the TCP socket).
|
||||
// to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket).
|
||||
// - **Event: 'push' (promise)**: signals the intention of a server push associated to this
|
||||
// request. `promise` is an IncomingPromise. If there's no listener for this event, the server
|
||||
// push is cancelled.
|
||||
|
@ -52,7 +52,7 @@
|
|||
// - has two subclasses for easier interface description: **IncomingRequest** and
|
||||
// **IncomingResponse**
|
||||
// - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated
|
||||
// [HTTP/2 Stream](stream.html) object (and not to the TCP socket).
|
||||
// [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket).
|
||||
//
|
||||
// - **Class: http2.IncomingRequest (IncomingMessage)**
|
||||
// - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the
|
||||
|
@ -87,11 +87,11 @@
|
|||
// but will function normally when falling back to using HTTP/1.1.
|
||||
//
|
||||
// - **Class: http2.Server**
|
||||
// - **Event: 'checkContinue'**: not in the spec, yet (see [http-spec#18][expect-continue])
|
||||
// - **Event: 'checkContinue'**: not in the spec
|
||||
// - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2
|
||||
// - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive
|
||||
// (PING frames)
|
||||
// - **Event: 'connect'**: not in the spec, yet (see [http-spec#230][connect])
|
||||
// - **Event: 'connect'**: not yet supported
|
||||
// - **server.setTimeout(msecs, [callback])**
|
||||
// - **server.timeout**
|
||||
//
|
||||
|
@ -119,11 +119,9 @@
|
|||
// - **Event: 'close'**
|
||||
// - **message.setTimeout(timeout, [callback])**
|
||||
//
|
||||
// [1]: http://nodejs.org/api/https.html
|
||||
// [2]: http://nodejs.org/api/http.html
|
||||
// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.4
|
||||
// [expect-continue]: https://github.com/http2/http2-spec/issues/18
|
||||
// [connect]: https://github.com/http2/http2-spec/issues/230
|
||||
// [1]: https://nodejs.org/api/https.html
|
||||
// [2]: https://nodejs.org/api/http.html
|
||||
// [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4
|
||||
|
||||
// Common server and client side code
|
||||
// ==================================
|
||||
|
@ -150,7 +148,6 @@ var deprecatedHeaders = [
|
|||
'host',
|
||||
'keep-alive',
|
||||
'proxy-connection',
|
||||
'te',
|
||||
'transfer-encoding',
|
||||
'upgrade'
|
||||
];
|
||||
|
@ -158,7 +155,7 @@ var deprecatedHeaders = [
|
|||
// When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback
|
||||
var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0'];
|
||||
|
||||
// Ciphersuite list based on the recommendations of http://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
// Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
// The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites
|
||||
var cipherSuites = [
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
|
@ -222,7 +219,7 @@ exports.serializers = protocol.serializers;
|
|||
// ---------------------
|
||||
|
||||
function IncomingMessage(stream) {
|
||||
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
|
||||
// * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
|
||||
PassThrough.call(this);
|
||||
stream.pipe(this);
|
||||
this.socket = this.stream = stream;
|
||||
|
@ -246,7 +243,7 @@ function IncomingMessage(stream) {
|
|||
}
|
||||
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
|
||||
|
||||
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3)
|
||||
// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3)
|
||||
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
|
||||
// of key-value pairs. This includes the target URI for the request, the status code for the
|
||||
// response, as well as HTTP header fields.
|
||||
|
@ -257,7 +254,11 @@ IncomingMessage.prototype._onHeaders = function _onHeaders(headers) {
|
|||
// * Store the _regular_ headers in `this.headers`
|
||||
for (var name in headers) {
|
||||
if (name[0] !== ':') {
|
||||
this.headers[name] = headers[name];
|
||||
if (name === 'set-cookie' && !Array.isArray(headers[name])) {
|
||||
this.headers[name] = [headers[name]];
|
||||
} else {
|
||||
this.headers[name] = headers[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,12 +286,13 @@ IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key
|
|||
|
||||
IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) {
|
||||
// * An HTTP/2.0 request or response MUST NOT include any of the following header fields:
|
||||
// Connection, Host, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, and Upgrade. A server
|
||||
// Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server
|
||||
// MUST treat the presence of any of these header fields as a stream error of type
|
||||
// PROTOCOL_ERROR.
|
||||
// If the TE header is present, it's only valid value is 'trailers'
|
||||
for (var i = 0; i < deprecatedHeaders.length; i++) {
|
||||
var key = deprecatedHeaders[i];
|
||||
if (key in headers) {
|
||||
if (key in headers || (key === 'te' && headers[key] !== 'trailers')) {
|
||||
this._log.error({ key: key, value: headers[key] }, 'Deprecated header found');
|
||||
this.stream.reset('PROTOCOL_ERROR');
|
||||
return;
|
||||
|
@ -317,12 +319,13 @@ IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers)
|
|||
// ---------------------
|
||||
|
||||
function OutgoingMessage() {
|
||||
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
|
||||
// * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
|
||||
Writable.call(this);
|
||||
|
||||
this._headers = {};
|
||||
this._trailers = undefined;
|
||||
this.headersSent = false;
|
||||
this.finished = false;
|
||||
|
||||
this.on('finish', this._finish);
|
||||
}
|
||||
|
@ -345,6 +348,7 @@ OutgoingMessage.prototype._finish = function _finish() {
|
|||
this.stream.headers(this._trailers);
|
||||
}
|
||||
}
|
||||
this.finished = true;
|
||||
this.stream.end();
|
||||
} else {
|
||||
this.once('socket', this._finish.bind(this));
|
||||
|
@ -353,11 +357,11 @@ OutgoingMessage.prototype._finish = function _finish() {
|
|||
|
||||
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
|
||||
if (this.headersSent) {
|
||||
throw new Error('Can\'t set headers after they are sent.');
|
||||
return this.emit('error', new Error('Can\'t set headers after they are sent.'));
|
||||
} else {
|
||||
name = name.toLowerCase();
|
||||
if (deprecatedHeaders.indexOf(name) !== -1) {
|
||||
throw new Error('Cannot set deprecated header: ' + name);
|
||||
return this.emit('error', new Error('Cannot set deprecated header: ' + name));
|
||||
}
|
||||
this._headers[name] = value;
|
||||
}
|
||||
|
@ -365,7 +369,7 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
|
|||
|
||||
OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
|
||||
if (this.headersSent) {
|
||||
throw new Error('Can\'t remove headers after they are sent.');
|
||||
return this.emit('error', new Error('Can\'t remove headers after they are sent.'));
|
||||
} else {
|
||||
delete this._headers[name.toLowerCase()];
|
||||
}
|
||||
|
@ -391,6 +395,36 @@ exports.IncomingRequest = IncomingRequest;
|
|||
exports.OutgoingResponse = OutgoingResponse;
|
||||
exports.ServerResponse = OutgoingResponse; // for API compatibility
|
||||
|
||||
// Forward events `event` on `source` to all listeners on `target`.
|
||||
//
|
||||
// Note: The calling context is `source`.
|
||||
function forwardEvent(event, source, target) {
|
||||
function forward() {
|
||||
var listeners = target.listeners(event);
|
||||
|
||||
var n = listeners.length;
|
||||
|
||||
// Special case for `error` event with no listeners.
|
||||
if (n === 0 && event === 'error') {
|
||||
var args = [event];
|
||||
args.push.apply(args, arguments);
|
||||
|
||||
target.emit.apply(target, args);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < n; ++i) {
|
||||
listeners[i].apply(source, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
source.on(event, forward);
|
||||
|
||||
// A reference to the function is necessary to be able to stop
|
||||
// forwarding.
|
||||
return forward;
|
||||
}
|
||||
|
||||
// Server class
|
||||
// ------------
|
||||
|
||||
|
@ -416,13 +450,18 @@ function Server(options) {
|
|||
this._server.removeAllListeners('secureConnection');
|
||||
this._server.on('secureConnection', function(socket) {
|
||||
var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
|
||||
if ((negotiatedProtocol === protocol.VERSION) && socket.servername) {
|
||||
// It's true that the client MUST use SNI, but if it doesn't, we don't care, don't fall back to HTTP/1,
|
||||
// since if the ALPN negotiation is otherwise successful, the client thinks we speak HTTP/2 but we don't.
|
||||
if (negotiatedProtocol === protocol.VERSION) {
|
||||
start(socket);
|
||||
} else {
|
||||
fallback(socket);
|
||||
}
|
||||
});
|
||||
this._server.on('request', this.emit.bind(this, 'request'));
|
||||
|
||||
forwardEvent('error', this._server, this);
|
||||
forwardEvent('listening', this._server, this);
|
||||
}
|
||||
|
||||
// HTTP2 over plain TCP
|
||||
|
@ -458,6 +497,11 @@ Server.prototype._start = function _start(socket) {
|
|||
var response = new OutgoingResponse(stream);
|
||||
var request = new IncomingRequest(stream);
|
||||
|
||||
// Some conformance to Node.js Https specs allows to distinguish clients:
|
||||
request.remoteAddress = socket.remoteAddress;
|
||||
request.remotePort = socket.remotePort;
|
||||
request.connection = request.socket = response.socket = socket;
|
||||
|
||||
request.once('ready', self.emit.bind(self, 'request', request, response));
|
||||
});
|
||||
|
||||
|
@ -484,11 +528,13 @@ Server.prototype._fallback = function _fallback(socket) {
|
|||
|
||||
// There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to
|
||||
// the backing TCP or HTTPS server.
|
||||
// [1]: http://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
|
||||
// [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
|
||||
Server.prototype.listen = function listen(port, hostname) {
|
||||
this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) },
|
||||
'Listening for incoming connections');
|
||||
this._server.listen.apply(this._server, arguments);
|
||||
|
||||
return this._server;
|
||||
};
|
||||
|
||||
Server.prototype.close = function close(callback) {
|
||||
|
@ -523,9 +569,9 @@ Object.defineProperty(Server.prototype, 'timeout', {
|
|||
// `server` to `this` since that means a listener. Instead, we forward the subscriptions.
|
||||
Server.prototype.on = function on(event, listener) {
|
||||
if ((event === 'upgrade') || (event === 'timeout')) {
|
||||
this._server.on(event, listener && listener.bind(this));
|
||||
return this._server.on(event, listener && listener.bind(this));
|
||||
} else {
|
||||
EventEmitter.prototype.on.call(this, event, listener);
|
||||
return EventEmitter.prototype.on.call(this, event, listener);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -536,6 +582,10 @@ Server.prototype.addContext = function addContext(hostname, credentials) {
|
|||
}
|
||||
};
|
||||
|
||||
Server.prototype.address = function address() {
|
||||
return this._server.address()
|
||||
};
|
||||
|
||||
function createServerRaw(options, requestListener) {
|
||||
if (typeof options === 'function') {
|
||||
requestListener = options;
|
||||
|
@ -602,7 +652,7 @@ function IncomingRequest(stream) {
|
|||
}
|
||||
IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } });
|
||||
|
||||
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3)
|
||||
// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3)
|
||||
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
|
||||
// of key-value pairs. This includes the target URI for the request, the status code for the
|
||||
// response, as well as HTTP header fields.
|
||||
|
@ -621,6 +671,10 @@ IncomingRequest.prototype._onHeaders = function _onHeaders(headers) {
|
|||
this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']);
|
||||
this.host = this._checkSpecialHeader(':authority', headers[':authority'] );
|
||||
this.url = this._checkSpecialHeader(':path' , headers[':path'] );
|
||||
if (!this.method || !this.scheme || !this.host || !this.url) {
|
||||
// This is invalid, and we've sent a RST_STREAM, so don't continue processing
|
||||
return;
|
||||
}
|
||||
|
||||
// * Host header is included in the headers object for backwards compatibility.
|
||||
this.headers.host = this.host;
|
||||
|
@ -684,12 +738,17 @@ OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() {
|
|||
}
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype._implicitHeader = function() {
|
||||
this._implicitHeaders();
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype.write = function write() {
|
||||
this._implicitHeaders();
|
||||
return OutgoingMessage.prototype.write.apply(this, arguments);
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype.end = function end() {
|
||||
this.finshed = true;
|
||||
this._implicitHeaders();
|
||||
return OutgoingMessage.prototype.end.apply(this, arguments);
|
||||
};
|
||||
|
@ -757,7 +816,12 @@ function requestRaw(options, callback) {
|
|||
if (options.protocol && options.protocol !== "http:") {
|
||||
throw new Error('This interface only supports http-schemed URLs');
|
||||
}
|
||||
return (options.agent || exports.globalAgent).request(options, callback);
|
||||
if (options.agent && typeof(options.agent.request) === 'function') {
|
||||
var agentOptions = util._extend({}, options);
|
||||
delete agentOptions.agent;
|
||||
return options.agent.request(agentOptions, callback);
|
||||
}
|
||||
return exports.globalAgent.request(options, callback);
|
||||
}
|
||||
|
||||
function requestTLS(options, callback) {
|
||||
|
@ -768,7 +832,12 @@ function requestTLS(options, callback) {
|
|||
if (options.protocol && options.protocol !== "https:") {
|
||||
throw new Error('This interface only supports https-schemed URLs');
|
||||
}
|
||||
return (options.agent || exports.globalAgent).request(options, callback);
|
||||
if (options.agent && typeof(options.agent.request) === 'function') {
|
||||
var agentOptions = util._extend({}, options);
|
||||
delete agentOptions.agent;
|
||||
return options.agent.request(agentOptions, callback);
|
||||
}
|
||||
return exports.globalAgent.request(options, callback);
|
||||
}
|
||||
|
||||
function getRaw(options, callback) {
|
||||
|
@ -779,7 +848,12 @@ function getRaw(options, callback) {
|
|||
if (options.protocol && options.protocol !== "http:") {
|
||||
throw new Error('This interface only supports http-schemed URLs');
|
||||
}
|
||||
return (options.agent || exports.globalAgent).get(options, callback);
|
||||
if (options.agent && typeof(options.agent.get) === 'function') {
|
||||
var agentOptions = util._extend({}, options);
|
||||
delete agentOptions.agent;
|
||||
return options.agent.get(agentOptions, callback);
|
||||
}
|
||||
return exports.globalAgent.get(options, callback);
|
||||
}
|
||||
|
||||
function getTLS(options, callback) {
|
||||
|
@ -790,7 +864,12 @@ function getTLS(options, callback) {
|
|||
if (options.protocol && options.protocol !== "https:") {
|
||||
throw new Error('This interface only supports https-schemed URLs');
|
||||
}
|
||||
return (options.agent || exports.globalAgent).get(options, callback);
|
||||
if (options.agent && typeof(options.agent.get) === 'function') {
|
||||
var agentOptions = util._extend({}, options);
|
||||
delete agentOptions.agent;
|
||||
return options.agent.get(agentOptions, callback);
|
||||
}
|
||||
return exports.globalAgent.get(options, callback);
|
||||
}
|
||||
|
||||
// Agent class
|
||||
|
@ -798,6 +877,7 @@ function getTLS(options, callback) {
|
|||
|
||||
function Agent(options) {
|
||||
EventEmitter.call(this);
|
||||
this.setMaxListeners(0);
|
||||
|
||||
options = util._extend({}, options);
|
||||
|
||||
|
@ -809,10 +889,9 @@ function Agent(options) {
|
|||
// generating the key identifying the connection, so we may get useless non-negotiated TLS
|
||||
// channels even if we ask for a negotiated one. This agent will contain only negotiated
|
||||
// channels.
|
||||
var agentOptions = {};
|
||||
agentOptions.ALPNProtocols = supportedProtocols;
|
||||
agentOptions.NPNProtocols = supportedProtocols;
|
||||
this._httpsAgent = new https.Agent(agentOptions);
|
||||
options.ALPNProtocols = supportedProtocols;
|
||||
options.NPNProtocols = supportedProtocols;
|
||||
this._httpsAgent = new https.Agent(options);
|
||||
|
||||
this.sockets = this._httpsAgent.sockets;
|
||||
this.requests = this._httpsAgent.requests;
|
||||
|
@ -834,7 +913,7 @@ Agent.prototype.request = function request(options, callback) {
|
|||
|
||||
if (!options.plain && options.protocol === 'http:') {
|
||||
this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1');
|
||||
throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.');
|
||||
this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.'));
|
||||
}
|
||||
|
||||
var request = new OutgoingRequest(this._log);
|
||||
|
@ -848,6 +927,7 @@ Agent.prototype.request = function request(options, callback) {
|
|||
options.host,
|
||||
options.port
|
||||
].join(':');
|
||||
var self = this;
|
||||
|
||||
// * There's an existing HTTP/2 connection to this host
|
||||
if (key in this.endpoints) {
|
||||
|
@ -863,6 +943,18 @@ Agent.prototype.request = function request(options, callback) {
|
|||
port: options.port,
|
||||
localAddress: options.localAddress
|
||||
});
|
||||
|
||||
endpoint.socket.on('error', function (error) {
|
||||
self._log.error('Socket error: ' + error.toString());
|
||||
request.emit('error', error);
|
||||
});
|
||||
|
||||
endpoint.on('error', function(error){
|
||||
self._log.error('Connection error: ' + error.toString());
|
||||
request.emit('error', error);
|
||||
});
|
||||
|
||||
this.endpoints[key] = endpoint;
|
||||
endpoint.pipe(endpoint.socket).pipe(endpoint);
|
||||
request._start(endpoint.createStream(), options);
|
||||
}
|
||||
|
@ -870,13 +962,24 @@ Agent.prototype.request = function request(options, callback) {
|
|||
// * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1
|
||||
else {
|
||||
var started = false;
|
||||
var createAgent = hasAgentOptions(options);
|
||||
options.ALPNProtocols = supportedProtocols;
|
||||
options.NPNProtocols = supportedProtocols;
|
||||
options.servername = options.host; // Server Name Indication
|
||||
options.agent = this._httpsAgent;
|
||||
options.ciphers = options.ciphers || cipherSuites;
|
||||
if (createAgent) {
|
||||
options.agent = new https.Agent(options);
|
||||
} else if (options.agent == null) {
|
||||
options.agent = this._httpsAgent;
|
||||
}
|
||||
var httpsRequest = https.request(options);
|
||||
|
||||
httpsRequest.on('error', function (error) {
|
||||
self._log.error('Socket error: ' + error.toString());
|
||||
self.removeAllListeners(key);
|
||||
request.emit('error', error);
|
||||
});
|
||||
|
||||
httpsRequest.on('socket', function(socket) {
|
||||
var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
|
||||
if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0
|
||||
|
@ -886,7 +989,6 @@ Agent.prototype.request = function request(options, callback) {
|
|||
}
|
||||
});
|
||||
|
||||
var self = this;
|
||||
function negotiated() {
|
||||
var endpoint;
|
||||
var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol;
|
||||
|
@ -935,6 +1037,15 @@ Agent.prototype.get = function get(options, callback) {
|
|||
return request;
|
||||
};
|
||||
|
||||
Agent.prototype.destroy = function(error) {
|
||||
if (this._httpsAgent) {
|
||||
this._httpsAgent.destroy();
|
||||
}
|
||||
for (var key in this.endpoints) {
|
||||
this.endpoints[key].close(error);
|
||||
}
|
||||
};
|
||||
|
||||
function unbundleSocket(socket) {
|
||||
socket.removeAllListeners('data');
|
||||
socket.removeAllListeners('end');
|
||||
|
@ -946,6 +1057,17 @@ function unbundleSocket(socket) {
|
|||
delete socket.onend;
|
||||
}
|
||||
|
||||
function hasAgentOptions(options) {
|
||||
return options.pfx != null ||
|
||||
options.key != null ||
|
||||
options.passphrase != null ||
|
||||
options.cert != null ||
|
||||
options.ca != null ||
|
||||
options.ciphers != null ||
|
||||
options.rejectUnauthorized != null ||
|
||||
options.secureProtocol != null;
|
||||
}
|
||||
|
||||
Object.defineProperty(Agent.prototype, 'maxSockets', {
|
||||
get: function getMaxSockets() {
|
||||
return this._httpsAgent.maxSockets;
|
||||
|
@ -971,6 +1093,7 @@ OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { construct
|
|||
|
||||
OutgoingRequest.prototype._start = function _start(stream, options) {
|
||||
this.stream = stream;
|
||||
this.options = options;
|
||||
|
||||
this._log = stream._log.child({ component: 'http' });
|
||||
|
||||
|
@ -996,8 +1119,8 @@ OutgoingRequest.prototype._start = function _start(stream, options) {
|
|||
this.headersSent = true;
|
||||
|
||||
this.emit('socket', this.stream);
|
||||
|
||||
var response = new IncomingResponse(this.stream);
|
||||
response.req = this;
|
||||
response.once('ready', this.emit.bind(this, 'response', response));
|
||||
|
||||
this.stream.on('promise', this._onPromise.bind(this));
|
||||
|
@ -1084,7 +1207,7 @@ function IncomingResponse(stream) {
|
|||
}
|
||||
IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } });
|
||||
|
||||
// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.4)
|
||||
// [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4)
|
||||
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
|
||||
// of key-value pairs. This includes the target URI for the request, the status code for the
|
||||
// response, as well as HTTP header fields.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// [node-http2][homepage] is an [HTTP/2 (draft 16)][http2] implementation for [node.js][node].
|
||||
// [node-http2][homepage] is an [HTTP/2][http2] implementation for [node.js][node].
|
||||
//
|
||||
// The core of the protocol is implemented in the protocol sub-directory. This directory provides
|
||||
// two important features on top of the protocol:
|
||||
|
@ -10,10 +10,10 @@
|
|||
// (which is in turn very similar to the [HTTP module API][node-http]).
|
||||
//
|
||||
// [homepage]: https://github.com/molnarg/node-http2
|
||||
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
|
||||
// [node]: http://nodejs.org/
|
||||
// [node-https]: http://nodejs.org/api/https.html
|
||||
// [node-http]: http://nodejs.org/api/http.html
|
||||
// [http2]: https://tools.ietf.org/html/rfc7540
|
||||
// [node]: https://nodejs.org/
|
||||
// [node-https]: https://nodejs.org/api/https.html
|
||||
// [node-http]: https://nodejs.org/api/http.html
|
||||
|
||||
module.exports = require('./http');
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
// provide a layer between the [framer](framer.html) and the
|
||||
// [connection handling component](connection.html).
|
||||
//
|
||||
// [node-transform]: http://nodejs.org/api/stream.html#stream_class_stream_transform
|
||||
// [node-objectmode]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
|
||||
// [http2-compression]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07
|
||||
// [node-transform]: https://nodejs.org/api/stream.html#stream_class_stream_transform
|
||||
// [node-objectmode]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options
|
||||
// [http2-compression]: https://tools.ietf.org/html/rfc7541
|
||||
|
||||
exports.HeaderTable = HeaderTable;
|
||||
exports.HuffmanTable = HuffmanTable;
|
||||
|
@ -35,8 +35,8 @@ var util = require('util');
|
|||
// The [Header Table] is a component used to associate headers to index values. It is basically an
|
||||
// ordered list of `[name, value]` pairs, so it's implemented as a subclass of `Array`.
|
||||
// In this implementation, the Header Table and the [Static Table] are handled as a single table.
|
||||
// [Header Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.2
|
||||
// [Static Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
|
||||
// [Header Table]: https://tools.ietf.org/html/rfc7541#section-2.3.2
|
||||
// [Static Table]: https://tools.ietf.org/html/rfc7541#section-2.3.1
|
||||
function HeaderTable(log, limit) {
|
||||
var self = HeaderTable.staticTable.map(entryFromPair);
|
||||
self._log = log;
|
||||
|
@ -70,7 +70,7 @@ function size(entry) {
|
|||
}
|
||||
|
||||
// The `add(index, entry)` can be used to [manage the header table][tablemgmt]:
|
||||
// [tablemgmt]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.3
|
||||
// [tablemgmt]: https://tools.ietf.org/html/rfc7541#section-4
|
||||
//
|
||||
// * it pushes the new `entry` at the beggining of the table
|
||||
// * before doing such a modification, it has to be ensured that the header table size will stay
|
||||
|
@ -115,9 +115,8 @@ HeaderTable.prototype.setSizeLimit = function setSizeLimit(limit) {
|
|||
this._enforceLimit(this._limit);
|
||||
};
|
||||
|
||||
// [The Static Table](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B)
|
||||
// [The Static Table](https://tools.ietf.org/html/rfc7541#section-2.3.1)
|
||||
// ------------------
|
||||
// [statictable]:http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
|
||||
|
||||
// The table is generated with feeding the table from the spec to the following sed command:
|
||||
//
|
||||
|
@ -208,14 +207,14 @@ function HeaderSetDecompressor(log, table) {
|
|||
|
||||
// `_transform` is the implementation of the [corresponding virtual function][_transform] of the
|
||||
// TransformStream class. It collects the data chunks for later processing.
|
||||
// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
|
||||
// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
|
||||
HeaderSetDecompressor.prototype._transform = function _transform(chunk, encoding, callback) {
|
||||
this._chunks.push(chunk);
|
||||
callback();
|
||||
};
|
||||
|
||||
// `execute(rep)` executes the given [header representation][representation].
|
||||
// [representation]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.4
|
||||
// [representation]: https://tools.ietf.org/html/rfc7541#section-6
|
||||
|
||||
// The *JavaScript object representation* of a header representation:
|
||||
//
|
||||
|
@ -242,7 +241,7 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) {
|
|||
var entry, pair;
|
||||
|
||||
if (rep.contextUpdate) {
|
||||
this.setTableSizeLimit(rep.newMaxSize);
|
||||
this._table.setSizeLimit(rep.newMaxSize);
|
||||
}
|
||||
|
||||
// * An _indexed representation_ entails the following actions:
|
||||
|
@ -281,7 +280,7 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) {
|
|||
// `_flush` is the implementation of the [corresponding virtual function][_flush] of the
|
||||
// TransformStream class. The whole decompressing process is done in `_flush`. It gets called when
|
||||
// the input stream is over.
|
||||
// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback
|
||||
// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback
|
||||
HeaderSetDecompressor.prototype._flush = function _flush(callback) {
|
||||
var buffer = concat(this._chunks);
|
||||
|
||||
|
@ -327,7 +326,7 @@ HeaderSetCompressor.prototype.send = function send(rep) {
|
|||
|
||||
// `_transform` is the implementation of the [corresponding virtual function][_transform] of the
|
||||
// TransformStream class. It processes the input headers one by one:
|
||||
// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
|
||||
// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
|
||||
HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, callback) {
|
||||
var name = pair[0].toLowerCase();
|
||||
var value = pair[1];
|
||||
|
@ -373,12 +372,12 @@ HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, c
|
|||
|
||||
// `_flush` is the implementation of the [corresponding virtual function][_flush] of the
|
||||
// TransformStream class. It gets called when there's no more header to compress. The final step:
|
||||
// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback
|
||||
// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback
|
||||
HeaderSetCompressor.prototype._flush = function _flush(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
// [Detailed Format](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-4)
|
||||
// [Detailed Format](https://tools.ietf.org/html/rfc7541#section-5)
|
||||
// -----------------
|
||||
|
||||
// ### Integer representation ###
|
||||
|
@ -1091,11 +1090,20 @@ function Compressor(log, type) {
|
|||
|
||||
assert((type === 'REQUEST') || (type === 'RESPONSE'));
|
||||
this._table = new HeaderTable(this._log);
|
||||
|
||||
this.tableSizeChangePending = false;
|
||||
this.lowestTableSizePending = 0;
|
||||
this.tableSizeSetting = DEFAULT_HEADER_TABLE_LIMIT;
|
||||
}
|
||||
|
||||
// Changing the header table size
|
||||
Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) {
|
||||
this._table.setSizeLimit(size);
|
||||
if (!this.tableSizeChangePending || size < this.lowestTableSizePending) {
|
||||
this.lowestTableSizePending = size;
|
||||
}
|
||||
this.tableSizeSetting = size;
|
||||
this.tableSizeChangePending = true;
|
||||
};
|
||||
|
||||
// `compress` takes a header set, and compresses it using a new `HeaderSetCompressor` stream
|
||||
|
@ -1103,6 +1111,16 @@ Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) {
|
|||
// but the API becomes simpler.
|
||||
Compressor.prototype.compress = function compress(headers) {
|
||||
var compressor = new HeaderSetCompressor(this._log, this._table);
|
||||
|
||||
if (this.tableSizeChangePending) {
|
||||
if (this.lowestTableSizePending < this.tableSizeSetting) {
|
||||
compressor.send({contextUpdate: true, newMaxSize: this.lowestTableSizePending,
|
||||
name: "", value: "", index: 0});
|
||||
}
|
||||
compressor.send({contextUpdate: true, newMaxSize: this.tableSizeSetting,
|
||||
name: "", value: "", index: 0});
|
||||
this.tableSizeChangePending = false;
|
||||
}
|
||||
var colonHeaders = [];
|
||||
var nonColonHeaders = [];
|
||||
|
||||
|
|
|
@ -353,6 +353,27 @@ Connection.prototype._receive = function _receive(frame, done) {
|
|||
this._onFirstFrameReceived(frame);
|
||||
}
|
||||
|
||||
// Do some sanity checking here before we create a stream
|
||||
if ((frame.type == 'SETTINGS' ||
|
||||
frame.type == 'PING' ||
|
||||
frame.type == 'GOAWAY') &&
|
||||
frame.stream != 0) {
|
||||
// Got connection-level frame on a stream - EEP!
|
||||
this.close('PROTOCOL_ERROR');
|
||||
return;
|
||||
} else if ((frame.type == 'DATA' ||
|
||||
frame.type == 'HEADERS' ||
|
||||
frame.type == 'PRIORITY' ||
|
||||
frame.type == 'RST_STREAM' ||
|
||||
frame.type == 'PUSH_PROMISE' ||
|
||||
frame.type == 'CONTINUATION') &&
|
||||
frame.stream == 0) {
|
||||
// Got stream-level frame on connection - EEP!
|
||||
this.close('PROTOCOL_ERROR');
|
||||
return;
|
||||
}
|
||||
// WINDOW_UPDATE can be on either stream or connection
|
||||
|
||||
// * gets the appropriate stream from the stream registry
|
||||
var stream = this._streamIds[frame.stream];
|
||||
|
||||
|
@ -401,7 +422,7 @@ Connection.prototype._onFirstFrameReceived = function _onFirstFrameReceived(fram
|
|||
this._log.debug('Receiving the first SETTINGS frame as part of the connection header.');
|
||||
} else {
|
||||
this._log.fatal({ frame: frame }, 'Invalid connection header: first frame is not SETTINGS.');
|
||||
this.emit('error');
|
||||
this.emit('error', 'PROTOCOL_ERROR');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ var assert = require('assert');
|
|||
|
||||
// Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
|
||||
// subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
|
||||
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
|
||||
var Duplex = require('stream').Duplex;
|
||||
|
||||
|
@ -19,7 +19,7 @@ exports.Flow = Flow;
|
|||
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
|
||||
// ([as described in the standard][1]) using this method
|
||||
//
|
||||
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9.2
|
||||
// [1]: https://tools.ietf.org/html/rfc7540#section-6.9.2
|
||||
|
||||
// API for child classes
|
||||
// ---------------------
|
||||
|
@ -42,7 +42,7 @@ exports.Flow = Flow;
|
|||
// * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA
|
||||
// frames, length of the payload for DATA frames) of the returned frame will be under `limit`.
|
||||
// Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the
|
||||
// same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0).
|
||||
// same thing as [in the original API](https://nodejs.org/api/stream.html#stream_stream_read_0).
|
||||
//
|
||||
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
|
||||
//
|
||||
|
@ -79,7 +79,7 @@ Flow.prototype._receive = function _receive(frame, callback) {
|
|||
// `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
|
||||
// to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
|
||||
// incoming frame is a WINDOW_UPDATE.
|
||||
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
|
||||
// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
|
||||
Flow.prototype._write = function _write(frame, encoding, callback) {
|
||||
var sentToUs = (this._flowControlId === undefined) || (frame.stream === this._flowControlId);
|
||||
|
||||
|
@ -147,7 +147,7 @@ Flow.prototype._send = function _send() {
|
|||
|
||||
// `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
|
||||
// items in the output queue.
|
||||
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
|
||||
// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
|
||||
Flow.prototype._read = function _read() {
|
||||
// * if the flow control queue is empty, then let the user push more frames
|
||||
if (this._queue.length === 0) {
|
||||
|
@ -167,7 +167,7 @@ Flow.prototype._read = function _read() {
|
|||
} while (moreNeeded && (this._queue.length > 0));
|
||||
this._readableState.sync = false;
|
||||
|
||||
assert((moreNeeded == false) || // * output queue is full
|
||||
assert((!moreNeeded) || // * output queue is full
|
||||
(this._queue.length === 0) || // * flow control queue is empty
|
||||
(!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
|
||||
}
|
||||
|
@ -249,8 +249,9 @@ Flow.prototype._parentPush = function _parentPush(frame) {
|
|||
// did not push the whole frame to the output queue (but maybe it did push part of the frame).
|
||||
Flow.prototype._push = function _push(frame) {
|
||||
var data = frame && (frame.type === 'DATA') && frame.data;
|
||||
var maxFrameLength = (this._window < 16384) ? this._window : 16384;
|
||||
|
||||
if (!data || (data.length <= this._window)) {
|
||||
if (!data || (data.length <= maxFrameLength)) {
|
||||
return this._parentPush(frame);
|
||||
}
|
||||
|
||||
|
@ -261,12 +262,12 @@ Flow.prototype._push = function _push(frame) {
|
|||
else {
|
||||
this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window },
|
||||
'Splitting out forwardable part of a DATA frame.');
|
||||
frame.data = data.slice(this._window);
|
||||
frame.data = data.slice(maxFrameLength);
|
||||
this._parentPush({
|
||||
type: 'DATA',
|
||||
flags: {},
|
||||
stream: frame.stream,
|
||||
data: data.slice(0, this._window)
|
||||
data: data.slice(0, maxFrameLength)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
@ -323,7 +324,9 @@ Flow.prototype._increaseWindow = function _increaseWindow(size) {
|
|||
this._log.error('Flow control window grew too large.');
|
||||
this.emit('error', 'FLOW_CONTROL_ERROR');
|
||||
} else {
|
||||
this.emit('window_update');
|
||||
if (size != 0) {
|
||||
this.emit('window_update');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]:
|
||||
// the Serializer and the Deserializer
|
||||
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_transform
|
||||
// [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
|
||||
// [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform
|
||||
// [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options
|
||||
var assert = require('assert');
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
|
@ -146,10 +146,10 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
|
|||
done();
|
||||
};
|
||||
|
||||
// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1)
|
||||
// [Frame Header](https://tools.ietf.org/html/rfc7540#section-4.1)
|
||||
// --------------------------------------------------------------
|
||||
//
|
||||
// HTTP/2.0 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1
|
||||
// HTTP/2 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1
|
||||
// bytes of data.
|
||||
//
|
||||
// Additional size limits can be set by specific application uses. HTTP limits the frame size to
|
||||
|
@ -235,6 +235,10 @@ Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
|
||||
if (buffer.length < 9) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
|
||||
var totallyWastedByte = buffer.readUInt8(0);
|
||||
var length = buffer.readUInt16BE(1);
|
||||
// We do this just for sanity checking later on, to make sure no one sent us a
|
||||
|
@ -269,7 +273,7 @@ Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
|
|||
// * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by
|
||||
// logging code and also serves as documentation for frame objects)
|
||||
|
||||
// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1)
|
||||
// [DATA Frames](https://tools.ietf.org/html/rfc7540#section-6.1)
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
|
||||
|
@ -297,18 +301,26 @@ Deserializer.DATA = function readData(buffer, frame) {
|
|||
var dataOffset = 0;
|
||||
var paddingLength = 0;
|
||||
if (frame.flags.PADDED) {
|
||||
if (buffer.length < 1) {
|
||||
// We must have at least one byte for padding control, but we don't. Bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
|
||||
dataOffset = 1;
|
||||
}
|
||||
|
||||
if (paddingLength) {
|
||||
if (paddingLength >= (buffer.length - 1)) {
|
||||
// We don't have enough room for the padding advertised - bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.data = buffer.slice(dataOffset, -1 * paddingLength);
|
||||
} else {
|
||||
frame.data = buffer.slice(dataOffset);
|
||||
}
|
||||
};
|
||||
|
||||
// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.2)
|
||||
// [HEADERS](https://tools.ietf.org/html/rfc7540#section-6.2)
|
||||
// --------------------------------------------------------------
|
||||
//
|
||||
// The HEADERS frame (type=0x1) allows the sender to create a stream.
|
||||
|
@ -365,6 +377,18 @@ Serializer.HEADERS = function writeHeadersPriority(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
|
||||
var minFrameLength = 0;
|
||||
if (frame.flags.PADDED) {
|
||||
minFrameLength += 1;
|
||||
}
|
||||
if (frame.flags.PRIORITY) {
|
||||
minFrameLength += 5;
|
||||
}
|
||||
if (buffer.length < minFrameLength) {
|
||||
// Peer didn't send enough data - bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
|
||||
var dataOffset = 0;
|
||||
var paddingLength = 0;
|
||||
if (frame.flags.PADDED) {
|
||||
|
@ -384,13 +408,17 @@ Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
|
|||
}
|
||||
|
||||
if (paddingLength) {
|
||||
if ((buffer.length - dataOffset) < paddingLength) {
|
||||
// Not enough data left to satisfy the advertised padding - bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.data = buffer.slice(dataOffset, -1 * paddingLength);
|
||||
} else {
|
||||
frame.data = buffer.slice(dataOffset);
|
||||
}
|
||||
};
|
||||
|
||||
// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.3)
|
||||
// [PRIORITY](https://tools.ietf.org/html/rfc7540#section-6.3)
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
|
||||
|
@ -427,6 +455,10 @@ Serializer.PRIORITY = function writePriority(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.PRIORITY = function readPriority(buffer, frame) {
|
||||
if (buffer.length < 5) {
|
||||
// PRIORITY frames are 5 bytes long. Bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
var dependencyData = new Buffer(4);
|
||||
buffer.copy(dependencyData, 0, 0, 4);
|
||||
frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
|
||||
|
@ -435,7 +467,7 @@ Deserializer.PRIORITY = function readPriority(buffer, frame) {
|
|||
frame.priorityWeight = buffer.readUInt8(4);
|
||||
};
|
||||
|
||||
// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.4)
|
||||
// [RST_STREAM](https://tools.ietf.org/html/rfc7540#section-6.4)
|
||||
// -----------------------------------------------------------
|
||||
//
|
||||
// The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
|
||||
|
@ -466,6 +498,10 @@ Serializer.RST_STREAM = function writeRstStream(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
|
||||
if (buffer.length < 4) {
|
||||
// RST_STREAM is 4 bytes long. Bad peer!
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.error = errorCodes[buffer.readUInt32BE(0)];
|
||||
if (!frame.error) {
|
||||
// Unknown error codes are considered equivalent to INTERNAL_ERROR
|
||||
|
@ -473,7 +509,7 @@ Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
|
|||
}
|
||||
};
|
||||
|
||||
// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.5)
|
||||
// [SETTINGS](https://tools.ietf.org/html/rfc7540#section-6.5)
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
|
||||
|
@ -580,7 +616,7 @@ definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
|
|||
// indicates the maximum size of a frame the receiver will allow.
|
||||
definedSettings[5] = { name: 'SETTINGS_MAX_FRAME_SIZE', flag: false };
|
||||
|
||||
// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.6)
|
||||
// [PUSH_PROMISE](https://tools.ietf.org/html/rfc7540#section-6.6)
|
||||
// ---------------------------------------------------------------
|
||||
//
|
||||
// The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
|
||||
|
@ -626,22 +662,31 @@ Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) {
|
||||
if (buffer.length < 4) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
var dataOffset = 0;
|
||||
var paddingLength = 0;
|
||||
if (frame.flags.PADDED) {
|
||||
if (buffer.length < 5) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
|
||||
dataOffset = 1;
|
||||
}
|
||||
frame.promised_stream = buffer.readUInt32BE(dataOffset) & 0x7fffffff;
|
||||
dataOffset += 4;
|
||||
if (paddingLength) {
|
||||
if ((buffer.length - dataOffset) < paddingLength) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.data = buffer.slice(dataOffset, -1 * paddingLength);
|
||||
} else {
|
||||
frame.data = buffer.slice(dataOffset);
|
||||
}
|
||||
};
|
||||
|
||||
// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.7)
|
||||
// [PING](https://tools.ietf.org/html/rfc7540#section-6.7)
|
||||
// -----------------------------------------------
|
||||
//
|
||||
// The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
|
||||
|
@ -671,7 +716,7 @@ Deserializer.PING = function readPing(buffer, frame) {
|
|||
frame.data = buffer;
|
||||
};
|
||||
|
||||
// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.8)
|
||||
// [GOAWAY](https://tools.ietf.org/html/rfc7540#section-6.8)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
|
||||
|
@ -714,6 +759,10 @@ Serializer.GOAWAY = function writeGoaway(frame, buffers) {
|
|||
};
|
||||
|
||||
Deserializer.GOAWAY = function readGoaway(buffer, frame) {
|
||||
if (buffer.length !== 8) {
|
||||
// GOAWAY must have 8 bytes
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff;
|
||||
frame.error = errorCodes[buffer.readUInt32BE(4)];
|
||||
if (!frame.error) {
|
||||
|
@ -722,7 +771,7 @@ Deserializer.GOAWAY = function readGoaway(buffer, frame) {
|
|||
}
|
||||
};
|
||||
|
||||
// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9)
|
||||
// [WINDOW_UPDATE](https://tools.ietf.org/html/rfc7540#section-6.9)
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
// The WINDOW_UPDATE frame (type=0x8) is used to implement flow control.
|
||||
|
@ -760,7 +809,7 @@ Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) {
|
|||
}
|
||||
};
|
||||
|
||||
// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.10)
|
||||
// [CONTINUATION](https://tools.ietf.org/html/rfc7540#section-6.10)
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments.
|
||||
|
@ -785,7 +834,7 @@ Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
|
|||
frame.data = buffer;
|
||||
};
|
||||
|
||||
// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-4)
|
||||
// [ALTSVC](https://tools.ietf.org/html/rfc7838#section-4)
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client.
|
||||
|
@ -810,13 +859,13 @@ frameFlags.ALTSVC = [];
|
|||
// octets, of the Origin field.
|
||||
//
|
||||
// Origin: An OPTIONAL sequence of characters containing ASCII
|
||||
// serialisation of an origin ([RFC6454](http://tools.ietf.org/html/rfc6454),
|
||||
// serialisation of an origin ([RFC6454](https://tools.ietf.org/html/rfc6454),
|
||||
// Section 6.2) that the alternate service is applicable to.
|
||||
//
|
||||
// Alt-Svc-Field-Value: A sequence of octets (length determined by
|
||||
// subtracting the length of all preceding fields from the frame
|
||||
// length) containing a value identical to the Alt-Svc field value
|
||||
// defined in (Section 3)[http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-3]
|
||||
// defined in (Section 3)[https://tools.ietf.org/html/rfc7838#section-3]
|
||||
// (ABNF production "Alt-Svc").
|
||||
|
||||
typeSpecificAttributes.ALTSVC = ['maxAge', 'port', 'protocolID', 'host',
|
||||
|
@ -986,7 +1035,13 @@ function unescape(s) {
|
|||
}
|
||||
|
||||
Deserializer.ALTSVC = function readAltSvc(buffer, frame) {
|
||||
if (buffer.length < 2) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
var originLength = buffer.readUInt16BE(0);
|
||||
if ((buffer.length - 2) < originLength) {
|
||||
return 'FRAME_SIZE_ERROR';
|
||||
}
|
||||
frame.origin = buffer.toString('ascii', 2, 2 + originLength);
|
||||
var fieldValue = buffer.toString('ascii', 2 + originLength);
|
||||
var values = parseHeaderValue(fieldValue, ',', splitHeaderParameters);
|
||||
|
@ -1034,7 +1089,7 @@ Serializer.BLOCKED = function writeBlocked(frame, buffers) {
|
|||
Deserializer.BLOCKED = function readBlocked(buffer, frame) {
|
||||
};
|
||||
|
||||
// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-7)
|
||||
// [Error Codes](https://tools.ietf.org/html/rfc7540#section-7)
|
||||
// ------------------------------------------------------------
|
||||
|
||||
var errorCodes = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 16)][http2]
|
||||
// This is an implementation of the [HTTP/2][http2]
|
||||
// framing layer for [node.js][node].
|
||||
//
|
||||
// The main building blocks are [node.js streams][node-stream] that are connected through pipes.
|
||||
|
@ -14,7 +14,7 @@
|
|||
// lifecycle and settings, and responsible for enforcing the connection level limits (flow
|
||||
// control, initiated stream limit)
|
||||
//
|
||||
// * [Stream](stream.html): implementation of the [HTTP/2 stream concept](http2-stream).
|
||||
// * [Stream](stream.html): implementation of the [HTTP/2 stream concept][http2-stream].
|
||||
// Implements the [stream state machine][http2-streamstate] defined by the standard, provides
|
||||
// management methods and events for using the stream (sending/receiving headers, data, etc.),
|
||||
// and enforces stream level constraints (flow control, sending only legal frames).
|
||||
|
@ -27,15 +27,14 @@
|
|||
// * [Serializer and Deserializer](framer.html): the lowest layer in the stack that transforms
|
||||
// between the binary and the JavaScript object representation of HTTP/2 frames
|
||||
//
|
||||
// [homepage]: https://github.com/molnarg/node-http2
|
||||
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
|
||||
// [http2-connheader]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-3.5
|
||||
// [http2-stream]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5
|
||||
// [http2-streamstate]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1
|
||||
// [node]: http://nodejs.org/
|
||||
// [node-stream]: http://nodejs.org/api/stream.html
|
||||
// [node-https]: http://nodejs.org/api/https.html
|
||||
// [node-http]: http://nodejs.org/api/http.html
|
||||
// [http2]: https://tools.ietf.org/html/rfc7540
|
||||
// [http2-connheader]: https://tools.ietf.org/html/rfc7540#section-3.5
|
||||
// [http2-stream]: https://tools.ietf.org/html/rfc7540#section-5
|
||||
// [http2-streamstate]: https://tools.ietf.org/html/rfc7540#section-5.1
|
||||
// [node]: https://nodejs.org/
|
||||
// [node-stream]: https://nodejs.org/api/stream.html
|
||||
// [node-https]: https://nodejs.org/api/https.html
|
||||
// [node-http]: https://nodejs.org/api/http.html
|
||||
|
||||
exports.VERSION = 'h2';
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ var assert = require('assert');
|
|||
// The Stream class
|
||||
// ================
|
||||
|
||||
// Stream is a [Duplex stream](http://nodejs.org/api/stream.html#stream_class_stream_duplex)
|
||||
// subclass that implements the [HTTP/2 Stream](http://http2.github.io/http2-spec/#rfc.section.3.4)
|
||||
// Stream is a [Duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex)
|
||||
// subclass that implements the [HTTP/2 Stream](https://tools.ietf.org/html/rfc7540#section-5)
|
||||
// concept. It has two 'sides': one that is used by the user to send/receive data (the `stream`
|
||||
// object itself) and one that is used by a Connection to read/write frames to/from the other peer
|
||||
// (`stream.upstream`).
|
||||
|
@ -40,7 +40,7 @@ exports.Stream = Stream;
|
|||
// that are to be sent/arrived to/from the peer and are related to this stream.
|
||||
//
|
||||
// Headers are always in the [regular node.js header format][1].
|
||||
// [1]: http://nodejs.org/api/http.html#http_message_headers
|
||||
// [1]: https://nodejs.org/api/http.html#http_message_headers
|
||||
|
||||
// Constructor
|
||||
// -----------
|
||||
|
@ -182,7 +182,7 @@ Stream.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin
|
|||
// [Flow](flow.html). The [Connection](connection.html) object instantiating the stream will read
|
||||
// and write frames to/from it. The stream itself is a regular [Duplex stream][1], and is used by
|
||||
// the user to write or read the body of the request.
|
||||
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
|
||||
// upstream side stream user side
|
||||
//
|
||||
|
@ -352,7 +352,7 @@ Stream.prototype._finishing = function _finishing() {
|
|||
}
|
||||
};
|
||||
|
||||
// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1)
|
||||
// [Stream States](https://tools.ietf.org/html/rfc7540#section-5.1)
|
||||
// ----------------
|
||||
//
|
||||
// +--------+
|
||||
|
@ -567,8 +567,9 @@ Stream.prototype._transition = function transition(sending, frame) {
|
|||
// can be used to close any of those streams.
|
||||
case 'CLOSED':
|
||||
if (PRIORITY || (sending && RST_STREAM) ||
|
||||
(receiving && WINDOW_UPDATE) ||
|
||||
(receiving && this._closedByUs &&
|
||||
(this._closedWithRst || WINDOW_UPDATE || RST_STREAM || ALTSVC))) {
|
||||
(this._closedWithRst || RST_STREAM || ALTSVC))) {
|
||||
/* No state change */
|
||||
} else {
|
||||
streamError = 'STREAM_CLOSED';
|
||||
|
@ -624,7 +625,7 @@ Stream.prototype._transition = function transition(sending, frame) {
|
|||
// * When sending something invalid, throwing an exception, since it is probably a bug.
|
||||
if (sending) {
|
||||
this._log.error(info, 'Sending illegal frame.');
|
||||
throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
|
||||
return this.emit('error', new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.'));
|
||||
}
|
||||
|
||||
// * In case of a serious problem, emitting and error and letting someone else handle it
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "http2",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.6",
|
||||
"description": "An HTTP/2 client and server implementation",
|
||||
"main": "lib/index.js",
|
||||
"engines" : {
|
||||
"node" : ">=0.10.19"
|
||||
"node" : ">=0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "*",
|
||||
|
@ -15,7 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000",
|
||||
"doc": "docco lib/* --output doc --layout parallel --css doc/docco.css"
|
||||
"doc": "docco lib/* --output doc --layout parallel --template root.jst --css doc/docco.css && docco lib/protocol/* --output doc/protocol --layout parallel --template protocol.jst --css doc/docco.css"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -3,7 +3,7 @@ var util = require('./util');
|
|||
|
||||
var Flow = require('../lib/protocol/flow').Flow;
|
||||
|
||||
var MAX_PAYLOAD_SIZE = 4096;
|
||||
var MAX_PAYLOAD_SIZE = 16384;
|
||||
|
||||
function createFlow(log) {
|
||||
var flowControlId = util.random(10, 100);
|
||||
|
|
|
@ -2,21 +2,32 @@ var expect = require('chai').expect;
|
|||
var util = require('./util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var url = require('url');
|
||||
var net = require('net');
|
||||
|
||||
var http2 = require('../lib/http');
|
||||
var https = require('https');
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
var options = {
|
||||
var serverOptions = {
|
||||
key: fs.readFileSync(path.join(__dirname, '../example/localhost.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '../example/localhost.crt')),
|
||||
rejectUnauthorized: true,
|
||||
log: util.serverLog
|
||||
};
|
||||
|
||||
http2.globalAgent = new http2.Agent({ log: util.clientLog });
|
||||
var agentOptions = {
|
||||
key: serverOptions.key,
|
||||
ca: serverOptions.cert,
|
||||
rejectUnauthorized: true,
|
||||
log: util.clientLog
|
||||
};
|
||||
|
||||
var globalAgent = new http2.Agent(agentOptions);
|
||||
|
||||
describe('http.js', function() {
|
||||
beforeEach(function() {
|
||||
http2.globalAgent = globalAgent;
|
||||
});
|
||||
describe('Server', function() {
|
||||
describe('new Server(options)', function() {
|
||||
it('should throw if called without \'plain\' or TLS options', function() {
|
||||
|
@ -28,9 +39,39 @@ describe('http.js', function() {
|
|||
}).to.throw(Error);
|
||||
});
|
||||
});
|
||||
describe('method `listen()`', function () {
|
||||
it('should emit `listening` event', function (done) {
|
||||
var server = http2.createServer(serverOptions);
|
||||
|
||||
server.on('listening', function () {
|
||||
server.close();
|
||||
|
||||
done();
|
||||
})
|
||||
|
||||
server.listen(0);
|
||||
});
|
||||
it('should emit `error` on failure', function (done) {
|
||||
var server = http2.createServer(serverOptions);
|
||||
|
||||
// This TCP server is used to explicitly take a port to make
|
||||
// server.listen() fails.
|
||||
var net = require('net').createServer();
|
||||
|
||||
server.on('error', function () {
|
||||
net.close()
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
net.listen(0, function () {
|
||||
server.listen(this.address().port);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('property `timeout`', function() {
|
||||
it('should be a proxy for the backing HTTPS server\'s `timeout` property', function() {
|
||||
var server = new http2.Server(options);
|
||||
var server = new http2.Server(serverOptions);
|
||||
var backingServer = server._server;
|
||||
var newTimeout = 10;
|
||||
server.timeout = newTimeout;
|
||||
|
@ -40,7 +81,7 @@ describe('http.js', function() {
|
|||
});
|
||||
describe('method `setTimeout(timeout, [callback])`', function() {
|
||||
it('should be a proxy for the backing HTTPS server\'s `setTimeout` method', function() {
|
||||
var server = new http2.Server(options);
|
||||
var server = new http2.Server(serverOptions);
|
||||
var backingServer = server._server;
|
||||
var newTimeout = 10;
|
||||
var newCallback = util.noop;
|
||||
|
@ -64,6 +105,31 @@ describe('http.js', function() {
|
|||
});
|
||||
});
|
||||
describe('method `request(options, [callback])`', function() {
|
||||
it('should use a new agent for request-specific TLS settings', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1234, function() {
|
||||
var options = url.parse('https://localhost:1234' + path);
|
||||
options.key = agentOptions.key;
|
||||
options.ca = agentOptions.ca;
|
||||
options.rejectUnauthorized = true;
|
||||
|
||||
http2.globalAgent = new http2.Agent({ log: util.clientLog });
|
||||
http2.get(options, function(response) {
|
||||
response.on('data', function(data) {
|
||||
expect(data.toString()).to.equal(message);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should throw when trying to use with \'http\' scheme', function() {
|
||||
expect(function() {
|
||||
var agent = new http2.Agent({ log: util.clientLog });
|
||||
|
@ -126,6 +192,31 @@ describe('http.js', function() {
|
|||
response.writeHead(200);
|
||||
response.writeHead(404);
|
||||
});
|
||||
it('field finished should be Boolean', function(){
|
||||
var stream = { _log: util.log, headers: function () {}, once: util.noop };
|
||||
var response = new http2.OutgoingResponse(stream);
|
||||
expect(response.finished).to.be.a('Boolean');
|
||||
});
|
||||
it('field finished should initially be false and then go to true when response completes',function(done){
|
||||
var res;
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
res = response;
|
||||
expect(res.finished).to.be.false;
|
||||
response.end('HiThere');
|
||||
});
|
||||
server.listen(1236, function() {
|
||||
http2.get('https://localhost:1236/finished-test', function(response) {
|
||||
response.on('data', function(data){
|
||||
var sink = data; //
|
||||
});
|
||||
response.on('end',function(){
|
||||
expect(res.finished).to.be.true;
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('test scenario', function() {
|
||||
describe('simple request', function() {
|
||||
|
@ -133,7 +224,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -153,12 +244,12 @@ describe('http.js', function() {
|
|||
it('should work as expected', function(originalDone) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
done = util.callNTimes(2, function() {
|
||||
var done = util.callNTimes(2, function() {
|
||||
server.close();
|
||||
originalDone();
|
||||
});
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -184,7 +275,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -214,7 +305,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
request.once('data', function(data) {
|
||||
expect(data.toString()).to.equal(message);
|
||||
|
@ -244,7 +335,7 @@ describe('http.js', function() {
|
|||
var headerName = 'name';
|
||||
var headerValue = 'value';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
// Request URL and headers
|
||||
expect(request.url).to.equal(path);
|
||||
expect(request.headers[headerName]).to.equal(headerValue);
|
||||
|
@ -258,6 +349,9 @@ describe('http.js', function() {
|
|||
response.removeHeader('nonexistent');
|
||||
expect(response.getHeader('nonexistent')).to.equal(undefined);
|
||||
|
||||
// A set-cookie header which should always be an array
|
||||
response.setHeader('set-cookie', 'foo');
|
||||
|
||||
// Don't send date
|
||||
response.sendDate = false;
|
||||
|
||||
|
@ -284,6 +378,8 @@ describe('http.js', function() {
|
|||
request.on('response', function(response) {
|
||||
expect(response.headers[headerName]).to.equal(headerValue);
|
||||
expect(response.headers['nonexistent']).to.equal(undefined);
|
||||
expect(response.headers['set-cookie']).to.an.instanceof(Array)
|
||||
expect(response.headers['set-cookie']).to.deep.equal(['foo'])
|
||||
expect(response.headers['date']).to.equal(undefined);
|
||||
response.on('data', function(data) {
|
||||
expect(data.toString()).to.equal(message);
|
||||
|
@ -352,7 +448,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = https.createServer(options, function(request, response) {
|
||||
var server = https.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -371,12 +467,12 @@ describe('http.js', function() {
|
|||
it('should fall back to HTTPS/1 successfully', function(originalDone) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
done = util.callNTimes(2, function() {
|
||||
var done = util.callNTimes(2, function() {
|
||||
server.close();
|
||||
originalDone();
|
||||
});
|
||||
|
||||
var server = https.createServer(options, function(request, response) {
|
||||
var server = https.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -402,13 +498,15 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1236, function() {
|
||||
https.get('https://localhost:1236' + path, function(response) {
|
||||
var options = url.parse('https://localhost:1236' + path);
|
||||
options.agent = new https.Agent(agentOptions);
|
||||
https.get(options, function(response) {
|
||||
response.on('data', function(data) {
|
||||
expect(data.toString()).to.equal(message);
|
||||
done();
|
||||
|
@ -422,7 +520,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -451,7 +549,7 @@ describe('http.js', function() {
|
|||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
@ -474,6 +572,104 @@ describe('http.js', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('https server node module specification conformance', function() {
|
||||
it('should provide API for remote HTTP 1.1 client address', function(done) {
|
||||
var remoteAddress = null;
|
||||
var remotePort = null;
|
||||
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
// HTTPS 1.1 client with Node 0.10 server
|
||||
if (!request.remoteAddress) {
|
||||
if (request.socket.socket) {
|
||||
remoteAddress = request.socket.socket.remoteAddress;
|
||||
remotePort = request.socket.socket.remotePort;
|
||||
} else {
|
||||
remoteAddress = request.socket.remoteAddress;
|
||||
remotePort = request.socket.remotePort;
|
||||
}
|
||||
} else {
|
||||
// HTTPS 1.1/2.0 client with Node 0.12 server
|
||||
remoteAddress = request.remoteAddress;
|
||||
remotePort = request.remotePort;
|
||||
}
|
||||
response.write('Pong');
|
||||
response.end();
|
||||
});
|
||||
|
||||
server.listen(1259, 'localhost', function() {
|
||||
var request = https.request({
|
||||
host: 'localhost',
|
||||
port: 1259,
|
||||
path: '/',
|
||||
ca: serverOptions.cert
|
||||
});
|
||||
request.write('Ping');
|
||||
request.end();
|
||||
request.on('response', function(response) {
|
||||
response.on('data', function(data) {
|
||||
var localAddress = response.socket.address();
|
||||
expect(remoteAddress).to.equal(localAddress.address);
|
||||
expect(remotePort).to.equal(localAddress.port);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should provide API for remote HTTP 2.0 client address', function(done) {
|
||||
var remoteAddress = null;
|
||||
var remotePort = null;
|
||||
var localAddress = null;
|
||||
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
remoteAddress = request.remoteAddress;
|
||||
remotePort = request.remotePort;
|
||||
response.write('Pong');
|
||||
response.end();
|
||||
});
|
||||
|
||||
server.listen(1258, 'localhost', function() {
|
||||
var request = http2.request({
|
||||
host: 'localhost',
|
||||
port: 1258,
|
||||
path: '/'
|
||||
});
|
||||
request.write('Ping');
|
||||
globalAgent.on('false:localhost:1258', function(endpoint) {
|
||||
localAddress = endpoint.socket.address();
|
||||
});
|
||||
request.end();
|
||||
request.on('response', function(response) {
|
||||
response.on('data', function(data) {
|
||||
expect(remoteAddress).to.equal(localAddress.address);
|
||||
expect(remotePort).to.equal(localAddress.port);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should expose net.Socket as .socket and .connection', function(done) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.socket).to.equal(request.connection);
|
||||
expect(request.socket).to.be.instanceof(net.Socket);
|
||||
response.write('Pong');
|
||||
response.end();
|
||||
done();
|
||||
});
|
||||
|
||||
server.listen(1248, 'localhost', function() {
|
||||
var request = https.request({
|
||||
host: 'localhost',
|
||||
port: 1248,
|
||||
path: '/',
|
||||
ca: serverOptions.cert
|
||||
});
|
||||
request.write('Ping');
|
||||
request.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request and response with trailers', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
|
@ -481,7 +677,7 @@ describe('http.js', function() {
|
|||
var requestTrailers = { 'content-md5': 'x' };
|
||||
var responseTrailers = { 'content-md5': 'y' };
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
request.on('data', util.noop);
|
||||
request.once('end', function() {
|
||||
|
@ -506,6 +702,52 @@ describe('http.js', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('Handle socket error', function () {
|
||||
it('HTTPS on Connection Refused error', function (done) {
|
||||
var path = '/x';
|
||||
var request = http2.request('https://127.0.0.1:6666' + path);
|
||||
|
||||
request.on('error', function (err) {
|
||||
expect(err.errno).to.equal('ECONNREFUSED');
|
||||
done();
|
||||
});
|
||||
|
||||
request.on('response', function (response) {
|
||||
server._server._handle.destroy();
|
||||
|
||||
response.on('data', util.noop);
|
||||
|
||||
response.once('end', function () {
|
||||
done(new Error('Request should have failed'));
|
||||
});
|
||||
});
|
||||
|
||||
request.end();
|
||||
|
||||
});
|
||||
it('HTTP on Connection Refused error', function (done) {
|
||||
var path = '/x';
|
||||
|
||||
var request = http2.raw.request('http://127.0.0.1:6666' + path);
|
||||
|
||||
request.on('error', function (err) {
|
||||
expect(err.errno).to.equal('ECONNREFUSED');
|
||||
done();
|
||||
});
|
||||
|
||||
request.on('response', function (response) {
|
||||
server._server._handle.destroy();
|
||||
|
||||
response.on('data', util.noop);
|
||||
|
||||
response.once('end', function () {
|
||||
done(new Error('Request should have failed'));
|
||||
});
|
||||
});
|
||||
|
||||
request.end();
|
||||
});
|
||||
});
|
||||
describe('server push', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
|
@ -513,7 +755,7 @@ describe('http.js', function() {
|
|||
var pushedPath = '/y';
|
||||
var pushedMessage = 'Hello world 2';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var server = http2.createServer(serverOptions, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
var push1 = response.push('/y');
|
||||
push1.end(pushedMessage);
|
||||
|
|
|
@ -192,7 +192,7 @@ describe('stream.js', function() {
|
|||
stream.headers({});
|
||||
stream.end();
|
||||
stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
|
||||
example_frames.slice(1).forEach(function(invalid_frame) {
|
||||
example_frames.slice(2).forEach(function(invalid_frame) {
|
||||
invalid_frame.count_change = util.noop;
|
||||
expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче