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:
Nicholas Hurley 2016-09-16 08:54:03 -07:00
Родитель dcc44df7e1
Коммит 3ce8296e41
21 изменённых файлов: 687 добавлений и 184 удалений

3
testing/xpcshell/node-http2/.gitignore поставляемый
Просмотреть файл

@ -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.');
});