-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
New spdy option to enable/disable HTTP/2 with HTTPS #1721
Changes from all commits
58ffad8
af6fe30
1f55c14
3049888
d97ed54
7884dc8
9167003
21afdb0
a79c362
76711b8
9afb2b9
d05a3e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,13 +89,19 @@ class Server { | |
if (options.lazy && !options.filename) { | ||
throw new Error("'filename' option must be set in lazy mode."); | ||
} | ||
|
||
|
||
// if the user enables http2, we can safely enable https | ||
if (options.http2 && !options.https) { | ||
options.https = true; | ||
} | ||
|
||
updateCompiler(compiler, options); | ||
|
||
this.stats = | ||
options.stats && Object.keys(options.stats).length | ||
? options.stats | ||
: Server.DEFAULT_STATS; | ||
|
||
this.hot = options.hot || options.hotOnly; | ||
this.headers = options.headers; | ||
this.progress = options.progress; | ||
|
@@ -647,7 +653,21 @@ class Server { | |
options.https.key = options.https.key || fakeCert; | ||
options.https.cert = options.https.cert || fakeCert; | ||
|
||
if (!options.https.spdy) { | ||
// Only prevent HTTP/2 if http2 is explicitly set to false | ||
const isHttp2 = options.http2 !== false; | ||
|
||
// note that options.spdy never existed. The user was able | ||
// to set options.https.spdy before, though it was not in the | ||
// docs. Keep options.https.spdy if the user sets it for | ||
// backwards compatability, but log a deprecation warning. | ||
if (options.https.spdy) { | ||
// for backwards compatability: if options.https.spdy was passed in before, | ||
// it was not altered in any way | ||
this.log.warn( | ||
'Providing custom spdy server options is deprecated and will be removed in the next major version.' | ||
); | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Deprecation message is ok 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a breaking change, if |
||
// if the normal https server gets this option, it will not affect it. | ||
options.https.spdy = { | ||
protocols: ['h2', 'http/1.1'], | ||
}; | ||
|
@@ -662,7 +682,14 @@ class Server { | |
// - https://github.com/nodejs/node/issues/21665 | ||
// - https://github.com/webpack/webpack-dev-server/issues/1449 | ||
// - https://github.com/expressjs/express/issues/3388 | ||
if (semver.gte(process.version, '10.0.0')) { | ||
if (semver.gte(process.version, '10.0.0') || !isHttp2) { | ||
if (options.http2) { | ||
// the user explicitly requested http2 but is not getting it because | ||
// of the node version. | ||
this.log.warn( | ||
'HTTP/2 is currently unsupported for Node 10.0.0 and above, but will be supported once Express supports it' | ||
); | ||
} | ||
this.listeningApp = https.createServer(options.https, app); | ||
} else { | ||
/* eslint-disable global-require */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,6 +162,9 @@ | |
} | ||
] | ||
}, | ||
"http2": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't remove |
||
"type": "boolean" | ||
}, | ||
"contentBase": { | ||
"anyOf": [ | ||
{ | ||
|
@@ -321,6 +324,7 @@ | |
"disableHostCheck": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-disablehostcheck)", | ||
"public": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserver-public)", | ||
"https": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-https)", | ||
"http2": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-http2)", | ||
"contentBase": "should be {Array} (https://webpack.js.org/configuration/dev-server/#devserver-contentbase)", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't remove, also we need create issue about documentation this (not related to PR right now, we should do this after merge) |
||
"watchContentBase": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-watchcontentbase)", | ||
"open": "should be {String|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-open)", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,6 +136,10 @@ function createConfig(config, argv, { port }) { | |
options.https = true; | ||
} | ||
|
||
if (argv.http2) { | ||
options.http2 = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion for a later/separate cleanup: There are a lot of const simpleOverrides = _.pick(argv, [
'http2',
// other simple if argv then set options
]);
_.merge(options, simpleOverrides); |
||
} | ||
|
||
if (argv.key) { | ||
options.key = argv.key; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -560,6 +560,28 @@ describe('createConfig', () => { | |
expect(config).toMatchSnapshot(); | ||
}); | ||
|
||
it('http2 option', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't remove this test, just move this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would stay in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
const config = createConfig( | ||
webpackConfig, | ||
Object.assign({}, argv, { https: true, http2: true }), | ||
{ port: 8080 } | ||
); | ||
|
||
expect(config).toMatchSnapshot(); | ||
}); | ||
|
||
it('http2 option (in devServer config)', () => { | ||
const config = createConfig( | ||
Object.assign({}, webpackConfig, { | ||
devServer: { https: true, http2: true }, | ||
}), | ||
argv, | ||
{ port: 8080 } | ||
); | ||
|
||
expect(config).toMatchSnapshot(); | ||
}); | ||
|
||
it('key option', () => { | ||
const config = createConfig( | ||
webpackConfig, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const request = require('supertest'); | ||
const semver = require('semver'); | ||
const helper = require('./helper'); | ||
const config = require('./fixtures/contentbase-config/webpack.config'); | ||
|
||
const contentBasePublic = path.join( | ||
__dirname, | ||
'fixtures/contentbase-config/public' | ||
); | ||
|
||
describe('http2', () => { | ||
let server; | ||
let req; | ||
|
||
// HTTP/2 will only work with node versions below 10.0.0 | ||
// since spdy is broken past that point, and this test will only | ||
// work above Node 8.8.0, since it is the first version where the | ||
// built-in http2 module is exposed without need for a flag | ||
// (https://nodejs.org/en/blog/release/v8.8.0/) | ||
// if someone is testing below this Node version and breaks this, | ||
// their tests will not catch it, but CI will catch it. | ||
if ( | ||
semver.gte(process.version, '8.8.0') && | ||
semver.lt(process.version, '10.0.0') | ||
) { | ||
/* eslint-disable global-require */ | ||
const http2 = require('http2'); | ||
/* eslint-enable global-require */ | ||
describe('http2 works with https', () => { | ||
beforeAll((done) => { | ||
server = helper.start( | ||
config, | ||
{ | ||
contentBase: contentBasePublic, | ||
https: true, | ||
http2: true, | ||
}, | ||
done | ||
); | ||
req = request(server.app); | ||
}); | ||
|
||
it('confirm http2 client can connect', (done) => { | ||
const client = http2.connect('https://localhost:8080', { | ||
rejectUnauthorized: false, | ||
}); | ||
client.on('error', (err) => console.error(err)); | ||
|
||
const http2Req = client.request({ ':path': '/' }); | ||
|
||
http2Req.on('response', (headers) => { | ||
expect(headers[':status']).toEqual(200); | ||
}); | ||
|
||
http2Req.setEncoding('utf8'); | ||
let data = ''; | ||
http2Req.on('data', (chunk) => { | ||
data += chunk; | ||
}); | ||
http2Req.on('end', () => { | ||
expect(data).toEqual(expect.stringMatching(/Heyo/)); | ||
done(); | ||
}); | ||
http2Req.end(); | ||
}); | ||
|
||
afterAll(helper.close); | ||
}); | ||
} | ||
|
||
describe('server works with http2 option, but without https option', () => { | ||
beforeAll((done) => { | ||
server = helper.start( | ||
config, | ||
{ | ||
contentBase: contentBasePublic, | ||
http2: true, | ||
}, | ||
done | ||
); | ||
req = request(server.app); | ||
}); | ||
|
||
it('Request to index', (done) => { | ||
req.get('/').expect(200, /Heyo/, done); | ||
}); | ||
|
||
afterAll(helper.close); | ||
}); | ||
|
||
describe('https without http2 disables HTTP/2', () => { | ||
beforeAll((done) => { | ||
server = helper.start( | ||
config, | ||
{ | ||
contentBase: contentBasePublic, | ||
https: true, | ||
http2: false, | ||
}, | ||
done | ||
); | ||
req = request(server.app); | ||
}); | ||
|
||
it('Request to index', (done) => { | ||
req | ||
.get('/') | ||
.expect(200, /Heyo/) | ||
.then(({ res }) => { | ||
expect(res.httpVersion).not.toEqual('2.0'); | ||
done(); | ||
}); | ||
}); | ||
|
||
afterAll(helper.close); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -474,6 +474,38 @@ Object { | |
} | ||
`; | ||
|
||
exports[`createConfig http2 option (in devServer config) 1`] = ` | ||
Object { | ||
"hot": true, | ||
"hotOnly": false, | ||
"http2": true, | ||
"https": true, | ||
"noInfo": true, | ||
"port": 8080, | ||
"publicPath": "/", | ||
"stats": Object { | ||
"cached": false, | ||
"cachedAssets": false, | ||
}, | ||
} | ||
`; | ||
|
||
exports[`createConfig http2 option 1`] = ` | ||
Object { | ||
"hot": true, | ||
"hotOnly": false, | ||
"http2": true, | ||
"https": true, | ||
"noInfo": true, | ||
"port": 8080, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add |
||
"publicPath": "/", | ||
"stats": Object { | ||
"cached": false, | ||
"cachedAssets": false, | ||
}, | ||
} | ||
`; | ||
|
||
exports[`createConfig https option (in devServer config) 1`] = ` | ||
Object { | ||
"hot": true, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's separate
spdy
andhttp2
options, because it is misleading,spdy
andhttps2
are difference protocols, better don't mix theirThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@evilebottnawi I considered
http2
an alias forspdy
becausehttp2
needsspdy
to work at the moment. As an alternative, I could do this, to allow for future separation ofhttp2
fromspdy
:When ONLY
http2
is enabled, the spdy server will be run (since this is the only way to run HTTP/2 on express at the moment), and these protocols will be provided to spdy:Then the exact same thing will happen as above if
http2
is enabled andspdy
is enabled.If ONLY
spdy
is enabled, theoptions.https.spdy
will not be set unless the user specifies it themselves. If the user does not specify it in their options, it will go to the default spdy value as specified here:Do you like this alternative better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's use the
http2
option insteadspdy
. And we can introducespdy
under other option in future becausespdy
!==http2
, also we need throw error if developer doesn't definedhttps
when usehttp2: true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that makes sense. Should user still be allowed to provide spdy options like this?:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Loonride i thought about it, let's do this in other PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@evilebottnawi i think it would help if the
http2
option was nested underhttps
because it makes it more obvious that http2 requires https. Perhaps even when onlyTreat that as if both https (with self-signed cert) & http2 are enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, http2 !== https2, but no one browsers don't support http2 without https, but for avoid misleading better contain 2 option (i think), new developers can start think what http2 === https, but i am open for feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I like your suggestion below: assume
https: true
whenhttp2: true
and no https config is specified (and maybe output something in verbose logging about the assumption).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, duh. That's exactly what this code is doing 🤦♂️