mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 950768 - http2 tests in node.js r=hurley r=mcmanus
This commit is contained in:
parent
9af4381ba2
commit
fab1e984e0
429
netwerk/test/unit/test_http2.js
Normal file
429
netwerk/test/unit/test_http2.js
Normal file
@ -0,0 +1,429 @@
|
||||
// test HTTP/2
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
|
||||
// Generate a small and a large post with known pre-calculated md5 sums
|
||||
function generateContent(size) {
|
||||
var content = "";
|
||||
for (var i = 0; i < size; i++) {
|
||||
content += "0";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
var posts = [];
|
||||
posts.push(generateContent(10));
|
||||
posts.push(generateContent(250000));
|
||||
|
||||
// pre-calculated md5sums (in hex) of the above posts
|
||||
var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
|
||||
'2ef8d3b6c8f329318eb1a119b12622b6'];
|
||||
|
||||
var bigListenerData = generateContent(128 * 1024);
|
||||
var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
|
||||
|
||||
function checkIsHttp2(request) {
|
||||
try {
|
||||
if (request.getResponseHeader("X-Firefox-Spdy") == "HTTP-draft-08/2.0") {
|
||||
if (request.getResponseHeader("X-Connection-Http2") == "yes") {
|
||||
return true;
|
||||
}
|
||||
return false; // Weird case, but the server disagrees with us
|
||||
}
|
||||
} catch (e) {
|
||||
// Nothing to do here
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var Http2CheckListener = function() {};
|
||||
|
||||
Http2CheckListener.prototype = {
|
||||
onStartRequestFired: false,
|
||||
onDataAvailableFired: false,
|
||||
isHttp2Connection: false,
|
||||
|
||||
onStartRequest: function testOnStartRequest(request, ctx) {
|
||||
this.onStartRequestFired = true;
|
||||
|
||||
if (!Components.isSuccessCode(request.status))
|
||||
do_throw("Channel should have a success code! (" + request.status + ")");
|
||||
if (!(request instanceof Components.interfaces.nsIHttpChannel))
|
||||
do_throw("Expecting an HTTP channel");
|
||||
|
||||
do_check_eq(request.responseStatus, 200);
|
||||
do_check_eq(request.requestSucceeded, true);
|
||||
},
|
||||
|
||||
onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
|
||||
read_stream(stream, cnt);
|
||||
},
|
||||
|
||||
onStopRequest: function testOnStopRequest(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isHttp2Connection);
|
||||
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Support for testing valid multiplexing of streams
|
||||
*/
|
||||
|
||||
var multiplexContent = generateContent(30*1024);
|
||||
var completed_channels = [];
|
||||
function register_completed_channel(listener) {
|
||||
completed_channels.push(listener);
|
||||
if (completed_channels.length == 2) {
|
||||
do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
}
|
||||
|
||||
/* Listener class to control the testing of multiplexing */
|
||||
var Http2MultiplexListener = function() {};
|
||||
|
||||
Http2MultiplexListener.prototype = new Http2CheckListener();
|
||||
|
||||
Http2MultiplexListener.prototype.streamID = 0;
|
||||
Http2MultiplexListener.prototype.buffer = "";
|
||||
|
||||
Http2MultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
|
||||
var data = read_stream(stream, cnt);
|
||||
this.buffer = this.buffer.concat(data);
|
||||
};
|
||||
|
||||
Http2MultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isHttp2Connection);
|
||||
do_check_true(this.buffer == multiplexContent);
|
||||
|
||||
// This is what does most of the hard work for us
|
||||
register_completed_channel(this);
|
||||
};
|
||||
|
||||
// Does the appropriate checks for header gatewaying
|
||||
var Http2HeaderListener = function(value) {
|
||||
this.value = value
|
||||
};
|
||||
|
||||
Http2HeaderListener.prototype = new Http2CheckListener();
|
||||
Http2HeaderListener.prototype.value = "";
|
||||
|
||||
Http2HeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value);
|
||||
read_stream(stream, cnt);
|
||||
};
|
||||
|
||||
var Http2PushListener = function() {};
|
||||
|
||||
Http2PushListener.prototype = new Http2CheckListener();
|
||||
|
||||
Http2PushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
if (ctx.originalURI.spec == "https://localhost:6944/push.js" ||
|
||||
ctx.originalURI.spec == "https://localhost:6944/push2.js") {
|
||||
do_check_eq(request.getResponseHeader("pushed"), "yes");
|
||||
}
|
||||
read_stream(stream, cnt);
|
||||
};
|
||||
|
||||
// Does the appropriate checks for a large GET response
|
||||
var Http2BigListener = function() {};
|
||||
|
||||
Http2BigListener.prototype = new Http2CheckListener();
|
||||
Http2BigListener.prototype.buffer = "";
|
||||
|
||||
Http2BigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
this.buffer = this.buffer.concat(read_stream(stream, cnt));
|
||||
// We know the server should send us the same data as our big post will be,
|
||||
// so the md5 should be the same
|
||||
do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
|
||||
};
|
||||
|
||||
Http2BigListener.prototype.onStopRequest = function(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isHttp2Connection);
|
||||
|
||||
// Don't want to flood output, so don't use do_check_eq
|
||||
do_check_true(this.buffer == bigListenerData);
|
||||
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
};
|
||||
|
||||
// Does the appropriate checks for POSTs
|
||||
var Http2PostListener = function(expected_md5) {
|
||||
this.expected_md5 = expected_md5;
|
||||
};
|
||||
|
||||
Http2PostListener.prototype = new Http2CheckListener();
|
||||
Http2PostListener.prototype.expected_md5 = "";
|
||||
|
||||
Http2PostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
read_stream(stream, cnt);
|
||||
do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
|
||||
};
|
||||
|
||||
function makeChan(url) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
// Make sure we make a HTTP2 connection and both us and the server mark it as such
|
||||
function test_http2_basic() {
|
||||
var chan = makeChan("https://localhost:6944/");
|
||||
var listener = new Http2CheckListener();
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Support for making sure XHR works over SPDY
|
||||
function checkXhr(xhr) {
|
||||
if (xhr.readyState != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_check_eq(xhr.status, 200);
|
||||
do_check_eq(checkIsHttp2(xhr), true);
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
// Fires off an XHR request over SPDY
|
||||
function test_http2_xhr() {
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
req.open("GET", "https://localhost:6944/", true);
|
||||
req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
|
||||
false);
|
||||
req.send(null);
|
||||
}
|
||||
|
||||
// Test to make sure we get multiplexing right
|
||||
function test_http2_multiplex() {
|
||||
var chan1 = makeChan("https://localhost:6944/multiplex1");
|
||||
var chan2 = makeChan("https://localhost:6944/multiplex2");
|
||||
var listener1 = new Http2MultiplexListener();
|
||||
var listener2 = new Http2MultiplexListener();
|
||||
chan1.asyncOpen(listener1, null);
|
||||
chan2.asyncOpen(listener2, null);
|
||||
}
|
||||
|
||||
// Test to make sure we gateway non-standard headers properly
|
||||
function test_http2_header() {
|
||||
var chan = makeChan("https://localhost:6944/header");
|
||||
var hvalue = "Headers are fun";
|
||||
var listener = new Http2HeaderListener(hvalue);
|
||||
chan.setRequestHeader("X-Test-Header", hvalue, false);
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
function test_http2_push1() {
|
||||
var chan = makeChan("https://localhost:6944/push");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new Http2PushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_http2_push2() {
|
||||
var chan = makeChan("https://localhost:6944/push.js");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new Http2PushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_http2_push3() {
|
||||
var chan = makeChan("https://localhost:6944/push2");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new Http2PushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_http2_push4() {
|
||||
var chan = makeChan("https://localhost:6944/push2.js");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new Http2PushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
// Make sure we handle GETs that cover more than 2 frames properly
|
||||
function test_http2_big() {
|
||||
var chan = makeChan("https://localhost:6944/big");
|
||||
var listener = new Http2BigListener();
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Support for doing a POST
|
||||
function do_post(content, chan, listener) {
|
||||
var stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.data = content;
|
||||
|
||||
var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
|
||||
uchan.setUploadStream(stream, "text/plain", stream.available());
|
||||
|
||||
chan.requestMethod = "POST";
|
||||
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Make sure we can do a simple POST
|
||||
function test_http2_post() {
|
||||
var chan = makeChan("https://localhost:6944/post");
|
||||
var listener = new Http2PostListener(md5s[0]);
|
||||
do_post(posts[0], chan, listener);
|
||||
}
|
||||
|
||||
// Make sure we can do a POST that covers more than 2 frames
|
||||
function test_http2_post_big() {
|
||||
var chan = makeChan("https://localhost:6944/post");
|
||||
var listener = new Http2PostListener(md5s[1]);
|
||||
do_post(posts[1], chan, listener);
|
||||
}
|
||||
|
||||
// hack - the header test resets the multiplex object on the server,
|
||||
// so make sure header is always run before the multiplex test.
|
||||
//
|
||||
// make sure post_big runs first to test race condition in restarting
|
||||
// a stalled stream when a SETTINGS frame arrives
|
||||
var tests = [ test_http2_post_big
|
||||
, test_http2_basic
|
||||
, test_http2_push1
|
||||
, test_http2_push2
|
||||
, test_http2_push3
|
||||
, test_http2_push4
|
||||
, test_http2_xhr
|
||||
, test_http2_header
|
||||
, test_http2_multiplex
|
||||
, test_http2_big
|
||||
, test_http2_post
|
||||
];
|
||||
var current_test = 0;
|
||||
|
||||
function run_next_test() {
|
||||
if (current_test < tests.length) {
|
||||
tests[current_test]();
|
||||
current_test++;
|
||||
do_test_pending();
|
||||
}
|
||||
}
|
||||
|
||||
// Support for making sure we can talk to the invalid cert the server presents
|
||||
var CertOverrideListener = function(host, port, bits) {
|
||||
this.host = host;
|
||||
if (port) {
|
||||
this.port = port;
|
||||
}
|
||||
this.bits = bits;
|
||||
};
|
||||
|
||||
CertOverrideListener.prototype = {
|
||||
host: null,
|
||||
port: -1,
|
||||
bits: null,
|
||||
|
||||
getInterface: function(aIID) {
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.nsIBadCertListener2) ||
|
||||
aIID.equals(Ci.nsIInterfaceRequestor) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
|
||||
var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
|
||||
var cos = Cc["@mozilla.org/security/certoverride;1"].
|
||||
getService(Ci.nsICertOverrideService);
|
||||
cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
function addCertOverride(host, port, bits) {
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
try {
|
||||
var url;
|
||||
if (port) {
|
||||
url = "https://" + host + ":" + port + "/";
|
||||
} else {
|
||||
url = "https://" + host + "/";
|
||||
}
|
||||
req.open("GET", url, false);
|
||||
req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
|
||||
req.send(null);
|
||||
} catch (e) {
|
||||
// This will fail since the server is not trusted yet
|
||||
}
|
||||
}
|
||||
|
||||
var prefs;
|
||||
var spdypref;
|
||||
var spdy3pref;
|
||||
var spdypush;
|
||||
var http2pref;
|
||||
|
||||
var loadGroup;
|
||||
|
||||
function resetPrefs() {
|
||||
prefs.setBoolPref("network.http.spdy.enabled", spdypref);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
|
||||
prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
// Set to allow the cert presented by our SPDY server
|
||||
do_get_profile();
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
|
||||
prefs.setIntPref("network.http.speculative-parallel-limit", 0);
|
||||
|
||||
addCertOverride("localhost", 6944,
|
||||
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
|
||||
Ci.nsICertOverrideService.ERROR_MISMATCH |
|
||||
Ci.nsICertOverrideService.ERROR_TIME);
|
||||
|
||||
prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
|
||||
|
||||
// Enable all versions of spdy to see that we auto negotiate spdy/3
|
||||
spdypref = prefs.getBoolPref("network.http.spdy.enabled");
|
||||
spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
|
||||
spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
|
||||
http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft");
|
||||
prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", true);
|
||||
prefs.setBoolPref("network.http.spdy.allow-push", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.http2draft", true);
|
||||
|
||||
loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
|
||||
|
||||
// And make go!
|
||||
run_next_test();
|
||||
}
|
@ -269,9 +269,11 @@ fail-if = os == "android"
|
||||
[test_socks.js]
|
||||
# Bug 675039: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
# spdy unit tests require us to have node available to run the spdy server
|
||||
# spdy and http2 unit tests require us to have node available to run the spdy and http2 server
|
||||
[test_spdy.js]
|
||||
run-if = hasNode
|
||||
run-if = hasNode && processor == "x86_64"
|
||||
[test_http2.js]
|
||||
run-if = hasNode && processor == "x86_64"
|
||||
[test_speculative_connect.js]
|
||||
[test_standardurl.js]
|
||||
[test_standardurl_port.js]
|
||||
|
@ -18,6 +18,9 @@ TEST_HARNESS_FILES := \
|
||||
head.js \
|
||||
node-spdy \
|
||||
moz-spdy \
|
||||
node-http2 \
|
||||
moz-http2 \
|
||||
node \
|
||||
$(NULL)
|
||||
|
||||
# Extra files needed from $(topsrcdir)/build
|
||||
|
174
testing/xpcshell/moz-http2/moz-http2.js
Normal file
174
testing/xpcshell/moz-http2/moz-http2.js
Normal file
@ -0,0 +1,174 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var http2 = require('../node-http2');
|
||||
var fs = require('fs');
|
||||
var url = require('url');
|
||||
var crypto = require('crypto');
|
||||
|
||||
function getHttpContent(path) {
|
||||
var content = '<!doctype html>' +
|
||||
'<html>' +
|
||||
'<head><title>HOORAY!</title></head>' +
|
||||
'<body>You Win! (by requesting' + path + ')</body>' +
|
||||
'</html>';
|
||||
return content;
|
||||
}
|
||||
|
||||
function generateContent(size) {
|
||||
var content = '';
|
||||
for (var i = 0; i < size; i++) {
|
||||
content += '0';
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/* This takes care of responding to the multiplexed request for us */
|
||||
var m = {
|
||||
mp1res: null,
|
||||
mp2res: null,
|
||||
buf: null,
|
||||
mp1start: 0,
|
||||
mp2start: 0,
|
||||
|
||||
checkReady: function() {
|
||||
if (this.mp1res != null && this.mp2res != null) {
|
||||
this.buf = generateContent(30*1024);
|
||||
this.mp1start = 0;
|
||||
this.mp2start = 0;
|
||||
this.send(this.mp1res, 0);
|
||||
setTimeout(this.send.bind(this, this.mp2res, 0), 5);
|
||||
}
|
||||
},
|
||||
|
||||
send: function(res, start) {
|
||||
var end = Math.min(start + 1024, this.buf.length);
|
||||
var content = this.buf.substring(start, end);
|
||||
res.write(content);
|
||||
if (end < this.buf.length) {
|
||||
setTimeout(this.send.bind(this, res, end), 10);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleRequest(req, res) {
|
||||
var u = url.parse(req.url);
|
||||
var content = getHttpContent(u.pathname);
|
||||
var push;
|
||||
|
||||
if (req.httpVersionMajor === 2) {
|
||||
res.setHeader('X-Connection-Http2', 'yes');
|
||||
res.setHeader('X-Http2-StreamId', '' + req.stream.id);
|
||||
} else {
|
||||
res.setHeader('X-Connection-Http2', 'no');
|
||||
}
|
||||
|
||||
if (u.pathname === '/exit') {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.writeHead(200);
|
||||
res.end('ok');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
else if ((u.pathname === '/multiplex1') && (req.httpVersionMajor === 2)) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.writeHead(200);
|
||||
m.mp1res = res;
|
||||
m.checkReady();
|
||||
return;
|
||||
}
|
||||
|
||||
else if ((u.pathname === '/multiplex2') && (req.httpVersionMajor === 2)) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.writeHead(200);
|
||||
m.mp2res = res;
|
||||
m.checkReady();
|
||||
return;
|
||||
}
|
||||
|
||||
else if (u.pathname === "/header") {
|
||||
var val = req.headers["x-test-header"];
|
||||
if (val) {
|
||||
res.setHeader("X-Received-Test-Header", val);
|
||||
}
|
||||
}
|
||||
|
||||
else if (u.pathname === "/push") {
|
||||
push = res.push('/push.js');
|
||||
push.writeHead(200, {
|
||||
'content-type': 'application/javascript',
|
||||
'pushed' : 'yes',
|
||||
'content-length' : 11,
|
||||
'X-Connection-Http2': 'yes'
|
||||
});
|
||||
push.end('// comments');
|
||||
content = '<head> <script src="push.js"/></head>body text';
|
||||
}
|
||||
|
||||
else if (u.pathname === "/push2") {
|
||||
push = res.push('/push2.js');
|
||||
push.writeHead(200, {
|
||||
'content-type': 'application/javascript',
|
||||
'pushed' : 'yes',
|
||||
// no content-length
|
||||
'X-Connection-Http2': 'yes'
|
||||
});
|
||||
push.end('// comments');
|
||||
content = '<head> <script src="push2.js"/></head>body text';
|
||||
}
|
||||
|
||||
else if (u.pathname === "/big") {
|
||||
content = generateContent(128 * 1024);
|
||||
var hash = crypto.createHash('md5');
|
||||
hash.update(content);
|
||||
var md5 = hash.digest('hex');
|
||||
res.setHeader("X-Expected-MD5", md5);
|
||||
}
|
||||
|
||||
else if (u.pathname === "/post") {
|
||||
if (req.method != "POST") {
|
||||
res.writeHead(405);
|
||||
res.end('Unexpected method: ' + req.method);
|
||||
return;
|
||||
}
|
||||
|
||||
var post_hash = crypto.createHash('md5');
|
||||
req.on('data', function receivePostData(chunk) {
|
||||
post_hash.update(chunk.toString());
|
||||
});
|
||||
req.on('end', function finishPost() {
|
||||
var md5 = post_hash.digest('hex');
|
||||
res.setHeader('X-Calculated-MD5', md5);
|
||||
res.writeHead(200);
|
||||
res.end(content);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.writeHead(200);
|
||||
res.end(content);
|
||||
}
|
||||
|
||||
// Set up the SSL certs for our server
|
||||
var options = {
|
||||
key: fs.readFileSync(__dirname + '/../moz-spdy/spdy-key.pem'),
|
||||
cert: fs.readFileSync(__dirname + '/../moz-spdy/spdy-cert.pem'),
|
||||
ca: fs.readFileSync(__dirname + '/../moz-spdy/spdy-ca.pem'),
|
||||
//, log: require('../node-http2/test/util').createLogger('server')
|
||||
};
|
||||
|
||||
var server = http2.createServer(options, handleRequest);
|
||||
server.on('connection', function(socket) {
|
||||
socket.on('error', function() {
|
||||
// Ignoring SSL socket errors, since they usually represent a connection that was tore down
|
||||
// by the browser because of an untrusted certificate. And this happens at least once, when
|
||||
// the first test case if done.
|
||||
});
|
||||
});
|
||||
server.listen(6944);
|
||||
console.log('HTTP2 server listening on port 6944');
|
BIN
testing/xpcshell/node
Executable file
BIN
testing/xpcshell/node
Executable file
Binary file not shown.
136
testing/xpcshell/node-http2/HISTORY.md
Normal file
136
testing/xpcshell/node-http2/HISTORY.md
Normal file
@ -0,0 +1,136 @@
|
||||
Version history
|
||||
===============
|
||||
|
||||
### 2.1.0 (2013-11-10) ###
|
||||
|
||||
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-07][draft-07]
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-2.1.0.tar.gz)
|
||||
|
||||
[draft-07]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-07
|
||||
|
||||
### 2.0.0 (2013-11-09) ###
|
||||
|
||||
* Splitting out everything that is not related to negotiating HTTP2 or the node-like HTTP API.
|
||||
These live in separate module from now on:
|
||||
[http2-protocol](https://github.com/molnarg/node-http2-protocol).
|
||||
* The only backwards incompatible change: the `Endpoint` class is not exported anymore. Use the
|
||||
http2-protocol module if you want to use this low level interface.
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-2.0.0.tar.gz)
|
||||
|
||||
### 1.0.1 (2013-10-14) ###
|
||||
|
||||
* Support for ALPN if node supports it (currently needs a custom build)
|
||||
* Fix for a few small issues
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-1.0.1.tar.gz)
|
||||
|
||||
### 1.0.0 (2013-09-23) ###
|
||||
|
||||
* Exporting Endpoint class
|
||||
* Support for 'filters' in Endpoint
|
||||
* The last time-based release
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-1.0.0.tar.gz)
|
||||
|
||||
### 0.4.1 (2013-09-15) ###
|
||||
|
||||
* Major performance improvements
|
||||
* Minor improvements to error handling
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/15/gsoc-week-number-13/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.4.1.tar.gz)
|
||||
|
||||
### 0.4.0 (2013-09-09) ###
|
||||
|
||||
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-06][draft-06]
|
||||
* Support for HTTP trailers
|
||||
* Support for TLS SNI (Server Name Indication)
|
||||
* Improved stream scheduling algorithm
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/09/gsoc-week-number-12/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.4.0.tar.gz)
|
||||
|
||||
[draft-06]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
|
||||
|
||||
### 0.3.1 (2013-09-03) ###
|
||||
|
||||
* Lot of testing, bugfixes
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/03/gsoc-week-number-11/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.3.1.tar.gz)
|
||||
|
||||
### 0.3.0 (2013-08-27) ###
|
||||
|
||||
* Support for prioritization
|
||||
* Small API compatibility improvements (compatibility with the standard node.js HTTP API)
|
||||
* Minor push API change
|
||||
* Ability to pass an external bunyan logger when creating a Server or Agent
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/27/gsoc-week-number-10/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.3.0.tar.gz)
|
||||
|
||||
### 0.2.1 (2013-08-20) ###
|
||||
|
||||
* Fixing a flow control bug
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.2.1.tar.gz)
|
||||
|
||||
### 0.2.0 (2013-08-19) ###
|
||||
|
||||
* Exposing server push in the public API
|
||||
* Connection pooling when operating as client
|
||||
* Much better API compatibility with the standard node.js HTTPS module
|
||||
* Logging improvements
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/19/gsoc-week-number-9/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.2.0.tar.gz)
|
||||
|
||||
### 0.1.1 (2013-08-12) ###
|
||||
|
||||
* Lots of bugfixes
|
||||
* Proper flow control for outgoing frames
|
||||
* Basic flow control for incoming frames
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/12/gsoc-week-number-8/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.1.1.tar.gz)
|
||||
|
||||
### 0.1.0 (2013-08-06) ###
|
||||
|
||||
* First release with public API (similar to the standard node HTTPS module)
|
||||
* Support for NPN negotiation (no ALPN or Upgrade yet)
|
||||
* Stream number limitation is in place
|
||||
* Push streams works but not exposed yet in the public API
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/05/gsoc-week-number-6-and-number-7/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.1.0.tar.gz)
|
||||
|
||||
### 0.0.6 (2013-07-19) ###
|
||||
|
||||
* `Connection` and `Endpoint` classes are usable, but not yet ready
|
||||
* Addition of an exmaple server and client
|
||||
* Using [istanbul](https://github.com/gotwarlost/istanbul) for measuring code coverage
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/19/gsoc-week-number-5/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.6.tar.gz)
|
||||
|
||||
### 0.0.5 (2013-07-14) ###
|
||||
|
||||
* `Stream` class is done
|
||||
* Public API stubs are in place
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/14/gsoc-week-number-4/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.5.tar.gz)
|
||||
|
||||
### 0.0.4 (2013-07-08) ###
|
||||
|
||||
* Added logging
|
||||
* Started `Stream` class implementation
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/08/gsoc-week-number-3/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.4.tar.gz)
|
||||
|
||||
### 0.0.3 (2013-07-03) ###
|
||||
|
||||
* Header compression is ready
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/03/the-http-slash-2-header-compression-implementation-of-node-http2/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.3.tar.gz)
|
||||
|
||||
### 0.0.2 (2013-07-01) ###
|
||||
|
||||
* Frame serialization and deserialization ready and updated to match the newest spec
|
||||
* Header compression implementation started
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/01/gsoc-week-number-2/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.2.tar.gz)
|
||||
|
||||
### 0.0.1 (2013-06-23) ###
|
||||
|
||||
* Frame serialization and deserialization largely done
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/06/23/gsoc-week-number-1/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.1.tar.gz)
|
23
testing/xpcshell/node-http2/LICENSE
Normal file
23
testing/xpcshell/node-http2/LICENSE
Normal file
@ -0,0 +1,23 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
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.
|
||||
|
167
testing/xpcshell/node-http2/README.md
Normal file
167
testing/xpcshell/node-http2/README.md
Normal file
@ -0,0 +1,167 @@
|
||||
node-http2
|
||||
==========
|
||||
|
||||
An HTTP/2 ([draft-ietf-httpbis-http2-07](http://tools.ietf.org/html/draft-ietf-httpbis-http2-76))
|
||||
client and server implementation for node.js.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```
|
||||
npm install http2
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The API is very similar to the [standard node.js HTTPS API](http://nodejs.org/api/https.html). The
|
||||
goal is the perfect API compatibility, with additional HTTP2 related extensions (like server push).
|
||||
|
||||
Detailed API documentation is primarily maintained in the `lib/http.js` file and is [available in
|
||||
the wiki](https://github.com/molnarg/node-http2/wiki/Public-API) as well.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
### Using as a server ###
|
||||
|
||||
```javascript
|
||||
var options = {
|
||||
key: fs.readFileSync('./example/localhost.key'),
|
||||
cert: fs.readFileSync('./example/localhost.crt')
|
||||
};
|
||||
|
||||
require('http2').createServer(options, function(request, response) {
|
||||
response.end('Hello world!');
|
||||
}).listen(8080);
|
||||
```
|
||||
|
||||
### Using as a client ###
|
||||
|
||||
```javascript
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
require('http2').get('https://localhost:8080/', function(response) {
|
||||
response.pipe(process.stdout);
|
||||
});
|
||||
```
|
||||
|
||||
### Simple static file server ###
|
||||
|
||||
An simple static file server serving up content from its own directory is available in the `example`
|
||||
directory. Running the server:
|
||||
|
||||
```bash
|
||||
$ node ./example/server.js
|
||||
```
|
||||
|
||||
### Simple command line client ###
|
||||
|
||||
An example client is also available. Downloading the server's own source code from the server:
|
||||
|
||||
```bash
|
||||
$ node ./example/client.js 'https://localhost:8080/server.js' >/tmp/server.js
|
||||
```
|
||||
|
||||
### Server push ###
|
||||
|
||||
For a server push example, see the source code of the example
|
||||
[server](https://github.com/molnarg/node-http2/blob/master/example/server.js) and
|
||||
[client](https://github.com/molnarg/node-http2/blob/master/example/client.js).
|
||||
|
||||
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.
|
||||
* 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
|
||||
[this list](https://github.com/molnarg/node-http2/issues?labels=feature) are not implemented yet
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
### Development dependencies ###
|
||||
|
||||
There's a few library you will need to have installed to do anything described in the following
|
||||
sections. After installing/cloning node-http2, run `npm install` in its directory to install
|
||||
development dependencies.
|
||||
|
||||
Used libraries:
|
||||
|
||||
* [mocha](http://visionmedia.github.io/mocha/) for tests
|
||||
* [chai](http://chaijs.com/) for assertions
|
||||
* [istanbul](https://github.com/gotwarlost/istanbul) for code coverage analysis
|
||||
* [docco](http://jashkenas.github.io/docco/) for developer documentation
|
||||
* [bunyan](https://github.com/trentm/node-bunyan) for logging
|
||||
|
||||
For pretty printing logs, you will also need a global install of bunyan (`npm install -g bunyan`).
|
||||
|
||||
### Developer documentation ###
|
||||
|
||||
The developer documentation is located in the `doc` directory. The docs are usually updated only
|
||||
before releasing a new version. To regenerate them manually, run `npm run-script prepublish`.
|
||||
There's a hosted version which is located [here](http://molnarg.github.io/node-http2/doc/).
|
||||
|
||||
### Running the tests ###
|
||||
|
||||
It's easy, just run `npm test`. The tests are written in BDD style, so they are a good starting
|
||||
point to understand the code.
|
||||
|
||||
### Test coverage ###
|
||||
|
||||
To generate a code coverage report, run `npm test --coverage` (which runs very slowly, be patient).
|
||||
Code coverage summary as of version 1.0.1:
|
||||
```
|
||||
Statements : 93.26% ( 1563/1676 )
|
||||
Branches : 84.85% ( 605/713 )
|
||||
Functions : 94.81% ( 201/212 )
|
||||
Lines : 93.23% ( 1557/1670 )
|
||||
```
|
||||
|
||||
There's a hosted version of the detailed (line-by-line) coverage report
|
||||
[here](http://molnarg.github.io/node-http2/coverage/lcov-report/lib/).
|
||||
|
||||
### Logging ###
|
||||
|
||||
Logging is turned off by default. You can turn it on by passing a bunyan logger as `log` option when
|
||||
creating a server or agent.
|
||||
|
||||
When using the example server or client, it's very easy to turn logging on: set the `HTTP2_LOG`
|
||||
environment variable to `fatal`, `error`, `warn`, `info`, `debug` or `trace` (the logging level).
|
||||
To log every single incoming and outgoing data chunk, use `HTTP2_LOG_DATA=1` besides
|
||||
`HTTP2_LOG=trace`. Log output goes to the standard error output. If the standard error is redirected
|
||||
into a file, then the log output is in bunyan's JSON format for easier post-mortem analysis.
|
||||
|
||||
Running the example server and client with `info` level logging output:
|
||||
|
||||
```bash
|
||||
$ HTTP2_LOG=info node ./example/server.js
|
||||
```
|
||||
|
||||
```bash
|
||||
$ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' >/dev/null
|
||||
```
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
Code contributions are always welcome! People who contributed to node-http2 so far:
|
||||
|
||||
* Nick Hurley
|
||||
* Mike Belshe
|
||||
|
||||
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
|
||||
Nick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
|
511
testing/xpcshell/node-http2/doc/docco.css
Normal file
511
testing/xpcshell/node-http2/doc/docco.css
Normal file
@ -0,0 +1,511 @@
|
||||
/*-------------- node-http2 customizations --------------------*/
|
||||
|
||||
ul.sections > li > div.annotation {
|
||||
min-width: 35em !important;
|
||||
max-width: 35em !important;
|
||||
}
|
||||
|
||||
#background {
|
||||
width: 35em !important;
|
||||
}
|
||||
|
||||
/*--------------------- Typography ----------------------------*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'aller-light';
|
||||
src: url('public/fonts/aller-light.eot');
|
||||
src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/aller-light.woff') format('woff'),
|
||||
url('public/fonts/aller-light.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'aller-bold';
|
||||
src: url('public/fonts/aller-bold.eot');
|
||||
src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/aller-bold.woff') format('woff'),
|
||||
url('public/fonts/aller-bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'novecento-bold';
|
||||
src: url('public/fonts/novecento-bold.eot');
|
||||
src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/novecento-bold.woff') format('woff'),
|
||||
url('public/fonts/novecento-bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/*--------------------- Layout ----------------------------*/
|
||||
html { height: 100%; }
|
||||
body {
|
||||
font-family: "aller-light";
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #30404f;
|
||||
margin: 0; padding: 0;
|
||||
height:100%;
|
||||
}
|
||||
#container { min-height: 100%; }
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: normal;
|
||||
font-family: "aller-bold";
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
margin: 15px 0 0px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #112233;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
font-family: "novecento-bold";
|
||||
text-transform: uppercase;
|
||||
margin: 30px 0 15px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
background: 1px solid #ddd;
|
||||
height: 1px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
pre, tt, code {
|
||||
font-size: 12px; line-height: 16px;
|
||||
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
.annotation pre {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 7px 10px;
|
||||
background: #fcfcfc;
|
||||
-moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
-webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.annotation pre code {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
border-left: 5px solid #ccc;
|
||||
margin: 0;
|
||||
padding: 1px 0 1px 1em;
|
||||
}
|
||||
.sections blockquote p {
|
||||
font-family: Menlo, Consolas, Monaco, monospace;
|
||||
font-size: 12px; line-height: 16px;
|
||||
color: #999;
|
||||
margin: 10px 0 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
ul.sections {
|
||||
list-style: none;
|
||||
padding:0 0 5px 0;;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
ul.sections > li > div {
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
-ms-box-sizing: border-box; /* ie */
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-khtml-box-sizing: border-box; /* konqueror */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
|
||||
/*---------------------- Jump Page -----------------------------*/
|
||||
#jump_to, #jump_page {
|
||||
margin: 0;
|
||||
background: white;
|
||||
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
|
||||
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
|
||||
font: 16px Arial;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#jump_to a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#jump_to a.large {
|
||||
display: none;
|
||||
}
|
||||
#jump_to a.small {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #676767;
|
||||
}
|
||||
|
||||
#jump_to, #jump_wrapper {
|
||||
position: fixed;
|
||||
right: 0; top: 0;
|
||||
padding: 10px 15px;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#jump_wrapper {
|
||||
display: none;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#jump_to:hover #jump_wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#jump_page {
|
||||
padding: 5px 0 3px;
|
||||
margin: 0 0 25px 25px;
|
||||
}
|
||||
|
||||
#jump_page .source {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
text-decoration: none;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#jump_page .source:hover {
|
||||
background: #f5f5ff;
|
||||
}
|
||||
|
||||
#jump_page .source:first-child {
|
||||
}
|
||||
|
||||
/*---------------------- Low resolutions (> 320px) ---------------------*/
|
||||
@media only screen and (min-width: 320px) {
|
||||
.pilwrap { display: none; }
|
||||
|
||||
ul.sections > li > div {
|
||||
display: block;
|
||||
padding:5px 10px 0 10px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.content {
|
||||
background: #f5f5ff;
|
||||
overflow-x:auto;
|
||||
-webkit-box-shadow: inset 0 0 5px #e5e5ee;
|
||||
box-shadow: inset 0 0 5px #e5e5ee;
|
||||
border: 1px solid #dedede;
|
||||
margin:5px 10px 5px 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation pre {
|
||||
margin: 7px 0 7px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation p tt, .annotation code {
|
||||
background: #f8f8ff;
|
||||
border: 1px solid #dedede;
|
||||
font-size: 12px;
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- (> 481px) ---------------------*/
|
||||
@media only screen and (min-width: 481px) {
|
||||
#container {
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
background-color: #F5F5FF;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
}
|
||||
pre, tt, code {
|
||||
line-height: 18px;
|
||||
}
|
||||
p, ul, ol {
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
|
||||
#jump_to {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#jump_wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
#jump_to, #jump_page {
|
||||
font: 10px Arial;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
#jump_page .source {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#jump_to a.large {
|
||||
display: inline-block;
|
||||
}
|
||||
#jump_to a.small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
width: 350px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #e5e5ee;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
ul.sections > li {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
ul.sections > li > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation {
|
||||
max-width: 350px;
|
||||
min-width: 350px;
|
||||
min-height: 5px;
|
||||
padding: 13px;
|
||||
overflow-x: hidden;
|
||||
white-space: normal;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
ul.sections > li > div.annotation pre {
|
||||
margin: 15px 0 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.content {
|
||||
padding: 13px;
|
||||
vertical-align: top;
|
||||
background: #f5f5ff;
|
||||
border: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.pilwrap {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.pilcrow {
|
||||
font: 12px Arial;
|
||||
text-decoration: none;
|
||||
color: #454545;
|
||||
position: absolute;
|
||||
top: 3px; left: -20px;
|
||||
padding: 1px 2px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
}
|
||||
.for-h1 .pilcrow {
|
||||
top: 47px;
|
||||
}
|
||||
.for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow {
|
||||
top: 35px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation:hover .pilcrow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- (> 1025px) ---------------------*/
|
||||
@media only screen and (min-width: 1025px) {
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
#background {
|
||||
width: 525px;
|
||||
}
|
||||
ul.sections > li > div.annotation {
|
||||
max-width: 525px;
|
||||
min-width: 525px;
|
||||
padding: 10px 25px 1px 50px;
|
||||
}
|
||||
ul.sections > li > div.content {
|
||||
padding: 9px 15px 16px 25px;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- Syntax Highlighting -----------------------------*/
|
||||
|
||||
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
|
||||
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
|
||||
/*
|
||||
|
||||
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
|
||||
*/
|
||||
|
||||
pre code {
|
||||
display: block; padding: 0.5em;
|
||||
color: #000;
|
||||
background: #f8f8ff
|
||||
}
|
||||
|
||||
pre .comment,
|
||||
pre .template_comment,
|
||||
pre .diff .header,
|
||||
pre .javadoc {
|
||||
color: #408080;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
pre .keyword,
|
||||
pre .assignment,
|
||||
pre .literal,
|
||||
pre .css .rule .keyword,
|
||||
pre .winutils,
|
||||
pre .javascript .title,
|
||||
pre .lisp .title,
|
||||
pre .subst {
|
||||
color: #954121;
|
||||
/*font-weight: bold*/
|
||||
}
|
||||
|
||||
pre .number,
|
||||
pre .hexcolor {
|
||||
color: #40a070
|
||||
}
|
||||
|
||||
pre .string,
|
||||
pre .tag .value,
|
||||
pre .phpdoc,
|
||||
pre .tex .formula {
|
||||
color: #219161;
|
||||
}
|
||||
|
||||
pre .title,
|
||||
pre .id {
|
||||
color: #19469D;
|
||||
}
|
||||
pre .params {
|
||||
color: #00F;
|
||||
}
|
||||
|
||||
pre .javascript .title,
|
||||
pre .lisp .title,
|
||||
pre .subst {
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
pre .class .title,
|
||||
pre .haskell .label,
|
||||
pre .tex .command {
|
||||
color: #458;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .tag,
|
||||
pre .tag .title,
|
||||
pre .rules .property,
|
||||
pre .django .tag .keyword {
|
||||
color: #000080;
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
pre .attribute,
|
||||
pre .variable,
|
||||
pre .instancevar,
|
||||
pre .lisp .body {
|
||||
color: #008080
|
||||
}
|
||||
|
||||
pre .regexp {
|
||||
color: #B68
|
||||
}
|
||||
|
||||
pre .class {
|
||||
color: #458;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .symbol,
|
||||
pre .ruby .symbol .string,
|
||||
pre .ruby .symbol .keyword,
|
||||
pre .ruby .symbol .keymethods,
|
||||
pre .lisp .keyword,
|
||||
pre .tex .special,
|
||||
pre .input_number {
|
||||
color: #990073
|
||||
}
|
||||
|
||||
pre .builtin,
|
||||
pre .constructor,
|
||||
pre .built_in,
|
||||
pre .lisp .title {
|
||||
color: #0086b3
|
||||
}
|
||||
|
||||
pre .preprocessor,
|
||||
pre .pi,
|
||||
pre .doctype,
|
||||
pre .shebang,
|
||||
pre .cdata {
|
||||
color: #999;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .deletion {
|
||||
background: #fdd
|
||||
}
|
||||
|
||||
pre .addition {
|
||||
background: #dfd
|
||||
}
|
||||
|
||||
pre .diff .change {
|
||||
background: #0086b3
|
||||
}
|
||||
|
||||
pre .chunk {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
pre .tex .formula {
|
||||
opacity: 0.5;
|
||||
}
|
1867
testing/xpcshell/node-http2/doc/http.html
Normal file
1867
testing/xpcshell/node-http2/doc/http.html
Normal file
File diff suppressed because it is too large
Load Diff
106
testing/xpcshell/node-http2/doc/index.html
Normal file
106
testing/xpcshell/node-http2/doc/index.html
Normal file
@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>index.js</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
||||
<link rel="stylesheet" media="all" href="docco.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="background"></div>
|
||||
|
||||
<ul id="jump_to">
|
||||
<li>
|
||||
<a class="large" href="javascript:void(0);">Jump To …</a>
|
||||
<a class="small" href="javascript:void(0);">+</a>
|
||||
<div id="jump_wrapper">
|
||||
<div id="jump_page">
|
||||
|
||||
|
||||
<a class="source" href="http.html">
|
||||
http.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="index.html">
|
||||
index.js
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="sections">
|
||||
|
||||
<li id="title">
|
||||
<div class="annotation">
|
||||
<h1>index.js</h1>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li id="section-1">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-1">¶</a>
|
||||
</div>
|
||||
<p><a href="https://github.com/molnarg/node-http2">node-http2</a> is an <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-06">HTTP/2 (draft 06)</a> implementation for <a href="http://nodejs.org/">node.js</a>.</p>
|
||||
<p>The core of the protocol is implemented by the <a href="https://github.com/molnarg/node-http2-protocol">http2-protocol</a> module. This module provides
|
||||
two important features on top of http2-protocol:</p>
|
||||
<ul>
|
||||
<li><p>Implementation of different negotiation schemes that can be used to start a HTTP2 connection.
|
||||
These include TLS ALPN, Upgrade and Plain TCP.</p>
|
||||
</li>
|
||||
<li><p>Providing an API very similar to the standard node.js <a href="http://nodejs.org/api/https.html">HTTPS module API</a>
|
||||
(which is in turn very similar to the <a href="http://nodejs.org/api/http.html">HTTP module API</a>).</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>module.exports = require(<span class="string">'./http'</span>);
|
||||
|
||||
<span class="comment">/*
|
||||
HTTP API
|
||||
|
||||
| ^
|
||||
| |
|
||||
+-------------|------------|------------------------------------------------------+
|
||||
| | | Server/Agent |
|
||||
| v | |
|
||||
| +----------+ +----------+ |
|
||||
| | Outgoing | | Incoming | |
|
||||
| | req/res. | | req/res. | |
|
||||
| +----------+ +----------+ |
|
||||
| | ^ |
|
||||
| | | |
|
||||
| +---------|------------|-------------------------------------+ +----- |
|
||||
| | | | Endpoint | | |
|
||||
| | | | | | |
|
||||
| | v | | | |
|
||||
| | +-----------------------+ +-------------------- | | |
|
||||
| | | Stream | | Stream ... | | |
|
||||
| | +-----------------------+ +-------------------- | | |
|
||||
| | | | |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| | | |
|
||||
| | | |
|
||||
| v | |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| | TCP stream | | ... |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------+
|
||||
|
||||
*/</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.eot
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.eot
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.ttf
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.ttf
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.woff
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-bold.woff
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.eot
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.eot
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.ttf
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.ttf
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.woff
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/aller-light.woff
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.eot
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.eot
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.ttf
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.ttf
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.woff
Executable file
BIN
testing/xpcshell/node-http2/doc/public/fonts/novecento-bold.woff
Executable file
Binary file not shown.
375
testing/xpcshell/node-http2/doc/public/stylesheets/normalize.css
vendored
Normal file
375
testing/xpcshell/node-http2/doc/public/stylesheets/normalize.css
vendored
Normal file
@ -0,0 +1,375 @@
|
||||
/*! normalize.css v2.0.1 | MIT License | git.io/normalize */
|
||||
|
||||
/* ==========================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects `block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects `inline-block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for `hidden` attribute not present in IE 8/9.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Sets default font family to sans-serif.
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses `h1` font sizes within `section` and `article` in Firefox 4+,
|
||||
* Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE 8/9, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets consistent quote types.
|
||||
*/
|
||||
|
||||
q {
|
||||
quotes: "\201C" "\201D" "\2018" "\2019";
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Removes border when inside `a` element in IE 8/9.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE 9.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE 8/9 and Safari 5.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE 8/9.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font family not being inherited in all browsers.
|
||||
* 2. Corrects font size not being inherited in all browsers.
|
||||
* 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Corrects inability to style clickable `input` types in iOS.
|
||||
* 3. Improves usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to `content-box` in IE 8/9.
|
||||
* 2. Removes excess padding in IE 8/9.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||
* 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE 8/9.
|
||||
* 2. Improves readability and alignment in all browsers.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
42
testing/xpcshell/node-http2/example/client.js
Normal file
42
testing/xpcshell/node-http2/example/client.js
Normal file
@ -0,0 +1,42 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var http2 = require('..');
|
||||
|
||||
http2.globalAgent = new http2.Agent({
|
||||
log: require('../test/util').createLogger('client')
|
||||
});
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
// Sending the request
|
||||
// It would be `var request = http2.get(process.argv.pop());` if we wouldn't care about plain mode
|
||||
var options = require('url').parse(process.argv.pop());
|
||||
options.plain = Boolean(process.env.HTTP2_PLAIN);
|
||||
var request = http2.request(options);
|
||||
request.end();
|
||||
|
||||
// Receiving the response
|
||||
request.on('response', function(response) {
|
||||
response.pipe(process.stdout);
|
||||
response.on('end', finish);
|
||||
});
|
||||
|
||||
// Receiving push streams
|
||||
request.on('push', function(pushRequest) {
|
||||
var filename = path.join(__dirname, '/push-' + push_count);
|
||||
push_count += 1;
|
||||
console.error('Receiving pushed resource: ' + pushRequest.url + ' -> ' + filename);
|
||||
pushRequest.on('response', function(pushResponse) {
|
||||
pushResponse.pipe(fs.createWriteStream(filename)).on('finish', finish);
|
||||
});
|
||||
});
|
||||
|
||||
// Quitting after both the response and the associated pushed resources have arrived
|
||||
var push_count = 0;
|
||||
var finished = 0;
|
||||
function finish() {
|
||||
finished += 1;
|
||||
if (finished === (1 + push_count)) {
|
||||
process.exit();
|
||||
}
|
||||
}
|
14
testing/xpcshell/node-http2/example/localhost.crt
Normal file
14
testing/xpcshell/node-http2/example/localhost.crt
Normal file
@ -0,0 +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=
|
||||
-----END CERTIFICATE-----
|
15
testing/xpcshell/node-http2/example/localhost.key
Normal file
15
testing/xpcshell/node-http2/example/localhost.key
Normal file
@ -0,0 +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=
|
||||
-----END RSA PRIVATE KEY-----
|
49
testing/xpcshell/node-http2/example/server.js
Normal file
49
testing/xpcshell/node-http2/example/server.js
Normal file
@ -0,0 +1,49 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var http2 = require('..');
|
||||
|
||||
var options = process.env.HTTP2_PLAIN ? {
|
||||
plain: true
|
||||
} : {
|
||||
key: fs.readFileSync(path.join(__dirname, '/localhost.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '/localhost.crt'))
|
||||
};
|
||||
|
||||
// Passing bunyan logger (optional)
|
||||
options.log = require('../test/util').createLogger('server');
|
||||
|
||||
// We cache one file to be able to do simple performance tests without waiting for the disk
|
||||
var cachedFile = fs.readFileSync(path.join(__dirname, './server.js'));
|
||||
var cachedUrl = '/server.js';
|
||||
|
||||
// Creating the server
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var filename = path.join(__dirname, request.url);
|
||||
|
||||
// Serving server.js from cache. Useful for microbenchmarks.
|
||||
if (request.url === cachedUrl) {
|
||||
response.end(cachedFile);
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
// If they download the certificate, push the private key too, they might need it.
|
||||
if (response.push && request.url === '/localhost.crt') {
|
||||
var push = response.push('/localhost.key');
|
||||
push.writeHead(200);
|
||||
fs.createReadStream(path.join(__dirname, '/localhost.key')).pipe(push);
|
||||
}
|
||||
|
||||
fs.createReadStream(filename).pipe(response);
|
||||
}
|
||||
|
||||
// Otherwise responding with 404.
|
||||
else {
|
||||
response.writeHead('404');
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(process.env.HTTP2_PORT || 8080);
|
985
testing/xpcshell/node-http2/lib/http.js
Normal file
985
testing/xpcshell/node-http2/lib/http.js
Normal file
@ -0,0 +1,985 @@
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
// The main governing power behind the http2 API design is that it should look very similar to the
|
||||
// existing node.js [HTTPS API][1] (which is, in turn, almost identical to the [HTTP API][2]). The
|
||||
// additional features of HTTP/2 are exposed as extensions to this API. Furthermore, node-http2
|
||||
// should fall back to using HTTP/1.1 if needed. Compatibility with undocumented or deprecated
|
||||
// elements of the node.js HTTP/HTTPS API is a non-goal.
|
||||
//
|
||||
// Additional and modified API elements
|
||||
// ------------------------------------
|
||||
//
|
||||
// - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation
|
||||
// see the [lib/endpoint.js](endpoint.html) file.
|
||||
//
|
||||
// - **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
|
||||
// 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'
|
||||
// request to which the push stream is a response; the possible options are identical to the
|
||||
// ones accepted by `http2.request`. Returns a ServerResponse object that can be used to send
|
||||
// the response headers and content.
|
||||
//
|
||||
// - **Class: http2.Agent**
|
||||
// - **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.
|
||||
//
|
||||
// - **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
|
||||
//
|
||||
// - **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).
|
||||
// - **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.
|
||||
// - **request.setPriority(priority)**: assign a priority to this request. `priority` is a number
|
||||
// between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30.
|
||||
//
|
||||
// - **Class: http2.IncomingMessage**
|
||||
// - 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).
|
||||
//
|
||||
// - **Class: http2.IncomingRequest (IncomingMessage)**
|
||||
// - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the
|
||||
// path, and never a full url (it contains the path in most cases in the HTTPS api as well).
|
||||
// - **message.scheme**: additional field. Mandatory HTTP/2 request metadata.
|
||||
// - **message.host**: additional field. Mandatory HTTP/2 request metadata. Note that this
|
||||
// replaces the old Host header field, but node-http2 will add Host to the `message.headers` for
|
||||
// backwards compatibility.
|
||||
//
|
||||
// - **Class: http2.IncomingPromise (IncomingRequest)**
|
||||
// - contains the metadata of the 'imaginary' request to which the server push is an answer.
|
||||
// - **Event: 'response' (response)**: signals the arrival of the actual push stream. `response`
|
||||
// is an IncomingResponse.
|
||||
// - **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.
|
||||
// - **promise.cancel()**: cancels the promised server push.
|
||||
// - **promise.setPriority(priority)**: assign a priority to this push stream. `priority` is a
|
||||
// number between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30.
|
||||
//
|
||||
// API elements not yet implemented
|
||||
// --------------------------------
|
||||
//
|
||||
// - **Class: http2.Server**
|
||||
// - **server.maxHeadersCount**
|
||||
//
|
||||
// API elements that are not applicable to HTTP/2
|
||||
// ----------------------------------------------
|
||||
//
|
||||
// The reason may be deprecation of certain HTTP/1.1 features, or that some API elements simply
|
||||
// don't make sense when using HTTP/2. These will not be present when a request is done with HTTP/2,
|
||||
// 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: '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])
|
||||
// - **server.setTimeout(msecs, [callback])**
|
||||
// - **server.timeout**
|
||||
//
|
||||
// - **Class: http2.ServerResponse**
|
||||
// - **Event: 'close'**
|
||||
// - **Event: 'timeout'**
|
||||
// - **response.writeContinue()**
|
||||
// - **response.writeHead(statusCode, [reasonPhrase], [headers])**: reasonPhrase will always be
|
||||
// ignored since [it's not supported in HTTP/2][3]
|
||||
// - **response.setTimeout(timeout, [callback])**
|
||||
//
|
||||
// - **Class: http2.Agent**
|
||||
// - **agent.maxSockets**: only affects HTTP/1 connection pool. When using HTTP/2, there's always
|
||||
// one connection per host.
|
||||
//
|
||||
// - **Class: http2.ClientRequest**
|
||||
// - **Event: 'upgrade'**
|
||||
// - **Event: 'connect'**
|
||||
// - **Event: 'continue'**
|
||||
// - **request.setTimeout(timeout, [callback])**
|
||||
// - **request.setNoDelay([noDelay])**
|
||||
// - **request.setSocketKeepAlive([enable], [initialDelay])**
|
||||
//
|
||||
// - **Class: http2.IncomingMessage**
|
||||
// - **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-04#section-8.1.3
|
||||
// [expect-continue]: https://github.com/http2/http2-spec/issues/18
|
||||
// [connect]: https://github.com/http2/http2-spec/issues/230
|
||||
|
||||
// Common server and client side code
|
||||
// ==================================
|
||||
|
||||
var net = require('net');
|
||||
var url = require('url');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var PassThrough = require('stream').PassThrough;
|
||||
var Readable = require('stream').Readable;
|
||||
var Writable = require('stream').Writable;
|
||||
var Endpoint = require('http2-protocol').Endpoint;
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
|
||||
exports.STATUS_CODES = http.STATUS_CODES;
|
||||
exports.IncomingMessage = IncomingMessage;
|
||||
exports.OutgoingMessage = OutgoingMessage;
|
||||
|
||||
var deprecatedHeaders = [
|
||||
'connection',
|
||||
'host',
|
||||
'keep-alive',
|
||||
'proxy-connection',
|
||||
'te',
|
||||
'transfer-encoding',
|
||||
'upgrade'
|
||||
];
|
||||
|
||||
// The implemented version of the HTTP/2 specification is [draft 04][1].
|
||||
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04
|
||||
var implementedVersion = 'HTTP-draft-08/2.0';
|
||||
|
||||
// When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback
|
||||
var supportedProtocols = [implementedVersion, 'http/1.1', 'http/1.0'];
|
||||
|
||||
// Logging
|
||||
// -------
|
||||
|
||||
// Logger shim, used when no logger is provided by the user.
|
||||
function noop() {}
|
||||
var defaultLogger = {
|
||||
fatal: noop,
|
||||
error: noop,
|
||||
warn : noop,
|
||||
info : noop,
|
||||
debug: noop,
|
||||
trace: noop,
|
||||
|
||||
child: function() { return this; }
|
||||
};
|
||||
|
||||
// Bunyan serializers exported by submodules that are worth adding when creating a logger.
|
||||
exports.serializers = require('http2-protocol').serializers;
|
||||
|
||||
// IncomingMessage class
|
||||
// ---------------------
|
||||
|
||||
function IncomingMessage(stream) {
|
||||
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
|
||||
PassThrough.call(this);
|
||||
stream.pipe(this);
|
||||
this.socket = this.stream = stream;
|
||||
|
||||
this._log = stream._log.child({ component: 'http' });
|
||||
|
||||
// * HTTP/2.0 does not define a way to carry the version identifier that is included in the
|
||||
// HTTP/1.1 request/status line. Version is always 2.0.
|
||||
this.httpVersion = '2.0';
|
||||
this.httpVersionMajor = 2;
|
||||
this.httpVersionMinor = 0;
|
||||
|
||||
// * `this.headers` will store the regular headers (and none of the special colon headers)
|
||||
this.headers = {};
|
||||
this.trailers = undefined;
|
||||
this._lastHeadersSeen = undefined;
|
||||
|
||||
// * Other metadata is filled in when the headers arrive.
|
||||
stream.once('headers', this._onHeaders.bind(this));
|
||||
stream.once('end', this._onEnd.bind(this));
|
||||
}
|
||||
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
|
||||
|
||||
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.1)
|
||||
// * `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.
|
||||
IncomingMessage.prototype._onHeaders = function _onHeaders(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
|
||||
// MUST treat the presence of any of these header fields as a stream error of type
|
||||
// PROTOCOL_ERROR.
|
||||
for (var i = 0; i < deprecatedHeaders.length; i++) {
|
||||
var key = deprecatedHeaders[i];
|
||||
if (key in headers) {
|
||||
this._log.error({ key: key, value: headers[key] }, 'Deprecated header found');
|
||||
this.stream.emit('error', 'PROTOCOL_ERROR');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// * Store the _regular_ headers in `this.headers`
|
||||
for (var name in headers) {
|
||||
if (name[0] !== ':') {
|
||||
this.headers[name] = headers[name];
|
||||
}
|
||||
}
|
||||
|
||||
// * The last header block, if it's not the first, will represent the trailers
|
||||
var self = this;
|
||||
this.stream.on('headers', function(headers) {
|
||||
self._lastHeadersSeen = headers;
|
||||
});
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._onEnd = function _onEnd() {
|
||||
this.trailers = this._lastHeadersSeen;
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.setTimeout = noop;
|
||||
|
||||
IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) {
|
||||
if ((typeof value !== 'string') || (value.length === 0)) {
|
||||
this._log.error({ key: key, value: value }, 'Invalid or missing special header field');
|
||||
this.stream.emit('error', 'PROTOCOL_ERROR');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
;
|
||||
|
||||
// OutgoingMessage class
|
||||
// ---------------------
|
||||
|
||||
function OutgoingMessage() {
|
||||
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
|
||||
Writable.call(this);
|
||||
|
||||
this._headers = {};
|
||||
this._trailers = undefined;
|
||||
this.headersSent = false;
|
||||
|
||||
this.on('finish', this._finish);
|
||||
}
|
||||
OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } });
|
||||
|
||||
OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) {
|
||||
if (this.stream) {
|
||||
this.stream.write(chunk, encoding, callback);
|
||||
} else {
|
||||
this.once('socket', this._write.bind(this, chunk, encoding, callback));
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype._finish = function _finish() {
|
||||
if (this.stream) {
|
||||
if (this._trailers) {
|
||||
if (this.request) {
|
||||
this.request.addTrailers(this._trailers);
|
||||
} else {
|
||||
this.stream.headers(this._trailers);
|
||||
}
|
||||
}
|
||||
this.stream.end();
|
||||
} else {
|
||||
this.once('socket', this._finish.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
|
||||
if (this.headersSent) {
|
||||
throw 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);
|
||||
}
|
||||
this._headers[name] = value;
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
|
||||
if (this.headersSent) {
|
||||
throw new Error('Can\'t remove headers after they are sent.');
|
||||
} else {
|
||||
delete this._headers[name.toLowerCase()];
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.getHeader = function getHeader(name) {
|
||||
return this._headers[name.toLowerCase()];
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) {
|
||||
this._trailers = trailers;
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.setTimeout = noop;
|
||||
|
||||
OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader;
|
||||
|
||||
// Server side
|
||||
// ===========
|
||||
|
||||
exports.createServer = createServer;
|
||||
exports.Server = Server;
|
||||
exports.IncomingRequest = IncomingRequest;
|
||||
exports.OutgoingResponse = OutgoingResponse;
|
||||
exports.ServerResponse = OutgoingResponse; // for API compatibility
|
||||
|
||||
// Server class
|
||||
// ------------
|
||||
|
||||
function Server(options) {
|
||||
options = util._extend({}, options);
|
||||
|
||||
this._log = (options.log || defaultLogger).child({ component: 'http' });
|
||||
this._settings = options.settings;
|
||||
|
||||
var start = this._start.bind(this);
|
||||
var fallback = this._fallback.bind(this);
|
||||
|
||||
// HTTP2 over TLS (using NPN or ALPN)
|
||||
if ((options.key && options.cert) || options.pfx) {
|
||||
this._log.info('Creating HTTP/2 server over TLS');
|
||||
this._mode = 'tls';
|
||||
options.ALPNProtocols = supportedProtocols;
|
||||
options.NPNProtocols = supportedProtocols;
|
||||
this._server = https.createServer(options);
|
||||
this._originalSocketListeners = this._server.listeners('secureConnection');
|
||||
this._server.removeAllListeners('secureConnection');
|
||||
this._server.on('secureConnection', function(socket) {
|
||||
var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
|
||||
if ((negotiatedProtocol === implementedVersion) && socket.servername) {
|
||||
start(socket);
|
||||
} else {
|
||||
fallback(socket);
|
||||
}
|
||||
});
|
||||
this._server.on('request', this.emit.bind(this, 'request'));
|
||||
}
|
||||
|
||||
// HTTP2 over plain TCP
|
||||
else if (options.plain) {
|
||||
this._log.info('Creating HTTP/2 server over plain TCP');
|
||||
this._mode = 'plain';
|
||||
this._server = net.createServer(start);
|
||||
}
|
||||
|
||||
// HTTP/2 with HTTP/1.1 upgrade
|
||||
else {
|
||||
this._log.error('Trying to create HTTP/2 server with Upgrade from HTTP/1.1');
|
||||
throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported. Please provide TLS keys.');
|
||||
}
|
||||
|
||||
this._server.on('close', this.emit.bind(this, 'close'));
|
||||
}
|
||||
Server.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Server } });
|
||||
|
||||
// Starting HTTP/2
|
||||
Server.prototype._start = function _start(socket) {
|
||||
var endpoint = new Endpoint(this._log, 'SERVER', this._settings);
|
||||
|
||||
this._log.info({ e: endpoint,
|
||||
client: socket.remoteAddress + ':' + socket.remotePort,
|
||||
SNI: socket.servername
|
||||
}, 'New incoming HTTP/2 connection');
|
||||
|
||||
endpoint.pipe(socket).pipe(endpoint);
|
||||
|
||||
var self = this;
|
||||
endpoint.on('stream', function _onStream(stream) {
|
||||
var response = new OutgoingResponse(stream);
|
||||
var request = new IncomingRequest(stream);
|
||||
|
||||
request.once('ready', self.emit.bind(self, 'request', request, response));
|
||||
});
|
||||
|
||||
endpoint.on('error', this.emit.bind(this, 'clientError'));
|
||||
|
||||
this.emit('connection', socket, endpoint);
|
||||
};
|
||||
|
||||
Server.prototype._fallback = function _fallback(socket) {
|
||||
var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
|
||||
|
||||
this._log.info({ client: socket.remoteAddress + ':' + socket.remotePort,
|
||||
protocol: negotiatedProtocol,
|
||||
SNI: socket.servername
|
||||
}, 'Falling back to simple HTTPS');
|
||||
|
||||
for (var i = 0; i < this._originalSocketListeners.length; i++) {
|
||||
this._originalSocketListeners[i].call(this._server, socket);
|
||||
}
|
||||
|
||||
this.emit('connection', 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
|
||||
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);
|
||||
};
|
||||
|
||||
Server.prototype.close = function close(callback) {
|
||||
this._log.info('Closing server');
|
||||
this._server.close(callback);
|
||||
};
|
||||
|
||||
Server.prototype.setTimeout = function setTimeout(timeout, callback) {
|
||||
if (this._mode === 'tls') {
|
||||
this._server.setTimeout(timeout, callback);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(Server.prototype, 'timeout', {
|
||||
get: function getTimeout() {
|
||||
if (this._mode === 'tls') {
|
||||
return this._server.timeout;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
set: function setTimeout(timeout) {
|
||||
if (this._mode === 'tls') {
|
||||
this._server.timeout = timeout;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to
|
||||
// `server`.There are events on the `http.Server` class where it makes difference whether someone is
|
||||
// listening on the event or not. In these cases, we can not simply forward the events from the
|
||||
// `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));
|
||||
} else {
|
||||
EventEmitter.prototype.on.call(this, event, listener);
|
||||
}
|
||||
};
|
||||
|
||||
// `addContext` is used to add Server Name Indication contexts
|
||||
Server.prototype.addContext = function addContext(hostname, credentials) {
|
||||
if (this._mode === 'tls') {
|
||||
this._server.addContext(hostname, credentials);
|
||||
}
|
||||
};
|
||||
|
||||
function createServer(options, requestListener) {
|
||||
if (typeof options === 'function') {
|
||||
requestListener = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
var server = new Server(options);
|
||||
|
||||
if (requestListener) {
|
||||
server.on('request', requestListener);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
// IncomingRequest class
|
||||
// ---------------------
|
||||
|
||||
function IncomingRequest(stream) {
|
||||
IncomingMessage.call(this, stream);
|
||||
}
|
||||
IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } });
|
||||
|
||||
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.1)
|
||||
// * `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.
|
||||
IncomingRequest.prototype._onHeaders = function _onHeaders(headers) {
|
||||
// * The ":method" header field includes the HTTP method
|
||||
// * The ":scheme" header field includes the scheme portion of the target URI
|
||||
// * The ":authority" header field includes the authority portion of the target URI
|
||||
// * The ":path" header field includes the path and query parts of the target URI.
|
||||
// This field MUST NOT be empty; URIs that do not contain a path component MUST include a value
|
||||
// of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header
|
||||
// field MUST include '*'.
|
||||
// * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A
|
||||
// server MUST treat the absence of any of these header fields, presence of multiple values, or
|
||||
// an invalid value as a stream error of type PROTOCOL_ERROR.
|
||||
this.method = this._checkSpecialHeader(':method' , headers[':method']);
|
||||
this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']);
|
||||
this.host = this._checkSpecialHeader(':authority', headers[':authority'] );
|
||||
this.url = this._checkSpecialHeader(':path' , headers[':path'] );
|
||||
|
||||
// * Host header is included in the headers object for backwards compatibility.
|
||||
this.headers.host = this.host;
|
||||
|
||||
// * Handling regular headers.
|
||||
IncomingMessage.prototype._onHeaders.call(this, headers);
|
||||
|
||||
// * Signaling that the headers arrived.
|
||||
this._log.info({ method: this.method, scheme: this.scheme, host: this.host,
|
||||
path: this.url, headers: this.headers }, 'Incoming request');
|
||||
this.emit('ready');
|
||||
};
|
||||
|
||||
// OutgoingResponse class
|
||||
// ----------------------
|
||||
|
||||
function OutgoingResponse(stream) {
|
||||
OutgoingMessage.call(this);
|
||||
|
||||
this._log = stream._log.child({ component: 'http' });
|
||||
|
||||
this.stream = stream;
|
||||
this.statusCode = 200;
|
||||
this.sendDate = true;
|
||||
|
||||
this.stream.once('headers', this._onRequestHeaders.bind(this));
|
||||
}
|
||||
OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } });
|
||||
|
||||
OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) {
|
||||
if (typeof reasonPhrase === 'string') {
|
||||
this._log.warn('Reason phrase argument was present but ignored by the writeHead method');
|
||||
} else {
|
||||
headers = reasonPhrase;
|
||||
}
|
||||
|
||||
for (var name in headers) {
|
||||
this.setHeader(name, headers[name]);
|
||||
}
|
||||
headers = this._headers;
|
||||
|
||||
if (this.sendDate && !('date' in this._headers)) {
|
||||
headers.date = (new Date()).toUTCString();
|
||||
}
|
||||
|
||||
this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response');
|
||||
|
||||
headers[':status'] = this.statusCode = statusCode;
|
||||
|
||||
this.stream.headers(headers);
|
||||
this.headersSent = true;
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() {
|
||||
if (!this.headersSent) {
|
||||
this.writeHead(this.statusCode);
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype.write = function write() {
|
||||
this._implicitHeaders();
|
||||
return OutgoingMessage.prototype.write.apply(this, arguments);
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype.end = function end() {
|
||||
this._implicitHeaders();
|
||||
return OutgoingMessage.prototype.end.apply(this, arguments);
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) {
|
||||
this._requestHeaders = headers;
|
||||
};
|
||||
|
||||
OutgoingResponse.prototype.push = function push(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = url.parse(options);
|
||||
}
|
||||
|
||||
if (!options.path) {
|
||||
throw new Error('`path` option is mandatory.');
|
||||
}
|
||||
|
||||
var promise = util._extend({
|
||||
':method': (options.method || 'GET').toUpperCase(),
|
||||
':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'],
|
||||
':authority': options.hostname || options.host || this._requestHeaders[':authority'],
|
||||
':path': options.path
|
||||
}, options.headers);
|
||||
|
||||
this._log.info({ method: promise[':method'], scheme: promise[':scheme'],
|
||||
authority: promise[':authority'], path: promise[':path'],
|
||||
headers: options.headers }, 'Promising push stream');
|
||||
|
||||
var pushStream = this.stream.promise(promise);
|
||||
|
||||
return new OutgoingResponse(pushStream);
|
||||
};
|
||||
|
||||
// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to
|
||||
// `request`. See `Server.prototype.on` for explanation.
|
||||
OutgoingResponse.prototype.on = function on(event, listener) {
|
||||
if (this.request && (event === 'timeout')) {
|
||||
this.request.on(event, listener && listener.bind(this));
|
||||
} else {
|
||||
OutgoingMessage.prototype.on.call(this, event, listener);
|
||||
}
|
||||
};
|
||||
|
||||
// Client side
|
||||
// ===========
|
||||
|
||||
exports.ClientRequest = OutgoingRequest; // for API compatibility
|
||||
exports.OutgoingRequest = OutgoingRequest;
|
||||
exports.IncomingResponse = IncomingResponse;
|
||||
exports.Agent = Agent;
|
||||
exports.globalAgent = undefined;
|
||||
exports.request = function request(options, callback) {
|
||||
return (options.agent || exports.globalAgent).request(options, callback);
|
||||
};
|
||||
exports.get = function get(options, callback) {
|
||||
return (options.agent || exports.globalAgent).get(options, callback);
|
||||
};
|
||||
|
||||
// Agent class
|
||||
// -----------
|
||||
|
||||
function Agent(options) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
options = util._extend({}, options);
|
||||
|
||||
this._settings = options.settings;
|
||||
this._log = (options.log || defaultLogger).child({ component: 'http' });
|
||||
this.endpoints = {};
|
||||
|
||||
// * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when
|
||||
// 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);
|
||||
|
||||
this.sockets = this._httpsAgent.sockets;
|
||||
this.requests = this._httpsAgent.requests;
|
||||
}
|
||||
Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } });
|
||||
|
||||
Agent.prototype.request = function request(options, callback) {
|
||||
if (typeof options === 'string') {
|
||||
options = url.parse(options);
|
||||
} else {
|
||||
options = util._extend({}, options);
|
||||
}
|
||||
|
||||
options.method = (options.method || 'GET').toUpperCase();
|
||||
options.protocol = options.protocol || 'https:';
|
||||
options.host = options.hostname || options.host || 'localhost';
|
||||
options.port = options.port || 443;
|
||||
options.path = options.path || '/';
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
var request = new OutgoingRequest(this._log);
|
||||
|
||||
if (callback) {
|
||||
request.on('response', callback);
|
||||
}
|
||||
|
||||
var key = [
|
||||
!!options.plain,
|
||||
options.host,
|
||||
options.port
|
||||
].join(':');
|
||||
|
||||
// * There's an existing HTTP/2 connection to this host
|
||||
if (key in this.endpoints) {
|
||||
var endpoint = this.endpoints[key];
|
||||
request._start(endpoint.createStream(), options);
|
||||
}
|
||||
|
||||
// * HTTP/2 over plain TCP
|
||||
else if (options.plain) {
|
||||
endpoint = new Endpoint(this._log, 'CLIENT', this._settings);
|
||||
endpoint.socket = net.connect({
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
localAddress: options.localAddress
|
||||
});
|
||||
endpoint.pipe(endpoint.socket).pipe(endpoint);
|
||||
request._start(endpoint.createStream(), options);
|
||||
}
|
||||
|
||||
// * HTTP/2 over TLS negotiated using NPN or ALPN
|
||||
else {
|
||||
var started = false;
|
||||
options.ALPNProtocols = supportedProtocols;
|
||||
options.NPNProtocols = supportedProtocols;
|
||||
options.servername = options.host; // Server Name Indication
|
||||
options.agent = this._httpsAgent;
|
||||
var httpsRequest = https.request(options);
|
||||
|
||||
httpsRequest.on('socket', function(socket) {
|
||||
var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
|
||||
if (negotiatedProtocol !== undefined) {
|
||||
negotiated();
|
||||
} else {
|
||||
socket.on('secureConnect', negotiated);
|
||||
}
|
||||
});
|
||||
|
||||
var self = this;
|
||||
function negotiated() {
|
||||
var endpoint;
|
||||
var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol;
|
||||
if (negotiatedProtocol === implementedVersion) {
|
||||
httpsRequest.socket.emit('agentRemove');
|
||||
unbundleSocket(httpsRequest.socket);
|
||||
endpoint = new Endpoint(self._log, 'CLIENT', self._settings);
|
||||
endpoint.socket = httpsRequest.socket;
|
||||
endpoint.pipe(endpoint.socket).pipe(endpoint);
|
||||
}
|
||||
if (started) {
|
||||
if (endpoint) {
|
||||
endpoint.close();
|
||||
} else {
|
||||
httpsRequest.abort();
|
||||
}
|
||||
} else {
|
||||
if (endpoint) {
|
||||
self._log.info({ e: endpoint, server: options.host + ':' + options.port },
|
||||
'New outgoing HTTP/2 connection');
|
||||
self.endpoints[key] = endpoint;
|
||||
self.emit(key, endpoint);
|
||||
} else {
|
||||
self.emit(key, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.once(key, function(endpoint) {
|
||||
started = true;
|
||||
if (endpoint) {
|
||||
request._start(endpoint.createStream(), options);
|
||||
} else {
|
||||
request._fallback(httpsRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
Agent.prototype.get = function get(options, callback) {
|
||||
var request = this.request(options, callback);
|
||||
request.end();
|
||||
return request;
|
||||
};
|
||||
|
||||
function unbundleSocket(socket) {
|
||||
socket.removeAllListeners('data');
|
||||
socket.removeAllListeners('end');
|
||||
socket.removeAllListeners('readable');
|
||||
socket.removeAllListeners('close');
|
||||
socket.removeAllListeners('error');
|
||||
socket.unpipe();
|
||||
delete socket.ondata;
|
||||
delete socket.onend;
|
||||
}
|
||||
|
||||
Object.defineProperty(Agent.prototype, 'maxSockets', {
|
||||
get: function getMaxSockets() {
|
||||
return this._httpsAgent.maxSockets;
|
||||
},
|
||||
set: function setMaxSockets(value) {
|
||||
this._httpsAgent.maxSockets = value;
|
||||
}
|
||||
});
|
||||
|
||||
exports.globalAgent = new Agent();
|
||||
|
||||
// OutgoingRequest class
|
||||
// ---------------------
|
||||
|
||||
function OutgoingRequest() {
|
||||
OutgoingMessage.call(this);
|
||||
|
||||
this._log = undefined;
|
||||
|
||||
this.stream = undefined;
|
||||
}
|
||||
OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } });
|
||||
|
||||
OutgoingRequest.prototype._start = function _start(stream, options) {
|
||||
this.stream = stream;
|
||||
|
||||
this._log = stream._log.child({ component: 'http' });
|
||||
|
||||
for (var key in options.headers) {
|
||||
this.setHeader(key, options.headers[key]);
|
||||
}
|
||||
var headers = this._headers;
|
||||
delete headers.host;
|
||||
|
||||
if (options.auth) {
|
||||
headers.authorization = 'Basic ' + new Buffer(options.auth).toString('base64');
|
||||
}
|
||||
|
||||
headers[':scheme'] = options.protocol.slice(0, -1);
|
||||
headers[':method'] = options.method;
|
||||
headers[':authority'] = options.host;
|
||||
headers[':path'] = options.path;
|
||||
|
||||
this._log.info({ scheme: headers[':scheme'], method: headers[':method'],
|
||||
authority: headers[':authority'], path: headers[':path'],
|
||||
headers: (options.headers || {}) }, 'Sending request');
|
||||
this.stream.headers(headers);
|
||||
this.headersSent = true;
|
||||
|
||||
this.emit('socket', this.stream);
|
||||
|
||||
var response = new IncomingResponse(this.stream);
|
||||
response.once('ready', this.emit.bind(this, 'response', response));
|
||||
|
||||
this.stream.on('promise', this._onPromise.bind(this));
|
||||
};
|
||||
|
||||
OutgoingRequest.prototype._fallback = function _fallback(request) {
|
||||
request.on('response', this.emit.bind(this, 'response'));
|
||||
this.stream = this.request = request;
|
||||
this.emit('socket', this.socket);
|
||||
};
|
||||
|
||||
OutgoingRequest.prototype.setPriority = function setPriority(priority) {
|
||||
if (this.stream) {
|
||||
this.stream.priority(priority);
|
||||
} else {
|
||||
this.once('socket', this.setPriority.bind(this, priority));
|
||||
}
|
||||
};
|
||||
|
||||
// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to
|
||||
// `request`. See `Server.prototype.on` for explanation.
|
||||
OutgoingRequest.prototype.on = function on(event, listener) {
|
||||
if (this.request && (event === 'upgrade')) {
|
||||
this.request.on(event, listener && listener.bind(this));
|
||||
} else {
|
||||
OutgoingMessage.prototype.on.call(this, event, listener);
|
||||
}
|
||||
};
|
||||
|
||||
// Methods only in fallback mode
|
||||
OutgoingRequest.prototype.setNoDelay = function setNoDelay(noDelay) {
|
||||
if (this.request) {
|
||||
this.request.setNoDelay(noDelay);
|
||||
} else if (!this.stream) {
|
||||
this.on('socket', this.setNoDelay.bind(this, noDelay));
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingRequest.prototype.setSocketKeepAlive = function setSocketKeepAlive(enable, initialDelay) {
|
||||
if (this.request) {
|
||||
this.request.setSocketKeepAlive(enable, initialDelay);
|
||||
} else if (!this.stream) {
|
||||
this.on('socket', this.setSocketKeepAlive.bind(this, enable, initialDelay));
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingRequest.prototype.setTimeout = function setTimeout(timeout, callback) {
|
||||
if (this.request) {
|
||||
this.request.setTimeout(timeout, callback);
|
||||
} else if (!this.stream) {
|
||||
this.on('socket', this.setTimeout.bind(this, timeout, callback));
|
||||
}
|
||||
};
|
||||
|
||||
// Aborting the request
|
||||
OutgoingRequest.prototype.abort = function abort() {
|
||||
if (this.request) {
|
||||
this.request.abort();
|
||||
} else if (this.stream) {
|
||||
this.stream.reset('CANCEL');
|
||||
} else {
|
||||
this.on('socket', this.abort.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
// Receiving push promises
|
||||
OutgoingRequest.prototype._onPromise = function _onPromise(stream, headers) {
|
||||
this._log.info({ push_stream: stream.id }, 'Receiving push promise');
|
||||
|
||||
var promise = new IncomingPromise(stream, headers);
|
||||
|
||||
if (this.listeners('push').length > 0) {
|
||||
this.emit('push', promise);
|
||||
} else {
|
||||
promise.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
// IncomingResponse class
|
||||
// ----------------------
|
||||
|
||||
function IncomingResponse(stream) {
|
||||
IncomingMessage.call(this, stream);
|
||||
}
|
||||
IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } });
|
||||
|
||||
// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.2)
|
||||
// * `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.
|
||||
IncomingResponse.prototype._onHeaders = function _onHeaders(headers) {
|
||||
// * A single ":status" header field is defined that carries the HTTP status code field. This
|
||||
// header field MUST be included in all responses.
|
||||
// * A client MUST treat the absence of the ":status" header field, the presence of multiple
|
||||
// values, or an invalid value as a stream error of type PROTOCOL_ERROR.
|
||||
// Note: currently, we do not enforce it strictly: we accept any format, and parse it as int
|
||||
// * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1
|
||||
// status line.
|
||||
this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status']));
|
||||
|
||||
// * Handling regular headers.
|
||||
IncomingMessage.prototype._onHeaders.call(this, headers);
|
||||
|
||||
// * Signaling that the headers arrived.
|
||||
this._log.info({ status: this.statusCode, headers: this.headers}, 'Incoming response');
|
||||
this.emit('ready');
|
||||
};
|
||||
|
||||
// IncomingPromise class
|
||||
// -------------------------
|
||||
|
||||
function IncomingPromise(responseStream, promiseHeaders) {
|
||||
var stream = new Readable();
|
||||
stream._read = noop;
|
||||
stream.push(null);
|
||||
stream._log = responseStream._log;
|
||||
|
||||
IncomingRequest.call(this, stream);
|
||||
|
||||
this._onHeaders(promiseHeaders);
|
||||
|
||||
this._responseStream = responseStream;
|
||||
|
||||
var response = new IncomingResponse(this._responseStream);
|
||||
response.once('ready', this.emit.bind(this, 'response', response));
|
||||
|
||||
this.stream.on('promise', this._onPromise.bind(this));
|
||||
}
|
||||
IncomingPromise.prototype = Object.create(IncomingRequest.prototype, { constructor: { value: IncomingPromise } });
|
||||
|
||||
IncomingPromise.prototype.cancel = function cancel() {
|
||||
this._responseStream.reset('CANCEL');
|
||||
};
|
||||
|
||||
IncomingPromise.prototype.setPriority = function setPriority(priority) {
|
||||
this._responseStream.priority(priority);
|
||||
};
|
||||
|
||||
IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise;
|
53
testing/xpcshell/node-http2/lib/index.js
Normal file
53
testing/xpcshell/node-http2/lib/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
// [node-http2][homepage] is an [HTTP/2 (draft 06)][http2] implementation for [node.js][node].
|
||||
//
|
||||
// The core of the protocol is implemented by the [http2-protocol] module. This module provides
|
||||
// two important features on top of http2-protocol:
|
||||
//
|
||||
// * Implementation of different negotiation schemes that can be used to start a HTTP2 connection.
|
||||
// These include TLS ALPN, Upgrade and Plain TCP.
|
||||
//
|
||||
// * Providing an API very similar to the standard node.js [HTTPS module API][node-https]
|
||||
// (which is in turn very similar to the [HTTP module API][node-http]).
|
||||
//
|
||||
// [homepage]: https://github.com/molnarg/node-http2
|
||||
// [http2-protocol]: https://github.com/molnarg/node-http2-protocol
|
||||
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
|
||||
// [node]: http://nodejs.org/
|
||||
// [node-https]: http://nodejs.org/api/https.html
|
||||
// [node-http]: http://nodejs.org/api/http.html
|
||||
|
||||
module.exports = require('./http');
|
||||
|
||||
/*
|
||||
HTTP API
|
||||
|
||||
| ^
|
||||
| |
|
||||
+-------------|------------|------------------------------------------------------+
|
||||
| | | Server/Agent |
|
||||
| v | |
|
||||
| +----------+ +----------+ |
|
||||
| | Outgoing | | Incoming | |
|
||||
| | req/res. | | req/res. | |
|
||||
| +----------+ +----------+ |
|
||||
| | ^ |
|
||||
| | | |
|
||||
| +---------|------------|-------------------------------------+ +----- |
|
||||
| | | | Endpoint | | |
|
||||
| | | | | | |
|
||||
| | v | | | |
|
||||
| | +-----------------------+ +-------------------- | | |
|
||||
| | | Stream | | Stream ... | | |
|
||||
| | +-----------------------+ +-------------------- | | |
|
||||
| | | | |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| | | |
|
||||
| | | |
|
||||
| v | |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| | TCP stream | | ... |
|
||||
| +------------------------------------------------------------+ +----- |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------+
|
||||
|
||||
*/
|
137
testing/xpcshell/node-http2/node_modules/http2-protocol/HISTORY.md
generated
vendored
Normal file
137
testing/xpcshell/node-http2/node_modules/http2-protocol/HISTORY.md
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
Version history
|
||||
===============
|
||||
|
||||
### 0.7.0 (2013-11-10) ###
|
||||
|
||||
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-07][draft-07]
|
||||
* [Tarball](https://github.com/molnarg/node-http2-protocol/archive/node-http2-protocol-0.7.0.tar.gz)
|
||||
|
||||
[draft-07]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-07
|
||||
|
||||
### 0.6.0 (2013-11-09) ###
|
||||
|
||||
* Splitting out node-http2-protocol from node-http2
|
||||
* The only exported class is `Endpoint`
|
||||
* Versioning will be based on the implemented protocol version with a '0.' prefix
|
||||
* [Tarball](https://github.com/molnarg/node-http2-protocol/archive/node-http2-protocol-0.6.0.tar.gz)
|
||||
|
||||
Version history as part of the [node-http](https://github.com/molnarg/node-http2) module
|
||||
----------------------------------------------------------------------------------------
|
||||
|
||||
### 1.0.1 (2013-10-14) ###
|
||||
|
||||
* Support for ALPN if node supports it (currently needs a custom build)
|
||||
* Fix for a few small issues
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-1.0.1.tar.gz)
|
||||
|
||||
### 1.0.0 (2013-09-23) ###
|
||||
|
||||
* Exporting Endpoint class
|
||||
* Support for 'filters' in Endpoint
|
||||
* The last time-based release
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-1.0.0.tar.gz)
|
||||
|
||||
### 0.4.1 (2013-09-15) ###
|
||||
|
||||
* Major performance improvements
|
||||
* Minor improvements to error handling
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/15/gsoc-week-number-13/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.4.1.tar.gz)
|
||||
|
||||
### 0.4.0 (2013-09-09) ###
|
||||
|
||||
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-06][draft-06]
|
||||
* Support for HTTP trailers
|
||||
* Support for TLS SNI (Server Name Indication)
|
||||
* Improved stream scheduling algorithm
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/09/gsoc-week-number-12/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.4.0.tar.gz)
|
||||
|
||||
[draft-06]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
|
||||
|
||||
### 0.3.1 (2013-09-03) ###
|
||||
|
||||
* Lot of testing, bugfixes
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/09/03/gsoc-week-number-11/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.3.1.tar.gz)
|
||||
|
||||
### 0.3.0 (2013-08-27) ###
|
||||
|
||||
* Support for prioritization
|
||||
* Small API compatibility improvements (compatibility with the standard node.js HTTP API)
|
||||
* Minor push API change
|
||||
* Ability to pass an external bunyan logger when creating a Server or Agent
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/27/gsoc-week-number-10/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.3.0.tar.gz)
|
||||
|
||||
### 0.2.1 (2013-08-20) ###
|
||||
|
||||
* Fixing a flow control bug
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.2.1.tar.gz)
|
||||
|
||||
### 0.2.0 (2013-08-19) ###
|
||||
|
||||
* Exposing server push in the public API
|
||||
* Connection pooling when operating as client
|
||||
* Much better API compatibility with the standard node.js HTTPS module
|
||||
* Logging improvements
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/19/gsoc-week-number-9/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.2.0.tar.gz)
|
||||
|
||||
### 0.1.1 (2013-08-12) ###
|
||||
|
||||
* Lots of bugfixes
|
||||
* Proper flow control for outgoing frames
|
||||
* Basic flow control for incoming frames
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/12/gsoc-week-number-8/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.1.1.tar.gz)
|
||||
|
||||
### 0.1.0 (2013-08-06) ###
|
||||
|
||||
* First release with public API (similar to the standard node HTTPS module)
|
||||
* Support for NPN negotiation (no ALPN or Upgrade yet)
|
||||
* Stream number limitation is in place
|
||||
* Push streams works but not exposed yet in the public API
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/08/05/gsoc-week-number-6-and-number-7/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.1.0.tar.gz)
|
||||
|
||||
### 0.0.6 (2013-07-19) ###
|
||||
|
||||
* `Connection` and `Endpoint` classes are usable, but not yet ready
|
||||
* Addition of an exmaple server and client
|
||||
* Using [istanbul](https://github.com/gotwarlost/istanbul) for measuring code coverage
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/19/gsoc-week-number-5/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.6.tar.gz)
|
||||
|
||||
### 0.0.5 (2013-07-14) ###
|
||||
|
||||
* `Stream` class is done
|
||||
* Public API stubs are in place
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/14/gsoc-week-number-4/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.5.tar.gz)
|
||||
|
||||
### 0.0.4 (2013-07-08) ###
|
||||
|
||||
* Added logging
|
||||
* Started `Stream` class implementation
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/08/gsoc-week-number-3/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.4.tar.gz)
|
||||
|
||||
### 0.0.3 (2013-07-03) ###
|
||||
|
||||
* Header compression is ready
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/03/the-http-slash-2-header-compression-implementation-of-node-http2/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.3.tar.gz)
|
||||
|
||||
### 0.0.2 (2013-07-01) ###
|
||||
|
||||
* Frame serialization and deserialization ready and updated to match the newest spec
|
||||
* Header compression implementation started
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/07/01/gsoc-week-number-2/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.2.tar.gz)
|
||||
|
||||
### 0.0.1 (2013-06-23) ###
|
||||
|
||||
* Frame serialization and deserialization largely done
|
||||
* [Blog post](http://gabor.molnar.es/blog/2013/06/23/gsoc-week-number-1/)
|
||||
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.1.tar.gz)
|
23
testing/xpcshell/node-http2/node_modules/http2-protocol/LICENSE
generated
vendored
Normal file
23
testing/xpcshell/node-http2/node_modules/http2-protocol/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
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.
|
||||
|
84
testing/xpcshell/node-http2/node_modules/http2-protocol/README.md
generated
vendored
Normal file
84
testing/xpcshell/node-http2/node_modules/http2-protocol/README.md
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
node-http2-protocol
|
||||
===================
|
||||
|
||||
An HTTP/2 ([draft-ietf-httpbis-http2-07](http://tools.ietf.org/html/draft-ietf-httpbis-http2-07))
|
||||
framing layer implementaion for node.js.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```
|
||||
npm install http2-protocol
|
||||
```
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
### Development dependencies ###
|
||||
|
||||
There's a few library you will need to have installed to do anything described in the following
|
||||
sections. After installing/cloning node-http2, run `npm install` in its directory to install
|
||||
development dependencies.
|
||||
|
||||
Used libraries:
|
||||
|
||||
* [mocha](http://visionmedia.github.io/mocha/) for tests
|
||||
* [chai](http://chaijs.com/) for assertions
|
||||
* [istanbul](https://github.com/gotwarlost/istanbul) for code coverage analysis
|
||||
* [docco](http://jashkenas.github.io/docco/) for developer documentation
|
||||
* [bunyan](https://github.com/trentm/node-bunyan) for logging
|
||||
|
||||
For pretty printing logs, you will also need a global install of bunyan (`npm install -g bunyan`).
|
||||
|
||||
### Developer documentation ###
|
||||
|
||||
The developer documentation is located in the `doc` directory. The docs are usually updated only
|
||||
before releasing a new version. To regenerate them manually, run `npm run-script prepublish`.
|
||||
There's a hosted version which is located [here](http://molnarg.github.io/node-http2/doc/).
|
||||
|
||||
### Running the tests ###
|
||||
|
||||
It's easy, just run `npm test`. The tests are written in BDD style, so they are a good starting
|
||||
point to understand the code.
|
||||
|
||||
### Test coverage ###
|
||||
|
||||
To generate a code coverage report, run `npm test --coverage` (it may be slow, be patient).
|
||||
Code coverage summary as of version 0.6.0:
|
||||
```
|
||||
Statements : 92.39% ( 1165/1261 )
|
||||
Branches : 86.57% ( 477/551 )
|
||||
Functions : 91.22% ( 135/148 )
|
||||
Lines : 92.35% ( 1159/1255 )
|
||||
```
|
||||
|
||||
There's a hosted version of the detailed (line-by-line) coverage report
|
||||
[here](http://molnarg.github.io/node-http2-protocol/coverage/lcov-report/lib/).
|
||||
|
||||
### Logging ###
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
Code contributions are always welcome! People who contributed to node-http2 so far:
|
||||
|
||||
* Nick Hurley
|
||||
* Mike Belshe
|
||||
|
||||
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
|
||||
Nick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
|
2364
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/compressor.html
generated
vendored
Normal file
2364
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/compressor.html
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1622
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/connection.html
generated
vendored
Normal file
1622
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/connection.html
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
511
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/docco.css
generated
vendored
Normal file
511
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/docco.css
generated
vendored
Normal file
@ -0,0 +1,511 @@
|
||||
/*-------------- node-http2 customizations --------------------*/
|
||||
|
||||
ul.sections > li > div.annotation {
|
||||
min-width: 35em !important;
|
||||
max-width: 35em !important;
|
||||
}
|
||||
|
||||
#background {
|
||||
width: 35em !important;
|
||||
}
|
||||
|
||||
/*--------------------- Typography ----------------------------*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'aller-light';
|
||||
src: url('public/fonts/aller-light.eot');
|
||||
src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/aller-light.woff') format('woff'),
|
||||
url('public/fonts/aller-light.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'aller-bold';
|
||||
src: url('public/fonts/aller-bold.eot');
|
||||
src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/aller-bold.woff') format('woff'),
|
||||
url('public/fonts/aller-bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'novecento-bold';
|
||||
src: url('public/fonts/novecento-bold.eot');
|
||||
src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('public/fonts/novecento-bold.woff') format('woff'),
|
||||
url('public/fonts/novecento-bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/*--------------------- Layout ----------------------------*/
|
||||
html { height: 100%; }
|
||||
body {
|
||||
font-family: "aller-light";
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #30404f;
|
||||
margin: 0; padding: 0;
|
||||
height:100%;
|
||||
}
|
||||
#container { min-height: 100%; }
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: normal;
|
||||
font-family: "aller-bold";
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
margin: 15px 0 0px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #112233;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
font-family: "novecento-bold";
|
||||
text-transform: uppercase;
|
||||
margin: 30px 0 15px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
background: 1px solid #ddd;
|
||||
height: 1px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
pre, tt, code {
|
||||
font-size: 12px; line-height: 16px;
|
||||
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
.annotation pre {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 7px 10px;
|
||||
background: #fcfcfc;
|
||||
-moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
-webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.annotation pre code {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
border-left: 5px solid #ccc;
|
||||
margin: 0;
|
||||
padding: 1px 0 1px 1em;
|
||||
}
|
||||
.sections blockquote p {
|
||||
font-family: Menlo, Consolas, Monaco, monospace;
|
||||
font-size: 12px; line-height: 16px;
|
||||
color: #999;
|
||||
margin: 10px 0 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
ul.sections {
|
||||
list-style: none;
|
||||
padding:0 0 5px 0;;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
ul.sections > li > div {
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
-ms-box-sizing: border-box; /* ie */
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-khtml-box-sizing: border-box; /* konqueror */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
|
||||
/*---------------------- Jump Page -----------------------------*/
|
||||
#jump_to, #jump_page {
|
||||
margin: 0;
|
||||
background: white;
|
||||
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
|
||||
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
|
||||
font: 16px Arial;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#jump_to a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#jump_to a.large {
|
||||
display: none;
|
||||
}
|
||||
#jump_to a.small {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #676767;
|
||||
}
|
||||
|
||||
#jump_to, #jump_wrapper {
|
||||
position: fixed;
|
||||
right: 0; top: 0;
|
||||
padding: 10px 15px;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#jump_wrapper {
|
||||
display: none;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#jump_to:hover #jump_wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#jump_page {
|
||||
padding: 5px 0 3px;
|
||||
margin: 0 0 25px 25px;
|
||||
}
|
||||
|
||||
#jump_page .source {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
text-decoration: none;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#jump_page .source:hover {
|
||||
background: #f5f5ff;
|
||||
}
|
||||
|
||||
#jump_page .source:first-child {
|
||||
}
|
||||
|
||||
/*---------------------- Low resolutions (> 320px) ---------------------*/
|
||||
@media only screen and (min-width: 320px) {
|
||||
.pilwrap { display: none; }
|
||||
|
||||
ul.sections > li > div {
|
||||
display: block;
|
||||
padding:5px 10px 0 10px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.content {
|
||||
background: #f5f5ff;
|
||||
overflow-x:auto;
|
||||
-webkit-box-shadow: inset 0 0 5px #e5e5ee;
|
||||
box-shadow: inset 0 0 5px #e5e5ee;
|
||||
border: 1px solid #dedede;
|
||||
margin:5px 10px 5px 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation pre {
|
||||
margin: 7px 0 7px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation p tt, .annotation code {
|
||||
background: #f8f8ff;
|
||||
border: 1px solid #dedede;
|
||||
font-size: 12px;
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- (> 481px) ---------------------*/
|
||||
@media only screen and (min-width: 481px) {
|
||||
#container {
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
background-color: #F5F5FF;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
}
|
||||
pre, tt, code {
|
||||
line-height: 18px;
|
||||
}
|
||||
p, ul, ol {
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
|
||||
#jump_to {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#jump_wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
#jump_to, #jump_page {
|
||||
font: 10px Arial;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
#jump_page .source {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#jump_to a.large {
|
||||
display: inline-block;
|
||||
}
|
||||
#jump_to a.small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
width: 350px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #e5e5ee;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
ul.sections > li {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
ul.sections > li > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation {
|
||||
max-width: 350px;
|
||||
min-width: 350px;
|
||||
min-height: 5px;
|
||||
padding: 13px;
|
||||
overflow-x: hidden;
|
||||
white-space: normal;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
ul.sections > li > div.annotation pre {
|
||||
margin: 15px 0 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.content {
|
||||
padding: 13px;
|
||||
vertical-align: top;
|
||||
background: #f5f5ff;
|
||||
border: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.pilwrap {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.pilcrow {
|
||||
font: 12px Arial;
|
||||
text-decoration: none;
|
||||
color: #454545;
|
||||
position: absolute;
|
||||
top: 3px; left: -20px;
|
||||
padding: 1px 2px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
}
|
||||
.for-h1 .pilcrow {
|
||||
top: 47px;
|
||||
}
|
||||
.for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow {
|
||||
top: 35px;
|
||||
}
|
||||
|
||||
ul.sections > li > div.annotation:hover .pilcrow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- (> 1025px) ---------------------*/
|
||||
@media only screen and (min-width: 1025px) {
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
#background {
|
||||
width: 525px;
|
||||
}
|
||||
ul.sections > li > div.annotation {
|
||||
max-width: 525px;
|
||||
min-width: 525px;
|
||||
padding: 10px 25px 1px 50px;
|
||||
}
|
||||
ul.sections > li > div.content {
|
||||
padding: 9px 15px 16px 25px;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------- Syntax Highlighting -----------------------------*/
|
||||
|
||||
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
|
||||
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
|
||||
/*
|
||||
|
||||
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
|
||||
*/
|
||||
|
||||
pre code {
|
||||
display: block; padding: 0.5em;
|
||||
color: #000;
|
||||
background: #f8f8ff
|
||||
}
|
||||
|
||||
pre .comment,
|
||||
pre .template_comment,
|
||||
pre .diff .header,
|
||||
pre .javadoc {
|
||||
color: #408080;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
pre .keyword,
|
||||
pre .assignment,
|
||||
pre .literal,
|
||||
pre .css .rule .keyword,
|
||||
pre .winutils,
|
||||
pre .javascript .title,
|
||||
pre .lisp .title,
|
||||
pre .subst {
|
||||
color: #954121;
|
||||
/*font-weight: bold*/
|
||||
}
|
||||
|
||||
pre .number,
|
||||
pre .hexcolor {
|
||||
color: #40a070
|
||||
}
|
||||
|
||||
pre .string,
|
||||
pre .tag .value,
|
||||
pre .phpdoc,
|
||||
pre .tex .formula {
|
||||
color: #219161;
|
||||
}
|
||||
|
||||
pre .title,
|
||||
pre .id {
|
||||
color: #19469D;
|
||||
}
|
||||
pre .params {
|
||||
color: #00F;
|
||||
}
|
||||
|
||||
pre .javascript .title,
|
||||
pre .lisp .title,
|
||||
pre .subst {
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
pre .class .title,
|
||||
pre .haskell .label,
|
||||
pre .tex .command {
|
||||
color: #458;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .tag,
|
||||
pre .tag .title,
|
||||
pre .rules .property,
|
||||
pre .django .tag .keyword {
|
||||
color: #000080;
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
pre .attribute,
|
||||
pre .variable,
|
||||
pre .instancevar,
|
||||
pre .lisp .body {
|
||||
color: #008080
|
||||
}
|
||||
|
||||
pre .regexp {
|
||||
color: #B68
|
||||
}
|
||||
|
||||
pre .class {
|
||||
color: #458;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .symbol,
|
||||
pre .ruby .symbol .string,
|
||||
pre .ruby .symbol .keyword,
|
||||
pre .ruby .symbol .keymethods,
|
||||
pre .lisp .keyword,
|
||||
pre .tex .special,
|
||||
pre .input_number {
|
||||
color: #990073
|
||||
}
|
||||
|
||||
pre .builtin,
|
||||
pre .constructor,
|
||||
pre .built_in,
|
||||
pre .lisp .title {
|
||||
color: #0086b3
|
||||
}
|
||||
|
||||
pre .preprocessor,
|
||||
pre .pi,
|
||||
pre .doctype,
|
||||
pre .shebang,
|
||||
pre .cdata {
|
||||
color: #999;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre .deletion {
|
||||
background: #fdd
|
||||
}
|
||||
|
||||
pre .addition {
|
||||
background: #dfd
|
||||
}
|
||||
|
||||
pre .diff .change {
|
||||
background: #0086b3
|
||||
}
|
||||
|
||||
pre .chunk {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
pre .tex .formula {
|
||||
opacity: 0.5;
|
||||
}
|
658
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/endpoint.html
generated
vendored
Normal file
658
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/endpoint.html
generated
vendored
Normal file
@ -0,0 +1,658 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>endpoint.js</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
||||
<link rel="stylesheet" media="all" href="docco.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="background"></div>
|
||||
|
||||
<ul id="jump_to">
|
||||
<li>
|
||||
<a class="large" href="javascript:void(0);">Jump To …</a>
|
||||
<a class="small" href="javascript:void(0);">+</a>
|
||||
<div id="jump_wrapper">
|
||||
<div id="jump_page">
|
||||
|
||||
|
||||
<a class="source" href="compressor.html">
|
||||
compressor.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="connection.html">
|
||||
connection.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="endpoint.html">
|
||||
endpoint.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="flow.html">
|
||||
flow.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="framer.html">
|
||||
framer.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="index.html">
|
||||
index.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="stream.html">
|
||||
stream.js
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="sections">
|
||||
|
||||
<li id="title">
|
||||
<div class="annotation">
|
||||
<h1>endpoint.js</h1>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li id="section-1">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-1">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> assert = require(<span class="string">'assert'</span>);
|
||||
|
||||
<span class="keyword">var</span> Serializer = require(<span class="string">'./framer'</span>).Serializer;
|
||||
<span class="keyword">var</span> Deserializer = require(<span class="string">'./framer'</span>).Deserializer;
|
||||
<span class="keyword">var</span> Compressor = require(<span class="string">'./compressor'</span>).Compressor;
|
||||
<span class="keyword">var</span> Decompressor = require(<span class="string">'./compressor'</span>).Decompressor;
|
||||
<span class="keyword">var</span> Connection = require(<span class="string">'./connection'</span>).Connection;
|
||||
<span class="keyword">var</span> Duplex = require(<span class="string">'stream'</span>).Duplex;
|
||||
<span class="keyword">var</span> Transform = require(<span class="string">'stream'</span>).Transform;
|
||||
|
||||
exports.Endpoint = Endpoint;</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-2">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-2">¶</a>
|
||||
</div>
|
||||
<h1 id="the-endpoint-class">The Endpoint class</h1>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-3">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-3">¶</a>
|
||||
</div>
|
||||
<h2 id="public-api">Public API</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-4">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-4">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li><p><strong>new Endpoint(log, role, settings, filters)</strong>: create a new Endpoint.</p>
|
||||
<ul>
|
||||
<li><code>log</code>: bunyan logger of the parent</li>
|
||||
<li><code>role</code>: 'CLIENT' or 'SERVER'</li>
|
||||
<li><code>settings</code>: initial HTTP/2 settings</li>
|
||||
<li><p><code>filters</code>: a map of functions that filter the traffic between components (for debugging or
|
||||
intentional failure injection).</p>
|
||||
<p>Filter functions get three arguments:</p>
|
||||
<ol>
|
||||
<li><code>frame</code>: the current frame</li>
|
||||
<li><code>forward(frame)</code>: function that can be used to forward a frame to the next component</li>
|
||||
<li><code>done()</code>: callback to signal the end of the filter process</li>
|
||||
</ol>
|
||||
<p>Valid filter names and their position in the stack:</p>
|
||||
<ul>
|
||||
<li><code>beforeSerialization</code>: after compression, before serialization</li>
|
||||
<li><code>beforeCompression</code>: after multiplexing, before compression</li>
|
||||
<li><code>afterDeserialization</code>: after deserialization, before decompression</li>
|
||||
<li><code>afterDecompression</code>: after decompression, before multiplexing</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p><strong>Event: 'stream' (Stream)</strong>: 'stream' event forwarded from the underlying Connection</p>
|
||||
</li>
|
||||
<li><p><strong>Event: 'error' (type)</strong>: signals an error</p>
|
||||
</li>
|
||||
<li><p><strong>createStream(): Stream</strong>: initiate a new stream (forwarded to the underlying Connection)</p>
|
||||
</li>
|
||||
<li><p><strong>close([error])</strong>: close the connection with an error code</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="constructor">Constructor</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-5">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-5">¶</a>
|
||||
</div>
|
||||
<p>The process of initialization:</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="function"><span class="keyword">function</span> <span class="title">Endpoint</span><span class="params">(log, role, settings, filters)</span> {</span>
|
||||
Duplex.call(<span class="keyword">this</span>);</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-6">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-6">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Initializing logging infrastructure</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>._log = log.child({ component: <span class="string">'endpoint'</span>, e: <span class="keyword">this</span> });</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-7">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-7">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>First part of the handshake process: sending and receiving the client connection header
|
||||
prelude.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> assert((role === <span class="string">'CLIENT'</span>) || role === <span class="string">'SERVER'</span>);
|
||||
<span class="keyword">if</span> (role === <span class="string">'CLIENT'</span>) {
|
||||
<span class="keyword">this</span>._writePrelude();
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._readPrelude();
|
||||
}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-8">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-8">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Initialization of component. This includes the second part of the handshake process:
|
||||
sending the first SETTINGS frame. This is done by the connection class right after
|
||||
initialization.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>._initializeDataFlow(role, settings, filters || {});</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-9">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-9">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Initialization of management code.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>._initializeManagement();</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-10">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-10">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Initializing error handling.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>._initializeErrorHandling();
|
||||
}
|
||||
Endpoint.prototype = Object.create(Duplex.prototype, { constructor: { value: Endpoint } });</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-11">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-11">¶</a>
|
||||
</div>
|
||||
<h2 id="handshake">Handshake</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-12">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-12">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> CLIENT_PRELUDE = <span class="keyword">new</span> Buffer(<span class="string">'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'</span>);</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-13">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-13">¶</a>
|
||||
</div>
|
||||
<p>Writing the client header is simple and synchronous.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Endpoint.prototype._writePrelude = <span class="function"><span class="keyword">function</span> <span class="title">_writePrelude</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._log.debug(<span class="string">'Sending the client connection header prelude.'</span>);
|
||||
<span class="keyword">this</span>.push(CLIENT_PRELUDE);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-14">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-14">¶</a>
|
||||
</div>
|
||||
<p>The asynchronous process of reading the client header:</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Endpoint.prototype._readPrelude = <span class="function"><span class="keyword">function</span> <span class="title">_readPrelude</span><span class="params">()</span> {</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-15">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-15">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>progress in the header is tracker using a <code>cursor</code></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> cursor = <span class="number">0</span>;</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-16">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-16">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li><code>_write</code> is temporarily replaced by the comparator function</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>._write = <span class="function"><span class="keyword">function</span> <span class="title">_temporalWrite</span><span class="params">(chunk, encoding, done)</span> {</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-17">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-17">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>which compares the stored header with the current <code>chunk</code> byte by byte and emits the
|
||||
'error' event if there's a byte that doesn't match</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> offset = cursor;
|
||||
<span class="keyword">while</span>(cursor < CLIENT_PRELUDE.length && (cursor - offset) < chunk.length) {
|
||||
<span class="keyword">if</span> (CLIENT_PRELUDE[cursor] !== chunk[cursor - offset]) {
|
||||
<span class="keyword">this</span>._log.fatal({ cursor: cursor, offset: offset, chunk: chunk },
|
||||
<span class="string">'Client connection header prelude does not match.'</span>);
|
||||
<span class="keyword">this</span>._error(<span class="string">'handshake'</span>, <span class="string">'PROTOCOL_ERROR'</span>);
|
||||
<span class="keyword">return</span>;
|
||||
}
|
||||
cursor += <span class="number">1</span>;
|
||||
}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-18">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-18">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>if the whole header is over, and there were no error then restore the original <code>_write</code>
|
||||
and call it with the remaining part of the current chunk</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (cursor === CLIENT_PRELUDE.length) {
|
||||
<span class="keyword">this</span>._log.debug(<span class="string">'Successfully received the client connection header prelude.'</span>);
|
||||
<span class="keyword">delete</span> <span class="keyword">this</span>._write;
|
||||
chunk = chunk.slice(cursor - offset);
|
||||
<span class="keyword">this</span>._write(chunk, encoding, done);
|
||||
}
|
||||
};
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-19">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-19">¶</a>
|
||||
</div>
|
||||
<h2 id="data-flow">Data flow</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-20">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-20">¶</a>
|
||||
</div>
|
||||
<pre><code>+---------------------------------------------+
|
||||
| |
|
||||
| +-------------------------------------+ |
|
||||
| | +---------+ +---------+ +---------+ | |
|
||||
| | | stream1 | | stream2 | | ... | | |
|
||||
| | +---------+ +---------+ +---------+ | |
|
||||
| | connection | |
|
||||
| +-------------------------------------+ |
|
||||
| | ^ |
|
||||
| pipe | | pipe |
|
||||
| v | |
|
||||
| +------------------+------------------+ |
|
||||
| | compressor | decompressor | |
|
||||
| +------------------+------------------+ |
|
||||
| | ^ |
|
||||
| pipe | | pipe |
|
||||
| v | |
|
||||
| +------------------+------------------+ |
|
||||
| | serializer | deserializer | |
|
||||
| +------------------+------------------+ |
|
||||
| | ^ |
|
||||
| _read() | | _write() |
|
||||
| v | |
|
||||
| +------------+ +-----------+ |
|
||||
| |output queue| |input queue| |
|
||||
+------+------------+-----+-----------+-------+
|
||||
| ^
|
||||
read() | | write()
|
||||
v |</code></pre>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="function"><span class="keyword">function</span> <span class="title">createTransformStream</span><span class="params">(filter)</span> {</span>
|
||||
<span class="keyword">var</span> transform = <span class="keyword">new</span> Transform({ objectMode: <span class="literal">true</span> });
|
||||
<span class="keyword">var</span> push = transform.push.bind(transform);
|
||||
transform._transform = <span class="function"><span class="keyword">function</span><span class="params">(frame, encoding, done)</span> {</span>
|
||||
filter(frame, push, done);
|
||||
};
|
||||
<span class="keyword">return</span> transform;
|
||||
}
|
||||
|
||||
<span class="function"><span class="keyword">function</span> <span class="title">pipeAndFilter</span><span class="params">(stream1, stream2, filter)</span> {</span>
|
||||
<span class="keyword">if</span> (filter) {
|
||||
stream1.pipe(createTransformStream(filter)).pipe(stream2);
|
||||
} <span class="keyword">else</span> {
|
||||
stream1.pipe(stream2);
|
||||
}
|
||||
}
|
||||
|
||||
<span class="keyword">var</span> MAX_HTTP_PAYLOAD_SIZE = <span class="number">16383</span>;
|
||||
|
||||
Endpoint.prototype._initializeDataFlow = <span class="function"><span class="keyword">function</span> <span class="title">_initializeDataFlow</span><span class="params">(role, settings, filters)</span> {</span>
|
||||
<span class="keyword">var</span> firstStreamId, compressorRole, decompressorRole;
|
||||
<span class="keyword">if</span> (role === <span class="string">'CLIENT'</span>) {
|
||||
firstStreamId = <span class="number">1</span>;
|
||||
compressorRole = <span class="string">'REQUEST'</span>;
|
||||
decompressorRole = <span class="string">'RESPONSE'</span>;
|
||||
} <span class="keyword">else</span> {
|
||||
firstStreamId = <span class="number">2</span>;
|
||||
compressorRole = <span class="string">'RESPONSE'</span>;
|
||||
decompressorRole = <span class="string">'REQUEST'</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">this</span>._serializer = <span class="keyword">new</span> Serializer(<span class="keyword">this</span>._log, MAX_HTTP_PAYLOAD_SIZE);
|
||||
<span class="keyword">this</span>._deserializer = <span class="keyword">new</span> Deserializer(<span class="keyword">this</span>._log, MAX_HTTP_PAYLOAD_SIZE);
|
||||
<span class="keyword">this</span>._compressor = <span class="keyword">new</span> Compressor(<span class="keyword">this</span>._log, compressorRole);
|
||||
<span class="keyword">this</span>._decompressor = <span class="keyword">new</span> Decompressor(<span class="keyword">this</span>._log, decompressorRole);
|
||||
<span class="keyword">this</span>._connection = <span class="keyword">new</span> Connection(<span class="keyword">this</span>._log, firstStreamId, settings);
|
||||
|
||||
pipeAndFilter(<span class="keyword">this</span>._connection, <span class="keyword">this</span>._compressor, filters.beforeCompression);
|
||||
pipeAndFilter(<span class="keyword">this</span>._compressor, <span class="keyword">this</span>._serializer, filters.beforeSerialization);
|
||||
pipeAndFilter(<span class="keyword">this</span>._deserializer, <span class="keyword">this</span>._decompressor, filters.afterDeserialization);
|
||||
pipeAndFilter(<span class="keyword">this</span>._decompressor, <span class="keyword">this</span>._connection, filters.afterDecompression);
|
||||
|
||||
<span class="keyword">this</span>._connection.on(<span class="string">'ACKNOWLEDGED_SETTINGS_HEADER_TABLE_SIZE'</span>,
|
||||
<span class="keyword">this</span>._decompressor.setTableSizeLimit.bind(<span class="keyword">this</span>._decompressor))
|
||||
<span class="keyword">this</span>._connection.on(<span class="string">'RECEIVING_SETTINGS_HEADER_TABLE_SIZE'</span>,
|
||||
<span class="keyword">this</span>._compressor.setTableSizeLimit.bind(<span class="keyword">this</span>._compressor))
|
||||
};
|
||||
|
||||
<span class="keyword">var</span> noread = {};
|
||||
Endpoint.prototype._read = <span class="function"><span class="keyword">function</span> <span class="title">_read</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._readableState.sync = <span class="literal">true</span>;
|
||||
<span class="keyword">var</span> moreNeeded = noread, chunk;
|
||||
<span class="keyword">while</span> (moreNeeded && (chunk = <span class="keyword">this</span>._serializer.read())) {
|
||||
moreNeeded = <span class="keyword">this</span>.push(chunk);
|
||||
}
|
||||
<span class="keyword">if</span> (moreNeeded === noread) {
|
||||
<span class="keyword">this</span>._serializer.once(<span class="string">'readable'</span>, <span class="keyword">this</span>._read.bind(<span class="keyword">this</span>));
|
||||
}
|
||||
<span class="keyword">this</span>._readableState.sync = <span class="literal">false</span>;
|
||||
};
|
||||
|
||||
Endpoint.prototype._write = <span class="function"><span class="keyword">function</span> <span class="title">_write</span><span class="params">(chunk, encoding, done)</span> {</span>
|
||||
<span class="keyword">this</span>._deserializer.write(chunk, encoding, done);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-21">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-21">¶</a>
|
||||
</div>
|
||||
<h2 id="management">Management</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-22">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-22">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Endpoint.prototype._initializeManagement = <span class="function"><span class="keyword">function</span> <span class="title">_initializeManagement</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._connection.on(<span class="string">'stream'</span>, <span class="keyword">this</span>.emit.bind(<span class="keyword">this</span>, <span class="string">'stream'</span>));
|
||||
};
|
||||
|
||||
Endpoint.prototype.createStream = <span class="function"><span class="keyword">function</span> <span class="title">createStream</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">return</span> <span class="keyword">this</span>._connection.createStream();
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-23">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-23">¶</a>
|
||||
</div>
|
||||
<h2 id="error-handling">Error handling</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-24">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-24">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Endpoint.prototype._initializeErrorHandling = <span class="function"><span class="keyword">function</span> <span class="title">_initializeErrorHandling</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._serializer.on(<span class="string">'error'</span>, <span class="keyword">this</span>._error.bind(<span class="keyword">this</span>, <span class="string">'serializer'</span>));
|
||||
<span class="keyword">this</span>._deserializer.on(<span class="string">'error'</span>, <span class="keyword">this</span>._error.bind(<span class="keyword">this</span>, <span class="string">'deserializer'</span>));
|
||||
<span class="keyword">this</span>._compressor.on(<span class="string">'error'</span>, <span class="keyword">this</span>._error.bind(<span class="keyword">this</span>, <span class="string">'compressor'</span>));
|
||||
<span class="keyword">this</span>._decompressor.on(<span class="string">'error'</span>, <span class="keyword">this</span>._error.bind(<span class="keyword">this</span>, <span class="string">'decompressor'</span>));
|
||||
<span class="keyword">this</span>._connection.on(<span class="string">'error'</span>, <span class="keyword">this</span>._error.bind(<span class="keyword">this</span>, <span class="string">'connection'</span>));
|
||||
|
||||
<span class="keyword">this</span>._connection.on(<span class="string">'peerError'</span>, <span class="keyword">this</span>.emit.bind(<span class="keyword">this</span>, <span class="string">'peerError'</span>));
|
||||
};
|
||||
|
||||
Endpoint.prototype._error = <span class="function"><span class="keyword">function</span> <span class="title">_error</span><span class="params">(component, error)</span> {</span>
|
||||
<span class="keyword">this</span>._log.fatal({ source: component, message: error }, <span class="string">'Fatal error, closing connection'</span>);
|
||||
<span class="keyword">this</span>.close(error);
|
||||
setImmediate(<span class="keyword">this</span>.emit.bind(<span class="keyword">this</span>, <span class="string">'error'</span>, error));
|
||||
};
|
||||
|
||||
Endpoint.prototype.close = <span class="function"><span class="keyword">function</span> <span class="title">close</span><span class="params">(error)</span> {</span>
|
||||
<span class="keyword">this</span>._connection.close(error);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-25">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-25">¶</a>
|
||||
</div>
|
||||
<h2 id="bunyan-serializers">Bunyan serializers</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-26">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-26">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.serializers = {};
|
||||
|
||||
<span class="keyword">var</span> nextId = <span class="number">0</span>;
|
||||
exports.serializers.e = <span class="function"><span class="keyword">function</span><span class="params">(endpoint)</span> {</span>
|
||||
<span class="keyword">if</span> (!(<span class="string">'id'</span> <span class="keyword">in</span> endpoint)) {
|
||||
endpoint.id = nextId;
|
||||
nextId += <span class="number">1</span>;
|
||||
}
|
||||
<span class="keyword">return</span> endpoint.id;
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
787
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/flow.html
generated
vendored
Normal file
787
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/flow.html
generated
vendored
Normal file
@ -0,0 +1,787 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>flow.js</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
||||
<link rel="stylesheet" media="all" href="docco.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="background"></div>
|
||||
|
||||
<ul id="jump_to">
|
||||
<li>
|
||||
<a class="large" href="javascript:void(0);">Jump To …</a>
|
||||
<a class="small" href="javascript:void(0);">+</a>
|
||||
<div id="jump_wrapper">
|
||||
<div id="jump_page">
|
||||
|
||||
|
||||
<a class="source" href="compressor.html">
|
||||
compressor.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="connection.html">
|
||||
connection.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="endpoint.html">
|
||||
endpoint.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="flow.html">
|
||||
flow.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="framer.html">
|
||||
framer.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="index.html">
|
||||
index.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="stream.html">
|
||||
stream.js
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="sections">
|
||||
|
||||
<li id="title">
|
||||
<div class="annotation">
|
||||
<h1>flow.js</h1>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li id="section-1">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-1">¶</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> assert = require(<span class="string">'assert'</span>);</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-2">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-2">¶</a>
|
||||
</div>
|
||||
<h1 id="the-flow-class">The Flow class</h1>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-3">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-3">¶</a>
|
||||
</div>
|
||||
<p>Flow is a <a href="http://nodejs.org/api/stream.html#stream_class_stream_duplex">Duplex stream</a> subclass which implements HTTP/2 flow control. It is designed to be
|
||||
subclassed by <a href="connection.html">Connection</a> and the <code>upstream</code> component of <a href="stream.html">Stream</a>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> Duplex = require(<span class="string">'stream'</span>).Duplex;
|
||||
|
||||
exports.Flow = Flow;</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-4">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-4">¶</a>
|
||||
</div>
|
||||
<h2 id="public-api">Public API</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-5">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-5">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li><p><strong>Event: 'error' (type)</strong>: signals an error</p>
|
||||
</li>
|
||||
<li><p><strong>setInitialWindow(size)</strong>: the initial flow control window size can be changed <em>any time</em>
|
||||
(<a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2">as described in the standard</a>) using this method</p>
|
||||
</li>
|
||||
<li><p><strong>disableRemoteFlowControl()</strong>: sends a WINDOW_UPDATE signaling that we don't want flow control</p>
|
||||
</li>
|
||||
<li><p><strong>disableLocalFlowControl()</strong>: disables flow control for outgoing frames</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="api-for-child-classes">API for child classes</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-6">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-6">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li><p><strong>new Flow([flowControlId])</strong>: creating a new flow that will listen for WINDOW_UPDATES frames
|
||||
with the given <code>flowControlId</code> (or every update frame if not given)</p>
|
||||
</li>
|
||||
<li><p><strong>_send()</strong>: called when more frames should be pushed. The child class is expected to override
|
||||
this (instead of the <code>_read</code> method of the Duplex class).</p>
|
||||
</li>
|
||||
<li><p><strong>_receive(frame, readyCallback)</strong>: called when there's an incoming frame. The child class is
|
||||
expected to override this (instead of the <code>_write</code> method of the Duplex class).</p>
|
||||
</li>
|
||||
<li><p><strong>push(frame): bool</strong>: schedules <code>frame</code> for sending.</p>
|
||||
<p>Returns <code>true</code> if it needs more frames in the output queue, <code>false</code> if the output queue is
|
||||
full, and <code>null</code> if did not push the frame into the output queue (instead, it pushed it into
|
||||
the flow control queue).</p>
|
||||
</li>
|
||||
<li><p><strong>read(limit): frame</strong>: like the regular <code>read</code>, but the 'flow control size' (0 for non-DATA
|
||||
frames, length of the payload for DATA frames) of the returned frame will be under <code>limit</code>.
|
||||
Small exception: pass -1 as <code>limit</code> if the max. flow control size is 0. <code>read(0)</code> means the
|
||||
same thing as <a href="http://nodejs.org/api/stream.html#stream_stream_read_0">in the original API</a>.</p>
|
||||
</li>
|
||||
<li><p><strong>getLastQueuedFrame(): frame</strong>: returns the last frame in output buffers</p>
|
||||
</li>
|
||||
<li><p><strong>_log</strong>: the Flow class uses the <code>_log</code> object of the parent</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="constructor">Constructor</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-7">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-7">¶</a>
|
||||
</div>
|
||||
<p>When a HTTP/2.0 connection is first established, new streams are created with an initial flow
|
||||
control window size of 65535 bytes.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> INITIAL_WINDOW_SIZE = <span class="number">65535</span>;</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-8">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-8">¶</a>
|
||||
</div>
|
||||
<p><code>flowControlId</code> is needed if only specific WINDOW_UPDATEs should be watched.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="function"><span class="keyword">function</span> <span class="title">Flow</span><span class="params">(flowControlId)</span> {</span>
|
||||
Duplex.call(<span class="keyword">this</span>, { objectMode: <span class="literal">true</span> });
|
||||
|
||||
<span class="keyword">this</span>._window = <span class="keyword">this</span>._initialWindow = INITIAL_WINDOW_SIZE;
|
||||
<span class="keyword">this</span>._flowControlId = flowControlId;
|
||||
<span class="keyword">this</span>._queue = [];
|
||||
<span class="keyword">this</span>._ended = <span class="literal">false</span>;
|
||||
<span class="keyword">this</span>._received = <span class="number">0</span>;
|
||||
<span class="keyword">this</span>._remoteFlowControlDisabled = <span class="literal">false</span>;
|
||||
}
|
||||
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-9">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-9">¶</a>
|
||||
</div>
|
||||
<h2 id="incoming-frames">Incoming frames</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-10">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-10">¶</a>
|
||||
</div>
|
||||
<p><code>_receive</code> is called when there's an incoming frame.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._receive = <span class="function"><span class="keyword">function</span> <span class="title">_receive</span><span class="params">(frame, callback)</span> {</span>
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'The _receive(frame, callback) method has to be overridden by the child class!'</span>);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-11">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-11">¶</a>
|
||||
</div>
|
||||
<p><code>_receive</code> is called by <code>_write</code> which in turn is <a href="http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1">called by Duplex</a> when someone <code>write()</code>s
|
||||
to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
|
||||
incoming frame is a WINDOW_UPDATE.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._write = <span class="function"><span class="keyword">function</span> <span class="title">_write</span><span class="params">(frame, encoding, callback)</span> {</span>
|
||||
<span class="keyword">if</span> (frame.flags.END_STREAM || (frame.type === <span class="string">'RST_STREAM'</span>)) {
|
||||
<span class="keyword">this</span>._ended = <span class="literal">true</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">if</span> ((frame.type === <span class="string">'DATA'</span>) && (frame.data.length > <span class="number">0</span>) && !<span class="keyword">this</span>._remoteFlowControlDisabled) {
|
||||
<span class="keyword">this</span>._receive(frame, <span class="function"><span class="keyword">function</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._received += frame.data.length;
|
||||
<span class="keyword">if</span> (!<span class="keyword">this</span>._restoreWindowTimer) {
|
||||
<span class="keyword">this</span>._restoreWindowTimer = setImmediate(<span class="keyword">this</span>._restoreWindow.bind(<span class="keyword">this</span>));
|
||||
}
|
||||
callback();
|
||||
}.bind(<span class="keyword">this</span>));
|
||||
}
|
||||
|
||||
<span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._receive(frame, callback);
|
||||
}
|
||||
|
||||
<span class="keyword">if</span> ((frame.type === <span class="string">'WINDOW_UPDATE'</span>) &&
|
||||
((<span class="keyword">this</span>._flowControlId === <span class="literal">undefined</span>) || (frame.stream === <span class="keyword">this</span>._flowControlId))) {
|
||||
<span class="keyword">this</span>._updateWindow(frame);
|
||||
}
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-12">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-12">¶</a>
|
||||
</div>
|
||||
<p><code>_restoreWindow</code> basically acknowledges the DATA frames received since it's last call. It sends
|
||||
a WINDOW_UPDATE that restores the flow control window of the remote end.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._restoreWindow = <span class="function"><span class="keyword">function</span> <span class="title">_restoreWindow</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">delete</span> <span class="keyword">this</span>._restoreWindowTimer;
|
||||
<span class="keyword">if</span> (!<span class="keyword">this</span>._ended && !<span class="keyword">this</span>._remoteFlowControlDisabled && (<span class="keyword">this</span>._received > <span class="number">0</span>)) {
|
||||
<span class="keyword">this</span>.push({
|
||||
type: <span class="string">'WINDOW_UPDATE'</span>,
|
||||
flags: {},
|
||||
stream: <span class="keyword">this</span>._flowControlId,
|
||||
window_size: <span class="keyword">this</span>._received
|
||||
});
|
||||
<span class="keyword">this</span>._received = <span class="number">0</span>;
|
||||
}
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-13">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-13">¶</a>
|
||||
</div>
|
||||
<p>Must be called after sending a SETTINGS frame that turns off flow control on the remote side.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.disableRemoteFlowControl = <span class="function"><span class="keyword">function</span> <span class="title">disableRemoteFlowControl</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._log.debug(<span class="string">'Turning off remote flow control'</span>);
|
||||
<span class="keyword">this</span>._remoteFlowControlDisabled = <span class="literal">true</span>;
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-14">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-14">¶</a>
|
||||
</div>
|
||||
<h2 id="outgoing-frames-sending-procedure">Outgoing frames - sending procedure</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-15">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-15">¶</a>
|
||||
</div>
|
||||
<pre><code> flow
|
||||
+-------------------------------------------------+
|
||||
| |
|
||||
+--------+ +---------+ |
|
||||
read() | output | _read() | flow | _send() |
|
||||
<----------| |<----------| control |<------------- |
|
||||
| buffer | | buffer | |
|
||||
+--------+ +---------+ |
|
||||
| input | |
|
||||
---------->| |-----------------------------------> |
|
||||
write() | buffer | _write() _receive() |
|
||||
+--------+ |
|
||||
| |
|
||||
+-------------------------------------------------+</code></pre>
|
||||
<p><code>_send</code> is called when more frames should be pushed to the output buffer.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._send = <span class="function"><span class="keyword">function</span> <span class="title">_send</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'The _send() method has to be overridden by the child class!'</span>);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-16">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-16">¶</a>
|
||||
</div>
|
||||
<p><code>_send</code> is called by <code>_read</code> which is in turn <a href="http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1">called by Duplex</a> when it wants to have more
|
||||
items in the output queue.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._read = <span class="function"><span class="keyword">function</span> <span class="title">_read</span><span class="params">()</span> {</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-17">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-17">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>if the flow control queue is empty, then let the user push more frames</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">this</span>._queue.length === <span class="number">0</span>) {
|
||||
<span class="keyword">this</span>._send();
|
||||
}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-18">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-18">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>if there are items in the flow control queue, then let's put them into the output queue (to
|
||||
the extent it is possible with respect to the window size and output queue feedback)</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._window > <span class="number">0</span>) {
|
||||
<span class="keyword">this</span>._readableState.sync = <span class="literal">true</span>; <span class="comment">// to avoid reentrant calls</span>
|
||||
<span class="keyword">do</span> {
|
||||
<span class="keyword">var</span> moreNeeded = <span class="keyword">this</span>._push(<span class="keyword">this</span>._queue[<span class="number">0</span>]);
|
||||
<span class="keyword">if</span> (moreNeeded !== <span class="literal">null</span>) {
|
||||
<span class="keyword">this</span>._queue.shift();
|
||||
}
|
||||
} <span class="keyword">while</span> (moreNeeded && (<span class="keyword">this</span>._queue.length > <span class="number">0</span>));
|
||||
<span class="keyword">this</span>._readableState.sync = <span class="literal">false</span>;
|
||||
|
||||
assert((moreNeeded == <span class="literal">false</span>) || <span class="comment">// * output queue is full</span>
|
||||
(<span class="keyword">this</span>._queue.length === <span class="number">0</span>) || <span class="comment">// * flow control queue is empty</span>
|
||||
(!<span class="keyword">this</span>._window && (<span class="keyword">this</span>._queue[<span class="number">0</span>].type === <span class="string">'DATA'</span>))); <span class="comment">// * waiting for window update</span>
|
||||
}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-19">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-19">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>otherwise, come back when the flow control window is positive</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>.once(<span class="string">'window_update'</span>, <span class="keyword">this</span>._read);
|
||||
}
|
||||
};
|
||||
|
||||
<span class="keyword">var</span> MAX_PAYLOAD_SIZE = <span class="number">4096</span>; <span class="comment">// Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-20">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-20">¶</a>
|
||||
</div>
|
||||
<p><code>read(limit)</code> is like the <code>read</code> of the Readable class, but it guarantess that the 'flow control
|
||||
size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
|
||||
be under <code>limit</code>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.read = <span class="function"><span class="keyword">function</span> <span class="title">read</span><span class="params">(limit)</span> {</span>
|
||||
<span class="keyword">if</span> (limit === <span class="number">0</span>) {
|
||||
<span class="keyword">return</span> Duplex.prototype.read.call(<span class="keyword">this</span>, <span class="number">0</span>);
|
||||
} <span class="keyword">else</span> <span class="keyword">if</span> (limit === -<span class="number">1</span>) {
|
||||
limit = <span class="number">0</span>;
|
||||
} <span class="keyword">else</span> <span class="keyword">if</span> ((limit === <span class="literal">undefined</span>) || (limit > MAX_PAYLOAD_SIZE)) {
|
||||
limit = MAX_PAYLOAD_SIZE;
|
||||
}</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-21">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-21">¶</a>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Looking at the first frame in the queue without pulling it out if possible. This will save
|
||||
a costly unshift if the frame proves to be too large to return.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> firstInQueue = <span class="keyword">this</span>._readableState.buffer[<span class="number">0</span>];
|
||||
<span class="keyword">var</span> frame = firstInQueue || Duplex.prototype.read.call(<span class="keyword">this</span>);
|
||||
|
||||
<span class="keyword">if</span> ((frame === <span class="literal">null</span>) || (frame.type !== <span class="string">'DATA'</span>) || (frame.data.length <= limit)) {
|
||||
<span class="keyword">if</span> (firstInQueue) {
|
||||
Duplex.prototype.read.call(<span class="keyword">this</span>);
|
||||
}
|
||||
<span class="keyword">return</span> frame;
|
||||
}
|
||||
|
||||
<span class="keyword">else</span> <span class="keyword">if</span> (limit <= <span class="number">0</span>) {
|
||||
<span class="keyword">if</span> (!firstInQueue) {
|
||||
<span class="keyword">this</span>.unshift(frame);
|
||||
}
|
||||
<span class="keyword">return</span> <span class="literal">null</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._log.trace({ frame: frame, size: frame.data.length, forwardable: limit },
|
||||
<span class="string">'Splitting out forwardable part of a DATA frame.'</span>);
|
||||
<span class="keyword">var</span> forwardable = {
|
||||
type: <span class="string">'DATA'</span>,
|
||||
flags: {},
|
||||
stream: frame.stream,
|
||||
data: frame.data.slice(<span class="number">0</span>, limit)
|
||||
};
|
||||
frame.data = frame.data.slice(limit);
|
||||
|
||||
<span class="keyword">if</span> (!firstInQueue) {
|
||||
<span class="keyword">this</span>.unshift(frame);
|
||||
}
|
||||
<span class="keyword">return</span> forwardable;
|
||||
}
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-22">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-22">¶</a>
|
||||
</div>
|
||||
<p><code>_parentPush</code> pushes the given <code>frame</code> into the output queue</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._parentPush = <span class="function"><span class="keyword">function</span> <span class="title">_parentPush</span><span class="params">(frame)</span> {</span>
|
||||
<span class="keyword">this</span>._log.trace({ frame: frame }, <span class="string">'Pushing frame into the output queue'</span>);
|
||||
|
||||
<span class="keyword">if</span> (frame && (frame.type === <span class="string">'DATA'</span>) && (<span class="keyword">this</span>._window !== <span class="literal">Infinity</span>)) {
|
||||
<span class="keyword">this</span>._log.trace({ window: <span class="keyword">this</span>._window, by: frame.data.length },
|
||||
<span class="string">'Decreasing flow control window size.'</span>);
|
||||
<span class="keyword">this</span>._window -= frame.data.length;
|
||||
assert(<span class="keyword">this</span>._window >= <span class="number">0</span>);
|
||||
}
|
||||
|
||||
<span class="keyword">return</span> Duplex.prototype.push.call(<span class="keyword">this</span>, frame);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-23">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-23">¶</a>
|
||||
</div>
|
||||
<p><code>_push(frame)</code> pushes <code>frame</code> into the output queue and decreases the flow control window size.
|
||||
It is capable of splitting DATA frames into smaller parts, if the window size is not enough to
|
||||
push the whole frame. The return value is similar to <code>push</code> except that it returns <code>null</code> if it
|
||||
did not push the whole frame to the output queue (but maybe it did push part of the frame).</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._push = <span class="function"><span class="keyword">function</span> <span class="title">_push</span><span class="params">(frame)</span> {</span>
|
||||
<span class="keyword">var</span> data = frame && (frame.type === <span class="string">'DATA'</span>) && frame.data;
|
||||
|
||||
<span class="keyword">if</span> (!data || (data.length <= <span class="keyword">this</span>._window)) {
|
||||
<span class="keyword">return</span> <span class="keyword">this</span>._parentPush(frame);
|
||||
}
|
||||
|
||||
<span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._window <= <span class="number">0</span>) {
|
||||
<span class="keyword">return</span> <span class="literal">null</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._log.trace({ frame: frame, size: frame.data.length, forwardable: <span class="keyword">this</span>._window },
|
||||
<span class="string">'Splitting out forwardable part of a DATA frame.'</span>);
|
||||
frame.data = data.slice(<span class="keyword">this</span>._window);
|
||||
<span class="keyword">this</span>._parentPush({
|
||||
type: <span class="string">'DATA'</span>,
|
||||
flags: {},
|
||||
stream: frame.stream,
|
||||
data: data.slice(<span class="number">0</span>, <span class="keyword">this</span>._window)
|
||||
});
|
||||
<span class="keyword">return</span> <span class="literal">null</span>;
|
||||
}
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-24">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-24">¶</a>
|
||||
</div>
|
||||
<p>Push <code>frame</code> into the flow control queue, or if it's empty, then directly into the output queue</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.push = <span class="function"><span class="keyword">function</span> <span class="title">push</span><span class="params">(frame)</span> {</span>
|
||||
<span class="keyword">if</span> (frame === <span class="literal">null</span>) {
|
||||
<span class="keyword">this</span>._log.debug(<span class="string">'Enqueueing outgoing End Of Stream'</span>);
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._log.debug({ frame: frame }, <span class="string">'Enqueueing outgoing frame'</span>);
|
||||
}
|
||||
|
||||
<span class="keyword">var</span> moreNeeded = <span class="literal">null</span>;
|
||||
<span class="keyword">if</span> (<span class="keyword">this</span>._queue.length === <span class="number">0</span>) {
|
||||
moreNeeded = <span class="keyword">this</span>._push(frame);
|
||||
}
|
||||
|
||||
<span class="keyword">if</span> (moreNeeded === <span class="literal">null</span>) {
|
||||
<span class="keyword">this</span>._queue.push(frame);
|
||||
}
|
||||
|
||||
<span class="keyword">return</span> moreNeeded;
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-25">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-25">¶</a>
|
||||
</div>
|
||||
<p><code>getLastQueuedFrame</code> returns the last frame in output buffers. This is primarily used by the
|
||||
<a href="stream.html">Stream</a> class to mark the last frame with END_STREAM flag.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.getLastQueuedFrame = <span class="function"><span class="keyword">function</span> <span class="title">getLastQueuedFrame</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">var</span> readableQueue = <span class="keyword">this</span>._readableState.buffer;
|
||||
<span class="keyword">return</span> <span class="keyword">this</span>._queue[<span class="keyword">this</span>._queue.length - <span class="number">1</span>] || readableQueue[readableQueue.length - <span class="number">1</span>];
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-26">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-26">¶</a>
|
||||
</div>
|
||||
<h2 id="outgoing-frames-managing-the-window-size">Outgoing frames - managing the window size</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-27">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-27">¶</a>
|
||||
</div>
|
||||
<p>Flow control window size is manipulated using the <code>_increaseWindow</code> method.</p>
|
||||
<ul>
|
||||
<li>Invoking it with <code>Infinite</code> means turning off flow control. Flow control cannot be enabled
|
||||
again once disabled. Any attempt to re-enable flow control MUST be rejected with a
|
||||
FLOW_CONTROL_ERROR error code.</li>
|
||||
<li>A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
|
||||
depends on it being a stream or the connection itself.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre><span class="keyword">var</span> WINDOW_SIZE_LIMIT = Math.pow(<span class="number">2</span>, <span class="number">31</span>) - <span class="number">1</span>;
|
||||
|
||||
Flow.prototype._increaseWindow = <span class="function"><span class="keyword">function</span> <span class="title">_increaseWindow</span><span class="params">(size)</span> {</span>
|
||||
<span class="keyword">if</span> ((<span class="keyword">this</span>._window === <span class="literal">Infinity</span>) && (size !== <span class="literal">Infinity</span>)) {
|
||||
<span class="keyword">this</span>._log.error(<span class="string">'Trying to increase flow control window after flow control was turned off.'</span>);
|
||||
<span class="keyword">this</span>.emit(<span class="string">'error'</span>, <span class="string">'FLOW_CONTROL_ERROR'</span>);
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>._log.trace({ window: <span class="keyword">this</span>._window, by: size }, <span class="string">'Increasing flow control window size.'</span>);
|
||||
<span class="keyword">this</span>._window += size;
|
||||
<span class="keyword">if</span> ((<span class="keyword">this</span>._window !== <span class="literal">Infinity</span>) && (<span class="keyword">this</span>._window > WINDOW_SIZE_LIMIT)) {
|
||||
<span class="keyword">this</span>._log.error(<span class="string">'Flow control window grew too large.'</span>);
|
||||
<span class="keyword">this</span>.emit(<span class="string">'error'</span>, <span class="string">'FLOW_CONTROL_ERROR'</span>);
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">this</span>.emit(<span class="string">'window_update'</span>);
|
||||
}
|
||||
}
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-28">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-28">¶</a>
|
||||
</div>
|
||||
<p>The <code>_updateWindow</code> method gets called every time there's an incoming WINDOW_UPDATE frame. It
|
||||
modifies the flow control window:</p>
|
||||
<ul>
|
||||
<li>Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
|
||||
END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
|
||||
flag set is ignored.</li>
|
||||
<li>A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
|
||||
specified in the frame.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype._updateWindow = <span class="function"><span class="keyword">function</span> <span class="title">_updateWindow</span><span class="params">(frame)</span> {</span>
|
||||
<span class="keyword">this</span>._increaseWindow(frame.flags.END_FLOW_CONTROL ? <span class="literal">Infinity</span> : frame.window_size);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-29">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-29">¶</a>
|
||||
</div>
|
||||
<p>A SETTINGS frame can alter the initial flow control window size for all current streams. When the
|
||||
value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
|
||||
calling the <code>setInitialWindow</code> method. The window size has to be modified by the difference
|
||||
between the new value and the old value.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.setInitialWindow = <span class="function"><span class="keyword">function</span> <span class="title">setInitialWindow</span><span class="params">(initialWindow)</span> {</span>
|
||||
<span class="keyword">this</span>._increaseWindow(initialWindow - <span class="keyword">this</span>._initialWindow);
|
||||
<span class="keyword">this</span>._initialWindow = initialWindow;
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li id="section-30">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-30">¶</a>
|
||||
</div>
|
||||
<p>Flow control for outgoing frames can be disabled by the peer with various methods.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>Flow.prototype.disableLocalFlowControl = <span class="function"><span class="keyword">function</span> <span class="title">disableLocalFlowControl</span><span class="params">()</span> {</span>
|
||||
<span class="keyword">this</span>._increaseWindow(<span class="literal">Infinity</span>);
|
||||
};</pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
1425
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/framer.html
generated
vendored
Normal file
1425
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/framer.html
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
164
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/index.html
generated
vendored
Normal file
164
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/index.html
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>index.js</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
||||
<link rel="stylesheet" media="all" href="docco.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="background"></div>
|
||||
|
||||
<ul id="jump_to">
|
||||
<li>
|
||||
<a class="large" href="javascript:void(0);">Jump To …</a>
|
||||
<a class="small" href="javascript:void(0);">+</a>
|
||||
<div id="jump_wrapper">
|
||||
<div id="jump_page">
|
||||
|
||||
|
||||
<a class="source" href="compressor.html">
|
||||
compressor.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="connection.html">
|
||||
connection.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="endpoint.html">
|
||||
endpoint.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="flow.html">
|
||||
flow.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="framer.html">
|
||||
framer.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="index.html">
|
||||
index.js
|
||||
</a>
|
||||
|
||||
|
||||
<a class="source" href="stream.html">
|
||||
stream.js
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="sections">
|
||||
|
||||
<li id="title">
|
||||
<div class="annotation">
|
||||
<h1>index.js</h1>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li id="section-1">
|
||||
<div class="annotation">
|
||||
|
||||
<div class="pilwrap ">
|
||||
<a class="pilcrow" href="#section-1">¶</a>
|
||||
</div>
|
||||
<p><a href="https://github.com/molnarg/node-http2">node-http2-protocol</a> is an implementation of the <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-06">HTTP/2 (draft 06)</a>
|
||||
framing layer for <a href="http://nodejs.org/">node.js</a>.</p>
|
||||
<p>The main building blocks are <a href="http://nodejs.org/api/stream.html">node.js streams</a> that are connected through pipes.</p>
|
||||
<p>The main components are:</p>
|
||||
<ul>
|
||||
<li><p><a href="endpoint.html">Endpoint</a>: represents an HTTP/2 endpoint (client or server). It's
|
||||
responsible for the the first part of the handshake process (sending/receiving the
|
||||
<a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#section-3.5">connection header</a>) and manages other components (framer, compressor,
|
||||
connection, streams) that make up a client or server.</p>
|
||||
</li>
|
||||
<li><p><a href="connection.html">Connection</a>: multiplexes the active HTTP/2 streams, manages connection
|
||||
lifecycle and settings, and responsible for enforcing the connection level limits (flow
|
||||
control, initiated stream limit)</p>
|
||||
</li>
|
||||
<li><p><a href="stream.html">Stream</a>: implementation of the <a href="http2-stream">HTTP/2 stream concept</a>.
|
||||
Implements the <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#section-5.1">stream state machine</a> 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).</p>
|
||||
</li>
|
||||
<li><p><a href="flow.html">Flow</a>: implements flow control for Connection and Stream as parent class.</p>
|
||||
</li>
|
||||
<li><p><a href="compressor.html">Compressor and Decompressor</a>: compression and decompression of HEADER and
|
||||
PUSH_PROMISE frames</p>
|
||||
</li>
|
||||
<li><p><a href="framer.html">Serializer and Deserializer</a>: the lowest layer in the stack that transforms
|
||||
between the binary and the JavaScript object representation of HTTP/2 frames</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content"><div class='highlight'><pre>exports.Endpoint = require(<span class="string">'./endpoint'</span>).Endpoint;
|
||||
|
||||
<span class="comment">/* Bunyan serializers exported by submodules that are worth adding when creating a logger. */</span>
|
||||
exports.serializers = {};
|
||||
<span class="keyword">var</span> modules = [<span class="string">'./framer'</span>, <span class="string">'./compressor'</span>, <span class="string">'./flow'</span>, <span class="string">'./connection'</span>, <span class="string">'./stream'</span>, <span class="string">'./endpoint'</span>];
|
||||
modules.map(require).forEach(<span class="function"><span class="keyword">function</span><span class="params">(module)</span> {</span>
|
||||
<span class="keyword">for</span> (<span class="keyword">var</span> name <span class="keyword">in</span> module.serializers) {
|
||||
exports.serializers[name] = module.serializers[name];
|
||||
}
|
||||
});
|
||||
|
||||
<span class="comment">/*
|
||||
Stream API Endpoint API
|
||||
Stream data
|
||||
|
||||
| ^ | ^
|
||||
| | | |
|
||||
| | | |
|
||||
+-----------|------------|---------------------------------------+
|
||||
| | | Endpoint |
|
||||
| | | |
|
||||
| +-------|------------|-----------------------------------+ |
|
||||
| | | | Connection | |
|
||||
| | v | | |
|
||||
| | +-----------------------+ +-------------------- | |
|
||||
| | | Stream | | Stream ... | |
|
||||
| | +-----------------------+ +-------------------- | |
|
||||
| | | ^ | ^ | |
|
||||
| | v | v | | |
|
||||
| | +------------+--+--------+--+------------+- ... | |
|
||||
| | | ^ | |
|
||||
| | | | | |
|
||||
| +-----------------------|--------|-----------------------+ |
|
||||
| | | |
|
||||
| v | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | Compressor | | Decompressor | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | ^ |
|
||||
| v | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | Serializer | | Deserializer | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | ^ |
|
||||
+---------------------------|--------|---------------------------+
|
||||
| |
|
||||
v |
|
||||
|
||||
Raw data
|
||||
|
||||
*/</span></pre></div></div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.eot
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.eot
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.ttf
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.ttf
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.woff
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-bold.woff
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.eot
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.eot
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.ttf
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.ttf
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.woff
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/aller-light.woff
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.eot
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.eot
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.ttf
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.ttf
generated
vendored
Executable file
Binary file not shown.
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.woff
generated
vendored
Executable file
BIN
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/fonts/novecento-bold.woff
generated
vendored
Executable file
Binary file not shown.
375
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/stylesheets/normalize.css
generated
vendored
Normal file
375
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/public/stylesheets/normalize.css
generated
vendored
Normal file
@ -0,0 +1,375 @@
|
||||
/*! normalize.css v2.0.1 | MIT License | git.io/normalize */
|
||||
|
||||
/* ==========================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects `block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects `inline-block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for `hidden` attribute not present in IE 8/9.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Sets default font family to sans-serif.
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses `h1` font sizes within `section` and `article` in Firefox 4+,
|
||||
* Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE 8/9, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets consistent quote types.
|
||||
*/
|
||||
|
||||
q {
|
||||
quotes: "\201C" "\201D" "\2018" "\2019";
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Removes border when inside `a` element in IE 8/9.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE 9.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE 8/9 and Safari 5.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE 8/9.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font family not being inherited in all browsers.
|
||||
* 2. Corrects font size not being inherited in all browsers.
|
||||
* 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Corrects inability to style clickable `input` types in iOS.
|
||||
* 3. Improves usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to `content-box` in IE 8/9.
|
||||
* 2. Removes excess padding in IE 8/9.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||
* 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE 8/9.
|
||||
* 2. Improves readability and alignment in all browsers.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
1307
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/stream.html
generated
vendored
Normal file
1307
testing/xpcshell/node-http2/node_modules/http2-protocol/doc/stream.html
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
testing/xpcshell/node-http2/node_modules/http2-protocol/example/client.js
generated
vendored
Normal file
42
testing/xpcshell/node-http2/node_modules/http2-protocol/example/client.js
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var http2 = require('..');
|
||||
|
||||
http2.globalAgent = new http2.Agent({
|
||||
log: require('../test/util').createLogger('client')
|
||||
});
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
// Sending the request
|
||||
// It would be `var request = http2.get(process.argv.pop());` if we wouldn't care about plain mode
|
||||
var options = require('url').parse(process.argv.pop());
|
||||
options.plain = Boolean(process.env.HTTP2_PLAIN);
|
||||
var request = http2.request(options);
|
||||
request.end();
|
||||
|
||||
// Receiving the response
|
||||
request.on('response', function(response) {
|
||||
response.pipe(process.stdout);
|
||||
response.on('end', finish);
|
||||
});
|
||||
|
||||
// Receiving push streams
|
||||
request.on('push', function(pushRequest) {
|
||||
var filename = path.join(__dirname, '/push-' + push_count);
|
||||
push_count += 1;
|
||||
console.error('Receiving pushed resource: ' + pushRequest.url + ' -> ' + filename);
|
||||
pushRequest.on('response', function(pushResponse) {
|
||||
pushResponse.pipe(fs.createWriteStream(filename)).on('finish', finish);
|
||||
});
|
||||
});
|
||||
|
||||
// Quitting after both the response and the associated pushed resources have arrived
|
||||
var push_count = 0;
|
||||
var finished = 0;
|
||||
function finish() {
|
||||
finished += 1;
|
||||
if (finished === (1 + push_count)) {
|
||||
process.exit();
|
||||
}
|
||||
}
|
14
testing/xpcshell/node-http2/node_modules/http2-protocol/example/localhost.crt
generated
vendored
Normal file
14
testing/xpcshell/node-http2/node_modules/http2-protocol/example/localhost.crt
generated
vendored
Normal file
@ -0,0 +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=
|
||||
-----END CERTIFICATE-----
|
15
testing/xpcshell/node-http2/node_modules/http2-protocol/example/localhost.key
generated
vendored
Normal file
15
testing/xpcshell/node-http2/node_modules/http2-protocol/example/localhost.key
generated
vendored
Normal file
@ -0,0 +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=
|
||||
-----END RSA PRIVATE KEY-----
|
49
testing/xpcshell/node-http2/node_modules/http2-protocol/example/server.js
generated
vendored
Normal file
49
testing/xpcshell/node-http2/node_modules/http2-protocol/example/server.js
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var http2 = require('..');
|
||||
|
||||
var options = process.env.HTTP2_PLAIN ? {
|
||||
plain: true
|
||||
} : {
|
||||
key: fs.readFileSync(path.join(__dirname, '/localhost.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '/localhost.crt'))
|
||||
};
|
||||
|
||||
// Passing bunyan logger (optional)
|
||||
options.log = require('../test/util').createLogger('server');
|
||||
|
||||
// We cache one file to be able to do simple performance tests without waiting for the disk
|
||||
var cachedFile = fs.readFileSync(path.join(__dirname, './server.js'));
|
||||
var cachedUrl = '/server.js';
|
||||
|
||||
// Creating the server
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
var filename = path.join(__dirname, request.url);
|
||||
|
||||
// Serving server.js from cache. Useful for microbenchmarks.
|
||||
if (request.url === cachedUrl) {
|
||||
response.end(cachedFile);
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
// If they download the certificate, push the private key too, they might need it.
|
||||
if (response.push && request.url === '/localhost.crt') {
|
||||
var push = response.push('/localhost.key');
|
||||
push.writeHead(200);
|
||||
fs.createReadStream(path.join(__dirname, '/localhost.key')).pipe(push);
|
||||
}
|
||||
|
||||
fs.createReadStream(filename).pipe(response);
|
||||
}
|
||||
|
||||
// Otherwise responding with 404.
|
||||
else {
|
||||
response.writeHead('404');
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(process.env.HTTP2_PORT || 8080);
|
1643
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/compressor.js
generated
vendored
Normal file
1643
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/compressor.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
619
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/connection.js
generated
vendored
Normal file
619
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/connection.js
generated
vendored
Normal file
@ -0,0 +1,619 @@
|
||||
var assert = require('assert');
|
||||
|
||||
// The Connection class
|
||||
// ====================
|
||||
|
||||
// The Connection class manages HTTP/2 connections. Each instance corresponds to one transport
|
||||
// stream (TCP stream). It operates by sending and receiving frames and is implemented as a
|
||||
// [Flow](flow.html) subclass.
|
||||
|
||||
var Flow = require('./flow').Flow;
|
||||
|
||||
exports.Connection = Connection;
|
||||
|
||||
// Public API
|
||||
// ----------
|
||||
|
||||
// * **new Connection(log, firstStreamId, settings)**: create a new Connection
|
||||
//
|
||||
// * **Event: 'error' (type)**: signals a connection level error made by the other end
|
||||
//
|
||||
// * **Event: 'peerError' (type)**: signals the receipt of a GOAWAY frame that contains an error
|
||||
// code other than NO_ERROR
|
||||
//
|
||||
// * **Event: 'stream' (stream)**: signals that there's an incoming stream
|
||||
//
|
||||
// * **createStream(): stream**: initiate a new stream
|
||||
//
|
||||
// * **set(settings, callback)**: change the value of one or more settings according to the
|
||||
// key-value pairs of `settings`. The callback is called after the peer acknowledged the changes.
|
||||
//
|
||||
// * **ping([callback])**: send a ping and call callback when the answer arrives
|
||||
//
|
||||
// * **close([error])**: close the stream with an error code
|
||||
|
||||
// Constructor
|
||||
// -----------
|
||||
|
||||
// The main aspects of managing the connection are:
|
||||
function Connection(log, firstStreamId, settings) {
|
||||
// * initializing the base class
|
||||
Flow.call(this, 0);
|
||||
|
||||
// * logging: every method uses the common logger object
|
||||
this._log = log.child({ component: 'connection' });
|
||||
|
||||
// * stream management
|
||||
this._initializeStreamManagement(firstStreamId);
|
||||
|
||||
// * lifecycle management
|
||||
this._initializeLifecycleManagement();
|
||||
|
||||
// * flow control
|
||||
this._initializeFlowControl();
|
||||
|
||||
// * settings management
|
||||
this._initializeSettingsManagement(settings);
|
||||
|
||||
// * multiplexing
|
||||
this._initializeMultiplexing();
|
||||
}
|
||||
Connection.prototype = Object.create(Flow.prototype, { constructor: { value: Connection } });
|
||||
|
||||
// Overview
|
||||
// --------
|
||||
|
||||
// | ^ | ^
|
||||
// v | v |
|
||||
// +--------------+ +--------------+
|
||||
// +---| stream1 |---| stream2 |---- .... ---+
|
||||
// | | +----------+ | | +----------+ | |
|
||||
// | | | stream1. | | | | stream2. | | |
|
||||
// | +-| upstream |-+ +-| upstream |-+ |
|
||||
// | +----------+ +----------+ |
|
||||
// | | ^ | ^ |
|
||||
// | v | v | |
|
||||
// | +-----+-------------+-----+-------- .... |
|
||||
// | ^ | | | |
|
||||
// | | v | | |
|
||||
// | +--------------+ | | |
|
||||
// | | stream0 | | | |
|
||||
// | | connection | | | |
|
||||
// | | management | multiplexing |
|
||||
// | +--------------+ flow control |
|
||||
// | | ^ |
|
||||
// | _read() | | _write() |
|
||||
// | v | |
|
||||
// | +------------+ +-----------+ |
|
||||
// | |output queue| |input queue| |
|
||||
// +----------------+------------+-+-----------+-----------------+
|
||||
// | ^
|
||||
// read() | | write()
|
||||
// v |
|
||||
|
||||
// Stream management
|
||||
// -----------------
|
||||
|
||||
var Stream = require('./stream').Stream;
|
||||
|
||||
// Initialization:
|
||||
Connection.prototype._initializeStreamManagement = function _initializeStreamManagement(firstStreamId) {
|
||||
// * streams are stored in two data structures:
|
||||
// * `_streamIds` is an id -> stream map of the streams that are allowed to receive frames.
|
||||
// * `_streamPriorities` is a priority -> [stream] map of stream that allowed to send frames.
|
||||
this._streamIds = [];
|
||||
this._streamPriorities = [];
|
||||
|
||||
// * The next outbound stream ID and the last inbound stream id
|
||||
this._nextStreamId = firstStreamId;
|
||||
this._lastIncomingStream = 0;
|
||||
|
||||
// * Calling `_writeControlFrame` when there's an incoming stream with 0 as stream ID
|
||||
this._streamIds[0] = { upstream: { write: this._writeControlFrame.bind(this) } };
|
||||
|
||||
// * By default, the number of concurrent outbound streams is not limited. The `_streamLimit` can
|
||||
// be set by the SETTINGS_MAX_CONCURRENT_STREAMS setting.
|
||||
this._streamSlotsFree = Infinity;
|
||||
this._streamLimit = Infinity;
|
||||
this.on('RECEIVING_SETTINGS_MAX_CONCURRENT_STREAMS', this._updateStreamLimit);
|
||||
};
|
||||
|
||||
// `_writeControlFrame` is called when there's an incoming frame in the `_control` stream. It
|
||||
// broadcasts the message by creating an event on it.
|
||||
Connection.prototype._writeControlFrame = function _writeControlFrame(frame) {
|
||||
if ((frame.type === 'SETTINGS') || (frame.type === 'PING') ||
|
||||
(frame.type === 'GOAWAY') || (frame.type === 'WINDOW_UPDATE')) {
|
||||
this._log.debug({ frame: frame }, 'Receiving connection level frame');
|
||||
this.emit(frame.type, frame);
|
||||
} else {
|
||||
this._log.error({ frame: frame }, 'Invalid connection level frame');
|
||||
this.emit('error', 'PROTOCOL_ERROR');
|
||||
}
|
||||
};
|
||||
|
||||
// Methods to manage the stream slot pool:
|
||||
Connection.prototype._updateStreamLimit = function _updateStreamLimit(newStreamLimit) {
|
||||
var wakeup = (this._streamSlotsFree === 0) && (newStreamLimit > this._streamLimit);
|
||||
this._streamSlotsFree += newStreamLimit - this._streamLimit;
|
||||
this._streamLimit = newStreamLimit;
|
||||
if (wakeup) {
|
||||
this.emit('wakeup');
|
||||
}
|
||||
};
|
||||
|
||||
Connection.prototype._changeStreamCount = function _changeStreamCount(change) {
|
||||
if (change) {
|
||||
this._log.trace({ free: this._streamSlotsFree, change: change },
|
||||
'Changing active stream count.');
|
||||
var wakeup = (this._streamSlotsFree === 0) && (change < 0);
|
||||
this._streamSlotsFree -= change;
|
||||
if (wakeup) {
|
||||
this.emit('wakeup');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Creating a new *inbound or outbound* stream with the given `id` (which is undefined in case of
|
||||
// an outbound stream) consists of three steps:
|
||||
//
|
||||
// 1. var stream = new Stream(this._log);
|
||||
// 2. this._allocateId(stream, id);
|
||||
// 2. this._allocatePriority(stream);
|
||||
|
||||
// Allocating an ID to a stream
|
||||
Connection.prototype._allocateId = function _allocateId(stream, id) {
|
||||
// * initiated stream without definite ID
|
||||
if (id === undefined) {
|
||||
id = this._nextStreamId;
|
||||
this._nextStreamId += 2;
|
||||
}
|
||||
|
||||
// * incoming stream with a legitim ID (larger than any previous and different parity than ours)
|
||||
else if ((id > this._lastIncomingStream) && ((id - this._nextStreamId) % 2 !== 0)) {
|
||||
this._lastIncomingStream = id;
|
||||
}
|
||||
|
||||
// * incoming stream with invalid ID
|
||||
else {
|
||||
this._log.error({ stream_id: id, lastIncomingStream: this._lastIncomingStream },
|
||||
'Invalid incoming stream ID.');
|
||||
this.emit('error', 'PROTOCOL_ERROR');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
assert(!(id in this._streamIds));
|
||||
|
||||
// * adding to `this._streamIds`
|
||||
this._log.trace({ s: stream, stream_id: id }, 'Allocating ID for stream.');
|
||||
this._streamIds[id] = stream;
|
||||
stream.id = id;
|
||||
this.emit('new_stream', stream, id);
|
||||
|
||||
// * handling stream errors as connection errors
|
||||
stream.on('error', this.emit.bind(this, 'error'));
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
// Allocating a priority to a stream, and managing priority changes
|
||||
Connection.prototype._allocatePriority = function _allocatePriority(stream) {
|
||||
this._log.trace({ s: stream }, 'Allocating priority for stream.');
|
||||
this._insert(stream, stream._priority);
|
||||
stream.on('priority', this._reprioritize.bind(this, stream));
|
||||
stream.upstream.on('readable', this.emit.bind(this, 'wakeup'));
|
||||
this.emit('wakeup');
|
||||
};
|
||||
|
||||
Connection.prototype._insert = function _insert(stream, priority) {
|
||||
if (priority in this._streamPriorities) {
|
||||
this._streamPriorities[priority].push(stream);
|
||||
} else {
|
||||
this._streamPriorities[priority] = [stream];
|
||||
}
|
||||
};
|
||||
|
||||
Connection.prototype._reprioritize = function _reprioritize(stream, priority) {
|
||||
var bucket = this._streamPriorities[stream._priority];
|
||||
var index = bucket.indexOf(stream);
|
||||
assert(index !== -1);
|
||||
bucket.splice(index, 1);
|
||||
if (bucket.length === 0) {
|
||||
delete this._streamPriorities[stream._priority];
|
||||
}
|
||||
|
||||
this._insert(stream, priority);
|
||||
};
|
||||
|
||||
// Creating an *inbound* stream with the given ID. It is called when there's an incoming frame to
|
||||
// a previously nonexistent stream.
|
||||
Connection.prototype._createIncomingStream = function _createIncomingStream(id) {
|
||||
this._log.debug({ stream_id: id }, 'New incoming stream.');
|
||||
|
||||
var stream = new Stream(this._log);
|
||||
this._allocateId(stream, id);
|
||||
this._allocatePriority(stream);
|
||||
this.emit('stream', stream, id);
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
// Creating an *outbound* stream
|
||||
Connection.prototype.createStream = function createStream() {
|
||||
this._log.trace('Creating new outbound stream.');
|
||||
|
||||
// * Receiving is enabled immediately, and an ID gets assigned to the stream
|
||||
var stream = new Stream(this._log);
|
||||
this._allocatePriority(stream);
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
// Multiplexing
|
||||
// ------------
|
||||
|
||||
Connection.prototype._initializeMultiplexing = function _initializeMultiplexing() {
|
||||
this.on('window_update', this.emit.bind(this, 'wakeup'));
|
||||
this._sendScheduled = false;
|
||||
this._firstFrameReceived = false;
|
||||
};
|
||||
|
||||
// The `_send` method is a virtual method of the [Flow class](flow.html) that has to be implemented
|
||||
// by child classes. It reads frames from streams and pushes them to the output buffer.
|
||||
Connection.prototype._send = function _send(immediate) {
|
||||
// * Do not do anything if the connection is already closed
|
||||
if (this._closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// * Collapsing multiple calls in a turn into a single deferred call
|
||||
if (immediate) {
|
||||
this._sendScheduled = false;
|
||||
} else {
|
||||
if (!this._sendScheduled) {
|
||||
this._sendScheduled = true;
|
||||
setImmediate(this._send.bind(this, true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.trace('Starting forwarding frames from streams.');
|
||||
|
||||
// * Looping through priority `bucket`s in priority order.
|
||||
priority_loop:
|
||||
for (var priority in this._streamPriorities) {
|
||||
var bucket = this._streamPriorities[priority];
|
||||
var nextBucket = [];
|
||||
|
||||
// * Forwarding frames from buckets with round-robin scheduling.
|
||||
// 1. pulling out frame
|
||||
// 2. if there's no frame, skip this stream
|
||||
// 3. if forwarding this frame would make `streamCount` greater than `streamLimit`, skip
|
||||
// this stream
|
||||
// 4. adding stream to the bucket of the next round
|
||||
// 5. assigning an ID to the frame (allocating an ID to the stream if there isn't already)
|
||||
// 6. if forwarding a PUSH_PROMISE, allocate ID to the promised stream
|
||||
// 7. forwarding the frame, changing `streamCount` as appropriate
|
||||
// 8. stepping to the next stream if there's still more frame needed in the output buffer
|
||||
// 9. switching to the bucket of the next round
|
||||
while (bucket.length > 0) {
|
||||
for (var index = 0; index < bucket.length; index++) {
|
||||
var stream = bucket[index];
|
||||
var frame = stream.upstream.read((this._window > 0) ? this._window : -1);
|
||||
|
||||
if (!frame) {
|
||||
continue;
|
||||
} else if (frame.count_change > this._streamSlotsFree) {
|
||||
stream.upstream.unshift(frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
nextBucket.push(stream);
|
||||
|
||||
if (frame.stream === undefined) {
|
||||
frame.stream = stream.id || this._allocateId(stream);
|
||||
}
|
||||
|
||||
if (frame.type === 'PUSH_PROMISE') {
|
||||
this._allocatePriority(frame.promised_stream);
|
||||
frame.promised_stream = this._allocateId(frame.promised_stream);
|
||||
}
|
||||
|
||||
this._log.trace({ s: stream, frame: frame }, 'Forwarding outgoing frame');
|
||||
var moreNeeded = this.push(frame);
|
||||
this._changeStreamCount(frame.count_change);
|
||||
|
||||
assert(moreNeeded !== null); // The frame shouldn't be unforwarded
|
||||
if (moreNeeded === false) {
|
||||
break priority_loop;
|
||||
}
|
||||
}
|
||||
|
||||
bucket = nextBucket;
|
||||
nextBucket = [];
|
||||
}
|
||||
}
|
||||
|
||||
// * if we couldn't forward any frame, then sleep until window update, or some other wakeup event
|
||||
if (moreNeeded === undefined) {
|
||||
this.once('wakeup', this._send.bind(this));
|
||||
}
|
||||
|
||||
this._log.trace({ moreNeeded: moreNeeded }, 'Stopping forwarding frames from streams.');
|
||||
};
|
||||
|
||||
// The `_receive` method is another virtual method of the [Flow class](flow.html) that has to be
|
||||
// implemented by child classes. It forwards the given frame to the appropriate stream:
|
||||
Connection.prototype._receive = function _receive(frame, done) {
|
||||
this._log.trace({ frame: frame }, 'Forwarding incoming frame');
|
||||
|
||||
// * first frame needs to be checked by the `_onFirstFrameReceived` method
|
||||
if (!this._firstFrameReceived) {
|
||||
this._firstFrameReceived = true;
|
||||
this._onFirstFrameReceived(frame);
|
||||
}
|
||||
|
||||
// * gets the appropriate stream from the stream registry
|
||||
var stream = this._streamIds[frame.stream];
|
||||
|
||||
// * or creates one if it's not in `this.streams`
|
||||
if (!stream) {
|
||||
stream = this._createIncomingStream(frame.stream);
|
||||
}
|
||||
|
||||
// * in case of PUSH_PROMISE, replaces the promised stream id with a new incoming stream
|
||||
if (frame.type === 'PUSH_PROMISE') {
|
||||
frame.promised_stream = this._createIncomingStream(frame.promised_stream);
|
||||
}
|
||||
|
||||
frame.count_change = this._changeStreamCount.bind(this);
|
||||
|
||||
// * and writes it to the `stream`'s `upstream`
|
||||
stream.upstream.write(frame);
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
// Settings management
|
||||
// -------------------
|
||||
|
||||
var defaultSettings = {
|
||||
SETTINGS_FLOW_CONTROL_OPTIONS: true
|
||||
};
|
||||
|
||||
// Settings management initialization:
|
||||
Connection.prototype._initializeSettingsManagement = function _initializeSettingsManagement(settings) {
|
||||
// * Setting up the callback queue for setting acknowledgements
|
||||
this._settingsAckCallbacks = [];
|
||||
|
||||
// * Sending the initial settings.
|
||||
this._log.debug({ settings: settings },
|
||||
'Sending the first SETTINGS frame as part of the connection header.');
|
||||
this.set(settings || defaultSettings);
|
||||
|
||||
// * Forwarding SETTINGS frames to the `_receiveSettings` method
|
||||
this.on('SETTINGS', this._receiveSettings);
|
||||
};
|
||||
|
||||
// * Checking that the first frame the other endpoint sends is SETTINGS
|
||||
Connection.prototype._onFirstFrameReceived = function _onFirstFrameReceived(frame) {
|
||||
if ((frame.stream === 0) && (frame.type === 'SETTINGS')) {
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
// Handling of incoming SETTINGS frames.
|
||||
Connection.prototype._receiveSettings = function _receiveSettings(frame) {
|
||||
// * If it's an ACK, call the appropriate callback
|
||||
if (frame.flags.ACK) {
|
||||
var callback = this._settingsAckCallbacks.shift();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// * If it's a setting change request, then send an ACK and change the appropriate settings
|
||||
else {
|
||||
if (!this._closed) {
|
||||
this.push({
|
||||
type: 'SETTINGS',
|
||||
flags: { ACK: true },
|
||||
stream: 0,
|
||||
settings: {}
|
||||
});
|
||||
}
|
||||
for (var name in frame.settings) {
|
||||
this.emit('RECEIVING_' + name, frame.settings[name]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Changing one or more settings value and sending out a SETTINGS frame
|
||||
Connection.prototype.set = function set(settings, callback) {
|
||||
// * Calling the callback and emitting event when the change is acknowledges
|
||||
callback = callback || function noop() {};
|
||||
var self = this;
|
||||
this._settingsAckCallbacks.push(function() {
|
||||
for (var name in settings) {
|
||||
self.emit('ACKNOWLEDGED_' + name, settings[name]);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
||||
// * Sending out the SETTINGS frame
|
||||
this.push({
|
||||
type: 'SETTINGS',
|
||||
flags: { ACK: false },
|
||||
stream: 0,
|
||||
settings: settings
|
||||
});
|
||||
for (var name in settings) {
|
||||
this.emit('SENDING_' + name, settings[name]);
|
||||
}
|
||||
};
|
||||
|
||||
// Lifecycle management
|
||||
// --------------------
|
||||
|
||||
// The main responsibilities of lifecycle management code:
|
||||
//
|
||||
// * keeping the connection alive by
|
||||
// * sending PINGs when the connection is idle
|
||||
// * answering PINGs
|
||||
// * ending the connection
|
||||
|
||||
Connection.prototype._initializeLifecycleManagement = function _initializeLifecycleManagement() {
|
||||
this._pings = {};
|
||||
this.on('PING', this._receivePing);
|
||||
this.on('GOAWAY', this._receiveGoaway);
|
||||
this._closed = false;
|
||||
};
|
||||
|
||||
// Generating a string of length 16 with random hexadecimal digits
|
||||
Connection.prototype._generatePingId = function _generatePingId() {
|
||||
do {
|
||||
var id = '';
|
||||
for (var i = 0; i < 16; i++) {
|
||||
id += Math.floor(Math.random()*16).toString(16);
|
||||
}
|
||||
} while(id in this._pings);
|
||||
return id;
|
||||
};
|
||||
|
||||
// Sending a ping and calling `callback` when the answer arrives
|
||||
Connection.prototype.ping = function ping(callback) {
|
||||
var id = this._generatePingId();
|
||||
var data = new Buffer(id, 'hex');
|
||||
this._pings[id] = callback;
|
||||
|
||||
this._log.debug({ data: data }, 'Sending PING.');
|
||||
this.push({
|
||||
type: 'PING',
|
||||
flags: {
|
||||
ACK: false
|
||||
},
|
||||
stream: 0,
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// Answering pings
|
||||
Connection.prototype._receivePing = function _receivePing(frame) {
|
||||
if (frame.flags.ACK) {
|
||||
var id = frame.data.toString('hex');
|
||||
if (id in this._pings) {
|
||||
this._log.debug({ data: frame.data }, 'Receiving answer for a PING.');
|
||||
var callback = this._pings[id];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
delete this._pings[id];
|
||||
} else {
|
||||
this._log.warn({ data: frame.data }, 'Unsolicited PING answer.');
|
||||
}
|
||||
|
||||
} else {
|
||||
this._log.debug({ data: frame.data }, 'Answering PING.');
|
||||
this.push({
|
||||
type: 'PING',
|
||||
flags: {
|
||||
ACK: true
|
||||
},
|
||||
stream: 0,
|
||||
data: frame.data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Terminating the connection
|
||||
Connection.prototype.close = function close(error) {
|
||||
if (this._closed) {
|
||||
this._log.warn('Trying to close an already closed connection');
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.debug({ error: error }, 'Closing the connection');
|
||||
this.push({
|
||||
type: 'GOAWAY',
|
||||
flags: {},
|
||||
stream: 0,
|
||||
last_stream: this._lastIncomingStream,
|
||||
error: error || 'NO_ERROR'
|
||||
});
|
||||
this.push(null);
|
||||
this._closed = true;
|
||||
};
|
||||
|
||||
Connection.prototype._receiveGoaway = function _receiveGoaway(frame) {
|
||||
this._log.debug({ error: frame.error }, 'Other end closed the connection');
|
||||
this.push(null);
|
||||
this._closed = true;
|
||||
if (frame.error !== 'NO_ERROR') {
|
||||
this.emit('peerError', frame.error);
|
||||
}
|
||||
};
|
||||
|
||||
// Flow control
|
||||
// ------------
|
||||
|
||||
Connection.prototype._initializeFlowControl = function _initializeFlowControl() {
|
||||
// Handling of initial window size of individual streams.
|
||||
this._initialStreamWindowSize = INITIAL_STREAM_WINDOW_SIZE;
|
||||
this.on('new_stream', function(stream) {
|
||||
stream.upstream.setInitialWindow(this._initialStreamWindowSize);
|
||||
if (this._remoteFlowControlDisabled) {
|
||||
stream.upstream.disableRemoteFlowControl();
|
||||
}
|
||||
});
|
||||
this.on('RECEIVING_SETTINGS_INITIAL_WINDOW_SIZE', this._setInitialStreamWindowSize);
|
||||
this.on('RECEIVING_SETTINGS_FLOW_CONTROL_OPTIONS', this._setLocalFlowControl);
|
||||
this.on('SENDING_SETTINGS_FLOW_CONTROL_OPTIONS', this._setRemoteFlowControl);
|
||||
this._streamIds[0].upstream.setInitialWindow = function noop() {};
|
||||
this._streamIds[0].upstream.disableRemoteFlowControl = function noop() {};
|
||||
};
|
||||
|
||||
// The initial connection flow control window is 65535 bytes.
|
||||
var INITIAL_STREAM_WINDOW_SIZE = 65535;
|
||||
|
||||
// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
|
||||
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the window size of all
|
||||
// stream by calling the `setInitialStreamWindowSize` method. The window size has to be modified by
|
||||
// the difference between the new value and the old value.
|
||||
Connection.prototype._setInitialStreamWindowSize = function _setInitialStreamWindowSize(size) {
|
||||
if ((this._initialStreamWindowSize === Infinity) && (size !== Infinity)) {
|
||||
this._log.error('Trying to manipulate initial flow control window size after flow control was turned off.');
|
||||
this.emit('error', 'FLOW_CONTROL_ERROR');
|
||||
} else {
|
||||
this._log.debug({ size: size }, 'Changing stream initial window size.');
|
||||
this._initialStreamWindowSize = size;
|
||||
this._streamIds.forEach(function(stream) {
|
||||
stream.upstream.setInitialWindow(size);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// `_setStreamFlowControl()` may be used to disable/enable flow control. In practice, it is just
|
||||
// for turning off flow control since it can not be turned on.
|
||||
Connection.prototype._setLocalFlowControl = function _setLocalFlowControl(disable) {
|
||||
if (disable) {
|
||||
this._increaseWindow(Infinity);
|
||||
this._setInitialStreamWindowSize(Infinity);
|
||||
} else if (this._initialStreamWindowSize === Infinity) {
|
||||
this._log.error('Trying to re-enable flow control after it was turned off.');
|
||||
this.emit('error', 'FLOW_CONTROL_ERROR');
|
||||
}
|
||||
};
|
||||
|
||||
Connection.prototype._setRemoteFlowControl = function _setRemoteFlowControl(disable) {
|
||||
if (disable) {
|
||||
this.disableRemoteFlowControl();
|
||||
this._streamIds.forEach(function(stream) {
|
||||
stream.upstream.disableRemoteFlowControl();
|
||||
});
|
||||
} else if (this._remoteFlowControlDisabled) {
|
||||
this._log.error('Trying to re-enable flow control after it was turned off.');
|
||||
throw new Error('Trying to re-enable flow control after it was turned off.');
|
||||
}
|
||||
};
|
264
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/endpoint.js
generated
vendored
Normal file
264
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/endpoint.js
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var Serializer = require('./framer').Serializer;
|
||||
var Deserializer = require('./framer').Deserializer;
|
||||
var Compressor = require('./compressor').Compressor;
|
||||
var Decompressor = require('./compressor').Decompressor;
|
||||
var Connection = require('./connection').Connection;
|
||||
var Duplex = require('stream').Duplex;
|
||||
var Transform = require('stream').Transform;
|
||||
|
||||
exports.Endpoint = Endpoint;
|
||||
|
||||
// The Endpoint class
|
||||
// ==================
|
||||
|
||||
// Public API
|
||||
// ----------
|
||||
|
||||
// - **new Endpoint(log, role, settings, filters)**: create a new Endpoint.
|
||||
//
|
||||
// - `log`: bunyan logger of the parent
|
||||
// - `role`: 'CLIENT' or 'SERVER'
|
||||
// - `settings`: initial HTTP/2 settings
|
||||
// - `filters`: a map of functions that filter the traffic between components (for debugging or
|
||||
// intentional failure injection).
|
||||
//
|
||||
// Filter functions get three arguments:
|
||||
// 1. `frame`: the current frame
|
||||
// 2. `forward(frame)`: function that can be used to forward a frame to the next component
|
||||
// 3. `done()`: callback to signal the end of the filter process
|
||||
//
|
||||
// Valid filter names and their position in the stack:
|
||||
// - `beforeSerialization`: after compression, before serialization
|
||||
// - `beforeCompression`: after multiplexing, before compression
|
||||
// - `afterDeserialization`: after deserialization, before decompression
|
||||
// - `afterDecompression`: after decompression, before multiplexing
|
||||
//
|
||||
// * **Event: 'stream' (Stream)**: 'stream' event forwarded from the underlying Connection
|
||||
//
|
||||
// * **Event: 'error' (type)**: signals an error
|
||||
//
|
||||
// * **createStream(): Stream**: initiate a new stream (forwarded to the underlying Connection)
|
||||
//
|
||||
// * **close([error])**: close the connection with an error code
|
||||
|
||||
// Constructor
|
||||
// -----------
|
||||
|
||||
// The process of initialization:
|
||||
function Endpoint(log, role, settings, filters) {
|
||||
Duplex.call(this);
|
||||
|
||||
// * Initializing logging infrastructure
|
||||
this._log = log.child({ component: 'endpoint', e: this });
|
||||
|
||||
// * First part of the handshake process: sending and receiving the client connection header
|
||||
// prelude.
|
||||
assert((role === 'CLIENT') || role === 'SERVER');
|
||||
if (role === 'CLIENT') {
|
||||
this._writePrelude();
|
||||
} else {
|
||||
this._readPrelude();
|
||||
}
|
||||
|
||||
// * Initialization of component. This includes the second part of the handshake process:
|
||||
// sending the first SETTINGS frame. This is done by the connection class right after
|
||||
// initialization.
|
||||
this._initializeDataFlow(role, settings, filters || {});
|
||||
|
||||
// * Initialization of management code.
|
||||
this._initializeManagement();
|
||||
|
||||
// * Initializing error handling.
|
||||
this._initializeErrorHandling();
|
||||
}
|
||||
Endpoint.prototype = Object.create(Duplex.prototype, { constructor: { value: Endpoint } });
|
||||
|
||||
// Handshake
|
||||
// ---------
|
||||
|
||||
var CLIENT_PRELUDE = new Buffer('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
|
||||
|
||||
// Writing the client header is simple and synchronous.
|
||||
Endpoint.prototype._writePrelude = function _writePrelude() {
|
||||
this._log.debug('Sending the client connection header prelude.');
|
||||
this.push(CLIENT_PRELUDE);
|
||||
};
|
||||
|
||||
// The asynchronous process of reading the client header:
|
||||
Endpoint.prototype._readPrelude = function _readPrelude() {
|
||||
// * progress in the header is tracker using a `cursor`
|
||||
var cursor = 0;
|
||||
|
||||
// * `_write` is temporarily replaced by the comparator function
|
||||
this._write = function _temporalWrite(chunk, encoding, done) {
|
||||
// * which compares the stored header with the current `chunk` byte by byte and emits the
|
||||
// 'error' event if there's a byte that doesn't match
|
||||
var offset = cursor;
|
||||
while(cursor < CLIENT_PRELUDE.length && (cursor - offset) < chunk.length) {
|
||||
if (CLIENT_PRELUDE[cursor] !== chunk[cursor - offset]) {
|
||||
this._log.fatal({ cursor: cursor, offset: offset, chunk: chunk },
|
||||
'Client connection header prelude does not match.');
|
||||
this._error('handshake', 'PROTOCOL_ERROR');
|
||||
return;
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
// * if the whole header is over, and there were no error then restore the original `_write`
|
||||
// and call it with the remaining part of the current chunk
|
||||
if (cursor === CLIENT_PRELUDE.length) {
|
||||
this._log.debug('Successfully received the client connection header prelude.');
|
||||
delete this._write;
|
||||
chunk = chunk.slice(cursor - offset);
|
||||
this._write(chunk, encoding, done);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Data flow
|
||||
// ---------
|
||||
|
||||
// +---------------------------------------------+
|
||||
// | |
|
||||
// | +-------------------------------------+ |
|
||||
// | | +---------+ +---------+ +---------+ | |
|
||||
// | | | stream1 | | stream2 | | ... | | |
|
||||
// | | +---------+ +---------+ +---------+ | |
|
||||
// | | connection | |
|
||||
// | +-------------------------------------+ |
|
||||
// | | ^ |
|
||||
// | pipe | | pipe |
|
||||
// | v | |
|
||||
// | +------------------+------------------+ |
|
||||
// | | compressor | decompressor | |
|
||||
// | +------------------+------------------+ |
|
||||
// | | ^ |
|
||||
// | pipe | | pipe |
|
||||
// | v | |
|
||||
// | +------------------+------------------+ |
|
||||
// | | serializer | deserializer | |
|
||||
// | +------------------+------------------+ |
|
||||
// | | ^ |
|
||||
// | _read() | | _write() |
|
||||
// | v | |
|
||||
// | +------------+ +-----------+ |
|
||||
// | |output queue| |input queue| |
|
||||
// +------+------------+-----+-----------+-------+
|
||||
// | ^
|
||||
// read() | | write()
|
||||
// v |
|
||||
|
||||
function createTransformStream(filter) {
|
||||
var transform = new Transform({ objectMode: true });
|
||||
var push = transform.push.bind(transform);
|
||||
transform._transform = function(frame, encoding, done) {
|
||||
filter(frame, push, done);
|
||||
};
|
||||
return transform;
|
||||
}
|
||||
|
||||
function pipeAndFilter(stream1, stream2, filter) {
|
||||
if (filter) {
|
||||
stream1.pipe(createTransformStream(filter)).pipe(stream2);
|
||||
} else {
|
||||
stream1.pipe(stream2);
|
||||
}
|
||||
}
|
||||
|
||||
var MAX_HTTP_PAYLOAD_SIZE = 16383;
|
||||
|
||||
Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, settings, filters) {
|
||||
var firstStreamId, compressorRole, decompressorRole;
|
||||
if (role === 'CLIENT') {
|
||||
firstStreamId = 1;
|
||||
compressorRole = 'REQUEST';
|
||||
decompressorRole = 'RESPONSE';
|
||||
} else {
|
||||
firstStreamId = 2;
|
||||
compressorRole = 'RESPONSE';
|
||||
decompressorRole = 'REQUEST';
|
||||
}
|
||||
|
||||
this._serializer = new Serializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
|
||||
this._deserializer = new Deserializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
|
||||
this._compressor = new Compressor(this._log, compressorRole);
|
||||
this._decompressor = new Decompressor(this._log, decompressorRole);
|
||||
this._connection = new Connection(this._log, firstStreamId, settings);
|
||||
|
||||
pipeAndFilter(this._connection, this._compressor, filters.beforeCompression);
|
||||
pipeAndFilter(this._compressor, this._serializer, filters.beforeSerialization);
|
||||
pipeAndFilter(this._deserializer, this._decompressor, filters.afterDeserialization);
|
||||
pipeAndFilter(this._decompressor, this._connection, filters.afterDecompression);
|
||||
|
||||
this._connection.on('ACKNOWLEDGED_SETTINGS_HEADER_TABLE_SIZE',
|
||||
this._decompressor.setTableSizeLimit.bind(this._decompressor))
|
||||
this._connection.on('RECEIVING_SETTINGS_HEADER_TABLE_SIZE',
|
||||
this._compressor.setTableSizeLimit.bind(this._compressor))
|
||||
};
|
||||
|
||||
var noread = {};
|
||||
Endpoint.prototype._read = function _read() {
|
||||
this._readableState.sync = true;
|
||||
var moreNeeded = noread, chunk;
|
||||
while (moreNeeded && (chunk = this._serializer.read())) {
|
||||
moreNeeded = this.push(chunk);
|
||||
}
|
||||
if (moreNeeded === noread) {
|
||||
this._serializer.once('readable', this._read.bind(this));
|
||||
}
|
||||
this._readableState.sync = false;
|
||||
};
|
||||
|
||||
Endpoint.prototype._write = function _write(chunk, encoding, done) {
|
||||
this._deserializer.write(chunk, encoding, done);
|
||||
};
|
||||
|
||||
// Management
|
||||
// --------------
|
||||
|
||||
Endpoint.prototype._initializeManagement = function _initializeManagement() {
|
||||
this._connection.on('stream', this.emit.bind(this, 'stream'));
|
||||
};
|
||||
|
||||
Endpoint.prototype.createStream = function createStream() {
|
||||
return this._connection.createStream();
|
||||
};
|
||||
|
||||
// Error handling
|
||||
// --------------
|
||||
|
||||
Endpoint.prototype._initializeErrorHandling = function _initializeErrorHandling() {
|
||||
this._serializer.on('error', this._error.bind(this, 'serializer'));
|
||||
this._deserializer.on('error', this._error.bind(this, 'deserializer'));
|
||||
this._compressor.on('error', this._error.bind(this, 'compressor'));
|
||||
this._decompressor.on('error', this._error.bind(this, 'decompressor'));
|
||||
this._connection.on('error', this._error.bind(this, 'connection'));
|
||||
|
||||
this._connection.on('peerError', this.emit.bind(this, 'peerError'));
|
||||
};
|
||||
|
||||
Endpoint.prototype._error = function _error(component, error) {
|
||||
this._log.fatal({ source: component, message: error }, 'Fatal error, closing connection');
|
||||
this.close(error);
|
||||
setImmediate(this.emit.bind(this, 'error', error));
|
||||
};
|
||||
|
||||
Endpoint.prototype.close = function close(error) {
|
||||
this._connection.close(error);
|
||||
};
|
||||
|
||||
// Bunyan serializers
|
||||
// ------------------
|
||||
|
||||
exports.serializers = {};
|
||||
|
||||
var nextId = 0;
|
||||
exports.serializers.e = function(endpoint) {
|
||||
if (!('id' in endpoint)) {
|
||||
endpoint.id = nextId;
|
||||
nextId += 1;
|
||||
}
|
||||
return endpoint.id;
|
||||
};
|
362
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/flow.js
generated
vendored
Normal file
362
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/flow.js
generated
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
var assert = require('assert');
|
||||
|
||||
// The Flow class
|
||||
// ==============
|
||||
|
||||
// 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
|
||||
|
||||
var Duplex = require('stream').Duplex;
|
||||
|
||||
exports.Flow = Flow;
|
||||
|
||||
// Public API
|
||||
// ----------
|
||||
|
||||
// * **Event: 'error' (type)**: signals an error
|
||||
//
|
||||
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
|
||||
// ([as described in the standard][1]) using this method
|
||||
//
|
||||
// * **disableRemoteFlowControl()**: sends a WINDOW_UPDATE signaling that we don't want flow control
|
||||
//
|
||||
// * **disableLocalFlowControl()**: disables flow control for outgoing frames
|
||||
//
|
||||
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2
|
||||
|
||||
// API for child classes
|
||||
// ---------------------
|
||||
|
||||
// * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
|
||||
// with the given `flowControlId` (or every update frame if not given)
|
||||
//
|
||||
// * **_send()**: called when more frames should be pushed. The child class is expected to override
|
||||
// this (instead of the `_read` method of the Duplex class).
|
||||
//
|
||||
// * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is
|
||||
// expected to override this (instead of the `_write` method of the Duplex class).
|
||||
//
|
||||
// * **push(frame): bool**: schedules `frame` for sending.
|
||||
//
|
||||
// Returns `true` if it needs more frames in the output queue, `false` if the output queue is
|
||||
// full, and `null` if did not push the frame into the output queue (instead, it pushed it into
|
||||
// the flow control queue).
|
||||
//
|
||||
// * **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).
|
||||
//
|
||||
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
|
||||
//
|
||||
// * **_log**: the Flow class uses the `_log` object of the parent
|
||||
|
||||
// Constructor
|
||||
// -----------
|
||||
|
||||
// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
|
||||
// control window size of 65535 bytes.
|
||||
var INITIAL_WINDOW_SIZE = 65535;
|
||||
|
||||
// `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched.
|
||||
function Flow(flowControlId) {
|
||||
Duplex.call(this, { objectMode: true });
|
||||
|
||||
this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
|
||||
this._flowControlId = flowControlId;
|
||||
this._queue = [];
|
||||
this._ended = false;
|
||||
this._received = 0;
|
||||
this._remoteFlowControlDisabled = false;
|
||||
}
|
||||
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });
|
||||
|
||||
// Incoming frames
|
||||
// ---------------
|
||||
|
||||
// `_receive` is called when there's an incoming frame.
|
||||
Flow.prototype._receive = function _receive(frame, callback) {
|
||||
throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
|
||||
};
|
||||
|
||||
// `_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
|
||||
Flow.prototype._write = function _write(frame, encoding, callback) {
|
||||
if (frame.flags.END_STREAM || (frame.type === 'RST_STREAM')) {
|
||||
this._ended = true;
|
||||
}
|
||||
|
||||
if ((frame.type === 'DATA') && (frame.data.length > 0) && !this._remoteFlowControlDisabled) {
|
||||
this._receive(frame, function() {
|
||||
this._received += frame.data.length;
|
||||
if (!this._restoreWindowTimer) {
|
||||
this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
|
||||
}
|
||||
callback();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
else {
|
||||
this._receive(frame, callback);
|
||||
}
|
||||
|
||||
if ((frame.type === 'WINDOW_UPDATE') &&
|
||||
((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
|
||||
this._updateWindow(frame);
|
||||
}
|
||||
};
|
||||
|
||||
// `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
|
||||
// a WINDOW_UPDATE that restores the flow control window of the remote end.
|
||||
Flow.prototype._restoreWindow = function _restoreWindow() {
|
||||
delete this._restoreWindowTimer;
|
||||
if (!this._ended && !this._remoteFlowControlDisabled && (this._received > 0)) {
|
||||
this.push({
|
||||
type: 'WINDOW_UPDATE',
|
||||
flags: {},
|
||||
stream: this._flowControlId,
|
||||
window_size: this._received
|
||||
});
|
||||
this._received = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Must be called after sending a SETTINGS frame that turns off flow control on the remote side.
|
||||
Flow.prototype.disableRemoteFlowControl = function disableRemoteFlowControl() {
|
||||
this._log.debug('Turning off remote flow control');
|
||||
this._remoteFlowControlDisabled = true;
|
||||
};
|
||||
|
||||
// Outgoing frames - sending procedure
|
||||
// -----------------------------------
|
||||
|
||||
// flow
|
||||
// +-------------------------------------------------+
|
||||
// | |
|
||||
// +--------+ +---------+ |
|
||||
// read() | output | _read() | flow | _send() |
|
||||
// <----------| |<----------| control |<------------- |
|
||||
// | buffer | | buffer | |
|
||||
// +--------+ +---------+ |
|
||||
// | input | |
|
||||
// ---------->| |-----------------------------------> |
|
||||
// write() | buffer | _write() _receive() |
|
||||
// +--------+ |
|
||||
// | |
|
||||
// +-------------------------------------------------+
|
||||
|
||||
// `_send` is called when more frames should be pushed to the output buffer.
|
||||
Flow.prototype._send = function _send() {
|
||||
throw new Error('The _send() method has to be overridden by the child class!');
|
||||
};
|
||||
|
||||
// `_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
|
||||
Flow.prototype._read = function _read() {
|
||||
// * if the flow control queue is empty, then let the user push more frames
|
||||
if (this._queue.length === 0) {
|
||||
this._send();
|
||||
}
|
||||
|
||||
// * if there are items in the flow control queue, then let's put them into the output queue (to
|
||||
// the extent it is possible with respect to the window size and output queue feedback)
|
||||
else if (this._window > 0) {
|
||||
this._readableState.sync = true; // to avoid reentrant calls
|
||||
do {
|
||||
var moreNeeded = this._push(this._queue[0]);
|
||||
if (moreNeeded !== null) {
|
||||
this._queue.shift();
|
||||
}
|
||||
} while (moreNeeded && (this._queue.length > 0));
|
||||
this._readableState.sync = false;
|
||||
|
||||
assert((moreNeeded == false) || // * output queue is full
|
||||
(this._queue.length === 0) || // * flow control queue is empty
|
||||
(!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
|
||||
}
|
||||
|
||||
// * otherwise, come back when the flow control window is positive
|
||||
else {
|
||||
this.once('window_update', this._read);
|
||||
}
|
||||
};
|
||||
|
||||
var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383
|
||||
|
||||
// `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
|
||||
// size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
|
||||
// be under `limit`.
|
||||
Flow.prototype.read = function read(limit) {
|
||||
if (limit === 0) {
|
||||
return Duplex.prototype.read.call(this, 0);
|
||||
} else if (limit === -1) {
|
||||
limit = 0;
|
||||
} else if ((limit === undefined) || (limit > MAX_PAYLOAD_SIZE)) {
|
||||
limit = MAX_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
// * Looking at the first frame in the queue without pulling it out if possible. This will save
|
||||
// a costly unshift if the frame proves to be too large to return.
|
||||
var firstInQueue = this._readableState.buffer[0];
|
||||
var frame = firstInQueue || Duplex.prototype.read.call(this);
|
||||
|
||||
if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= limit)) {
|
||||
if (firstInQueue) {
|
||||
Duplex.prototype.read.call(this);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
else if (limit <= 0) {
|
||||
if (!firstInQueue) {
|
||||
this.unshift(frame);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
else {
|
||||
this._log.trace({ frame: frame, size: frame.data.length, forwardable: limit },
|
||||
'Splitting out forwardable part of a DATA frame.');
|
||||
var forwardable = {
|
||||
type: 'DATA',
|
||||
flags: {},
|
||||
stream: frame.stream,
|
||||
data: frame.data.slice(0, limit)
|
||||
};
|
||||
frame.data = frame.data.slice(limit);
|
||||
|
||||
if (!firstInQueue) {
|
||||
this.unshift(frame);
|
||||
}
|
||||
return forwardable;
|
||||
}
|
||||
};
|
||||
|
||||
// `_parentPush` pushes the given `frame` into the output queue
|
||||
Flow.prototype._parentPush = function _parentPush(frame) {
|
||||
this._log.trace({ frame: frame }, 'Pushing frame into the output queue');
|
||||
|
||||
if (frame && (frame.type === 'DATA') && (this._window !== Infinity)) {
|
||||
this._log.trace({ window: this._window, by: frame.data.length },
|
||||
'Decreasing flow control window size.');
|
||||
this._window -= frame.data.length;
|
||||
assert(this._window >= 0);
|
||||
}
|
||||
|
||||
return Duplex.prototype.push.call(this, frame);
|
||||
};
|
||||
|
||||
// `_push(frame)` pushes `frame` into the output queue and decreases the flow control window size.
|
||||
// It is capable of splitting DATA frames into smaller parts, if the window size is not enough to
|
||||
// push the whole frame. The return value is similar to `push` except that it returns `null` if it
|
||||
// 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;
|
||||
|
||||
if (!data || (data.length <= this._window)) {
|
||||
return this._parentPush(frame);
|
||||
}
|
||||
|
||||
else if (this._window <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
this._parentPush({
|
||||
type: 'DATA',
|
||||
flags: {},
|
||||
stream: frame.stream,
|
||||
data: data.slice(0, this._window)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
|
||||
Flow.prototype.push = function push(frame) {
|
||||
if (frame === null) {
|
||||
this._log.debug('Enqueueing outgoing End Of Stream');
|
||||
} else {
|
||||
this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
|
||||
}
|
||||
|
||||
var moreNeeded = null;
|
||||
if (this._queue.length === 0) {
|
||||
moreNeeded = this._push(frame);
|
||||
}
|
||||
|
||||
if (moreNeeded === null) {
|
||||
this._queue.push(frame);
|
||||
}
|
||||
|
||||
return moreNeeded;
|
||||
};
|
||||
|
||||
// `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the
|
||||
// [Stream](stream.html) class to mark the last frame with END_STREAM flag.
|
||||
Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() {
|
||||
var readableQueue = this._readableState.buffer;
|
||||
return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1];
|
||||
};
|
||||
|
||||
// Outgoing frames - managing the window size
|
||||
// ------------------------------------------
|
||||
|
||||
// Flow control window size is manipulated using the `_increaseWindow` method.
|
||||
//
|
||||
// * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled
|
||||
// again once disabled. Any attempt to re-enable flow control MUST be rejected with a
|
||||
// FLOW_CONTROL_ERROR error code.
|
||||
// * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
|
||||
// depends on it being a stream or the connection itself.
|
||||
|
||||
var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;
|
||||
|
||||
Flow.prototype._increaseWindow = function _increaseWindow(size) {
|
||||
if ((this._window === Infinity) && (size !== Infinity)) {
|
||||
this._log.error('Trying to increase flow control window after flow control was turned off.');
|
||||
this.emit('error', 'FLOW_CONTROL_ERROR');
|
||||
} else {
|
||||
this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
|
||||
this._window += size;
|
||||
if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
|
||||
this._log.error('Flow control window grew too large.');
|
||||
this.emit('error', 'FLOW_CONTROL_ERROR');
|
||||
} else {
|
||||
this.emit('window_update');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
|
||||
// modifies the flow control window:
|
||||
//
|
||||
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
|
||||
// END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
|
||||
// flag set is ignored.
|
||||
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
|
||||
// specified in the frame.
|
||||
Flow.prototype._updateWindow = function _updateWindow(frame) {
|
||||
this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size);
|
||||
};
|
||||
|
||||
// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
|
||||
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
|
||||
// calling the `setInitialWindow` method. The window size has to be modified by the difference
|
||||
// between the new value and the old value.
|
||||
Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) {
|
||||
this._increaseWindow(initialWindow - this._initialWindow);
|
||||
this._initialWindow = initialWindow;
|
||||
};
|
||||
|
||||
// Flow control for outgoing frames can be disabled by the peer with various methods.
|
||||
Flow.prototype.disableLocalFlowControl = function disableLocalFlowControl() {
|
||||
this._increaseWindow(Infinity);
|
||||
};
|
761
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js
generated
vendored
Normal file
761
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js
generated
vendored
Normal file
@ -0,0 +1,761 @@
|
||||
// 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
|
||||
var assert = require('assert');
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
|
||||
exports.Serializer = Serializer;
|
||||
exports.Deserializer = Deserializer;
|
||||
|
||||
var logData = Boolean(process.env.HTTP2_LOG_DATA);
|
||||
|
||||
// Serializer
|
||||
// ----------
|
||||
//
|
||||
// Frame Objects
|
||||
// * * * * * * * --+---------------------------
|
||||
// | |
|
||||
// v v Buffers
|
||||
// [] -----> Payload Ser. --[buffers]--> Header Ser. --> * * * *
|
||||
// empty adds payload adds header
|
||||
// array buffers buffer
|
||||
|
||||
function Serializer(log, sizeLimit) {
|
||||
this._log = log.child({ component: 'serializer' });
|
||||
this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
|
||||
Transform.call(this, { objectMode: true });
|
||||
}
|
||||
Serializer.prototype = Object.create(Transform.prototype, { constructor: { value: Serializer } });
|
||||
|
||||
// When there's an incoming frame object, it first generates the frame type specific part of the
|
||||
// frame (payload), and then then adds the header part which holds fields that are common to all
|
||||
// frame types (like the length of the payload).
|
||||
Serializer.prototype._transform = function _transform(frame, encoding, done) {
|
||||
this._log.trace({ frame: frame }, 'Outgoing frame');
|
||||
|
||||
assert(frame.type in Serializer, 'Unknown frame type: ' + frame.type);
|
||||
|
||||
var buffers = [];
|
||||
Serializer[frame.type](frame, buffers);
|
||||
Serializer.commonHeader(frame, buffers);
|
||||
|
||||
assert(buffers[0].readUInt16BE(0) <= this._sizeLimit, 'Frame too large!');
|
||||
|
||||
for (var i = 0; i < buffers.length; i++) {
|
||||
if (logData) {
|
||||
this._log.trace({ data: buffers[i] }, 'Outgoing data');
|
||||
}
|
||||
this.push(buffers[i]);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
// Deserializer
|
||||
// ------------
|
||||
//
|
||||
// Buffers
|
||||
// * * * * --------+-------------------------
|
||||
// | |
|
||||
// v v Frame Objects
|
||||
// {} -----> Header Des. --{frame}--> Payload Des. --> * * * * * * *
|
||||
// empty adds parsed adds parsed
|
||||
// object header properties payload properties
|
||||
|
||||
function Deserializer(log, sizeLimit) {
|
||||
this._log = log.child({ component: 'deserializer' });
|
||||
this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
|
||||
Transform.call(this, { objectMode: true });
|
||||
this._next(COMMON_HEADER_SIZE);
|
||||
}
|
||||
Deserializer.prototype = Object.create(Transform.prototype, { constructor: { value: Deserializer } });
|
||||
|
||||
// The Deserializer is stateful, and it's two main alternating states are: *waiting for header* and
|
||||
// *waiting for payload*. The state is stored in the boolean property `_waitingForHeader`.
|
||||
//
|
||||
// When entering a new state, a `_buffer` is created that will hold the accumulated data (header or
|
||||
// payload). The `_cursor` is used to track the progress.
|
||||
Deserializer.prototype._next = function(size) {
|
||||
this._cursor = 0;
|
||||
this._buffer = new Buffer(size);
|
||||
this._waitingForHeader = !this._waitingForHeader;
|
||||
if (this._waitingForHeader) {
|
||||
this._frame = {};
|
||||
}
|
||||
};
|
||||
|
||||
// Parsing an incoming buffer is an iterative process because it can hold multiple frames if it's
|
||||
// large enough. A `cursor` is used to track the progress in parsing the incoming `chunk`.
|
||||
Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
|
||||
var cursor = 0;
|
||||
|
||||
if (logData) {
|
||||
this._log.trace({ data: chunk }, 'Incoming data');
|
||||
}
|
||||
|
||||
while(cursor < chunk.length) {
|
||||
// The content of an incoming buffer is first copied to `_buffer`. If it can't hold the full
|
||||
// chunk, then only a part of it is copied.
|
||||
var toCopy = Math.min(chunk.length - cursor, this._buffer.length - this._cursor);
|
||||
chunk.copy(this._buffer, this._cursor, cursor, cursor + toCopy);
|
||||
this._cursor += toCopy;
|
||||
cursor += toCopy;
|
||||
|
||||
// When `_buffer` is full, it's content gets parsed either as header or payload depending on
|
||||
// the actual state.
|
||||
|
||||
// If it's header then the parsed data is stored in a temporary variable and then the
|
||||
// deserializer waits for the specified length payload.
|
||||
if ((this._cursor === this._buffer.length) && this._waitingForHeader) {
|
||||
var payloadSize = Deserializer.commonHeader(this._buffer, this._frame);
|
||||
if (payloadSize <= this._sizeLimit) {
|
||||
this._next(payloadSize);
|
||||
} else {
|
||||
this.emit('error', 'FRAME_SIZE_ERROR');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's payload then the the frame object is finalized and then gets pushed out.
|
||||
// Unknown frame types are ignored.
|
||||
//
|
||||
// Note: If we just finished the parsing of a header and the payload length is 0, this branch
|
||||
// will also run.
|
||||
if ((this._cursor === this._buffer.length) && !this._waitingForHeader) {
|
||||
if (this._frame.type) {
|
||||
var error = Deserializer[this._frame.type](this._buffer, this._frame);
|
||||
if (error) {
|
||||
this._log.error('Incoming frame parsing error: ' + error);
|
||||
this.emit('error', 'PROTOCOL_ERROR');
|
||||
} else {
|
||||
this._log.trace({ frame: this._frame }, 'Incoming frame');
|
||||
this.push(this._frame);
|
||||
}
|
||||
} else {
|
||||
this._log.warn({ frame: this._frame }, 'Unknown type incoming frame');
|
||||
}
|
||||
this._next(COMMON_HEADER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
// [Frame Header](http://http2.github.io/http2-spec/#FrameHeader)
|
||||
// --------------------------------------------------------------
|
||||
//
|
||||
// HTTP/2.0 frames share a common base format consisting of an 8-byte header followed by 0 to 65535
|
||||
// bytes of data.
|
||||
//
|
||||
// Additional size limits can be set by specific application uses. HTTP limits the frame size to
|
||||
// 16,383 octets.
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | R | Length (14) | Type (8) | Flags (8) |
|
||||
// +-+-+---------------------------+---------------+---------------+
|
||||
// |R| Stream Identifier (31) |
|
||||
// +-+-------------------------------------------------------------+
|
||||
// | Frame Data (0...) ...
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// The fields of the frame header are defined as:
|
||||
//
|
||||
// * R:
|
||||
// A reserved 2-bit field. The semantics of these bits are undefined and the bits MUST remain
|
||||
// unset (0) when sending and MUST be ignored when receiving.
|
||||
//
|
||||
// * Length:
|
||||
// The length of the frame data expressed as an unsigned 14-bit integer. The 8 bytes of the frame
|
||||
// header are not included in this value.
|
||||
//
|
||||
// * Type:
|
||||
// The 8-bit type of the frame. The frame type determines how the remainder of the frame header
|
||||
// and data are interpreted. Implementations MUST ignore unsupported and unrecognized frame types.
|
||||
//
|
||||
// * Flags:
|
||||
// An 8-bit field reserved for frame-type specific boolean flags.
|
||||
//
|
||||
// Flags are assigned semantics specific to the indicated frame type. Flags that have no defined
|
||||
// semantics for a particular frame type MUST be ignored, and MUST be left unset (0) when sending.
|
||||
//
|
||||
// * R:
|
||||
// A reserved 1-bit field. The semantics of this bit are undefined and the bit MUST remain unset
|
||||
// (0) when sending and MUST be ignored when receiving.
|
||||
//
|
||||
// * Stream Identifier:
|
||||
// A 31-bit stream identifier. The value 0 is reserved for frames that are associated with the
|
||||
// connection as a whole as opposed to an individual stream.
|
||||
//
|
||||
// The structure and content of the remaining frame data is dependent entirely on the frame type.
|
||||
|
||||
var COMMON_HEADER_SIZE = 8;
|
||||
var MAX_PAYLOAD_SIZE = 16383;
|
||||
|
||||
var frameTypes = [];
|
||||
|
||||
var frameFlags = {};
|
||||
|
||||
var genericAttributes = ['type', 'flags', 'stream'];
|
||||
|
||||
var typeSpecificAttributes = {};
|
||||
|
||||
Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
|
||||
var headerBuffer = new Buffer(COMMON_HEADER_SIZE);
|
||||
|
||||
var size = 0;
|
||||
for (var i = 0; i < buffers.length; i++) {
|
||||
size += buffers[i].length;
|
||||
}
|
||||
assert(size <= MAX_PAYLOAD_SIZE, size);
|
||||
headerBuffer.writeUInt16BE(size, 0);
|
||||
|
||||
var typeId = frameTypes.indexOf(frame.type); // If we are here then the type is valid for sure
|
||||
headerBuffer.writeUInt8(typeId, 2);
|
||||
|
||||
var flagByte = 0;
|
||||
for (var flag in frame.flags) {
|
||||
var position = frameFlags[frame.type].indexOf(flag);
|
||||
assert(position !== -1, 'Unknown flag for frame type ' + frame.type + ': ' + flag);
|
||||
if (frame.flags[flag]) {
|
||||
flagByte |= (1 << position);
|
||||
}
|
||||
}
|
||||
headerBuffer.writeUInt8(flagByte, 3);
|
||||
|
||||
assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream);
|
||||
headerBuffer.writeUInt32BE(frame.stream || 0, 4);
|
||||
|
||||
buffers.unshift(headerBuffer);
|
||||
};
|
||||
|
||||
Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
|
||||
var length = buffer.readUInt16BE(0);
|
||||
|
||||
frame.type = frameTypes[buffer.readUInt8(2)];
|
||||
|
||||
frame.flags = {};
|
||||
var flagByte = buffer.readUInt8(3);
|
||||
var definedFlags = frameFlags[frame.type];
|
||||
for (var i = 0; i < definedFlags.length; i++) {
|
||||
frame.flags[definedFlags[i]] = Boolean(flagByte & (1 << i));
|
||||
}
|
||||
|
||||
frame.stream = buffer.readUInt32BE(4) & 0x7fffffff;
|
||||
|
||||
return length;
|
||||
};
|
||||
|
||||
// Frame types
|
||||
// ===========
|
||||
|
||||
// Every frame type is registered in the following places:
|
||||
//
|
||||
// * `frameTypes`: a register of frame type codes (used by `commonHeader()`)
|
||||
// * `frameFlags`: a register of valid flags for frame types (used by `commonHeader()`)
|
||||
// * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by
|
||||
// logging code and also serves as documentation for frame objects)
|
||||
|
||||
// [DATA Frames](http://http2.github.io/http2-spec/#DataFrames)
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
|
||||
// stream.
|
||||
//
|
||||
// The DATA frame defines the following flags:
|
||||
//
|
||||
// * END_STREAM (0x1):
|
||||
// Bit 1 being set indicates that this frame is the last that the endpoint will send for the
|
||||
// identified stream.
|
||||
// * RESERVED (0x2):
|
||||
// Bit 2 is reserved for future use.
|
||||
|
||||
frameTypes[0x0] = 'DATA';
|
||||
|
||||
frameFlags.DATA = ['END_STREAM', 'RESERVED'];
|
||||
|
||||
typeSpecificAttributes.DATA = ['data'];
|
||||
|
||||
Serializer.DATA = function writeData(frame, buffers) {
|
||||
buffers.push(frame.data);
|
||||
};
|
||||
|
||||
Deserializer.DATA = function readData(buffer, frame) {
|
||||
frame.data = buffer;
|
||||
};
|
||||
|
||||
// [HEADERS](http://http2.github.io/http2-spec/#HEADERS)
|
||||
// --------------------------------------------------------------
|
||||
//
|
||||
// The HEADERS frame (type=0x1) allows the sender to create a stream.
|
||||
//
|
||||
// The HEADERS frame defines the following flags:
|
||||
//
|
||||
// * END_STREAM (0x1):
|
||||
// Bit 1 being set indicates that this frame is the last that the endpoint will send for the
|
||||
// identified stream.
|
||||
// * RESERVED (0x2):
|
||||
// Bit 2 is reserved for future use.
|
||||
// * END_HEADERS (0x4):
|
||||
// The END_HEADERS bit indicates that this frame contains the entire payload necessary to provide
|
||||
// a complete set of headers.
|
||||
// * PRIORITY (0x8):
|
||||
// Bit 4 being set indicates that the first four octets of this frame contain a single reserved
|
||||
// bit and a 31-bit priority.
|
||||
|
||||
frameTypes[0x1] = 'HEADERS';
|
||||
|
||||
frameFlags.HEADERS = ['END_STREAM', 'RESERVED', 'END_HEADERS', 'PRIORITY'];
|
||||
|
||||
typeSpecificAttributes.HEADERS = ['priority', 'headers', 'data'];
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |X| (Optional) Priority (31) |
|
||||
// +-+-------------------------------------------------------------+
|
||||
// | Header Block (*) ...
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// The payload of a HEADERS frame contains a Headers Block
|
||||
|
||||
Serializer.HEADERS = function writeHeadersPriority(frame, buffers) {
|
||||
if (frame.flags.PRIORITY) {
|
||||
var buffer = new Buffer(4);
|
||||
assert((0 <= frame.priority) && (frame.priority <= 0xffffffff), frame.priority);
|
||||
buffer.writeUInt32BE(frame.priority, 0);
|
||||
buffers.push(buffer);
|
||||
}
|
||||
buffers.push(frame.data);
|
||||
};
|
||||
|
||||
Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
|
||||
if (frame.flags.PRIORITY) {
|
||||
frame.priority = buffer.readUInt32BE(0) & 0x7fffffff;
|
||||
frame.data = buffer.slice(4);
|
||||
} else {
|
||||
frame.data = buffer;
|
||||
}
|
||||
};
|
||||
|
||||
// [PRIORITY](http://http2.github.io/http2-spec/#PRIORITY)
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
|
||||
//
|
||||
// The PRIORITY frame does not define any flags.
|
||||
|
||||
frameTypes[0x2] = 'PRIORITY';
|
||||
|
||||
frameFlags.PRIORITY = [];
|
||||
|
||||
typeSpecificAttributes.PRIORITY = ['priority'];
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |X| Priority (31) |
|
||||
// +-+-------------------------------------------------------------+
|
||||
//
|
||||
// The payload of a PRIORITY frame contains a single reserved bit and a 31-bit priority.
|
||||
|
||||
Serializer.PRIORITY = function writePriority(frame, buffers) {
|
||||
var buffer = new Buffer(4);
|
||||
buffer.writeUInt32BE(frame.priority, 0);
|
||||
buffers.push(buffer);
|
||||
};
|
||||
|
||||
Deserializer.PRIORITY = function readPriority(buffer, frame) {
|
||||
frame.priority = buffer.readUInt32BE(0);
|
||||
};
|
||||
|
||||
// [RST_STREAM](http://http2.github.io/http2-spec/#RST_STREAM)
|
||||
// -----------------------------------------------------------
|
||||
//
|
||||
// The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
|
||||
//
|
||||
// No type-flags are defined.
|
||||
|
||||
frameTypes[0x3] = 'RST_STREAM';
|
||||
|
||||
frameFlags.RST_STREAM = [];
|
||||
|
||||
typeSpecificAttributes.RST_STREAM = ['error'];
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Error Code (32) |
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the error
|
||||
// code (see Error Codes). The error code indicates why the stream is being terminated.
|
||||
|
||||
Serializer.RST_STREAM = function writeRstStream(frame, buffers) {
|
||||
var buffer = new Buffer(4);
|
||||
var code = errorCodes.indexOf(frame.error);
|
||||
assert((0 <= code) && (code <= 0xffffffff), code);
|
||||
buffer.writeUInt32BE(code, 0);
|
||||
buffers.push(buffer);
|
||||
};
|
||||
|
||||
Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
|
||||
frame.error = errorCodes[buffer.readUInt32BE(0)];
|
||||
};
|
||||
|
||||
// [SETTINGS](http://http2.github.io/http2-spec/#SETTINGS)
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
|
||||
// communicate.
|
||||
//
|
||||
// The SETTINGS frame defines the following flag:
|
||||
|
||||
// * ACK (0x1):
|
||||
// Bit 1 being set indicates that this frame acknowledges receipt and application of the peer's
|
||||
// SETTINGS frame.
|
||||
frameTypes[0x4] = 'SETTINGS';
|
||||
|
||||
frameFlags.SETTINGS = ['ACK'];
|
||||
|
||||
typeSpecificAttributes.SETTINGS = ['settings'];
|
||||
|
||||
// The payload of a SETTINGS frame consists of zero or more settings. Each setting consists of an
|
||||
// 8-bit reserved field, an unsigned 24-bit setting identifier, and an unsigned 32-bit value.
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved(8) | Setting Identifier (24) |
|
||||
// +---------------+-----------------------------------------------+
|
||||
// | Value (32) |
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// Each setting in a SETTINGS frame replaces the existing value for that setting. Settings are
|
||||
// processed in the order in which they appear, and a receiver of a SETTINGS frame does not need to
|
||||
// maintain any state other than the current value of settings. Therefore, the value of a setting
|
||||
// is the last value that is seen by a receiver. This permits the inclusion of the same settings
|
||||
// multiple times in the same SETTINGS frame, though doing so does nothing other than waste
|
||||
// connection capacity.
|
||||
|
||||
Serializer.SETTINGS = function writeSettings(frame, buffers) {
|
||||
var settings = [], settingsLeft = Object.keys(frame.settings);
|
||||
definedSettings.forEach(function(setting, id) {
|
||||
if (setting.name in frame.settings) {
|
||||
settingsLeft.splice(settingsLeft.indexOf(setting.name), 1);
|
||||
var value = frame.settings[setting.name];
|
||||
settings.push({ id: id, value: setting.flag ? Boolean(value) : value });
|
||||
}
|
||||
});
|
||||
assert(settingsLeft.length === 0, 'Unknown settings: ' + settingsLeft.join(', '));
|
||||
|
||||
var buffer = new Buffer(settings.length * 8);
|
||||
for (var i = 0; i < settings.length; i++) {
|
||||
buffer.writeUInt32BE(settings[i].id & 0xffffff, i*8);
|
||||
buffer.writeUInt32BE(settings[i].value, i*8 + 4);
|
||||
}
|
||||
|
||||
buffers.push(buffer);
|
||||
};
|
||||
|
||||
Deserializer.SETTINGS = function readSettings(buffer, frame) {
|
||||
frame.settings = {};
|
||||
|
||||
if (buffer.length % 8 !== 0) {
|
||||
return 'Invalid SETTINGS frame';
|
||||
}
|
||||
for (var i = 0; i < buffer.length / 8; i++) {
|
||||
var id = buffer.readUInt32BE(i*8) & 0xffffff;
|
||||
var setting = definedSettings[id];
|
||||
if (setting) {
|
||||
var value = buffer.readUInt32BE(i*8 + 4);
|
||||
frame.settings[setting.name] = setting.flag ? Boolean(value & 0x1) : value;
|
||||
} else {
|
||||
/* Unknown setting, ignoring */
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The following settings are defined:
|
||||
var definedSettings = [];
|
||||
|
||||
// * SETTINGS_HEADER_TABLE_SIZE (1):
|
||||
// Allows the sender to inform the remote endpoint of the size of the header compression table
|
||||
// used to decode header blocks.
|
||||
definedSettings[1] = { name: 'SETTINGS_HEADER_TABLE_SIZE', flag: false };
|
||||
|
||||
// * SETTINGS_ENABLE_PUSH (2):
|
||||
// This setting can be use to disable server push. An endpoint MUST NOT send a PUSH_PROMISE frame
|
||||
// if it receives this setting set to a value of 0. The default value is 1, which indicates that
|
||||
// push is permitted.
|
||||
definedSettings[2] = { name: 'SETTINGS_ENABLE_PUSH', flag: true };
|
||||
|
||||
// * SETTINGS_MAX_CONCURRENT_STREAMS (4):
|
||||
// indicates the maximum number of concurrent streams that the sender will allow.
|
||||
definedSettings[4] = { name: 'SETTINGS_MAX_CONCURRENT_STREAMS', flag: false };
|
||||
|
||||
// * SETTINGS_INITIAL_WINDOW_SIZE (7):
|
||||
// indicates the sender's initial stream window size (in bytes) for new streams.
|
||||
definedSettings[7] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
|
||||
|
||||
// * SETTINGS_FLOW_CONTROL_OPTIONS (10):
|
||||
// indicates that streams directed to the sender will not be subject to flow control. The least
|
||||
// significant bit (0x1) is set to indicate that new streams are not flow controlled. All other
|
||||
// bits are reserved.
|
||||
definedSettings[10] = { name: 'SETTINGS_FLOW_CONTROL_OPTIONS', flag: true };
|
||||
|
||||
// [PUSH_PROMISE](http://http2.github.io/http2-spec/#PUSH_PROMISE)
|
||||
// ---------------------------------------------------------------
|
||||
//
|
||||
// The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
|
||||
// sender intends to initiate.
|
||||
//
|
||||
// The PUSH_PROMISE frame defines the following flags:
|
||||
//
|
||||
// * END_PUSH_PROMISE (0x4):
|
||||
// The END_PUSH_PROMISE bit indicates that this frame contains the entire payload necessary to
|
||||
// provide a complete set of headers.
|
||||
|
||||
frameTypes[0x5] = 'PUSH_PROMISE';
|
||||
|
||||
frameFlags.PUSH_PROMISE = ['RESERVED1', 'RESERVED2', 'END_PUSH_PROMISE'];
|
||||
|
||||
typeSpecificAttributes.PUSH_PROMISE = ['promised_stream', 'headers', 'data'];
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |X| Promised-Stream-ID (31) |
|
||||
// +-+-------------------------------------------------------------+
|
||||
// | Header Block (*) ...
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// The PUSH_PROMISE frame includes the unsigned 31-bit identifier of
|
||||
// the stream the endpoint plans to create along with a minimal set of headers that provide
|
||||
// additional context for the stream.
|
||||
|
||||
Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) {
|
||||
var buffer = new Buffer(4);
|
||||
|
||||
var promised_stream = frame.promised_stream;
|
||||
assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream);
|
||||
buffer.writeUInt32BE(promised_stream, 0);
|
||||
|
||||
buffers.push(buffer);
|
||||
buffers.push(frame.data);
|
||||
};
|
||||
|
||||
Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) {
|
||||
frame.promised_stream = buffer.readUInt32BE(0) & 0x7fffffff;
|
||||
frame.data = buffer.slice(4);
|
||||
};
|
||||
|
||||
// [PING](http://http2.github.io/http2-spec/#PING)
|
||||
// -----------------------------------------------
|
||||
//
|
||||
// The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
|
||||
// sender, as well as determining whether an idle connection is still functional.
|
||||
//
|
||||
// The PING frame defines one type-specific flag:
|
||||
//
|
||||
// * ACK (0x1):
|
||||
// Bit 1 being set indicates that this PING frame is a PING response.
|
||||
|
||||
frameTypes[0x6] = 'PING';
|
||||
|
||||
frameFlags.PING = ['ACK'];
|
||||
|
||||
typeSpecificAttributes.PING = ['data'];
|
||||
|
||||
// In addition to the frame header, PING frames MUST contain 8 additional octets of opaque data.
|
||||
|
||||
Serializer.PING = function writePing(frame, buffers) {
|
||||
buffers.push(frame.data);
|
||||
};
|
||||
|
||||
Deserializer.PING = function readPing(buffer, frame) {
|
||||
if (buffer.length !== 8) {
|
||||
return 'Invalid size PING frame';
|
||||
}
|
||||
frame.data = buffer;
|
||||
};
|
||||
|
||||
// [GOAWAY](http://http2.github.io/http2-spec/#GOAWAY)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
|
||||
//
|
||||
// The GOAWAY frame does not define any flags.
|
||||
|
||||
frameTypes[0x7] = 'GOAWAY';
|
||||
|
||||
frameFlags.GOAWAY = [];
|
||||
|
||||
typeSpecificAttributes.GOAWAY = ['last_stream', 'error'];
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |X| Last-Stream-ID (31) |
|
||||
// +-+-------------------------------------------------------------+
|
||||
// | Error Code (32) |
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
// The last stream identifier in the GOAWAY frame contains the highest numbered stream identifier
|
||||
// for which the sender of the GOAWAY frame has received frames on and might have taken some action
|
||||
// on.
|
||||
//
|
||||
// The GOAWAY frame also contains a 32-bit error code (see Error Codes) that contains the reason for
|
||||
// closing the connection.
|
||||
|
||||
Serializer.GOAWAY = function writeGoaway(frame, buffers) {
|
||||
var buffer = new Buffer(8);
|
||||
|
||||
var last_stream = frame.last_stream;
|
||||
assert((0 <= last_stream) && (last_stream <= 0x7fffffff), last_stream);
|
||||
buffer.writeUInt32BE(last_stream, 0);
|
||||
|
||||
var code = errorCodes.indexOf(frame.error);
|
||||
assert((0 <= code) && (code <= 0xffffffff), code);
|
||||
buffer.writeUInt32BE(code, 4);
|
||||
|
||||
buffers.push(buffer);
|
||||
};
|
||||
|
||||
Deserializer.GOAWAY = function readGoaway(buffer, frame) {
|
||||
frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff;
|
||||
frame.error = errorCodes[buffer.readUInt32BE(4)];
|
||||
};
|
||||
|
||||
// [WINDOW_UPDATE](http://http2.github.io/http2-spec/#WINDOW_UPDATE)
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
// The WINDOW_UPDATE frame (type=0x9) is used to implement flow control.
|
||||
//
|
||||
// The WINDOW_UPDATE frame does not define any flags.
|
||||
|
||||
frameTypes[0x9] = 'WINDOW_UPDATE';
|
||||
|
||||
frameFlags.WINDOW_UPDATE = [];
|
||||
|
||||
typeSpecificAttributes.WINDOW_UPDATE = ['window_size'];
|
||||
|
||||
// The payload of a WINDOW_UPDATE frame is a 32-bit value indicating the additional number of bytes
|
||||
// that the sender can transmit in addition to the existing flow control window. The legal range
|
||||
// for this field is 1 to 2^31 - 1 (0x7fffffff) bytes; the most significant bit of this value is
|
||||
// reserved.
|
||||
|
||||
Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) {
|
||||
var buffer = new Buffer(4);
|
||||
|
||||
var window_size = frame.window_size;
|
||||
assert((0 <= window_size) && (window_size <= 0x7fffffff), window_size);
|
||||
buffer.writeUInt32BE(window_size, 0);
|
||||
|
||||
buffers.push(buffer);
|
||||
};
|
||||
|
||||
Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) {
|
||||
frame.window_size = buffer.readUInt32BE(0) & 0x7fffffff;
|
||||
};
|
||||
|
||||
// [CONTINUATION](http://http2.github.io/http2-spec/#CONTINUATION)
|
||||
// ------------------------------------------------------------
|
||||
//
|
||||
// The CONTINUATION frame (type=0xA) is used to continue a sequence of header block fragments.
|
||||
//
|
||||
// The CONTINUATION frame defines the following flag:
|
||||
//
|
||||
// * END_HEADERS (0x4):
|
||||
// The END_HEADERS bit indicates that this frame ends the sequence of header block fragments
|
||||
// necessary to provide a complete set of headers.
|
||||
|
||||
frameTypes[0xA] = 'CONTINUATION';
|
||||
|
||||
frameFlags.CONTINUATION = ['RESERVED1', 'RESERVED2', 'END_HEADERS'];
|
||||
|
||||
typeSpecificAttributes.CONTINUATION = ['headers', 'data'];
|
||||
|
||||
Serializer.CONTINUATION = function writeContinuation(frame, buffers) {
|
||||
buffers.push(frame.data);
|
||||
};
|
||||
|
||||
Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
|
||||
frame.data = buffer;
|
||||
};
|
||||
|
||||
// [Error Codes](http://http2.github.io/http2-spec/#ErrorCodes)
|
||||
// ------------------------------------------------------------
|
||||
|
||||
var errorCodes = [
|
||||
'NO_ERROR',
|
||||
'PROTOCOL_ERROR',
|
||||
'INTERNAL_ERROR',
|
||||
'FLOW_CONTROL_ERROR',
|
||||
'SETTINGS_TIMEOUT',
|
||||
'STREAM_CLOSED',
|
||||
'FRAME_SIZE_ERROR',
|
||||
'REFUSED_STREAM',
|
||||
'CANCEL',
|
||||
'COMPRESSION_ERROR',
|
||||
'CONNECT_ERROR'
|
||||
];
|
||||
errorCodes[420] = 'ENHANCE_YOUR_CALM';
|
||||
|
||||
// Logging
|
||||
// -------
|
||||
|
||||
// [Bunyan serializers](https://github.com/trentm/node-bunyan#serializers) to improve logging output
|
||||
// for debug messages emitted in this component.
|
||||
exports.serializers = {};
|
||||
|
||||
// * `frame` serializer: it transforms data attributes from Buffers to hex strings and filters out
|
||||
// flags that are not present.
|
||||
var frameCounter = 0;
|
||||
exports.serializers.frame = function(frame) {
|
||||
if (!frame) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('id' in frame) {
|
||||
return frame.id;
|
||||
}
|
||||
|
||||
frame.id = frameCounter;
|
||||
frameCounter += 1;
|
||||
|
||||
var logEntry = { id: frame.id };
|
||||
genericAttributes.concat(typeSpecificAttributes[frame.type]).forEach(function(name) {
|
||||
logEntry[name] = frame[name];
|
||||
});
|
||||
|
||||
if (frame.data instanceof Buffer) {
|
||||
if (logEntry.data.length > 50) {
|
||||
logEntry.data = frame.data.slice(0, 47).toString('hex') + '...';
|
||||
} else {
|
||||
logEntry.data = frame.data.toString('hex');
|
||||
}
|
||||
|
||||
if (!('length' in logEntry)) {
|
||||
logEntry.length = frame.data.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.promised_stream instanceof Object) {
|
||||
logEntry.promised_stream = 'stream-' + frame.promised_stream.id;
|
||||
}
|
||||
|
||||
logEntry.flags = Object.keys(frame.flags || {}).filter(function(name) {
|
||||
return frame.flags[name] === true;
|
||||
});
|
||||
|
||||
return logEntry;
|
||||
};
|
||||
|
||||
// * `data` serializer: it simply transforms a buffer to a hex string.
|
||||
exports.serializers.data = function(data) {
|
||||
return data.toString('hex');
|
||||
};
|
90
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/index.js
generated
vendored
Normal file
90
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/index.js
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 06)][http2]
|
||||
// framing layer for [node.js][node].
|
||||
//
|
||||
// The main building blocks are [node.js streams][node-stream] that are connected through pipes.
|
||||
//
|
||||
// The main components are:
|
||||
//
|
||||
// * [Endpoint](endpoint.html): represents an HTTP/2 endpoint (client or server). It's
|
||||
// responsible for the the first part of the handshake process (sending/receiving the
|
||||
// [connection header][http2-connheader]) and manages other components (framer, compressor,
|
||||
// connection, streams) that make up a client or server.
|
||||
//
|
||||
// * [Connection](connection.html): multiplexes the active HTTP/2 streams, manages connection
|
||||
// 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).
|
||||
// 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).
|
||||
//
|
||||
// * [Flow](flow.html): implements flow control for Connection and Stream as parent class.
|
||||
//
|
||||
// * [Compressor and Decompressor](compressor.html): compression and decompression of HEADER and
|
||||
// PUSH_PROMISE frames
|
||||
//
|
||||
// * [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-06
|
||||
// [http2-connheader]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#section-3.5
|
||||
// [http2-stream]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#section-5
|
||||
// [http2-streamstate]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#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
|
||||
|
||||
exports.Endpoint = require('./endpoint').Endpoint;
|
||||
|
||||
/* Bunyan serializers exported by submodules that are worth adding when creating a logger. */
|
||||
exports.serializers = {};
|
||||
var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint'];
|
||||
modules.map(require).forEach(function(module) {
|
||||
for (var name in module.serializers) {
|
||||
exports.serializers[name] = module.serializers[name];
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Stream API Endpoint API
|
||||
Stream data
|
||||
|
||||
| ^ | ^
|
||||
| | | |
|
||||
| | | |
|
||||
+-----------|------------|---------------------------------------+
|
||||
| | | Endpoint |
|
||||
| | | |
|
||||
| +-------|------------|-----------------------------------+ |
|
||||
| | | | Connection | |
|
||||
| | v | | |
|
||||
| | +-----------------------+ +-------------------- | |
|
||||
| | | Stream | | Stream ... | |
|
||||
| | +-----------------------+ +-------------------- | |
|
||||
| | | ^ | ^ | |
|
||||
| | v | v | | |
|
||||
| | +------------+--+--------+--+------------+- ... | |
|
||||
| | | ^ | |
|
||||
| | | | | |
|
||||
| +-----------------------|--------|-----------------------+ |
|
||||
| | | |
|
||||
| v | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | Compressor | | Decompressor | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | ^ |
|
||||
| v | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | Serializer | | Deserializer | |
|
||||
| +--------------------------+ +--------------------------+ |
|
||||
| | ^ |
|
||||
+---------------------------|--------|---------------------------+
|
||||
| |
|
||||
v |
|
||||
|
||||
Raw data
|
||||
|
||||
*/
|
615
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js
generated
vendored
Normal file
615
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js
generated
vendored
Normal file
@ -0,0 +1,615 @@
|
||||
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)
|
||||
// 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`).
|
||||
|
||||
var Duplex = require('stream').Duplex;
|
||||
|
||||
exports.Stream = Stream;
|
||||
|
||||
// Public API
|
||||
// ----------
|
||||
|
||||
// * **new Stream(log)**: create a new Stream
|
||||
//
|
||||
// * **Event: 'headers' (headers)**: signals incoming headers
|
||||
//
|
||||
// * **Event: 'promise' (stream, headers)**: signals an incoming push promise
|
||||
//
|
||||
// * **Event: 'priority' (priority)**: signals a priority change. `priority` is a number between 0
|
||||
// (highest priority) and 2^31-1 (lowest priority). Default value is 2^30.
|
||||
//
|
||||
// * **Event: 'error' (type)**: signals an error
|
||||
//
|
||||
// * **headers(headers)**: send headers
|
||||
//
|
||||
// * **promise(headers): Stream**: promise a stream
|
||||
//
|
||||
// * **priority(priority)**: set the priority of the stream. Priority can be changed by the peer
|
||||
// too, but once it is set locally, it can not be changed remotely.
|
||||
//
|
||||
// * **reset(error)**: reset the stream with an error code
|
||||
//
|
||||
// * **upstream**: a [Flow](flow.js) that is used by the parent connection to write/read frames
|
||||
// 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
|
||||
|
||||
// Constructor
|
||||
// -----------
|
||||
|
||||
// The main aspects of managing the stream are:
|
||||
function Stream(log) {
|
||||
Duplex.call(this);
|
||||
|
||||
// * logging
|
||||
this._log = log.child({ component: 'stream', s: this });
|
||||
|
||||
// * receiving and sending stream management commands
|
||||
this._initializeManagement();
|
||||
|
||||
// * sending and receiving frames to/from the upstream connection
|
||||
this._initializeDataFlow();
|
||||
|
||||
// * maintaining the state of the stream (idle, open, closed, etc.) and error detection
|
||||
this._initializeState();
|
||||
}
|
||||
|
||||
Stream.prototype = Object.create(Duplex.prototype, { constructor: { value: Stream } });
|
||||
|
||||
// Managing the stream
|
||||
// -------------------
|
||||
|
||||
// the default stream priority is 2^30
|
||||
var DEFAULT_PRIORITY = Math.pow(2, 30);
|
||||
var MAX_PRIORITY = Math.pow(2, 31) - 1;
|
||||
|
||||
// PUSH_PROMISE and HEADERS are forwarded to the user through events.
|
||||
Stream.prototype._initializeManagement = function _initializeManagement() {
|
||||
this._resetSent = false;
|
||||
this._priority = DEFAULT_PRIORITY;
|
||||
this._letPeerPrioritize = true;
|
||||
};
|
||||
|
||||
Stream.prototype.promise = function promise(headers) {
|
||||
var stream = new Stream(this._log);
|
||||
stream._priority = Math.min(this._priority + 1, MAX_PRIORITY);
|
||||
this._pushUpstream({
|
||||
type: 'PUSH_PROMISE',
|
||||
flags: {},
|
||||
stream: this.id,
|
||||
promised_stream: stream,
|
||||
headers: headers
|
||||
});
|
||||
return stream;
|
||||
};
|
||||
|
||||
Stream.prototype._onPromise = function _onPromise(frame) {
|
||||
this.emit('promise', frame.promised_stream, frame.headers);
|
||||
};
|
||||
|
||||
Stream.prototype.headers = function headers(headers) {
|
||||
this._pushUpstream({
|
||||
type: 'HEADERS',
|
||||
flags: {},
|
||||
stream: this.id,
|
||||
headers: headers
|
||||
});
|
||||
};
|
||||
|
||||
Stream.prototype._onHeaders = function _onHeaders(frame) {
|
||||
if (frame.priority !== undefined) {
|
||||
this.priority(frame.priority, true);
|
||||
}
|
||||
this.emit('headers', frame.headers);
|
||||
};
|
||||
|
||||
Stream.prototype.priority = function priority(priority, peer) {
|
||||
if ((peer && this._letPeerPrioritize) || !peer) {
|
||||
if (!peer) {
|
||||
this._letPeerPrioritize = false;
|
||||
|
||||
var lastFrame = this.upstream.getLastQueuedFrame();
|
||||
if (lastFrame && ((lastFrame.type === 'HEADERS') || (lastFrame.type === 'PRIORITY'))) {
|
||||
lastFrame.priority = priority;
|
||||
} else {
|
||||
this._pushUpstream({
|
||||
type: 'PRIORITY',
|
||||
flags: {},
|
||||
stream: this.id,
|
||||
priority: priority
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._log.debug({ priority: priority }, 'Changing priority');
|
||||
this.emit('priority', priority);
|
||||
this._priority = priority;
|
||||
}
|
||||
};
|
||||
|
||||
Stream.prototype._onPriority = function _onPriority(frame) {
|
||||
this.priority(frame.priority, true);
|
||||
};
|
||||
|
||||
// Resetting the stream. Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for
|
||||
// any stream.
|
||||
Stream.prototype.reset = function reset(error) {
|
||||
if (!this._resetSent) {
|
||||
this._resetSent = true;
|
||||
this._pushUpstream({
|
||||
type: 'RST_STREAM',
|
||||
flags: {},
|
||||
stream: this.id,
|
||||
error: error
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Data flow
|
||||
// ---------
|
||||
|
||||
// The incoming and the generated outgoing frames are received/transmitted on the `this.upstream`
|
||||
// [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
|
||||
|
||||
// upstream side stream user side
|
||||
//
|
||||
// +------------------------------------+
|
||||
// | |
|
||||
// +------------------+ |
|
||||
// | upstream | |
|
||||
// | | |
|
||||
// +--+ | +--|
|
||||
// read() | | _send() | _write() | | write(buf)
|
||||
// <--------------|B |<--------------|--------------| B|<------------
|
||||
// | | | | |
|
||||
// frames +--+ | +--| buffers
|
||||
// | | | | |
|
||||
// -------------->|B |---------------|------------->| B|------------>
|
||||
// write(frame) | | _receive() | _read() | | read()
|
||||
// +--+ | +--|
|
||||
// | | |
|
||||
// | | |
|
||||
// +------------------+ |
|
||||
// | |
|
||||
// +------------------------------------+
|
||||
//
|
||||
// B: input or output buffer
|
||||
|
||||
var Flow = require('./flow').Flow;
|
||||
|
||||
Stream.prototype._initializeDataFlow = function _initializeDataFlow() {
|
||||
this.id = undefined;
|
||||
|
||||
this._ended = false;
|
||||
|
||||
this.upstream = new Flow();
|
||||
this.upstream._log = this._log;
|
||||
this.upstream._send = this._send.bind(this);
|
||||
this.upstream._receive = this._receive.bind(this);
|
||||
this.upstream.write = this._writeUpstream.bind(this);
|
||||
this.upstream.on('error', this.emit.bind(this, 'error'));
|
||||
|
||||
this.on('finish', this._finishing);
|
||||
};
|
||||
|
||||
Stream.prototype._pushUpstream = function _pushUpstream(frame) {
|
||||
this.upstream.push(frame);
|
||||
this._transition(true, frame);
|
||||
};
|
||||
|
||||
// Overriding the upstream's `write` allows us to act immediately instead of waiting for the input
|
||||
// queue to empty. This is important in case of control frames.
|
||||
Stream.prototype._writeUpstream = function _writeUpstream(frame) {
|
||||
this._log.debug({ frame: frame }, 'Receiving frame');
|
||||
|
||||
var moreNeeded = Flow.prototype.write.call(this.upstream, frame);
|
||||
|
||||
// * Transition to a new state if that's the effect of receiving the frame
|
||||
this._transition(false, frame);
|
||||
|
||||
// * If it's a control frame. Call the appropriate handler method.
|
||||
if (frame.type === 'HEADERS') {
|
||||
this._onHeaders(frame);
|
||||
} else if (frame.type === 'PUSH_PROMISE') {
|
||||
this._onPromise(frame);
|
||||
} else if (frame.type === 'PRIORITY') {
|
||||
this._onPriority(frame);
|
||||
}
|
||||
|
||||
// * If it's an invalid stream level frame, emit error
|
||||
else if ((frame.type !== 'DATA') &&
|
||||
(frame.type !== 'WINDOW_UPDATE') &&
|
||||
(frame.type !== 'RST_STREAM')) {
|
||||
this._log.error({ frame: frame }, 'Invalid stream level frame');
|
||||
this.emit('error', 'PROTOCOL_ERROR');
|
||||
}
|
||||
|
||||
return moreNeeded;
|
||||
};
|
||||
|
||||
// The `_receive` method (= `upstream._receive`) gets called when there's an incoming frame.
|
||||
Stream.prototype._receive = function _receive(frame, ready) {
|
||||
// * If it's a DATA frame, then push the payload into the output buffer on the other side.
|
||||
// Call ready when the other side is ready to receive more.
|
||||
if (!this._ended && (frame.type === 'DATA')) {
|
||||
var moreNeeded = this.push(frame.data);
|
||||
if (!moreNeeded) {
|
||||
this._receiveMore = ready;
|
||||
}
|
||||
}
|
||||
|
||||
// * Any frame may signal the end of the stream with the END_STREAM flag
|
||||
if (!this._ended && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) {
|
||||
this.push(null);
|
||||
this._ended = true;
|
||||
}
|
||||
|
||||
// * Postpone calling `ready` if `push()` returned a falsy value
|
||||
if (this._receiveMore !== ready) {
|
||||
ready();
|
||||
}
|
||||
};
|
||||
|
||||
// The `_read` method is called when the user side is ready to receive more data. If there's a
|
||||
// pending write on the upstream, then call its pending ready callback to receive more frames.
|
||||
Stream.prototype._read = function _read() {
|
||||
if (this._receiveMore) {
|
||||
var receiveMore = this._receiveMore;
|
||||
delete this._receiveMore;
|
||||
receiveMore();
|
||||
}
|
||||
};
|
||||
|
||||
// The `write` method gets called when there's a write request from the user.
|
||||
Stream.prototype._write = function _write(buffer, encoding, ready) {
|
||||
// * Chunking is done by the upstream Flow.
|
||||
var moreNeeded = this._pushUpstream({
|
||||
type: 'DATA',
|
||||
flags: {},
|
||||
stream: this.id,
|
||||
data: buffer
|
||||
});
|
||||
|
||||
// * Call ready when upstream is ready to receive more frames.
|
||||
if (moreNeeded) {
|
||||
ready();
|
||||
} else {
|
||||
this._sendMore = ready;
|
||||
}
|
||||
};
|
||||
|
||||
// The `_send` (= `upstream._send`) method is called when upstream is ready to receive more frames.
|
||||
// If there's a pending write on the user side, then call its pending ready callback to receive more
|
||||
// writes.
|
||||
Stream.prototype._send = function _send() {
|
||||
if (this._sendMore) {
|
||||
var sendMore = this._sendMore;
|
||||
delete this._sendMore;
|
||||
sendMore();
|
||||
}
|
||||
};
|
||||
|
||||
// When the stream is finishing (the user calls `end()` on it), then we have to set the `END_STREAM`
|
||||
// flag on the last frame. If there's no frame in the queue, or if it doesn't support this flag,
|
||||
// then we create a 0 length DATA frame. We could do this all the time, but putting the flag on an
|
||||
// existing frame is a nice optimization.
|
||||
var emptyBuffer = new Buffer(0);
|
||||
Stream.prototype._finishing = function _finishing() {
|
||||
var endFrame = {
|
||||
type: 'DATA',
|
||||
flags: { END_STREAM: true },
|
||||
stream: this.id,
|
||||
data: emptyBuffer
|
||||
};
|
||||
var lastFrame = this.upstream.getLastQueuedFrame();
|
||||
if (lastFrame && ((lastFrame.type === 'DATA') || (lastFrame.type === 'HEADERS'))) {
|
||||
this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.');
|
||||
lastFrame.flags.END_STREAM = true;
|
||||
this._transition(true, endFrame);
|
||||
} else {
|
||||
this._pushUpstream(endFrame);
|
||||
}
|
||||
};
|
||||
|
||||
// [Stream States](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#StreamStates)
|
||||
// ----------------
|
||||
//
|
||||
// +--------+
|
||||
// PP | | PP
|
||||
// ,--------| idle |--------.
|
||||
// / | | \
|
||||
// v +--------+ v
|
||||
// +----------+ | +----------+
|
||||
// | | | H | |
|
||||
// ,---| reserved | | | reserved |---.
|
||||
// | | (local) | v | (remote) | |
|
||||
// | +----------+ +--------+ +----------+ |
|
||||
// | | ES | | ES | |
|
||||
// | | H ,-------| open |-------. | H |
|
||||
// | | / | | \ | |
|
||||
// | v v +--------+ v v |
|
||||
// | +----------+ | +----------+ |
|
||||
// | | half | | | half | |
|
||||
// | | closed | | R | closed | |
|
||||
// | | (remote) | | | (local) | |
|
||||
// | +----------+ | +----------+ |
|
||||
// | | v | |
|
||||
// | | ES / R +--------+ ES / R | |
|
||||
// | `----------->| |<-----------' |
|
||||
// | R | closed | R |
|
||||
// `-------------------->| |<--------------------'
|
||||
// +--------+
|
||||
|
||||
// Streams begin in the IDLE state and transitions happen when there's an incoming or outgoing frame
|
||||
Stream.prototype._initializeState = function _initializeState() {
|
||||
this.state = 'IDLE';
|
||||
this._initiated = undefined;
|
||||
this._closedByUs = undefined;
|
||||
this._closedWithRst = undefined;
|
||||
};
|
||||
|
||||
// Only `_setState` should change `this.state` directly. It also logs the state change and notifies
|
||||
// interested parties using the 'state' event.
|
||||
Stream.prototype._setState = function transition(state) {
|
||||
assert(this.state !== state);
|
||||
this._log.debug({ from: this.state, to: state }, 'State transition');
|
||||
this.state = state;
|
||||
this.emit('state', state);
|
||||
};
|
||||
|
||||
// A state is 'active' if the stream in that state counts towards the concurrency limit. Streams
|
||||
// that are in the "open" state, or either of the "half closed" states count toward this limit.
|
||||
function activeState(state) {
|
||||
return ((state === 'HALF_CLOSED_LOCAL') || (state === 'HALF_CLOSED_REMOTE') || (state === 'OPEN'));
|
||||
}
|
||||
|
||||
// `_transition` is called every time there's an incoming or outgoing frame. It manages state
|
||||
// transitions, and detects stream errors. A stream error is always caused by a frame that is not
|
||||
// allowed in the current state.
|
||||
Stream.prototype._transition = function transition(sending, frame) {
|
||||
var receiving = !sending;
|
||||
var error = undefined;
|
||||
|
||||
var DATA = false, HEADERS = false, PRIORITY = false;
|
||||
var RST_STREAM = false, PUSH_PROMISE = false, WINDOW_UPDATE = false;
|
||||
switch(frame.type) {
|
||||
case 'DATA' : DATA = true; break;
|
||||
case 'HEADERS' : HEADERS = true; break;
|
||||
case 'PRIORITY' : PRIORITY = true; break;
|
||||
case 'RST_STREAM' : RST_STREAM = true; break;
|
||||
case 'PUSH_PROMISE' : PUSH_PROMISE = true; break;
|
||||
case 'WINDOW_UPDATE': WINDOW_UPDATE = true; break;
|
||||
}
|
||||
|
||||
var previousState = this.state;
|
||||
|
||||
switch (this.state) {
|
||||
// All streams start in the **idle** state. In this state, no frames have been exchanged.
|
||||
//
|
||||
// * Sending or receiving a HEADERS frame causes the stream to become "open".
|
||||
//
|
||||
// When the HEADERS frame contains the END_STREAM flags, then two state transitions happen.
|
||||
case 'IDLE':
|
||||
if (HEADERS) {
|
||||
this._setState('OPEN');
|
||||
if (frame.flags.END_STREAM) {
|
||||
this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
|
||||
}
|
||||
this._initiated = sending;
|
||||
} else if (sending && RST_STREAM) {
|
||||
this._setState('CLOSED');
|
||||
} else {
|
||||
error = 'PROTOCOL_ERROR';
|
||||
}
|
||||
break;
|
||||
|
||||
// A stream in the **reserved (local)** state is one that has been promised by sending a
|
||||
// PUSH_PROMISE frame.
|
||||
//
|
||||
// * The endpoint can send a HEADERS frame. This causes the stream to open in a "half closed
|
||||
// (remote)" state.
|
||||
// * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
|
||||
// releases the stream reservation.
|
||||
// * An endpoint may receive PRIORITY frame in this state.
|
||||
// * An endpoint MUST NOT send any other type of frame in this state.
|
||||
case 'RESERVED_LOCAL':
|
||||
if (sending && HEADERS) {
|
||||
this._setState('HALF_CLOSED_REMOTE');
|
||||
} else if (RST_STREAM) {
|
||||
this._setState('CLOSED');
|
||||
} else if (receiving && PRIORITY) {
|
||||
/* No state change */
|
||||
} else {
|
||||
error = 'PROTOCOL_ERROR';
|
||||
}
|
||||
break;
|
||||
|
||||
// A stream in the **reserved (remote)** state has been reserved by a remote peer.
|
||||
//
|
||||
// * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
|
||||
// releases the stream reservation.
|
||||
// * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
|
||||
// * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream.
|
||||
// * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
|
||||
case 'RESERVED_REMOTE':
|
||||
if (RST_STREAM) {
|
||||
this._setState('CLOSED');
|
||||
} else if (receiving && HEADERS) {
|
||||
this._setState('HALF_CLOSED_LOCAL');
|
||||
} else if (sending && PRIORITY) {
|
||||
/* No state change */
|
||||
} else {
|
||||
error = 'PROTOCOL_ERROR';
|
||||
}
|
||||
break;
|
||||
|
||||
// The **open** state is where both peers can send frames. In this state, sending peers observe
|
||||
// advertised stream level flow control limits.
|
||||
//
|
||||
// * From this state either endpoint can send a frame with a END_STREAM flag set, which causes
|
||||
// the stream to transition into one of the "half closed" states: an endpoint sending a
|
||||
// END_STREAM flag causes the stream state to become "half closed (local)"; an endpoint
|
||||
// receiving a END_STREAM flag causes the stream state to become "half closed (remote)".
|
||||
// * Either endpoint can send a RST_STREAM frame from this state, causing it to transition
|
||||
// immediately to "closed".
|
||||
case 'OPEN':
|
||||
if (frame.flags.END_STREAM) {
|
||||
this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
|
||||
} else if (RST_STREAM) {
|
||||
this._setState('CLOSED');
|
||||
} else {
|
||||
/* No state change */
|
||||
}
|
||||
break;
|
||||
|
||||
// A stream that is **half closed (local)** cannot be used for sending frames.
|
||||
//
|
||||
// * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
|
||||
// flag is received, or when either peer sends a RST_STREAM frame.
|
||||
// * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
|
||||
// * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
|
||||
case 'HALF_CLOSED_LOCAL':
|
||||
if (RST_STREAM || (receiving && frame.flags.END_STREAM)) {
|
||||
this._setState('CLOSED');
|
||||
} else if (receiving || (sending && (PRIORITY || WINDOW_UPDATE))) {
|
||||
/* No state change */
|
||||
} else {
|
||||
error = 'PROTOCOL_ERROR';
|
||||
}
|
||||
break;
|
||||
|
||||
// A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
|
||||
// In this state, an endpoint is no longer obligated to maintain a receiver flow control window
|
||||
// if it performs flow control.
|
||||
//
|
||||
// * If an endpoint receives additional frames for a stream that is in this state it MUST
|
||||
// respond with a stream error of type STREAM_CLOSED.
|
||||
// * A stream can transition from this state to "closed" by sending a frame that contains a
|
||||
// END_STREAM flag, or when either peer sends a RST_STREAM frame.
|
||||
// * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
|
||||
// * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream.
|
||||
case 'HALF_CLOSED_REMOTE':
|
||||
if (RST_STREAM || (sending && frame.flags.END_STREAM)) {
|
||||
this._setState('CLOSED');
|
||||
} else if (sending || (receiving && (WINDOW_UPDATE || PRIORITY))) {
|
||||
/* No state change */
|
||||
} else {
|
||||
error = 'PROTOCOL_ERROR';
|
||||
}
|
||||
break;
|
||||
|
||||
// The **closed** state is the terminal state.
|
||||
//
|
||||
// * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame
|
||||
// after receiving a RST_STREAM or a frame containing a END_STREAM flag on that stream MUST
|
||||
// treat that as a stream error of type STREAM_CLOSED.
|
||||
// * WINDOW_UPDATE, PRIORITY or RST_STREAM frames can be received in this state for a short
|
||||
// period after a frame containing an END_STREAM flag is sent. Until the remote peer receives
|
||||
// and processes the frame bearing the END_STREAM flag, it might send either frame type.
|
||||
// Endpoints MUST ignore WINDOW_UPDATE frames received in this state, though endpoints MAY
|
||||
// choose to treat WINDOW_UPDATE frames that arrive a significant time after sending
|
||||
// END_STREAM as a connection error of type PROTOCOL_ERROR.
|
||||
// * If this state is reached as a result of sending a RST_STREAM frame, the peer that receives
|
||||
// the RST_STREAM might have already sent - or enqueued for sending - frames on the stream
|
||||
// that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that
|
||||
// it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
|
||||
// to limit the period over which it ignores frames and treat frames that arrive after this
|
||||
// time as being in error.
|
||||
// * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
|
||||
// causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
|
||||
// can be used to close any of those streams.
|
||||
case 'CLOSED':
|
||||
if ((sending && RST_STREAM) ||
|
||||
(receiving && this._closedByUs &&
|
||||
(this._closedWithRst || WINDOW_UPDATE || PRIORITY || RST_STREAM))) {
|
||||
/* No state change */
|
||||
} else {
|
||||
error = 'STREAM_CLOSED';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Noting that the connection was closed by the other endpoint. It may be important in edge cases.
|
||||
// For example, when the peer tries to cancel a promised stream, but we already sent every data
|
||||
// on it, then the stream is in CLOSED state, yet we want to ignore the incoming RST_STREAM.
|
||||
if ((this.state === 'CLOSED') && (previousState !== 'CLOSED')) {
|
||||
this._closedByUs = sending;
|
||||
this._closedWithRst = RST_STREAM;
|
||||
}
|
||||
|
||||
// Sending/receiving a PUSH_PROMISE
|
||||
//
|
||||
// * Sending a PUSH_PROMISE frame marks the associated stream for later use. The stream state
|
||||
// for the reserved stream transitions to "reserved (local)".
|
||||
// * Receiving a PUSH_PROMISE frame marks the associated stream as reserved by the remote peer.
|
||||
// The state of the stream becomes "reserved (remote)".
|
||||
if (PUSH_PROMISE && !error) {
|
||||
/* This assertion must hold, because _transition is called immediately when a frame is written
|
||||
to the stream. If it would be called when a frame gets out of the input queue, the state
|
||||
of the reserved could have been changed by then. */
|
||||
assert(frame.promised_stream.state === 'IDLE', frame.promised_stream.state);
|
||||
frame.promised_stream._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE');
|
||||
frame.promised_stream._initiated = sending;
|
||||
}
|
||||
|
||||
// Signaling how sending/receiving this frame changes the active stream count (-1, 0 or +1)
|
||||
if (this._initiated) {
|
||||
var change = (activeState(this.state) - activeState(previousState));
|
||||
if (sending) {
|
||||
frame.count_change = change;
|
||||
} else {
|
||||
frame.count_change(change);
|
||||
}
|
||||
} else if (sending) {
|
||||
frame.count_change = 0;
|
||||
}
|
||||
|
||||
// Common error handling.
|
||||
if (error) {
|
||||
var info = {
|
||||
error: error,
|
||||
frame: frame,
|
||||
state: this.state,
|
||||
closedByUs: this._closedByUs,
|
||||
closedWithRst: this._closedWithRst
|
||||
};
|
||||
|
||||
// * 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.');
|
||||
}
|
||||
|
||||
// * When receiving something invalid, sending an RST_STREAM using the `reset` method.
|
||||
// This will automatically cause a transition to the CLOSED state.
|
||||
else {
|
||||
this._log.error(info, 'Received illegal frame.');
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Bunyan serializers
|
||||
// ------------------
|
||||
|
||||
exports.serializers = {};
|
||||
|
||||
var nextId = 0;
|
||||
exports.serializers.s = function(stream) {
|
||||
if (!('_id' in stream)) {
|
||||
stream._id = nextId;
|
||||
nextId += 1;
|
||||
}
|
||||
return stream._id;
|
||||
};
|
52
testing/xpcshell/node-http2/node_modules/http2-protocol/package.json
generated
vendored
Normal file
52
testing/xpcshell/node-http2/node_modules/http2-protocol/package.json
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "http2-protocol",
|
||||
"version": "0.7.0",
|
||||
"description": "A JavaScript implementation of the HTTP/2 framing layer",
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": "0.10.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "*",
|
||||
"chai": "*",
|
||||
"mocha": "*",
|
||||
"docco": "*",
|
||||
"bunyan": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "istanbul test _mocha -- --reporter spec --slow 200",
|
||||
"prepublish": "docco lib/* --output doc --layout parallel --css doc/docco.css"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/molnarg/node-http2-protocol.git"
|
||||
},
|
||||
"homepage": "https://github.com/molnarg/node-http2-protocol",
|
||||
"bugs": {
|
||||
"url": "https://github.com/molnarg/node-http2-protocol/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"http2",
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"author": {
|
||||
"name": "Gábor Molnár",
|
||||
"email": "gabor@molnar.es",
|
||||
"url": "http://gabor.molnar.es"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Nick Hurley"
|
||||
},
|
||||
{
|
||||
"name": "Mike Belshe"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"readme": "node-http2-protocol\n===================\n\nAn HTTP/2 ([draft-ietf-httpbis-http2-07](http://tools.ietf.org/html/draft-ietf-httpbis-http2-07))\nframing layer implementaion for node.js.\n\nInstallation\n------------\n\n```\nnpm install http2-protocol\n```\n\nExamples\n--------\n\nAPI\n---\n\nDevelopment\n-----------\n\n### Development dependencies ###\n\nThere's a few library you will need to have installed to do anything described in the following\nsections. After installing/cloning node-http2, run `npm install` in its directory to install\ndevelopment dependencies.\n\nUsed libraries:\n\n* [mocha](http://visionmedia.github.io/mocha/) for tests\n* [chai](http://chaijs.com/) for assertions\n* [istanbul](https://github.com/gotwarlost/istanbul) for code coverage analysis\n* [docco](http://jashkenas.github.io/docco/) for developer documentation\n* [bunyan](https://github.com/trentm/node-bunyan) for logging\n\nFor pretty printing logs, you will also need a global install of bunyan (`npm install -g bunyan`).\n\n### Developer documentation ###\n\nThe developer documentation is located in the `doc` directory. The docs are usually updated only\nbefore releasing a new version. To regenerate them manually, run `npm run-script prepublish`.\nThere's a hosted version which is located [here](http://molnarg.github.io/node-http2/doc/).\n\n### Running the tests ###\n\nIt's easy, just run `npm test`. The tests are written in BDD style, so they are a good starting\npoint to understand the code.\n\n### Test coverage ###\n\nTo generate a code coverage report, run `npm test --coverage` (it may be slow, be patient).\nCode coverage summary as of version 0.6.0:\n```\nStatements : 92.39% ( 1165/1261 )\nBranches : 86.57% ( 477/551 )\nFunctions : 91.22% ( 135/148 )\nLines : 92.35% ( 1159/1255 )\n```\n\nThere's a hosted version of the detailed (line-by-line) coverage report\n[here](http://molnarg.github.io/node-http2-protocol/coverage/lcov-report/lib/).\n\n### Logging ###\n\nContributors\n------------\n\nCode contributions are always welcome! People who contributed to node-http2 so far:\n\n* Nick Hurley\n* Mike Belshe\n\nSpecial thanks to Google for financing the development of this module as part of their [Summer of\nCode program](https://developers.google.com/open-source/soc/) (project: [HTTP/2 prototype server\nimplementation](https://google-melange.appspot.com/gsoc/project/google/gsoc2013/molnarg/5001)), and\nNick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.\n\nLicense\n-------\n\nThe MIT License\n\nCopyright (C) 2013 Gábor Molnár <gabor@molnar.es>\n",
|
||||
"_id": "http2-protocol@0.7.0",
|
||||
"_from": "http2-protocol@0.7.x"
|
||||
}
|
427
testing/xpcshell/node-http2/node_modules/http2-protocol/test/compressor.js
generated
vendored
Normal file
427
testing/xpcshell/node-http2/node_modules/http2-protocol/test/compressor.js
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var compressor = require('../lib/compressor');
|
||||
var HeaderTable = compressor.HeaderTable;
|
||||
var HuffmanTable = compressor.HuffmanTable;
|
||||
var HeaderSetCompressor = compressor.HeaderSetCompressor;
|
||||
var HeaderSetDecompressor = compressor.HeaderSetDecompressor;
|
||||
var Compressor = compressor.Compressor;
|
||||
var Decompressor = compressor.Decompressor;
|
||||
|
||||
var test_integers = [{
|
||||
N: 5,
|
||||
I: 10,
|
||||
buffer: new Buffer([10])
|
||||
}, {
|
||||
N: 0,
|
||||
I: 10,
|
||||
buffer: new Buffer([10])
|
||||
}, {
|
||||
N: 5,
|
||||
I: 1337,
|
||||
buffer: new Buffer([31, 128 + 26, 10])
|
||||
}, {
|
||||
N: 0,
|
||||
I: 1337,
|
||||
buffer: new Buffer([128 + 57, 10])
|
||||
}];
|
||||
|
||||
var test_strings = [{
|
||||
string: 'www.foo.com',
|
||||
buffer: new Buffer('88db6d898b5a44b74f', 'hex')
|
||||
}, {
|
||||
string: 'éáűőúöüó€',
|
||||
buffer: new Buffer('13C3A9C3A1C5B1C591C3BAC3B6C3BCC3B3E282AC', 'hex')
|
||||
}];
|
||||
|
||||
test_huffman_request = {
|
||||
'GET': 'f77778ff',
|
||||
'http': 'ce3177',
|
||||
'/': '0f',
|
||||
'www.foo.com': 'db6d898b5a44b74f',
|
||||
'https': 'ce31743f',
|
||||
'www.bar.com': 'db6d897a1e44b74f',
|
||||
'no-cache': '63654a1398ff',
|
||||
'/custom-path.css': '04eb08b7495c88e644c21f',
|
||||
'custom-key': '4eb08b749790fa7f',
|
||||
'custom-value': '4eb08b74979a17a8ff'
|
||||
};
|
||||
|
||||
test_huffman_response = {
|
||||
'302': '409f',
|
||||
'private': 'c31b39bf387f',
|
||||
'Mon, 21 OCt 2013 20:13:21 GMT': 'a2fba20320f2ebcc0c490062d2434c827a1d',
|
||||
': https://www.bar.com': '6871cf3c326ebd7e9e9e926e7e32557dbf',
|
||||
'200': '311f',
|
||||
'Mon, 21 OCt 2013 20:13:22 GMT': 'a2fba20320f2ebcc0c490062d2434cc27a1d',
|
||||
'https://www.bar.com': 'e39e7864dd7afd3d3d24dcfc64aafb7f',
|
||||
'gzip': 'e1fbb30f',
|
||||
'foo=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAALASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\
|
||||
QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\
|
||||
QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\
|
||||
IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\
|
||||
IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234ZZZZZZZZZZ\
|
||||
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1234 m\
|
||||
ax-age=3600; version=1': 'df7dfb36eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76\
|
||||
eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb\
|
||||
76eddbb76eddbb7e3b69ecf0fe7e1fd7f3d5fe7f7e5fd79f6f97cbbfe9b7fbfebcfb\
|
||||
7cbbfe9b7fbf8f87f3f0febcfcbb7bfe9b7e3fd79f6f977fd36ff7f1febb7e9b7fbf\
|
||||
8fc7f9f0db4f67f5e7dbe5f4efdbfdf891a13f1db4f6787f3f0febf9eaff3fbf2feb\
|
||||
cfb7cbe5dff4dbfdff5e7dbe5dff4dbfdfc7c3f9f87f5e7e5dbdff4dbf1febcfb7cb\
|
||||
bfe9b7fbf8ff5dbf4dbfdfc7e3fcf86da7b3faf3edf2fa77edfefc48d09f8eda7b3c\
|
||||
3f9f87f5fcf57f9fdf97f5e7dbe5f2effa6dfeffaf3edf2effa6dfefe3e1fcfc3faf\
|
||||
3f2edeffa6df8ff5e7dbe5dff4dbfdfc7faedfa6dfefe3f1fe7c36d3d9fd79f6f97d\
|
||||
3bf6ff7e24684fc76d3d9e1fcfc3fafe7abfcfefcbfaf3edf2f977fd36ff7fd79f6f\
|
||||
977fd36ff7f1f0fe7e1fd79f976f7fd36fc7faf3edf2effa6dfefe3fd76fd36ff7f1\
|
||||
f8ff3e1b69ecfebcfb7cbe9dfb7fbf123427fcff3fcff3fcff3fcff3fcff3fcff3fc\
|
||||
ff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3\
|
||||
fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcf\
|
||||
f3fcff3fcff08d090b5fd237f086c44a23ef0e70c72b2fbb617f',
|
||||
'foo=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\
|
||||
ZZZZZZZZZZZZZZZZZZZZZZZZZZLASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\
|
||||
QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\
|
||||
QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\
|
||||
IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\
|
||||
IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\
|
||||
ax-age=3600; version=1': 'df7dfb3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcf\
|
||||
f3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3f\
|
||||
cff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff\
|
||||
3e3b69ecf0fe7e1fd7f3d5fe7f7e5fd79f6f97cbbfe9b7fbfebcfb7cbbfe9b7fbf8f\
|
||||
87f3f0febcfcbb7bfe9b7e3fd79f6f977fd36ff7f1febb7e9b7fbf8fc7f9f0db4f67\
|
||||
f5e7dbe5f4efdbfdf891a13f1db4f6787f3f0febf9eaff3fbf2febcfb7cbe5dff4db\
|
||||
fdff5e7dbe5dff4dbfdfc7c3f9f87f5e7e5dbdff4dbf1febcfb7cbbfe9b7fbf8ff5d\
|
||||
bf4dbfdfc7e3fcf86da7b3faf3edf2fa77edfefc48d09f8eda7b3c3f9f87f5fcf57f\
|
||||
9fdf97f5e7dbe5f2effa6dfeffaf3edf2effa6dfefe3e1fcfc3faf3f2edeffa6df8f\
|
||||
f5e7dbe5dff4dbfdfc7faedfa6dfefe3f1fe7c36d3d9fd79f6f97d3bf6ff7e24684f\
|
||||
c76d3d9e1fcfc3fafe7abfcfefcbfaf3edf2f977fd36ff7fd79f6f977fd36ff7f1f0\
|
||||
fe7e1fd79f976f7fd36fc7faf3edf2effa6dfefe3fd76fd36ff7f1f8ff3e1b69ecfe\
|
||||
bcfb7cbe9dfb7fbf1234276eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76edd\
|
||||
bb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76eddbb76e\
|
||||
ddbb76eddbb48d090b5fd237f086c44a23ef0e70c72b2fbb617f'
|
||||
};
|
||||
|
||||
var test_headers = [{
|
||||
header: {
|
||||
name: 1,
|
||||
value: 'GET',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('02' + '03474554', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 6,
|
||||
value: 'http',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('07' + '83ce3177', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 5,
|
||||
value: '/',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('06' + '012f', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 3,
|
||||
value: 'www.foo.com',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('04' + '88db6d898b5a44b74f', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 2,
|
||||
value: 'https',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('03' + '84ce31743f', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 1,
|
||||
value: 'www.bar.com',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('02' + '88db6d897a1e44b74f', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 28,
|
||||
value: 'no-cache',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('1d' + '8663654a1398ff', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 3,
|
||||
value: 3,
|
||||
index: false
|
||||
},
|
||||
buffer: new Buffer('83', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 5,
|
||||
value: 5,
|
||||
index: false
|
||||
},
|
||||
buffer: new Buffer('85', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 4,
|
||||
value: '/custom-path.css',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('05' + '8b04eb08b7495c88e644c21f', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 'custom-key',
|
||||
value: 'custom-value',
|
||||
index: true
|
||||
},
|
||||
buffer: new Buffer('00' + '884eb08b749790fa7f' + '894eb08b74979a17a8ff', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 2,
|
||||
value: 2,
|
||||
index: false
|
||||
},
|
||||
buffer: new Buffer('82', 'hex')
|
||||
}, {
|
||||
header: {
|
||||
name: 6,
|
||||
value: 6,
|
||||
index: false
|
||||
},
|
||||
buffer: new Buffer('86', 'hex')
|
||||
}];
|
||||
|
||||
var test_header_sets = [{
|
||||
headers: {
|
||||
':method': 'GET',
|
||||
':scheme': 'http',
|
||||
':path': '/',
|
||||
':authority': 'www.foo.com'
|
||||
},
|
||||
buffer: util.concat(test_headers.slice(0, 4).map(function(test) { return test.buffer; }))
|
||||
}, {
|
||||
headers: {
|
||||
':method': 'GET',
|
||||
':scheme': 'https',
|
||||
':path': '/',
|
||||
':authority': 'www.bar.com',
|
||||
'cache-control': 'no-cache'
|
||||
},
|
||||
buffer: util.concat(test_headers.slice(4, 9).map(function(test) { return test.buffer; }))
|
||||
}, {
|
||||
headers: {
|
||||
':method': 'GET',
|
||||
':scheme': 'https',
|
||||
':path': '/custom-path.css',
|
||||
':authority': 'www.bar.com',
|
||||
'custom-key': 'custom-value'
|
||||
},
|
||||
buffer: util.concat(test_headers.slice(9, 13).map(function(test) { return test.buffer; }))
|
||||
}, {
|
||||
headers: {
|
||||
':method': 'GET',
|
||||
':scheme': 'https',
|
||||
':path': '/custom-path.css',
|
||||
':authority': ['www.foo.com', 'www.bar.com'],
|
||||
'custom-key': 'custom-value'
|
||||
},
|
||||
buffer: test_headers[3].buffer
|
||||
}, {
|
||||
headers: {
|
||||
':status': '200',
|
||||
'user-agent': 'my-user-agent',
|
||||
'cookie': ['first', 'second', 'third', 'third'],
|
||||
'verylong': (new Buffer(9000)).toString('hex')
|
||||
}
|
||||
}];
|
||||
|
||||
describe('compressor.js', function() {
|
||||
describe('HeaderTable', function() {
|
||||
});
|
||||
|
||||
describe('HuffmanTable', function() {
|
||||
describe('method encode(buffer)', function() {
|
||||
it('should return the Huffman encoded version of the input buffer', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var decoded in test_huffman_request) {
|
||||
var encoded = test_huffman_request[decoded];
|
||||
expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
|
||||
}
|
||||
table = HuffmanTable.responseHuffmanTable;
|
||||
for (decoded in test_huffman_response) {
|
||||
encoded = test_huffman_response[decoded];
|
||||
expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
|
||||
}
|
||||
});
|
||||
})
|
||||
describe('method decode(buffer)', function() {
|
||||
it('should return the Huffman decoded version of the input buffer', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var decoded in test_huffman_request) {
|
||||
var encoded = test_huffman_request[decoded];
|
||||
expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded)
|
||||
}
|
||||
table = HuffmanTable.responseHuffmanTable;
|
||||
for (decoded in test_huffman_response) {
|
||||
encoded = test_huffman_response[decoded];
|
||||
expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded)
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('HeaderSetCompressor', function() {
|
||||
describe('static method .integer(I, N)', function() {
|
||||
it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() {
|
||||
for (var i = 0; i < test_integers.length; i++) {
|
||||
var test = test_integers[i];
|
||||
test.buffer.cursor = 0;
|
||||
expect(util.concat(HeaderSetCompressor.integer(test.I, test.N))).to.deep.equal(test.buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('static method .string(string)', function() {
|
||||
it('should return an array of buffers that represent the encoded form of the string', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var i = 0; i < test_strings.length; i++) {
|
||||
var test = test_strings[i];
|
||||
expect(util.concat(HeaderSetCompressor.string(test.string, table))).to.deep.equal(test.buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('static method .header({ name, value, index })', function() {
|
||||
it('should return an array of buffers that represent the encoded form of the header', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var i = 0; i < test_headers.length; i++) {
|
||||
var test = test_headers[i];
|
||||
expect(util.concat(HeaderSetCompressor.header(test.header, table))).to.deep.equal(test.buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HeaderSetDecompressor', function() {
|
||||
describe('static method .integer(buffer, N)', function() {
|
||||
it('should return the parsed N-prefix coded number and increase the cursor property of buffer', function() {
|
||||
for (var i = 0; i < test_integers.length; i++) {
|
||||
var test = test_integers[i];
|
||||
test.buffer.cursor = 0;
|
||||
expect(HeaderSetDecompressor.integer(test.buffer, test.N)).to.equal(test.I);
|
||||
expect(test.buffer.cursor).to.equal(test.buffer.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('static method .string(buffer)', function() {
|
||||
it('should return the parsed string and increase the cursor property of buffer', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var i = 0; i < test_strings.length; i++) {
|
||||
var test = test_strings[i];
|
||||
test.buffer.cursor = 0;
|
||||
expect(HeaderSetDecompressor.string(test.buffer, table)).to.equal(test.string);
|
||||
expect(test.buffer.cursor).to.equal(test.buffer.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('static method .header(buffer)', function() {
|
||||
it('should return the parsed header and increase the cursor property of buffer', function() {
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
for (var i = 0; i < test_headers.length; i++) {
|
||||
var test = test_headers[i];
|
||||
test.buffer.cursor = 0;
|
||||
expect(HeaderSetDecompressor.header(test.buffer, table)).to.deep.equal(test.header);
|
||||
expect(test.buffer.cursor).to.equal(test.buffer.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Decompressor', function() {
|
||||
describe('method decompress(buffer)', function() {
|
||||
it('should return the parsed header set in { name1: value1, name2: [value2, value3], ... } format', function() {
|
||||
var decompressor = new Decompressor(util.log, 'REQUEST');
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var header_set = test_header_sets[i];
|
||||
expect(decompressor.decompress(header_set.buffer)).to.deep.equal(header_set.headers);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('transform stream', function() {
|
||||
it('should emit an error event if a series of header frames is interleaved with other frames', function() {
|
||||
var decompressor = new Decompressor(util.log, 'REQUEST');
|
||||
var error_occured = false;
|
||||
decompressor.on('error', function() {
|
||||
error_occured = true;
|
||||
});
|
||||
decompressor.write({
|
||||
type: 'HEADERS',
|
||||
flags: {
|
||||
END_HEADERS: false
|
||||
},
|
||||
data: new Buffer(5)
|
||||
});
|
||||
decompressor.write({
|
||||
type: 'DATA',
|
||||
flags: {},
|
||||
data: new Buffer(5)
|
||||
});
|
||||
expect(error_occured).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invariant', function() {
|
||||
describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() {
|
||||
it('should be true for any header set if the states are synchronized', function() {
|
||||
var compressor = new Compressor(util.log, 'REQUEST');
|
||||
var decompressor = new Decompressor(util.log, 'REQUEST');
|
||||
var n = test_header_sets.length;
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var headers = test_header_sets[i%n].headers;
|
||||
var compressed = compressor.compress(headers);
|
||||
var decompressed = decompressor.decompress(compressed);
|
||||
expect(decompressed).to.deep.equal(headers);
|
||||
expect(compressor._table).to.deep.equal(decompressor._table);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('source.pipe(compressor).pipe(decompressor).pipe(destination)', function() {
|
||||
it('should behave like source.pipe(destination) for a stream of frames', function(done) {
|
||||
var compressor = new Compressor(util.log, 'RESPONSE');
|
||||
var decompressor = new Decompressor(util.log, 'RESPONSE');
|
||||
var n = test_header_sets.length;
|
||||
compressor.pipe(decompressor);
|
||||
for (var i = 0; i < 10; i++) {
|
||||
compressor.write({
|
||||
type: i%2 ? 'HEADERS' : 'PUSH_PROMISE',
|
||||
flags: {},
|
||||
headers: test_header_sets[i%n].headers
|
||||
});
|
||||
}
|
||||
setTimeout(function() {
|
||||
for (var j = 0; j < 10; j++) {
|
||||
expect(decompressor.read().headers).to.deep.equal(test_header_sets[j%n].headers);
|
||||
}
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() {
|
||||
it('should be true for any buffer', function() {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var buffer = [];
|
||||
while (Math.random() > 0.1) {
|
||||
buffer.push(Math.floor(Math.random() * 256))
|
||||
}
|
||||
buffer = new Buffer(buffer);
|
||||
var table = HuffmanTable.requestHuffmanTable;
|
||||
var result = table.decode(table.encode(buffer));
|
||||
expect(result).to.deep.equal(buffer);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
280
testing/xpcshell/node-http2/node_modules/http2-protocol/test/connection.js
generated
vendored
Normal file
280
testing/xpcshell/node-http2/node_modules/http2-protocol/test/connection.js
generated
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var Connection = require('../lib/connection').Connection;
|
||||
|
||||
var settings = {
|
||||
SETTINGS_MAX_CONCURRENT_STREAMS: 100,
|
||||
SETTINGS_INITIAL_WINDOW_SIZE: 100000
|
||||
};
|
||||
|
||||
var MAX_PRIORITY = Math.pow(2, 31) - 1;
|
||||
var MAX_RANDOM_PRIORITY = 10;
|
||||
|
||||
function randomPriority() {
|
||||
return Math.floor(Math.random() * (MAX_RANDOM_PRIORITY + 1));
|
||||
}
|
||||
|
||||
function expectPriorityOrder(priorities) {
|
||||
priorities.forEach(function(bucket, priority) {
|
||||
bucket.forEach(function(stream) {
|
||||
expect(stream._priority).to.be.equal(priority);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('connection.js', function() {
|
||||
describe('Connection class', function() {
|
||||
describe('method ._insert(stream)', function() {
|
||||
it('should insert the stream in _streamPriorities in a place determined by stream._priority', function() {
|
||||
var streams = [];
|
||||
var connection = Object.create(Connection.prototype, { _streamPriorities: { value: streams }});
|
||||
var streamCount = 10;
|
||||
|
||||
for (var i = 0; i < streamCount; i++) {
|
||||
var stream = { _priority: randomPriority() };
|
||||
connection._insert(stream, stream._priority);
|
||||
expect(connection._streamPriorities[stream._priority]).to.include(stream);
|
||||
}
|
||||
|
||||
expectPriorityOrder(connection._streamPriorities);
|
||||
});
|
||||
});
|
||||
describe('method ._reprioritize(stream)', function() {
|
||||
it('should eject and then insert the stream in _streamPriorities in a place determined by stream._priority', function() {
|
||||
var streams = [];
|
||||
var connection = Object.create(Connection.prototype, { _streamPriorities: { value: streams }});
|
||||
var streamCount = 10;
|
||||
var oldPriority, newPriority, stream;
|
||||
|
||||
for (var i = 0; i < streamCount; i++) {
|
||||
oldPriority = randomPriority();
|
||||
while ((newPriority = randomPriority()) === oldPriority);
|
||||
stream = { _priority: oldPriority };
|
||||
connection._insert(stream, oldPriority);
|
||||
connection._reprioritize(stream, newPriority);
|
||||
stream._priority = newPriority;
|
||||
|
||||
expect(connection._streamPriorities[newPriority]).to.include(stream);
|
||||
expect(connection._streamPriorities[oldPriority] || []).to.not.include(stream);
|
||||
}
|
||||
|
||||
expectPriorityOrder(streams);
|
||||
});
|
||||
});
|
||||
describe('invalid operation', function() {
|
||||
describe('disabling and the re-enabling flow control', function() {
|
||||
it('should result in an error event with type "FLOW_CONTROL_ERROR"', function(done) {
|
||||
var connection = new Connection(util.log, 1, settings);
|
||||
|
||||
connection.on('error', function(error) {
|
||||
expect(error).to.equal('FLOW_CONTROL_ERROR');
|
||||
done();
|
||||
});
|
||||
|
||||
connection._setLocalFlowControl(true);
|
||||
connection._setLocalFlowControl(false);
|
||||
});
|
||||
});
|
||||
describe('manipulating flow control window after flow control was turned off', function() {
|
||||
it('should result in an error event with type "FLOW_CONTROL_ERROR"', function(done) {
|
||||
var connection = new Connection(util.log, 1, settings);
|
||||
|
||||
connection.on('error', function(error) {
|
||||
expect(error).to.equal('FLOW_CONTROL_ERROR');
|
||||
done();
|
||||
});
|
||||
|
||||
connection._setLocalFlowControl(true);
|
||||
connection._setInitialStreamWindowSize(10);
|
||||
});
|
||||
});
|
||||
describe('disabling flow control twice', function() {
|
||||
it('should be ignored', function() {
|
||||
var connection = new Connection(util.log, 1, settings);
|
||||
|
||||
connection._setLocalFlowControl(true);
|
||||
connection._setLocalFlowControl(true);
|
||||
});
|
||||
});
|
||||
describe('enabling flow control when already enabled', function() {
|
||||
it('should be ignored', function() {
|
||||
var connection = new Connection(util.log, 1, settings);
|
||||
|
||||
connection._setLocalFlowControl(false);
|
||||
});
|
||||
});
|
||||
describe('unsolicited ping answer', function() {
|
||||
it('should be ignored', function() {
|
||||
var connection = new Connection(util.log, 1, settings);
|
||||
|
||||
connection._receivePing({
|
||||
stream: 0,
|
||||
type: 'PING',
|
||||
flags: {
|
||||
'PONG': true
|
||||
},
|
||||
data: new Buffer(8)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('test scenario', function() {
|
||||
var c, s;
|
||||
beforeEach(function() {
|
||||
c = new Connection(util.log.child({ role: 'client' }), 1, settings);
|
||||
s = new Connection(util.log.child({ role: 'client' }), 2, settings);
|
||||
c.pipe(s).pipe(c);
|
||||
});
|
||||
|
||||
describe('connection setup', function() {
|
||||
it('should work as expected', function(done) {
|
||||
setTimeout(function() {
|
||||
// If there are no exception until this, then we're done
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
describe('sending/receiving a request', function() {
|
||||
it('should work as expected', function(done) {
|
||||
// Request and response data
|
||||
var request_headers = {
|
||||
':method': 'GET',
|
||||
':path': '/'
|
||||
};
|
||||
var request_data = new Buffer(0);
|
||||
var response_headers = {
|
||||
':status': '200'
|
||||
};
|
||||
var response_data = new Buffer('12345678', 'hex');
|
||||
|
||||
// Setting up server
|
||||
s.on('stream', function(server_stream) {
|
||||
server_stream.on('headers', function(headers) {
|
||||
expect(headers).to.deep.equal(request_headers);
|
||||
server_stream.headers(response_headers);
|
||||
server_stream.end(response_data);
|
||||
});
|
||||
});
|
||||
|
||||
// Sending request
|
||||
var client_stream = c.createStream();
|
||||
client_stream.headers(request_headers);
|
||||
client_stream.end(request_data);
|
||||
|
||||
// Waiting for answer
|
||||
done = util.callNTimes(2, done);
|
||||
client_stream.on('headers', function(headers) {
|
||||
expect(headers).to.deep.equal(response_headers);
|
||||
done();
|
||||
});
|
||||
client_stream.on('readable', function() {
|
||||
expect(client_stream.read()).to.deep.equal(response_data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('server push', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var request_headers = { ':method': 'get', ':path': '/' };
|
||||
var response_headers = { ':status': '200' };
|
||||
var push_request_headers = { ':method': 'get', ':path': '/x' };
|
||||
var push_response_headers = { ':status': '200' };
|
||||
var response_content = new Buffer(10);
|
||||
var push_content = new Buffer(10);
|
||||
|
||||
done = util.callNTimes(5, done);
|
||||
|
||||
s.on('stream', function(response) {
|
||||
response.headers(response_headers);
|
||||
|
||||
var pushed = response.promise(push_request_headers);
|
||||
pushed.headers(push_response_headers);
|
||||
pushed.end(push_content);
|
||||
|
||||
response.end(response_content);
|
||||
});
|
||||
|
||||
var request = c.createStream();
|
||||
request.headers(request_headers);
|
||||
request.end();
|
||||
request.on('headers', function(headers) {
|
||||
expect(headers).to.deep.equal(response_headers);
|
||||
done();
|
||||
});
|
||||
request.on('readable', function() {
|
||||
expect(request.read()).to.deep.equal(response_content);
|
||||
done();
|
||||
});
|
||||
request.on('promise', function(pushed, headers) {
|
||||
expect(headers).to.deep.equal(push_request_headers);
|
||||
pushed.on('headers', function(headers) {
|
||||
expect(headers).to.deep.equal(response_headers);
|
||||
done();
|
||||
});
|
||||
pushed.on('readable', function() {
|
||||
expect(pushed.read()).to.deep.equal(push_content);
|
||||
done();
|
||||
});
|
||||
pushed.on('end', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('ping from client', function() {
|
||||
it('should work as expected', function(done) {
|
||||
c.ping(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('ping from server', function() {
|
||||
it('should work as expected', function(done) {
|
||||
s.ping(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('creating two streams and then using them in reverse order', function() {
|
||||
it('should not result in non-monotonous local ID ordering', function() {
|
||||
var s1 = c.createStream();
|
||||
var s2 = c.createStream();
|
||||
s2.headers({ ':method': 'get', ':path': '/' });
|
||||
s1.headers({ ':method': 'get', ':path': '/' });
|
||||
});
|
||||
});
|
||||
describe('creating two promises and then using them in reverse order', function() {
|
||||
it('should not result in non-monotonous local ID ordering', function(done) {
|
||||
s.on('stream', function(response) {
|
||||
response.headers({ ':status': '200' });
|
||||
|
||||
var p1 = s.createStream();
|
||||
var p2 = s.createStream();
|
||||
response.promise(p2, { ':method': 'get', ':path': '/p2' });
|
||||
response.promise(p1, { ':method': 'get', ':path': '/p1' });
|
||||
p2.headers({ ':status': '200' });
|
||||
p1.headers({ ':status': '200' });
|
||||
});
|
||||
|
||||
var request = c.createStream();
|
||||
request.headers({ ':method': 'get', ':path': '/' });
|
||||
|
||||
done = util.callNTimes(2, done);
|
||||
request.on('promise', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('closing the connection on one end', function() {
|
||||
it('should result in closed streams on both ends', function(done) {
|
||||
done = util.callNTimes(2, done);
|
||||
c.on('end', done);
|
||||
s.on('end', done);
|
||||
|
||||
c.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
41
testing/xpcshell/node-http2/node_modules/http2-protocol/test/endpoint.js
generated
vendored
Normal file
41
testing/xpcshell/node-http2/node_modules/http2-protocol/test/endpoint.js
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var endpoint = require('../lib/endpoint');
|
||||
var Endpoint = endpoint.Endpoint;
|
||||
|
||||
var settings = {
|
||||
SETTINGS_MAX_CONCURRENT_STREAMS: 100,
|
||||
SETTINGS_INITIAL_WINDOW_SIZE: 100000
|
||||
};
|
||||
|
||||
describe('endpoint.js', function() {
|
||||
describe('scenario', function() {
|
||||
describe('connection setup', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var c = new Endpoint(util.log.child({ role: 'client' }), 'CLIENT', settings);
|
||||
var s = new Endpoint(util.log.child({ role: 'client' }), 'SERVER', settings);
|
||||
|
||||
util.log.debug('Test initialization over, starting piping.');
|
||||
c.pipe(s).pipe(c);
|
||||
|
||||
setTimeout(function() {
|
||||
// If there are no exception until this, then we're done
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('bunyan serializer', function() {
|
||||
describe('`e`', function() {
|
||||
var format = endpoint.serializers.e;
|
||||
it('should assign a unique ID to each endpoint', function() {
|
||||
var c = new Endpoint(util.log.child({ role: 'client' }), 'CLIENT', settings);
|
||||
var s = new Endpoint(util.log.child({ role: 'client' }), 'SERVER', settings);
|
||||
expect(format(c)).to.not.equal(format(s));
|
||||
expect(format(c)).to.equal(format(c));
|
||||
expect(format(s)).to.equal(format(s));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
210
testing/xpcshell/node-http2/node_modules/http2-protocol/test/flow.js
generated
vendored
Normal file
210
testing/xpcshell/node-http2/node_modules/http2-protocol/test/flow.js
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var Flow = require('../lib/flow').Flow;
|
||||
|
||||
function createFlow(log) {
|
||||
var flowControlId = util.random(10, 100);
|
||||
var flow = new Flow(flowControlId);
|
||||
flow._log = util.log.child(log || {});
|
||||
return flow;
|
||||
}
|
||||
|
||||
describe('flow.js', function() {
|
||||
describe('Flow class', function() {
|
||||
var flow;
|
||||
beforeEach(function() {
|
||||
flow = createFlow();
|
||||
});
|
||||
|
||||
describe('._receive(frame, callback) method', function() {
|
||||
it('is called when there\'s a frame in the input buffer to be consumed', function(done) {
|
||||
var frame = { type: 'PRIORITY', flags: {}, priority: 1 };
|
||||
flow._receive = function _receive(receivedFrame, callback) {
|
||||
expect(receivedFrame).to.equal(frame);
|
||||
callback();
|
||||
};
|
||||
flow.write(frame, done);
|
||||
});
|
||||
it('has to be overridden by the child class, otherwise it throws', function() {
|
||||
expect(flow._receive.bind(flow)).to.throw(Error);
|
||||
});
|
||||
});
|
||||
describe('._send() method', function() {
|
||||
it('is called when the output buffer should be filled with more frames and the flow' +
|
||||
'control queue is empty', function() {
|
||||
var sendCalled = 0;
|
||||
var notFlowControlledFrame = { type: 'PRIORITY', flags: {}, priority: 1 };
|
||||
flow._send = function _send() {
|
||||
sendCalled += 1;
|
||||
this.push(notFlowControlledFrame);
|
||||
};
|
||||
expect(flow.read()).to.equal(notFlowControlledFrame);
|
||||
|
||||
flow._window = 0;
|
||||
flow._queue.push({ type: 'DATA', flags: {}, data: { length: 1 } });
|
||||
expect(flow.read()).to.equal(null);
|
||||
|
||||
expect(sendCalled).to.equal(1);
|
||||
});
|
||||
it('has to be overridden by the child class, otherwise it throws', function() {
|
||||
expect(flow._send.bind(flow)).to.throw(Error);
|
||||
});
|
||||
});
|
||||
describe('._increaseWindow(size) method', function() {
|
||||
it('should increase `this._window` by `size`', function() {
|
||||
flow._send = util.noop;
|
||||
flow._window = 0;
|
||||
|
||||
var increase1 = util.random(0,100);
|
||||
var increase2 = util.random(0,100);
|
||||
flow._increaseWindow(increase1);
|
||||
flow._increaseWindow(increase2);
|
||||
expect(flow._window).to.equal(increase1 + increase2);
|
||||
|
||||
flow._increaseWindow(Infinity);
|
||||
expect(flow._window).to.equal(Infinity);
|
||||
});
|
||||
it('should emit error when increasing with a finite `size` when `_window` is infinite', function() {
|
||||
flow._send = util.noop;
|
||||
flow._increaseWindow(Infinity);
|
||||
var increase = util.random(1,100);
|
||||
|
||||
expect(flow._increaseWindow.bind(flow, increase)).to.throw('Uncaught, unspecified "error" event.');
|
||||
});
|
||||
it('should emit error when `_window` grows over the window limit', function() {
|
||||
var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;
|
||||
flow._send = util.noop;
|
||||
flow._window = 0;
|
||||
|
||||
flow._increaseWindow(WINDOW_SIZE_LIMIT);
|
||||
expect(flow._increaseWindow.bind(flow, 1)).to.throw('Uncaught, unspecified "error" event.');
|
||||
|
||||
});
|
||||
});
|
||||
describe('.disableLocalFlowControl() method', function() {
|
||||
it('should increase `this._window` by Infinity', function() {
|
||||
flow._send = util.noop;
|
||||
flow.disableLocalFlowControl();
|
||||
expect(flow._window).to.equal(Infinity);
|
||||
});
|
||||
});
|
||||
describe('.read() method', function() {
|
||||
describe('when the flow control queue is not empty', function() {
|
||||
it('should return the first item in the queue if the window is enough', function() {
|
||||
var priorityFrame = { type: 'PRIORITY', flags: {}, priority: 1 };
|
||||
var dataFrame = { type: 'DATA', flags: {}, data: { length: 10 } };
|
||||
flow._send = util.noop;
|
||||
flow._window = 10;
|
||||
flow._queue = [priorityFrame, dataFrame];
|
||||
|
||||
expect(flow.read()).to.equal(priorityFrame);
|
||||
expect(flow.read()).to.equal(dataFrame);
|
||||
});
|
||||
it('should also split DATA frames when needed', function() {
|
||||
var buffer = new Buffer(10);
|
||||
var dataFrame = { type: 'DATA', flags: {}, stream: util.random(0, 100), data: buffer };
|
||||
flow._send = util.noop;
|
||||
flow._window = 5;
|
||||
flow._queue = [dataFrame];
|
||||
|
||||
var expectedFragment = { flags: {}, type: 'DATA', stream: dataFrame.stream, data: buffer.slice(0,5) };
|
||||
expect(flow.read()).to.deep.equal(expectedFragment);
|
||||
expect(dataFrame.data).to.deep.equal(buffer.slice(5));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('.push(frame) method', function() {
|
||||
it('should push `frame` into the output queue or the flow control queue', function() {
|
||||
var priorityFrame = { type: 'PRIORITY', flags: {}, priority: 1 };
|
||||
var dataFrame = { type: 'DATA', flags: {}, data: { length: 10 } };
|
||||
flow._window = 10;
|
||||
|
||||
flow.push(dataFrame); // output queue
|
||||
flow.push(dataFrame); // flow control queue, because of depleted window
|
||||
flow.push(priorityFrame); // flow control queue, because it's not empty
|
||||
|
||||
expect(flow.read()).to.be.equal(dataFrame);
|
||||
expect(flow._queue[0]).to.be.equal(dataFrame);
|
||||
expect(flow._queue[1]).to.be.equal(priorityFrame);
|
||||
});
|
||||
});
|
||||
describe('.write() method', function() {
|
||||
it('call with a DATA frame should trigger sending WINDOW_UPDATE if remote flow control is not' +
|
||||
'disabled', function(done) {
|
||||
flow._remoteFlowControlDisabled = false;
|
||||
flow._window = 100;
|
||||
flow._send = util.noop;
|
||||
flow._receive = function(frame, callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
var buffer = new Buffer(util.random(10, 100));
|
||||
flow.write({ type: 'DATA', flags: {}, data: buffer });
|
||||
flow.once('readable', function() {
|
||||
expect(flow.read()).to.be.deep.equal({
|
||||
type: 'WINDOW_UPDATE',
|
||||
flags: {},
|
||||
stream: flow._flowControlId,
|
||||
window_size: buffer.length
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('test scenario', function() {
|
||||
var flow1, flow2;
|
||||
beforeEach(function() {
|
||||
flow1 = createFlow({ flow: 1 });
|
||||
flow2 = createFlow({ flow: 2 });
|
||||
flow1._flowControlId = flow2._flowControlId;
|
||||
flow1._remoteFlowControlDisabled = flow2._remoteFlowControlDisabled = false;
|
||||
flow1._send = flow2._send = util.noop;
|
||||
flow1._receive = flow2._receive = function(frame, callback) { callback(); };
|
||||
});
|
||||
|
||||
describe('sending a large data stream', function() {
|
||||
it('should work as expected', function(done) {
|
||||
// Sender side
|
||||
var frameNumber = util.random(5, 8);
|
||||
var input = [];
|
||||
flow1._send = function _send() {
|
||||
if (input.length >= frameNumber) {
|
||||
this.push({ type: 'DATA', flags: { END_STREAM: true }, data: new Buffer(0) });
|
||||
this.push(null);
|
||||
} else {
|
||||
var buffer = new Buffer(util.random(1000, 100000));
|
||||
input.push(buffer);
|
||||
this.push({ type: 'DATA', flags: {}, data: buffer });
|
||||
}
|
||||
};
|
||||
|
||||
// Receiver side
|
||||
var output = [];
|
||||
flow2._receive = function _receive(frame, callback) {
|
||||
if (frame.type === 'DATA') {
|
||||
output.push(frame.data);
|
||||
}
|
||||
if (frame.flags.END_STREAM) {
|
||||
this.emit('end_stream');
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
// Checking results
|
||||
flow2.on('end_stream', function() {
|
||||
input = util.concat(input);
|
||||
output = util.concat(output);
|
||||
|
||||
expect(input).to.deep.equal(output);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
// Start piping
|
||||
flow1.pipe(flow2).pipe(flow1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
252
testing/xpcshell/node-http2/node_modules/http2-protocol/test/framer.js
generated
vendored
Normal file
252
testing/xpcshell/node-http2/node_modules/http2-protocol/test/framer.js
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var framer = require('../lib/framer');
|
||||
var Serializer = framer.Serializer;
|
||||
var Deserializer = framer.Deserializer;
|
||||
|
||||
var frame_types = {
|
||||
DATA: ['data'],
|
||||
HEADERS: ['priority', 'data'],
|
||||
PRIORITY: ['priority'],
|
||||
RST_STREAM: ['error'],
|
||||
SETTINGS: ['settings'],
|
||||
PUSH_PROMISE: ['promised_stream', 'data'],
|
||||
PING: ['data'],
|
||||
GOAWAY: ['last_stream', 'error'],
|
||||
WINDOW_UPDATE: ['window_size'],
|
||||
CONTINUATION: ['data']
|
||||
};
|
||||
|
||||
var test_frames = [{
|
||||
frame: {
|
||||
type: 'DATA',
|
||||
flags: { END_STREAM: false, RESERVED: false },
|
||||
stream: 10,
|
||||
|
||||
data: new Buffer('12345678', 'hex')
|
||||
},
|
||||
// length + type + flags + stream + content
|
||||
buffer: new Buffer('0004' + '00' + '00' + '0000000A' + '12345678', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'HEADERS',
|
||||
flags: { END_STREAM: false, RESERVED: false, END_HEADERS: false, PRIORITY: false },
|
||||
stream: 15,
|
||||
|
||||
data: new Buffer('12345678', 'hex')
|
||||
},
|
||||
buffer: new Buffer('0004' + '01' + '00' + '0000000F' + '12345678', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'HEADERS',
|
||||
flags: { END_STREAM: false, RESERVED: false, END_HEADERS: false, PRIORITY: true },
|
||||
stream: 15,
|
||||
|
||||
priority: 3,
|
||||
data: new Buffer('12345678', 'hex')
|
||||
},
|
||||
buffer: new Buffer('0008' + '01' + '08' + '0000000F' + '00000003' + '12345678', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'PRIORITY',
|
||||
flags: { },
|
||||
stream: 10,
|
||||
|
||||
priority: 3
|
||||
},
|
||||
buffer: new Buffer('0004' + '02' + '00' + '0000000A' + '00000003', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'RST_STREAM',
|
||||
flags: { },
|
||||
stream: 10,
|
||||
|
||||
error: 'INTERNAL_ERROR'
|
||||
},
|
||||
buffer: new Buffer('0004' + '03' + '00' + '0000000A' + '00000002', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'SETTINGS',
|
||||
flags: { ACK: false },
|
||||
stream: 10,
|
||||
|
||||
settings: {
|
||||
SETTINGS_HEADER_TABLE_SIZE: 0x12345678,
|
||||
SETTINGS_ENABLE_PUSH: true,
|
||||
SETTINGS_MAX_CONCURRENT_STREAMS: 0x01234567,
|
||||
SETTINGS_INITIAL_WINDOW_SIZE: 0x89ABCDEF,
|
||||
SETTINGS_FLOW_CONTROL_OPTIONS: true
|
||||
}
|
||||
},
|
||||
buffer: new Buffer('0028' + '04' + '00' + '0000000A' + '00' + '000001' + '12345678' +
|
||||
'00' + '000002' + '00000001' +
|
||||
'00' + '000004' + '01234567' +
|
||||
'00' + '000007' + '89ABCDEF' +
|
||||
'00' + '00000A' + '00000001', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'PUSH_PROMISE',
|
||||
flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false },
|
||||
stream: 15,
|
||||
|
||||
promised_stream: 3,
|
||||
data: new Buffer('12345678', 'hex')
|
||||
},
|
||||
buffer: new Buffer('0008' + '05' + '00' + '0000000F' + '00000003' + '12345678', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'PING',
|
||||
flags: { ACK: false },
|
||||
stream: 15,
|
||||
|
||||
data: new Buffer('1234567887654321', 'hex')
|
||||
},
|
||||
buffer: new Buffer('0008' + '06' + '00' + '0000000F' + '1234567887654321', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'GOAWAY',
|
||||
flags: { },
|
||||
stream: 10,
|
||||
|
||||
last_stream: 0x12345678,
|
||||
error: 'PROTOCOL_ERROR'
|
||||
},
|
||||
buffer: new Buffer('0008' + '07' + '00' + '0000000A' + '12345678' + '00000001', 'hex')
|
||||
|
||||
}, {
|
||||
frame: {
|
||||
type: 'WINDOW_UPDATE',
|
||||
flags: { },
|
||||
stream: 10,
|
||||
|
||||
window_size: 0x12345678
|
||||
},
|
||||
buffer: new Buffer('0004' + '09' + '00' + '0000000A' + '12345678', 'hex')
|
||||
}, {
|
||||
frame: {
|
||||
type: 'CONTINUATION',
|
||||
flags: { RESERVED1: false, RESERVED2: false, END_HEADERS: true },
|
||||
stream: 10,
|
||||
|
||||
data: new Buffer('12345678', 'hex')
|
||||
},
|
||||
// length + type + flags + stream + content
|
||||
buffer: new Buffer('0004' + '0A' + '04' + '0000000A' + '12345678', 'hex')
|
||||
}];
|
||||
|
||||
describe('framer.js', function() {
|
||||
describe('Serializer', function() {
|
||||
describe('static method .commonHeader({ type, flags, stream }, buffer_array)', function() {
|
||||
it('should add the appropriate 8 byte header buffer in front of the others', function() {
|
||||
for (var i = 0; i < test_frames.length; i++) {
|
||||
var test = test_frames[i];
|
||||
var buffers = [test.buffer.slice(8)];
|
||||
var header_buffer = test.buffer.slice(0,8);
|
||||
Serializer.commonHeader(test.frame, buffers);
|
||||
expect(buffers[0]).to.deep.equal(header_buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(frame_types).forEach(function(type) {
|
||||
var tests = test_frames.filter(function(test) { return test.frame.type === type; });
|
||||
var frame_shape = '{ ' + frame_types[type].join(', ') + ' }';
|
||||
describe('static method .' + type + '(' + frame_shape + ', buffer_array)', function() {
|
||||
it('should push buffers to the array that make up a ' + type + ' type payload', function() {
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
var test = tests[i];
|
||||
var buffers = [];
|
||||
Serializer[type](test.frame, buffers);
|
||||
expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(8));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform stream', function() {
|
||||
it('should transform frame objects to appropriate buffers', function() {
|
||||
var stream = new Serializer(util.log);
|
||||
|
||||
for (var i = 0; i < test_frames.length; i++) {
|
||||
var test = test_frames[i];
|
||||
stream.write(test.frame);
|
||||
var chunk, buffer = new Buffer(0);
|
||||
while (chunk = stream.read()) {
|
||||
buffer = util.concat([buffer, chunk]);
|
||||
}
|
||||
expect(buffer).to.be.deep.equal(test.buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Deserializer', function() {
|
||||
describe('static method .commonHeader(header_buffer, frame)', function() {
|
||||
it('should augment the frame object with these properties: { type, flags, stream })', function() {
|
||||
for (var i = 0; i < test_frames.length; i++) {
|
||||
var test = test_frames[i], frame = {};
|
||||
Deserializer.commonHeader(test.buffer.slice(0,8), frame);
|
||||
expect(frame).to.deep.equal({
|
||||
type: test.frame.type,
|
||||
flags: test.frame.flags,
|
||||
stream: test.frame.stream
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(frame_types).forEach(function(type) {
|
||||
var tests = test_frames.filter(function(test) { return test.frame.type === type; });
|
||||
var frame_shape = '{ ' + frame_types[type].join(', ') + ' }';
|
||||
describe('static method .' + type + '(payload_buffer, frame)', function() {
|
||||
it('should augment the frame object with these properties: ' + frame_shape, function() {
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
var test = tests[i];
|
||||
var frame = {
|
||||
type: test.frame.type,
|
||||
flags: test.frame.flags,
|
||||
stream: test.frame.stream
|
||||
};
|
||||
Deserializer[type](test.buffer.slice(8), frame);
|
||||
expect(frame).to.deep.equal(test.frame);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform stream', function() {
|
||||
it('should transform buffers to appropriate frame object', function() {
|
||||
var stream = new Deserializer(util.log);
|
||||
|
||||
var shuffled = util.shuffleBuffers(test_frames.map(function(test) { return test.buffer; }));
|
||||
shuffled.forEach(stream.write.bind(stream));
|
||||
|
||||
for (var j = 0; j < test_frames.length; j++) {
|
||||
expect(stream.read()).to.be.deep.equal(test_frames[j].frame);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bunyan formatter', function() {
|
||||
describe('`frame`', function() {
|
||||
var format = framer.serializers.frame;
|
||||
it('should assign a unique ID to each frame', function() {
|
||||
var frame1 = { type: 'DATA', data: new Buffer(10) };
|
||||
var frame2 = { type: 'PRIORITY', priority: 1 };
|
||||
expect(format(frame1).id).to.be.equal(format(frame1));
|
||||
expect(format(frame2).id).to.be.equal(format(frame2));
|
||||
expect(format(frame1)).to.not.be.equal(format(frame2));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
416
testing/xpcshell/node-http2/node_modules/http2-protocol/test/stream.js
generated
vendored
Normal file
416
testing/xpcshell/node-http2/node_modules/http2-protocol/test/stream.js
generated
vendored
Normal file
@ -0,0 +1,416 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
|
||||
var stream = require('../lib/stream');
|
||||
var Stream = stream.Stream;
|
||||
|
||||
function createStream() {
|
||||
var stream = new Stream(util.log);
|
||||
stream.upstream._window = Infinity;
|
||||
stream.upstream._remoteFlowControlDisabled = true;
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Execute a list of commands and assertions
|
||||
var recorded_events = ['state', 'error', 'window_update', 'headers', 'promise'];
|
||||
function execute_sequence(stream, sequence, done) {
|
||||
if (!done) {
|
||||
done = sequence;
|
||||
sequence = stream;
|
||||
stream = createStream();
|
||||
}
|
||||
|
||||
var outgoing_frames = [];
|
||||
|
||||
var emit = stream.emit, events = [];
|
||||
stream.emit = function(name) {
|
||||
if (recorded_events.indexOf(name) !== -1) {
|
||||
events.push({ name: name, data: Array.prototype.slice.call(arguments, 1) });
|
||||
}
|
||||
return emit.apply(this, arguments);
|
||||
};
|
||||
|
||||
var commands = [], checks = [];
|
||||
sequence.forEach(function(step) {
|
||||
if ('method' in step || 'incoming' in step || 'outgoing' in step || 'wait' in step || 'set_state' in step) {
|
||||
commands.push(step);
|
||||
}
|
||||
|
||||
if ('outgoing' in step || 'event' in step || 'active' in step) {
|
||||
checks.push(step);
|
||||
}
|
||||
});
|
||||
|
||||
var activeCount = 0;
|
||||
function count_change(change) {
|
||||
activeCount += change;
|
||||
}
|
||||
|
||||
function execute(callback) {
|
||||
var command = commands.shift();
|
||||
if (command) {
|
||||
if ('method' in command) {
|
||||
var value = stream[command.method.name].apply(stream, command.method.arguments);
|
||||
if (command.method.ret) {
|
||||
command.method.ret(value);
|
||||
}
|
||||
execute(callback);
|
||||
} else if ('incoming' in command) {
|
||||
command.incoming.count_change = count_change;
|
||||
stream.upstream.write(command.incoming);
|
||||
execute(callback);
|
||||
} else if ('outgoing' in command) {
|
||||
outgoing_frames.push(stream.upstream.read());
|
||||
execute(callback);
|
||||
} else if ('set_state' in command) {
|
||||
stream.state = command.set_state;
|
||||
execute(callback);
|
||||
} else if ('wait' in command) {
|
||||
setTimeout(execute.bind(null, callback), command.wait);
|
||||
} else {
|
||||
throw new Error('Invalid command', command);
|
||||
}
|
||||
} else {
|
||||
setTimeout(callback, 5);
|
||||
}
|
||||
}
|
||||
|
||||
function check() {
|
||||
checks.forEach(function(check) {
|
||||
if ('outgoing' in check) {
|
||||
var frame = outgoing_frames.shift();
|
||||
for (var key in check.outgoing) {
|
||||
expect(frame).to.have.property(key).that.deep.equals(check.outgoing[key]);
|
||||
}
|
||||
count_change(frame.count_change);
|
||||
} else if ('event' in check) {
|
||||
var event = events.shift();
|
||||
expect(event.name).to.be.equal(check.event.name);
|
||||
check.event.data.forEach(function(data, index) {
|
||||
expect(event.data[index]).to.deep.equal(data);
|
||||
});
|
||||
} else if ('active' in check) {
|
||||
expect(activeCount).to.be.equal(check.active);
|
||||
} else {
|
||||
throw new Error('Invalid check', check);
|
||||
}
|
||||
});
|
||||
done();
|
||||
}
|
||||
|
||||
setImmediate(execute.bind(null, check));
|
||||
}
|
||||
|
||||
var example_frames = [
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} },
|
||||
{ type: 'RST_STREAM', flags: {}, error: 'CANCEL' },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log) }
|
||||
];
|
||||
|
||||
var invalid_incoming_frames = {
|
||||
IDLE: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} },
|
||||
{ type: 'RST_STREAM', flags: {}, error: 'CANCEL' }
|
||||
],
|
||||
RESERVED_LOCAL: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} }
|
||||
],
|
||||
RESERVED_REMOTE: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} }
|
||||
],
|
||||
OPEN: [
|
||||
],
|
||||
HALF_CLOSED_LOCAL: [
|
||||
],
|
||||
HALF_CLOSED_REMOTE: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} }
|
||||
]
|
||||
};
|
||||
|
||||
var invalid_outgoing_frames = {
|
||||
IDLE: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} }
|
||||
],
|
||||
RESERVED_LOCAL: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} }
|
||||
],
|
||||
RESERVED_REMOTE: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} }
|
||||
],
|
||||
OPEN: [
|
||||
],
|
||||
HALF_CLOSED_LOCAL: [
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {} }
|
||||
],
|
||||
HALF_CLOSED_REMOTE: [
|
||||
],
|
||||
CLOSED: [
|
||||
{ type: 'PRIORITY', flags: {}, priority: 1 },
|
||||
{ type: 'WINDOW_UPDATE', flags: {}, settings: {} },
|
||||
{ type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
|
||||
{ type: 'DATA', flags: {}, data: new Buffer(5) },
|
||||
{ type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log) }
|
||||
]
|
||||
};
|
||||
|
||||
describe('stream.js', function() {
|
||||
describe('Stream class', function() {
|
||||
describe('._transition(sending, frame) method', function() {
|
||||
it('should emit error, and answer RST_STREAM for invalid incoming frames', function() {
|
||||
Object.keys(invalid_incoming_frames).forEach(function(state) {
|
||||
invalid_incoming_frames[state].forEach(function(invalid_frame) {
|
||||
var stream = createStream();
|
||||
stream.state = state;
|
||||
expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
|
||||
});
|
||||
});
|
||||
|
||||
// CLOSED state as a result of incoming END_STREAM (or RST_STREAM)
|
||||
var stream = createStream();
|
||||
stream.headers({});
|
||||
stream.end();
|
||||
stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
|
||||
example_frames.forEach(function(invalid_frame) {
|
||||
invalid_frame.count_change = util.noop;
|
||||
expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
|
||||
});
|
||||
|
||||
// CLOSED state as a result of outgoing END_STREAM
|
||||
var stream = createStream();
|
||||
stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
|
||||
stream.headers({});
|
||||
stream.end();
|
||||
example_frames.slice(3).forEach(function(invalid_frame) {
|
||||
invalid_frame.count_change = util.noop;
|
||||
expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
|
||||
});
|
||||
});
|
||||
it('should throw exception for invalid outgoing frames', function() {
|
||||
Object.keys(invalid_outgoing_frames).forEach(function(state) {
|
||||
invalid_outgoing_frames[state].forEach(function(invalid_frame) {
|
||||
var stream = createStream();
|
||||
stream.state = state;
|
||||
expect(stream._transition.bind(stream, true, invalid_frame)).to.throw(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should close the stream when there\'s an incoming or outgoing RST_STREAM', function() {
|
||||
[
|
||||
'RESERVED_LOCAL',
|
||||
'RESERVED_REMOTE',
|
||||
'OPEN',
|
||||
'HALF_CLOSED_LOCAL',
|
||||
'HALF_CLOSED_REMOTE'
|
||||
].forEach(function(state) {
|
||||
[true, false].forEach(function(sending) {
|
||||
var stream = createStream();
|
||||
stream.state = state;
|
||||
stream._transition(sending, { type: 'RST_STREAM', flags: {} });
|
||||
expect(stream.state).to.be.equal('CLOSED');
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should ignore any incoming frame after sending reset', function() {
|
||||
var stream = createStream();
|
||||
stream.reset();
|
||||
example_frames.forEach(stream._transition.bind(stream, false));
|
||||
});
|
||||
it('should ignore certain incoming frames after closing the stream with END_STREAM', function() {
|
||||
var stream = createStream();
|
||||
stream.upstream.write({ type: 'HEADERS', flags: { END_STREAM: true }, headers:{} });
|
||||
stream.headers({});
|
||||
stream.end();
|
||||
example_frames.slice(0,3).forEach(function(frame) {
|
||||
frame.count_change = util.noop;
|
||||
stream._transition(false, frame);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('test scenario', function() {
|
||||
describe('sending request', function() {
|
||||
it('should trigger the appropriate state transitions and outgoing frames', function(done) {
|
||||
execute_sequence([
|
||||
{ method : { name: 'headers', arguments: [{ ':path': '/' }] } },
|
||||
{ outgoing: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
|
||||
{ event : { name: 'state', data: ['OPEN'] } },
|
||||
|
||||
{ wait : 5 },
|
||||
{ method : { name: 'end', arguments: [] } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
|
||||
{ outgoing: { type: 'DATA', flags: { END_STREAM: true }, data: new Buffer(0) } },
|
||||
|
||||
{ wait : 10 },
|
||||
{ incoming: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
|
||||
{ incoming: { type: 'DATA' , flags: { END_STREAM: true }, data: new Buffer(5) } },
|
||||
{ event : { name: 'headers', data: [{ ':status': 200 }] } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 0 }
|
||||
], done);
|
||||
});
|
||||
});
|
||||
describe('answering request', function() {
|
||||
it('should trigger the appropriate state transitions and outgoing frames', function(done) {
|
||||
var payload = new Buffer(5);
|
||||
execute_sequence([
|
||||
{ incoming: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
|
||||
{ event : { name: 'state', data: ['OPEN'] } },
|
||||
{ event : { name: 'headers', data: [{ ':path': '/' }] } },
|
||||
|
||||
{ wait : 5 },
|
||||
{ incoming: { type: 'DATA', flags: { }, data: new Buffer(5) } },
|
||||
{ incoming: { type: 'DATA', flags: { END_STREAM: true }, data: new Buffer(10) } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
|
||||
|
||||
{ wait : 5 },
|
||||
{ method : { name: 'headers', arguments: [{ ':status': 200 }] } },
|
||||
{ outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
|
||||
|
||||
{ wait : 5 },
|
||||
{ method : { name: 'end', arguments: [payload] } },
|
||||
{ outgoing: { type: 'DATA', flags: { END_STREAM: true }, data: payload } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 0 }
|
||||
], done);
|
||||
});
|
||||
});
|
||||
describe('sending push stream', function() {
|
||||
it('should trigger the appropriate state transitions and outgoing frames', function(done) {
|
||||
var payload = new Buffer(5);
|
||||
var pushStream;
|
||||
|
||||
execute_sequence([
|
||||
// receiving request
|
||||
{ incoming: { type: 'HEADERS', flags: { END_STREAM: true }, headers: { ':path': '/' } } },
|
||||
{ event : { name: 'state', data: ['OPEN'] } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
|
||||
{ event : { name: 'headers', data: [{ ':path': '/' }] } },
|
||||
|
||||
// sending response headers
|
||||
{ wait : 5 },
|
||||
{ method : { name: 'headers', arguments: [{ ':status': '200' }] } },
|
||||
{ outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': '200' } } },
|
||||
|
||||
// sending push promise
|
||||
{ method : { name: 'promise', arguments: [{ ':path': '/' }], ret: function(str) { pushStream = str; } } },
|
||||
{ outgoing: { type: 'PUSH_PROMISE', flags: { }, headers: { ':path': '/' } } },
|
||||
|
||||
// sending response data
|
||||
{ method : { name: 'end', arguments: [payload] } },
|
||||
{ outgoing: { type: 'DATA', flags: { END_STREAM: true }, data: payload } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 0 }
|
||||
], function() {
|
||||
// initial state of the promised stream
|
||||
expect(pushStream.state).to.equal('RESERVED_LOCAL');
|
||||
|
||||
execute_sequence(pushStream, [
|
||||
// push headers
|
||||
{ wait : 5 },
|
||||
{ method : { name: 'headers', arguments: [{ ':status': '200' }] } },
|
||||
{ outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': '200' } } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
|
||||
|
||||
// push data
|
||||
{ method : { name: 'end', arguments: [payload] } },
|
||||
{ outgoing: { type: 'DATA', flags: { END_STREAM: true }, data: payload } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 1 }
|
||||
], done);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('receiving push stream', function() {
|
||||
it('should trigger the appropriate state transitions and outgoing frames', function(done) {
|
||||
var payload = new Buffer(5);
|
||||
var original_stream = createStream();
|
||||
var promised_stream = createStream();
|
||||
|
||||
done = util.callNTimes(2, done);
|
||||
|
||||
execute_sequence(original_stream, [
|
||||
// sending request headers
|
||||
{ method : { name: 'headers', arguments: [{ ':path': '/' }] } },
|
||||
{ method : { name: 'end', arguments: [] } },
|
||||
{ outgoing: { type: 'HEADERS', flags: { END_STREAM: true }, headers: { ':path': '/' } } },
|
||||
{ event : { name: 'state', data: ['OPEN'] } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
|
||||
|
||||
// receiving response headers
|
||||
{ wait : 10 },
|
||||
{ incoming: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
|
||||
{ event : { name: 'headers', data: [{ ':status': 200 }] } },
|
||||
|
||||
// receiving push promise
|
||||
{ incoming: { type: 'PUSH_PROMISE', flags: { }, headers: { ':path': '/2.html' }, promised_stream: promised_stream } },
|
||||
{ event : { name: 'promise', data: [promised_stream, { ':path': '/2.html' }] } },
|
||||
|
||||
// receiving response data
|
||||
{ incoming: { type: 'DATA' , flags: { END_STREAM: true }, data: payload } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 0 }
|
||||
], done);
|
||||
|
||||
execute_sequence(promised_stream, [
|
||||
// initial state of the promised stream
|
||||
{ event : { name: 'state', data: ['RESERVED_REMOTE'] } },
|
||||
|
||||
// push headers
|
||||
{ wait : 10 },
|
||||
{ incoming: { type: 'HEADERS', flags: { END_STREAM: false }, headers: { ':status': 200 } } },
|
||||
{ event : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
|
||||
{ event : { name: 'headers', data: [{ ':status': 200 }] } },
|
||||
|
||||
// push data
|
||||
{ incoming: { type: 'DATA', flags: { END_STREAM: true }, data: payload } },
|
||||
{ event : { name: 'state', data: ['CLOSED'] } },
|
||||
|
||||
{ active : 0 }
|
||||
], done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bunyan formatter', function() {
|
||||
describe('`s`', function() {
|
||||
var format = stream.serializers.s;
|
||||
it('should assign a unique ID to each frame', function() {
|
||||
var stream1 = createStream();
|
||||
var stream2 = createStream();
|
||||
expect(format(stream1)).to.be.equal(format(stream1));
|
||||
expect(format(stream2)).to.be.equal(format(stream2));
|
||||
expect(format(stream1)).to.not.be.equal(format(stream2));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
87
testing/xpcshell/node-http2/node_modules/http2-protocol/test/util.js
generated
vendored
Normal file
87
testing/xpcshell/node-http2/node_modules/http2-protocol/test/util.js
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
function noop() {}
|
||||
exports.noop = noop;
|
||||
|
||||
if (process.env.HTTP2_LOG) {
|
||||
var logOutput = process.stderr;
|
||||
if (process.stderr.isTTY) {
|
||||
var bin = path.resolve(path.dirname(require.resolve('bunyan')), '..', 'bin', 'bunyan');
|
||||
if(bin && fs.existsSync(bin)) {
|
||||
logOutput = spawn(bin, ['-o', 'short'], {
|
||||
stdio: [null, process.stderr, process.stderr]
|
||||
}).stdin;
|
||||
}
|
||||
}
|
||||
exports.createLogger = function(name) {
|
||||
return require('bunyan').createLogger({
|
||||
name: name,
|
||||
stream: logOutput,
|
||||
level: process.env.HTTP2_LOG,
|
||||
serializers: require('../lib/http').serializers
|
||||
});
|
||||
};
|
||||
exports.log = exports.createLogger('test');
|
||||
} else {
|
||||
exports.createLogger = function() {
|
||||
return exports.log;
|
||||
};
|
||||
exports.log = {
|
||||
fatal: noop,
|
||||
error: noop,
|
||||
warn : noop,
|
||||
info : noop,
|
||||
debug: noop,
|
||||
trace: noop,
|
||||
|
||||
child: function() { return this; }
|
||||
};
|
||||
}
|
||||
|
||||
exports.callNTimes = function callNTimes(limit, done) {
|
||||
if (limit === 0) {
|
||||
done();
|
||||
} else {
|
||||
var i = 0;
|
||||
return function() {
|
||||
i += 1;
|
||||
if (i === limit) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Concatenate an array of buffers into a new buffer
|
||||
exports.concat = function concat(buffers) {
|
||||
var size = 0;
|
||||
for (var i = 0; i < buffers.length; i++) {
|
||||
size += buffers[i].length;
|
||||
}
|
||||
|
||||
var concatenated = new Buffer(size);
|
||||
for (var cursor = 0, j = 0; j < buffers.length; cursor += buffers[j].length, j++) {
|
||||
buffers[j].copy(concatenated, cursor);
|
||||
}
|
||||
|
||||
return concatenated;
|
||||
};
|
||||
|
||||
exports.random = function random(min, max) {
|
||||
return min + Math.floor(Math.random() * (max - min + 1));
|
||||
};
|
||||
|
||||
// Concatenate an array of buffers and then cut them into random size buffers
|
||||
exports.shuffleBuffers = function shuffleBuffers(buffers) {
|
||||
var concatenated = exports.concat(buffers), output = [], written = 0;
|
||||
|
||||
while (written < concatenated.length) {
|
||||
var chunk_size = Math.min(concatenated.length - written, Math.ceil(Math.random()*20));
|
||||
output.push(concatenated.slice(written, written + chunk_size));
|
||||
written += chunk_size;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
55
testing/xpcshell/node-http2/package.json
Normal file
55
testing/xpcshell/node-http2/package.json
Normal file
File diff suppressed because one or more lines are too long
422
testing/xpcshell/node-http2/test/http.js
Normal file
422
testing/xpcshell/node-http2/test/http.js
Normal file
@ -0,0 +1,422 @@
|
||||
var expect = require('chai').expect;
|
||||
var util = require('./util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var http2 = require('../lib/http');
|
||||
var https = require('https');
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
var options = {
|
||||
key: fs.readFileSync(path.join(__dirname, '../example/localhost.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '../example/localhost.crt')),
|
||||
log: util.log
|
||||
};
|
||||
|
||||
http2.globalAgent = new http2.Agent({ log: util.log });
|
||||
|
||||
describe('http.js', function() {
|
||||
describe('Server', function() {
|
||||
describe('new Server(options)', function() {
|
||||
it('should throw if called without \'plain\' or TLS options', function() {
|
||||
expect(function() {
|
||||
new http2.Server();
|
||||
}).to.throw(Error);
|
||||
expect(function() {
|
||||
http2.createServer(util.noop);
|
||||
}).to.throw(Error);
|
||||
});
|
||||
});
|
||||
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 backingServer = server._server;
|
||||
var newTimeout = 10;
|
||||
server.timeout = newTimeout;
|
||||
expect(server.timeout).to.be.equal(newTimeout);
|
||||
expect(backingServer.timeout).to.be.equal(newTimeout);
|
||||
});
|
||||
});
|
||||
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 backingServer = server._server;
|
||||
var newTimeout = 10;
|
||||
var newCallback = util.noop;
|
||||
backingServer.setTimeout = function(timeout, callback) {
|
||||
expect(timeout).to.be.equal(newTimeout);
|
||||
expect(callback).to.be.equal(newCallback);
|
||||
};
|
||||
server.setTimeout(newTimeout, newCallback);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Agent', function() {
|
||||
describe('property `maxSockets`', function() {
|
||||
it('should be a proxy for the backing HTTPS agent\'s `maxSockets` property', function() {
|
||||
var agent = new http2.Agent({ log: util.log });
|
||||
var backingAgent = agent._httpsAgent;
|
||||
var newMaxSockets = backingAgent.maxSockets + 1;
|
||||
agent.maxSockets = newMaxSockets;
|
||||
expect(agent.maxSockets).to.be.equal(newMaxSockets);
|
||||
expect(backingAgent.maxSockets).to.be.equal(newMaxSockets);
|
||||
});
|
||||
});
|
||||
describe('method `request(options, [callback])`', function() {
|
||||
it('should throw when trying to use with \'http\' scheme', function() {
|
||||
expect(function() {
|
||||
var agent = new http2.Agent({ log: util.log });
|
||||
agent.request({ protocol: 'http:' });
|
||||
}).to.throw(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('OutgoingRequest', function() {
|
||||
function testFallbackProxyMethod(name, originalArguments, done) {
|
||||
var request = new http2.OutgoingRequest();
|
||||
|
||||
// When in HTTP/2 mode, this call should be ignored
|
||||
request.stream = { reset: util.noop };
|
||||
request[name].apply(request, originalArguments);
|
||||
delete request.stream;
|
||||
|
||||
// When in fallback mode, this call should be forwarded
|
||||
request[name].apply(request, originalArguments);
|
||||
var mockFallbackRequest = { on: util.noop };
|
||||
mockFallbackRequest[name] = function() {
|
||||
expect(arguments).to.deep.equal(originalArguments);
|
||||
done();
|
||||
};
|
||||
request._fallback(mockFallbackRequest);
|
||||
}
|
||||
describe('method `setNoDelay(noDelay)`', function() {
|
||||
it('should act as a proxy for the backing HTTPS agent\'s `setNoDelay` method', function(done) {
|
||||
testFallbackProxyMethod('setNoDelay', [true], done);
|
||||
});
|
||||
});
|
||||
describe('method `setSocketKeepAlive(enable, initialDelay)`', function() {
|
||||
it('should act as a proxy for the backing HTTPS agent\'s `setSocketKeepAlive` method', function(done) {
|
||||
testFallbackProxyMethod('setSocketKeepAlive', [true, util.random(10, 100)], done);
|
||||
});
|
||||
});
|
||||
describe('method `setTimeout(timeout, [callback])`', function() {
|
||||
it('should act as a proxy for the backing HTTPS agent\'s `setTimeout` method', function(done) {
|
||||
testFallbackProxyMethod('setTimeout', [util.random(10, 100), util.noop], done);
|
||||
});
|
||||
});
|
||||
describe('method `abort()`', function() {
|
||||
it('should act as a proxy for the backing HTTPS agent\'s `abort` method', function(done) {
|
||||
testFallbackProxyMethod('abort', [], done);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('test scenario', function() {
|
||||
describe('simple request', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1234, function() {
|
||||
http2.get('https://localhost:1234' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request with payload', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
request.once('readable', function() {
|
||||
expect(request.read().toString()).to.equal(message);
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(1240, function() {
|
||||
var request = http2.request({
|
||||
host: 'localhost',
|
||||
port: 1240,
|
||||
path: path
|
||||
});
|
||||
request.write(message);
|
||||
request.end();
|
||||
request.on('response', function() {
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request with custom status code and headers', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
var headerName = 'name';
|
||||
var headerValue = 'value';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
// Request URL and headers
|
||||
expect(request.url).to.equal(path);
|
||||
expect(request.headers[headerName]).to.equal(headerValue);
|
||||
|
||||
// A header to be overwritten later
|
||||
response.setHeader(headerName, 'to be overwritten');
|
||||
expect(response.getHeader(headerName)).to.equal('to be overwritten');
|
||||
|
||||
// A header to be deleted
|
||||
response.setHeader('nonexistent', 'x');
|
||||
response.removeHeader('nonexistent');
|
||||
expect(response.getHeader('nonexistent')).to.equal(undefined);
|
||||
|
||||
// Don't send date
|
||||
response.sendDate = false;
|
||||
|
||||
// Specifying more headers, the status code and a reason phrase with `writeHead`
|
||||
var moreHeaders = {};
|
||||
moreHeaders[headerName] = headerValue;
|
||||
response.writeHead(600, 'to be discarded', moreHeaders);
|
||||
expect(response.getHeader(headerName)).to.equal(headerValue);
|
||||
|
||||
// Empty response body
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1239, function() {
|
||||
var headers = {};
|
||||
headers[headerName] = headerValue;
|
||||
var request = http2.request({
|
||||
host: 'localhost',
|
||||
port: 1239,
|
||||
path: path,
|
||||
headers: headers
|
||||
});
|
||||
request.end();
|
||||
request.on('response', function(response) {
|
||||
expect(response.headers[headerName]).to.equal(headerValue);
|
||||
expect(response.headers['nonexistent']).to.equal(undefined);
|
||||
expect(response.headers['date']).to.equal(undefined);
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request over plain TCP', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer({
|
||||
plain: true,
|
||||
log: util.log
|
||||
}, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1237, function() {
|
||||
var request = http2.request({
|
||||
plain: true,
|
||||
host: 'localhost',
|
||||
port: 1237,
|
||||
path: path
|
||||
}, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request to an HTTPS/1 server', function() {
|
||||
it('should fall back to HTTPS/1 successfully', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = https.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(5678, function() {
|
||||
http2.get('https://localhost:5678' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('HTTPS/1 request to a HTTP/2 server', function() {
|
||||
it('should fall back to HTTPS/1 successfully', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1236, function() {
|
||||
https.get('https://localhost:1236' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('two parallel request', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1237, function() {
|
||||
done = util.callNTimes(2, done);
|
||||
// 1. request
|
||||
http2.get('https://localhost:1237' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
// 2. request
|
||||
http2.get('https://localhost:1237' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('two subsequent request', function() {
|
||||
it('should use the same HTTP/2 connection', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1238, function() {
|
||||
// 1. request
|
||||
http2.get('https://localhost:1238' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
|
||||
// 2. request
|
||||
http2.get('https://localhost:1238' + path, function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('request and response with trailers', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
var requestTrailers = { 'content-md5': 'x' };
|
||||
var responseTrailers = { 'content-md5': 'y' };
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
request.on('data', util.noop);
|
||||
request.once('end', function() {
|
||||
expect(request.trailers).to.deep.equal(requestTrailers);
|
||||
response.write(message);
|
||||
response.addTrailers(responseTrailers);
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(1241, function() {
|
||||
var request = http2.request('https://localhost:1241' + path);
|
||||
request.addTrailers(requestTrailers);
|
||||
request.end();
|
||||
request.on('response', function(response) {
|
||||
response.on('data', util.noop);
|
||||
response.once('end', function() {
|
||||
expect(response.trailers).to.deep.equal(responseTrailers);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('server push', function() {
|
||||
it('should work as expected', function(done) {
|
||||
var path = '/x';
|
||||
var message = 'Hello world';
|
||||
var pushedPath = '/y';
|
||||
var pushedMessage = 'Hello world 2';
|
||||
|
||||
var server = http2.createServer(options, function(request, response) {
|
||||
expect(request.url).to.equal(path);
|
||||
var push1 = response.push('/y');
|
||||
push1.end(pushedMessage);
|
||||
var push2 = response.push({ path: '/y', protocol: 'https:' });
|
||||
push2.end(pushedMessage);
|
||||
response.end(message);
|
||||
});
|
||||
|
||||
server.listen(1235, function() {
|
||||
var request = http2.get('https://localhost:1235' + path);
|
||||
done = util.callNTimes(5, done);
|
||||
|
||||
request.on('response', function(response) {
|
||||
response.on('readable', function() {
|
||||
expect(response.read().toString()).to.equal(message);
|
||||
done();
|
||||
});
|
||||
response.on('end', done);
|
||||
});
|
||||
|
||||
request.on('push', function(promise) {
|
||||
expect(promise.url).to.be.equal(pushedPath);
|
||||
promise.on('response', function(pushStream) {
|
||||
pushStream.on('readable', function() {
|
||||
expect(pushStream.read().toString()).to.equal(pushedMessage);
|
||||
done();
|
||||
});
|
||||
pushStream.on('end', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
87
testing/xpcshell/node-http2/test/util.js
Normal file
87
testing/xpcshell/node-http2/test/util.js
Normal file
@ -0,0 +1,87 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
function noop() {}
|
||||
exports.noop = noop;
|
||||
|
||||
if (process.env.HTTP2_LOG) {
|
||||
var logOutput = process.stderr;
|
||||
if (process.stderr.isTTY) {
|
||||
var bin = path.resolve(path.dirname(require.resolve('bunyan')), '..', 'bin', 'bunyan');
|
||||
if(bin && fs.existsSync(bin)) {
|
||||
logOutput = spawn(bin, ['-o', 'short'], {
|
||||
stdio: [null, process.stderr, process.stderr]
|
||||
}).stdin;
|
||||
}
|
||||
}
|
||||
exports.createLogger = function(name) {
|
||||
return require('bunyan').createLogger({
|
||||
name: name,
|
||||
stream: logOutput,
|
||||
level: process.env.HTTP2_LOG,
|
||||
serializers: require('../lib/http').serializers
|
||||
});
|
||||
};
|
||||
exports.log = exports.createLogger('test');
|
||||
} else {
|
||||
exports.createLogger = function() {
|
||||
return exports.log;
|
||||
};
|
||||
exports.log = {
|
||||
fatal: noop,
|
||||
error: noop,
|
||||
warn : noop,
|
||||
info : noop,
|
||||
debug: noop,
|
||||
trace: noop,
|
||||
|
||||
child: function() { return this; }
|
||||
};
|
||||
}
|
||||
|
||||
exports.callNTimes = function callNTimes(limit, done) {
|
||||
if (limit === 0) {
|
||||
done();
|
||||
} else {
|
||||
var i = 0;
|
||||
return function() {
|
||||
i += 1;
|
||||
if (i === limit) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Concatenate an array of buffers into a new buffer
|
||||
exports.concat = function concat(buffers) {
|
||||
var size = 0;
|
||||
for (var i = 0; i < buffers.length; i++) {
|
||||
size += buffers[i].length;
|
||||
}
|
||||
|
||||
var concatenated = new Buffer(size);
|
||||
for (var cursor = 0, j = 0; j < buffers.length; cursor += buffers[j].length, j++) {
|
||||
buffers[j].copy(concatenated, cursor);
|
||||
}
|
||||
|
||||
return concatenated;
|
||||
};
|
||||
|
||||
exports.random = function random(min, max) {
|
||||
return min + Math.floor(Math.random() * (max - min + 1));
|
||||
};
|
||||
|
||||
// Concatenate an array of buffers and then cut them into random size buffers
|
||||
exports.shuffleBuffers = function shuffleBuffers(buffers) {
|
||||
var concatenated = exports.concat(buffers), output = [], written = 0;
|
||||
|
||||
while (written < concatenated.length) {
|
||||
var chunk_size = Math.min(concatenated.length - written, Math.ceil(Math.random()*20));
|
||||
output.push(concatenated.slice(written, written + chunk_size));
|
||||
written += chunk_size;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
@ -69,6 +69,7 @@ proto._init = function _init(base, options, handler) {
|
||||
}
|
||||
|
||||
options.NPNProtocols = ['spdy/3', 'spdy/2', 'http/1.1', 'http/1.0'];
|
||||
options.ALPNProtocols = ['spdy/3', 'spdy/2', 'http/1.1', 'http/1.0'];
|
||||
state.options = options;
|
||||
state.reqHandler = handler;
|
||||
|
||||
@ -79,7 +80,7 @@ proto._init = function _init(base, options, handler) {
|
||||
}
|
||||
|
||||
// Use https if NPN is not supported
|
||||
if (!process.features.tls_npn && !options.debug && !options.plain) {
|
||||
if (!process.features.tls_npn && !process.features.tls_alpn && !options.debug && !options.plain) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
@ -226,7 +227,8 @@ proto._onConnection = function _onConnection(socket) {
|
||||
state = this._spdyState;
|
||||
|
||||
// Fallback to HTTPS if needed
|
||||
if ((!socket.npnProtocol || !socket.npnProtocol.match(/spdy/)) &&
|
||||
var selectedProtocol = socket.npnProtocol || socket.alpnProtocol;
|
||||
if ((!selectedProtocol || !selectedProtocol.match(/spdy/)) &&
|
||||
!state.options.debug && !state.options.plain) {
|
||||
return state.handler.call(this, socket);
|
||||
}
|
||||
|
@ -753,7 +753,7 @@ class XPCShellTests(object):
|
||||
return wrapped
|
||||
setattr(self.log, fun_name, wrap(unwrapped))
|
||||
|
||||
self.nodeProc = None
|
||||
self.nodeProc = {}
|
||||
|
||||
def buildTestList(self):
|
||||
"""
|
||||
@ -940,33 +940,38 @@ class XPCShellTests(object):
|
||||
|
||||
# We try to find the node executable in the path given to us by the user in
|
||||
# the MOZ_NODE_PATH environment variable
|
||||
localPath = os.getenv('MOZ_NODE_PATH', None)
|
||||
# localPath = os.getenv('MOZ_NODE_PATH', None)
|
||||
# Temporarily, we use the node binary in this directory
|
||||
localPath = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'node')
|
||||
if localPath and os.path.exists(localPath) and os.path.isfile(localPath):
|
||||
nodeBin = localPath
|
||||
|
||||
if nodeBin:
|
||||
self.log.info('Found node at %s' % (nodeBin,))
|
||||
|
||||
def startServer(name, serverJs):
|
||||
if os.path.exists(serverJs):
|
||||
# OK, we found our SPDY server, let's try to get it running
|
||||
self.log.info('Found %s at %s' % (name, serverJs))
|
||||
try:
|
||||
# We pipe stdin to node because the spdy server will exit when its
|
||||
# stdin reaches EOF
|
||||
process = Popen([nodeBin, serverJs], stdin=PIPE, stdout=PIPE,
|
||||
stderr=STDOUT, env=self.env, cwd=os.getcwd())
|
||||
self.nodeProc[name] = process
|
||||
|
||||
# Check to make sure the server starts properly by waiting for it to
|
||||
# tell us it's started
|
||||
msg = process.stdout.readline()
|
||||
if 'server listening' in msg:
|
||||
nodeMozInfo['hasNode'] = True # Todo: refactor this
|
||||
except OSError, e:
|
||||
# This occurs if the subprocess couldn't be started
|
||||
self.log.error('Could not run %s server: %s' % (name, str(e)))
|
||||
|
||||
myDir = os.path.split(os.path.abspath(__file__))[0]
|
||||
mozSpdyJs = os.path.join(myDir, 'moz-spdy', 'moz-spdy.js')
|
||||
|
||||
if os.path.exists(mozSpdyJs):
|
||||
# OK, we found our SPDY server, let's try to get it running
|
||||
self.log.info('Found moz-spdy at %s' % (mozSpdyJs,))
|
||||
stdout, stderr = self.getPipes()
|
||||
try:
|
||||
# We pipe stdin to node because the spdy server will exit when its
|
||||
# stdin reaches EOF
|
||||
self.nodeProc = Popen([nodeBin, mozSpdyJs], stdin=PIPE, stdout=PIPE,
|
||||
stderr=STDOUT, env=self.env, cwd=os.getcwd())
|
||||
|
||||
# Check to make sure the server starts properly by waiting for it to
|
||||
# tell us it's started
|
||||
msg = self.nodeProc.stdout.readline()
|
||||
if msg.startswith('SPDY server listening'):
|
||||
nodeMozInfo['hasNode'] = True
|
||||
except OSError, e:
|
||||
# This occurs if the subprocess couldn't be started
|
||||
self.log.error('Could not run node SPDY server: %s' % (str(e),))
|
||||
startServer('moz-spdy', os.path.join(myDir, 'moz-spdy', 'moz-spdy.js'))
|
||||
startServer('moz-http2', os.path.join(myDir, 'moz-http2', 'moz-http2.js'))
|
||||
|
||||
mozinfo.update(nodeMozInfo)
|
||||
|
||||
@ -974,10 +979,9 @@ class XPCShellTests(object):
|
||||
"""
|
||||
Shut down our node process, if it exists
|
||||
"""
|
||||
if self.nodeProc:
|
||||
self.log.info('Node SPDY server shutting down ...')
|
||||
# moz-spdy exits when its stdin reaches EOF, so force that to happen here
|
||||
self.nodeProc.communicate()
|
||||
for name, proc in self.nodeProc.iteritems():
|
||||
self.log.info('Node %s server shutting down ...' % name)
|
||||
proc.terminate()
|
||||
|
||||
def writeXunitResults(self, results, name=None, filename=None, fh=None):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user