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 http/2. #3730

Open
wants to merge 6 commits into
base: 5.0
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
@@ -1,2 +1,3 @@
coverage
node_modules
test/support/http2wrapper.js
4 changes: 4 additions & 0 deletions .travis.yml
Expand Up @@ -16,6 +16,10 @@ matrix:
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
- node_js: "9"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
- node_js: "10"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
- node_js: "10"
env: HTTP2_TEST=1
allow_failures:
# Allow the nightly installs to fail
- env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
Expand Down
2 changes: 2 additions & 0 deletions appveyor.yml
Expand Up @@ -10,6 +10,8 @@ environment:
- nodejs_version: "6.11"
- nodejs_version: "7.10"
- nodejs_version: "8.4"
- nodejs_version: "10"
HTTP2_TEST: 1
cache:
- node_modules
install:
Expand Down
34 changes: 34 additions & 0 deletions examples/http2/index.js
@@ -0,0 +1,34 @@
var http2 = require('http2');
var fs = require('fs');
var path = require('path');
var express = require('../../');
var keys = path.join(__dirname, 'keys');
var app = express();
var style = fs.readFileSync(path.resolve(__dirname, './static/style.css'), 'utf8');
function pushStyle(res) {
res.createPushResponse({
':path': '/style.css',
':status': 200,
'content-type': 'text/css'
}, function(err, newResponse) {
newResponse.setHeader('content-type', 'text/css');
newResponse.end(style);
});
}

app.use(express.static('static', {setHeaders: function(res, file) {
if (file.indexOf('index.html') > -1) {
pushStyle(res);
}
}}));

var server = http2.createSecureServer({
key: fs.readFileSync(path.join(keys, 'test_key.pem')),
cert: fs.readFileSync(path.join(keys, 'test_cert.pem'))
}, app);

/* istanbul ignore next */
if (!module.parent) {
server.listen(3000);
console.log('Express started on port 3000');
}
16 changes: 16 additions & 0 deletions examples/http2/keys/test_cert.pem
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICjzCCAfgCCQCduAjYszOZ3DANBgkqhkiG9w0BAQUFADCBizELMAkGA1UEBhMC
Q04xEzARBgNVBAgTCkd1YW5nIERvbmcxFDASBgNVBAcTC0d1YW5nIFpob3VlMQ4w
DAYDVQQKEwVUQkVEUDENMAsGA1UECxMEVEVTVDEQMA4GA1UEAxMHZmVuZ21rMjEg
MB4GCSqGSIb3DQEJARYRZmVuZ21rMkBnbWFpbC5jb20wHhcNMTIwOTIzMTQxMDI5
WhcNMTIxMDIzMTQxMDI5WjCBizELMAkGA1UEBhMCQ04xEzARBgNVBAgTCkd1YW5n
IERvbmcxFDASBgNVBAcTC0d1YW5nIFpob3VlMQ4wDAYDVQQKEwVUQkVEUDENMAsG
A1UECxMEVEVTVDEQMA4GA1UEAxMHZmVuZ21rMjEgMB4GCSqGSIb3DQEJARYRZmVu
Z21rMkBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALOEtchk
KBK8WTqwXR2Aov2mc0+igyQTGbxBDSyyULHPiecMqOBHs5bV4DL1pc/01hLKIp4T
2j2KNTTmeivrtKd3wMQL7A+IgyqdmeqRi98pYUylFZrHxb9Kiwm7mpHanodmgnTT
zOluEpi/K9h9zM0DbIOynsOh9/w4E2Aq6JvrAgMBAAEwDQYJKoZIhvcNAQEFBQAD
gYEAnPd0JvCKQQBrm9jI6TkJKmfBa4NH0wUpMQv/bo2NWw1tA8fTQYb0S4aTep5Q
JdYctLQeE7abY1fpXFIwFY/FC0rE3alkEK+4PlCXvHGTYMiq90oH0JtlEqYTdTWJ
i99gtHarMEfzejyY3VDa2XFGmZrQCP6Co5NGDjAEr2A4ECg=
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions examples/http2/keys/test_key.pem
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCzhLXIZCgSvFk6sF0dgKL9pnNPooMkExm8QQ0sslCxz4nnDKjg
R7OW1eAy9aXP9NYSyiKeE9o9ijU05nor67Snd8DEC+wPiIMqnZnqkYvfKWFMpRWa
x8W/SosJu5qR2p6HZoJ008zpbhKYvyvYfczNA2yDsp7Doff8OBNgKuib6wIDAQAB
AoGAAp2tdHUZLGS4PCWzxalJNr8FMSTiGlV464hbI8qZaG3oyYgisdn5oPoO4U85
ElW0BOQTKxCI/pqT+ehd4WP25u+RXBqOSfpIRQvY2RjXmeyrkDEZWATP/BUa/Oqa
0YitEsAXvt3pQli+LVc9GZSFZQECgwDVdAs4n7DdQlkLwIECQQDmFL9rIE/6wF3h
fJkvPFs67MJgMF/T4omLnv/FGSH7KBpjFHts9AbPIGjD1dadRpmHxk7ahbSTKMxu
uoZ1R1irAkEAx73MW4fJDQZDdJHwskYyGXuL99Fcr8xz6YZv75tm5O3eF2a/UvoO
UIgDGpTIWFrm+gli27p3J0rJhhOiI4JJwQJAYOjUR3bwuRlVcahdjTvK4WLf7Evz
0PdWH+z0pjwTyAn4M0tpQVb3lz57YiErqEsYV8v7Yqd2i5VfpjQCdlt6yQJBAIpm
7kph/SLEO0tzsGenEiHsJKFT9bhun8ape7h4YsSwOdrXPC0fzXlptVTe0S+/1Rpe
FJ0SSGv2e0snIYsfRUECQQCP8VOp3IIE8beytDoqn3QbWvobx94NVhHKUX5UB6C+
bhY0LpTTFb8VMfSkICZXhbpcKf5zIdRjOh0ZLDeZJl5v
-----END RSA PRIVATE KEY-----
16 changes: 16 additions & 0 deletions examples/http2/static/index.html
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<script src="s1.js" type="text/javascript"></script>
<script src="s2.js" type="text/javascript"></script>
<script src="s3.js" type="text/javascript"></script>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div>
Express with Http2 Example
Check out the network tab, notice how all the connections use the same connection ID
and the style gets via push (most of the time :) )
</div>
</body>
</html>
1 change: 1 addition & 0 deletions examples/http2/static/s1.js
@@ -0,0 +1 @@
console.log('Loaded');
1 change: 1 addition & 0 deletions examples/http2/static/s2.js
@@ -0,0 +1 @@
console.log('Loaded');
1 change: 1 addition & 0 deletions examples/http2/static/s3.js
@@ -0,0 +1 @@
console.log('Loaded');
8 changes: 8 additions & 0 deletions examples/http2/static/style.css
@@ -0,0 +1,8 @@
body {
margin: 0;
padding: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
3 changes: 3 additions & 0 deletions examples/mvc/index.js
Expand Up @@ -27,6 +27,9 @@ app.response.message = function(msg){
sess.messages.push(msg);
return this;
};
if(app.isHttp2Supported){
app.http2Response.message = app.response.message;
}

// log
if (!module.parent) app.use(logger('dev'));
Expand Down
31 changes: 27 additions & 4 deletions lib/application.js
Expand Up @@ -19,6 +19,7 @@ var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var compileETag = require('./utils').compileETag;
var isHttp2Supported = require('./utils').isHttp2Supported;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var flatten = require('array-flatten');
Expand All @@ -27,7 +28,11 @@ var resolve = require('path').resolve;
var Router = require('router');
var setPrototypeOf = require('setprototypeof')
var slice = Array.prototype.slice;
var http2Request = null;

if (isHttp2Supported) {
http2Request = require('http2').Http2ServerRequest;
}
/**
* Application prototype.
*/
Expand Down Expand Up @@ -114,6 +119,11 @@ app.defaultConfiguration = function defaultConfiguration() {
setPrototypeOf(this.response, parent.response)
setPrototypeOf(this.engines, parent.engines)
setPrototypeOf(this.settings, parent.settings)
// set prototype for http2 requests/response
if (isHttp2Supported) {
setPrototypeOf(this.http2Request, parent.http2Request)
setPrototypeOf(this.http2Response, parent.http2Response)
}
});

// setup locals
Expand All @@ -130,6 +140,9 @@ app.defaultConfiguration = function defaultConfiguration() {
this.set('views', resolve('views'));
this.set('jsonp callback name', 'callback');

// http2 support
this.isHttp2Supported = isHttp2Supported;

if (env === 'production') {
this.enable('view cache');
}
Expand Down Expand Up @@ -161,8 +174,13 @@ app.handle = function handle(req, res, callback) {
res.req = req;

// alter the prototypes
setPrototypeOf(req, this.request)
setPrototypeOf(res, this.response)
if (isHttp2Supported && req instanceof http2Request) {
setPrototypeOf(req, this.http2Request)
setPrototypeOf(res, this.http2Response)
} else {
setPrototypeOf(req, this.request)
setPrototypeOf(res, this.response)
}

// setup locals
if (!res.locals) {
Expand Down Expand Up @@ -225,8 +243,13 @@ app.use = function use(fn) {
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
if (orig.isHttp2Supported && req instanceof http2Request) {
setPrototypeOf(req, orig.http2Request)
setPrototypeOf(res, orig.http2Response)
} else {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
}
next(err);
});
});
Expand Down
23 changes: 21 additions & 2 deletions lib/express.js
Expand Up @@ -19,7 +19,13 @@ var proto = require('./application');
var Router = require('router');
var req = require('./request');
var res = require('./response');

var isHttp2Supported = require('./utils').isHttp2Supported;
var http2Req = null;
var http2Res = null;
if (isHttp2Supported) {
http2Req = require('./http2Request');
http2Res = require('./http2Response');
}
/**
* Expose `createApplication()`.
*/
Expand All @@ -34,7 +40,7 @@ exports = module.exports = createApplication;
*/

function createApplication() {
var app = function(req, res, next) {
var app = function (req, res, next) {
app.handle(req, res, next);
};

Expand All @@ -51,6 +57,16 @@ function createApplication() {
app: { configurable: true, enumerable: true, writable: true, value: app }
})

if (isHttp2Supported) {
app.http2Request = Object.create(http2Req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
});

app.http2Response = Object.create(http2Res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
});
}

app.init();
return app;
}
Expand All @@ -62,6 +78,9 @@ function createApplication() {
exports.application = proto;
exports.request = req;
exports.response = res;
exports.http2Request = http2Req;
exports.http2Response = http2Res;
exports.isHttp2Supported = isHttp2Supported;

/**
* Expose constructors.
Expand Down
44 changes: 44 additions & 0 deletions lib/http2Request.js
@@ -0,0 +1,44 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/

'use strict';

/**
* Module dependencies.
* @private
*/
var http2 = require('http2');
var HTTP2_HEADER_AUTHORITY = http2.constants.HTTP2_HEADER_AUTHORITY;
var decorator = require('./requestDecorator');
var http2Req = decorator(Object.create(http2.Http2ServerRequest.prototype));

/**
* req.host and req.hostname refer to ':authority' haeder for compatibility.
*/

Object.defineProperty(http2Req, 'host', {
configurable: true,
enumerable: true,
get: function host() {
var trust = this.app.get('trust proxy fn');
var val = this.get('X-Forwarded-Host');

if (!val || !trust(this.connection.remoteAddress, 0)) {
val = this.get(HTTP2_HEADER_AUTHORITY);
}

return val || undefined;
}
});

/**
* Module exports.
* @public
*/

module.exports = http2Req;
29 changes: 29 additions & 0 deletions lib/http2Response.js
@@ -0,0 +1,29 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/

'use strict';

/**
* Module dependencies.
* @private
*/
var Http2ServerResponse = require('http2').Http2ServerResponse;
var decorator = require('./responseDecorator');
/**
* Response prototype.
* @public
*/

var res = decorator(Object.create(Http2ServerResponse.prototype));

/**
* Module exports.
* @public
*/

module.exports = res;