Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Node-http2 fork in favor of new Agent and Server #18

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
92 changes: 87 additions & 5 deletions lib/agent.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,94 @@
var http2 = require('http2.js');
/* global console */
var OutgoingRequest = require('http2.js').OutgoingRequest;
var Agent = require('http2.js').Agent;
var Endpoint = require('http2.js').protocol.Endpoint;
var url = require('url');

// TODO extend AGENT
function Http2CacheAgent() {
http2.Agent.apply(this, arguments);
Agent.apply(this, arguments);
}

Http2CacheAgent.prototype = Object.create(http2.Agent.prototype, {
// TODO override http2.Agent
Http2CacheAgent.prototype = Object.create(Agent.prototype, {
// Overide Server here
request: {
value: function request(options, callback) {

if (typeof options === 'string') {
options = url.parse(options);
} else {
options = Object.assign({}, 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');
this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.'));
}

var request = new OutgoingRequest(this._log);

if (callback) {
request.on('response', callback);
}

// Re-use transportUrl endPoint if specified
var key = ([
options.transportUrl
]).join(':');

// * There's an existing HTTP/2 connection to this host
var endpoint;
if (key in this.endpoints && this.endpoints[key]) {
endpoint = this.endpoints[key];
request._start(endpoint.createStream(), options);
}

// * HTTP/2 over generic stream transport
else if (options.transport) {
endpoint = new Endpoint(this._log, 'CLIENT', this._settings);
endpoint.socket = options.transport;

var self = this;

endpoint.socket.on('error', function(error) {
self._log.error('Socket error: ' + error.toString());
request.emit('error', error);
});

endpoint.on('error', function(error) {
self._log.error('Connection error: ' + error.toString());
request.emit('error', error);
});

endpoint.socket.on('close', function(error) {
// DPW This is sort of a hack to protect against
// the reuse of a endpoint that has the underlying
// connection closed. It would probably be better
// to implement this near lin 933 (if (key in this.endpoints))
// by checking the endpoint state (requires new API to expose)

// Alternatively, this could be a bug with my WS connection
// not emitting an error when it is unexpectedly closed ??
delete self.endpoints[key];
});

this.endpoints[key] = endpoint;
endpoint.pipe(endpoint.socket).pipe(endpoint);
request._start(endpoint.createStream(), options);

// Fallback
} else {
request = Agent.prototype.request.apply(this, arguments);
}

return request;
}
}
});

exports.Agent = Http2CacheAgent;
54 changes: 54 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* global console */
var Server = require('http2.js').Server,
logger = require('./logger');

function Http2CacheServer(options) {

options = Object.assign({}, options);

this._log = (options.log || logger.defaultLogger).child({
component: 'http'
});
this._settings = options.settings;

var start = this._start.bind(this);
var fallback = this._fallback.bind(this);

// HTTP2 over any generic transport
if (options.transport) {
this._mode = 'plain';
this._server = options.transport(options, start);
this._server.on('close', this.emit.bind(this, 'close'));
} else {
Server.apply(this, arguments);
}
}

Http2CacheServer.prototype = Object.create(Server.prototype, {
// Overide Server here
});

function createServer(options, requestListener) {
if (typeof options === 'function') {
requestListener = options;
options = {};
}

if (options.pfx || (options.key && options.cert)) {
throw new Error('options.pfx, options.key, and options.cert are nonsensical!');
}

options.plain = true;
var server = new Http2CacheServer(options);

if (requestListener) {
server.on('request', requestListener);
}

return server;
}

module.exports = {
Http2CacheServer: Http2CacheServer,
createServer: createServer
};
56 changes: 56 additions & 0 deletions lib/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,62 @@ var HTTP_METHODS = [
'CONNECT'
];

var STATUS_CODES = {
'202': 'Accepted',
'502': 'Bad Gateway',
'400': 'Bad Request',
'409': 'Conflict',
'100': 'Continue',
'201': 'Created',
'417': 'Expectation Failed',
'424': 'Failed Dependency',
'403': 'Forbidden',
'504': 'Gateway Timeout',
'410': 'Gone',
'505': 'HTTP Version Not Supported',
'419': 'Insufficient Space on Resource',
'507': 'Insufficient Storage',
'500': 'Server Error',
'411': 'Length Required',
'423': 'Locked',
'420': 'Method Failure',
'405': 'Method Not Allowed',
'301': 'Moved Permanently',
'302': 'Moved Temporarily',
'207': 'Multi-Status',
'300': 'Multiple Choices',
'511': 'Network Authentication Required',
'204': 'No Content',
'203': 'Non Authoritative Information',
'406': 'Not Acceptable',
'404': 'Not Found',
'501': 'Not Implemented',
'304': 'Not Modified',
'200': 'OK',
'206': 'Partial Content',
'402': 'Payment Required',
'308': 'Permanent Redirect',
'412': 'Precondition Failed',
'428': 'Precondition Required',
'102': 'Processing',
'407': 'Proxy Authentication Required',
'431': 'Request Header Fields Too Large',
'408': 'Request Timeout',
'413': 'Request Entity Too Large',
'414': 'Request-URI Too Long',
'416': 'Requested Range Not Satisfiable',
'205': 'Reset Content',
'303': 'See Other',
'503': 'Service Unavailable',
'101': 'Switching Protocols',
'307': 'Temporary Redirect',
'429': 'Too Many Requests',
'401': 'Unauthorized',
'422': 'Unprocessable Entity',
'415': 'Unsupported Media Type',
'305': 'Use Proxy'
};

// ProgressEvent
function ProgressEvent(type) {
this.type = type;
Expand Down
2 changes: 1 addition & 1 deletion test/http2-proxy-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('http2-proxy', function () {
done();
}
}

// start config http2 server
socket = getSocketServer({
port: 7081
Expand Down
3 changes: 1 addition & 2 deletions test/http2-xhr-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ if (typeof XMLHttpRequest === 'undefined') {
XMLHttpRequest = require("xhr2").XMLHttpRequest;
}
/* jshint ignore:end */

require("../lib/http2-cache");

var FormData = require("../lib/form-data").FormData,
Expand Down Expand Up @@ -48,7 +47,7 @@ describe('http2-xhr', function () {
socketOnRequest = function (request, response) {
throw new Error("socketOnRequest Unexpected request: " + request.url);
};

// start config http2 server
socket = getSocketServer({
port: 7081
Expand Down
140 changes: 140 additions & 0 deletions test/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* global console */
var chai = require('chai');
var assert = chai.assert;

/* jshint ignore:start */
if (typeof XMLHttpRequest === 'undefined') {
XMLHttpRequest = require("xhr2").XMLHttpRequest;
}
/* jshint ignore:end */
require("../lib/http2-cache");

var getSocketServer = require('./test-utils.js').getSocketServer,
getConfigServer = require('./test-utils.js').getConfigServer;

describe('http2-proxy', function () {

var config = {
'transport': 'ws://localhost:7081/',
'push': 'http://cache-endpoint1/stream',
'proxy': [
'http://cache-endpoint1/'
]
};

var config2 = {
'transport': 'ws://localhost:7082/path',
'proxy': [
'http://cache-endpoint2/'
]
};

// serves the config files
var configServer, configServer2;

before(function (done) {

var completed = 0;

function doneOn2() {
completed++;
if (completed === 2) {
done();
}
}

configServer = getConfigServer({
config: config,
port: 7080
}, doneOn2);

configServer2 = getConfigServer({
config: config2,
port: 7090
}, doneOn2);
});

after(function (done) {

var completed = 0;

function doneOn2() {
completed++;
if (completed === 2) {
done();
}
}

configServer.close(doneOn2);
configServer2.close(doneOn2);
});


var socket;
var socket2;
var socketOnRequest;
var socket2OnRequest;

beforeEach(function (done) {
// starts the 2 h2overWs servers
socketOnRequest = function (request, response) {
throw new Error("socketOnRequest Unexpected request: " + request.url);
};
//
socket2OnRequest = function (request, response) {
throw new Error("socket2OnRequest Unexpected request: " + request.url);
};

var completed = 0;

function doneOn2() {
completed++;
if (completed === 2) {
done();
}
}

// start config http2 server
socket = getSocketServer({
port: 7081
}, function (request, response) {
socketOnRequest(request, response);
}, doneOn2);

// start config2 http2 server
socket2 = getSocketServer({
port: 7082
}, function (request, response) {
socket2OnRequest(request, response);
}, doneOn2);
});

afterEach(function (done) {

var completed = 0;

function doneOn2() {
completed++;
if (completed === 2) {
done();
}
}

socket.close(doneOn2);
socket2.close(doneOn2);
});

it('proxy() with empty params throws exception', function () {
assert.throws(function () {
XMLHttpRequest.proxy();
});
});

it('should load config and start stream for pushs when h2PushPath is set in config', function (done) {
socketOnRequest = function (request) {
assert.equal(request.url, '/stream', 'should be on streaming url');
done();
};
XMLHttpRequest.proxy(["http://localhost:7080/config"]);
});
});