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

Support Http2 #29

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage
node_modules
/test/support/supertest/http2wrapper.js
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_js:
- "7.10"
- "8.9"
- "9.3"
- "10.10"
sudo: false
cache:
directories:
Expand All @@ -34,3 +35,7 @@ script:
- "test -z $(npm -ps ls eslint ) || npm run-script lint"
after_script:
- "test -e ./coverage/lcov.info && npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"
matrix:
include:
- node_js: "10"
env: HTTP2_TEST=1
16 changes: 14 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function vhost (hostname, handle) {
*/

function hostnameof (req) {
var host = req.headers.host
var host = ishttp2(req) ? req.headers[':authority'] : req.headers.host

if (!host) {
return
Expand Down Expand Up @@ -137,7 +137,7 @@ function hostregexp (val) {
*/

function vhostof (req, regexp) {
var host = req.headers.host
var host = ishttp2(req) ? req.headers[':authority'] : req.headers.host
var hostname = hostnameof(req)

if (!hostname) {
Expand All @@ -162,3 +162,15 @@ function vhostof (req, regexp) {

return obj
}

/**
* Check if a request is a http2 request.
*
* @param {Object} request
* @return {Boolean}
* @public
*/

function ishttp2 (req) {
return req.httpVersionMajor === 2
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"eslint-plugin-standard": "3.0.1",
"istanbul": "0.4.5",
"mocha": "2.5.3",
"supertest": "1.1.0"
"supertest": "2.0"
},
"files": [
"LICENSE",
Expand All @@ -31,6 +31,7 @@
"scripts": {
"lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --reporter spec --bail --check-leaks test/",
"test-http2": "HTTP2_TEST=1 mocha --reporter spec --bail --check-leaks test/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
}
Expand Down
188 changes: 188 additions & 0 deletions test/support/supertest/http2wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
'use strict';

const http2 = require('http2');
const Stream = require('stream');
const util = require('util');
const net = require('net');
const tls = require('tls');
const parse = require('url').parse;

const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_HOST,
HTTP2_HEADER_SET_COOKIE,
NGHTTP2_CANCEL,
} = http2.constants;


function setProtocol(protocol) {
return {
request: function (options) {
return new Request(protocol, options);
}
}
}

function Request(protocol, options) {
Stream.call(this);
const defaultPort = protocol === 'https:' ? 443 : 80;
const defaultHost = 'localhost'
const port = options.port || defaultPort;
const host = options.host || defaultHost;

delete options.port
delete options.host

this.method = options.method.toUpperCase();
this.path = options.path;
this.protocol = protocol;
this.host = host;

delete options.method
delete options.path

const sessionOptions = Object.assign({}, options);
if (options.socketPath) {
sessionOptions.socketPath = options.socketPath;
sessionOptions.createConnection = this.createUnixConnection.bind(this);
}

this._headers = {};

const session = http2.connect(`${protocol}//${host}:${port}`, sessionOptions);
this.setHeader('host', `${host}:${port}`)

session.on('error', (err) => this.emit('error', err));

this.session = session;
}

/**
* Inherit from `Stream` (which inherits from `EventEmitter`).
*/
util.inherits(Request, Stream);

Request.prototype.createUnixConnection = function (authority, options) {
switch (this.protocol) {
case 'http:':
return net.connect(options.socketPath);
case 'https:':
options.ALPNProtocols = ['h2'];
options.servername = this.host;
options.allowHalfOpen = true;
return tls.connect(options.socketPath, options);
default:
throw new Error('Unsupported protocol', this.protocol);
}
}

Request.prototype.setNoDelay = function (bool) {
// We can not use setNoDelay with HTTP/2.
// Node 10 limits http2session.socket methods to ones safe to use with HTTP/2.
// See also https://nodejs.org/api/http2.html#http2_http2session_socket
}

Request.prototype.getFrame = function () {
if (this.frame) {
return this.frame;
}

const method = {
[HTTP2_HEADER_PATH]: this.path,
[HTTP2_HEADER_METHOD]: this.method,
}

let headers = this.mapToHttp2Header(this._headers);

headers = Object.assign(headers, method);

const frame = this.session.request(headers);
frame.once('response', (headers, flags) => {
headers = this.mapToHttpHeader(headers);
frame.headers = headers;
frame.status = frame.statusCode = headers[HTTP2_HEADER_STATUS];
this.emit('response', frame);
});

this._headerSent = true;

frame.once('drain', () => this.emit('drain'));
frame.on('error', (err) => this.emit('error', err));
frame.on('close', () => this.session.close());

this.frame = frame;
return frame;
}

Request.prototype.mapToHttpHeader = function (headers) {
const keys = Object.keys(headers);
const http2Headers = {};
for (var i = 0; i < keys.length; i++) {
let key = keys[i];
let value = headers[key];
key = key.toLowerCase();
switch (key) {
case HTTP2_HEADER_SET_COOKIE:
value = Array.isArray(value) ? value : [value];
break;
default:
break;
}
http2Headers[key] = value;
}
return http2Headers;
}

Request.prototype.mapToHttp2Header = function (headers) {
const keys = Object.keys(headers);
const http2Headers = {};
for (var i = 0; i < keys.length; i++) {
let key = keys[i];
let value = headers[key];
key = key.toLowerCase();
switch (key) {
case HTTP2_HEADER_HOST:
key = HTTP2_HEADER_AUTHORITY;
value = /^http\:\/\/|^https\:\/\//.test(value) ? parse(value).host : value;
break;
default:
break;
}
http2Headers[key] = value;
}
return http2Headers;
}

Request.prototype.setHeader = function (name, value) {
this._headers[name.toLowerCase()] = value;
}

Request.prototype.getHeader = function (name) {
return this._headers[name.toLowerCase()];
}

Request.prototype.write = function (data, encoding) {
const frame = this.getFrame();
return frame.write(data, encoding);
};

Request.prototype.pipe = function (stream, options) {
const frame = this.getFrame();
return frame.pipe(stream, options);
}

Request.prototype.end = function (data) {
const frame = this.getFrame();
frame.end(data);
}

Request.prototype.abort = function (data) {
const frame = this.getFrame();
frame.close(NGHTTP2_CANCEL);
this.session.destroy();
}

exports.setProtocol = setProtocol;
32 changes: 32 additions & 0 deletions test/support/supertest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var request = require('supertest')

if (process.env.HTTP2_TEST) {
var http2 = require('http2')
var http2wrapper = require('./http2wrapper')
var agent = require('superagent')
var tls = require('tls')
agent.protocols = {
'http:': http2wrapper.setProtocol('http:'),
'https:': http2wrapper.setProtocol('https:')
}
request.Test.prototype.serverAddress = function (app, path, host) {
var addr = app.address()
var port
var protocol

if (!addr) this._server = app.listen(0)
port = app.address().port

protocol = app instanceof tls.Server ? 'https' : 'http'
return protocol + '://' + (host || '127.0.0.1') + ':' + port + path
}
var originalRequest = request
request = function (app) {
if (typeof app === 'function') {
app = http2.createServer(app)
}
return originalRequest(app)
}
}

module.exports = request
6 changes: 5 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@

var assert = require('assert')
var http = require('http')
var request = require('supertest')
var request = require('./support/supertest')
var vhost = require('..')

if (process.env.HTTP2_TEST) {
http = require('http2')
}

describe('vhost(hostname, server)', function () {
it('should route by Host', function (done) {
var vhosts = []
Expand Down