-
-
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1721 +/- ##
==========================================
- Coverage 87.54% 87.54% -0.01%
==========================================
Files 9 9
Lines 578 586 +8
Branches 170 173 +3
==========================================
+ Hits 506 513 +7
- Misses 60 61 +1
Partials 12 12
Continue to review full report at Codecov.
|
lib/Server.js
Outdated
@@ -645,9 +644,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') || !options.spdy) { |
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.
I think !options.http2
should be added here.
test/Spdy.test.js
Outdated
}); | ||
|
||
it('Request to index', (done) => { | ||
req.get('/').expect(200, /Heyo/, done); |
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.
Please check http/2
or not.
it('Request to index', (done) => {
req
.get('/')
.expect(200, /Heyo/)
.then(({ res }) => {
expect(res.httpVersion).toEqual('2.0');
done();
});
});
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.
@hiroppy I've realized this is more difficult than it seems. Supertest sends an HTTP/1.1 request in the test, so the spdy server communicates back with 1.1. I confirmed in the browser that the protocol is h2 and spdy works. The two options I think are: use Node's built-in http2 module for the test and try to communicate with the server using it to confirm HTTP/2 works, or use puppeteer to test with a browser that supports HTTP/2 and confirm that the h2 protocol is used. What do you think?
test/Spdy.test.js
Outdated
}); | ||
}); | ||
|
||
afterEach(helper.close); |
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.
why is this necessary?
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.
Sorry, mistake from copying HTTPS tests over as a template. I will remove it.
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.
See comment above, thanks!
if (options.spdy || options.http2) { | ||
options.spdy = true; | ||
options.http2 = 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
and http2
options, because it is misleading, spdy
and https2
are difference protocols, better don't mix their
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 considered http2
an alias for spdy
because http2
needs spdy
to work at the moment. As an alternative, I could do this, to allow for future separation of http2
from spdy
:
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:
options.https.spdy = {
protocols: ['h2', 'http/1.1'],
};
Then the exact same thing will happen as above if http2
is enabled and spdy
is enabled.
If ONLY spdy
is enabled, the options.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:
protocols: ['h2','spdy/3.1', 'spdy/3', 'spdy/2','http/1.1', 'http/1.0']
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 instead spdy
. And we can introduce spdy
under other option in future because spdy
!== http2
, also we need throw error if developer doesn't defined https
when use http2: 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?:
{
contentBase: './dist',
watchContentBase: true,
https: {
spdy: {
protocols: ['h2', 'http/1.1' ...]
}
},
http2: 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.
@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 under https
because it makes it more obvious that http2 requires https. Perhaps even when only
{
https: {
http2: true,
},
}
Treat 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
when http2: 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 🤦♂️
…ode's http2 module
I have removed the |
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.
Code looks really good, let's keep compatibility (no breaking change) and improve DX
lib/Server.js
Outdated
options.spdy = true; | ||
options.http2 = true; | ||
if (options.http2 && !options.https) { | ||
throw new Error("'https' option must be enabled to use HTTP/2"); |
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.
I was wrong, it is poor DX, better set https: true
in this case, no one browsers don't support http2
without ssl
so, we can generate certificate
sefely
lib/Server.js
Outdated
} | ||
options.https.spdy = { | ||
protocols: ['h2', 'http/1.1'], | ||
}; |
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.
Unfortunately, it is breaking change, we can't remove spdy
option in this version, spdy, how we should rewrite this part:
const isHttp2 = options.http2 || options.spdy
// Log deprecation `spdy` option.
if (semver.gte(process.version, '10.0.0') || !isHttp2) {
this.listeningApp = https.createServer(options.https, app);
} else {
options.https.spdy = {
protocols: ['http/1.1'],
};
// Allow to using https with `http1.1`
if (isHttp2) {
options.https.spdy.protocols.unshift('h2');
}
/* eslint-disable global-require */
// The relevant issues are:
// https://github.com/spdy-http2/node-spdy/issues/350
// https://github.com/webpack/webpack-dev-server/issues/1592
this.listeningApp = require('spdy').createServer(options.https, app);
/* eslint-enable global-require */
}
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 added the spdy
option earlier in this PR, it wouldn't be a breaking change to remove it. Currently it looks like this in the code:
if (!options.https.spdy) {
options.https.spdy = {
protocols: ['h2', 'http/1.1'],
};
}
if (semver.gte(process.version, '10.0.0')) {
this.listeningApp = https.createServer(options.https, app);
} else {
/* eslint-disable global-require */
// The relevant issues are:
// https://github.com/spdy-http2/node-spdy/issues/350
// https://github.com/webpack/webpack-dev-server/issues/1592
this.listeningApp = require('spdy').createServer(options.https, app);
/* eslint-enable global-require */
}
} else {
this.listeningApp = http.createServer(app);
}
Actually to avoid breaking changes, I think it should be like this:
const isHttp2 = options.http2 || options.https.spdy;
if (options.https.spdy) {
// log deprecation warning, this means they provided it as part of https option
}
if (semver.gte(process.version, '10.0.0') || !isHttp2) {
this.listeningApp = https.createServer(options.https, app);
} else {
if (!options.https.spdy) {
options.https.spdy = {
protocols: ['h2', 'http/1.1'],
};
}
/* eslint-disable global-require */
// The relevant issues are:
// https://github.com/spdy-http2/node-spdy/issues/350
// https://github.com/webpack/webpack-dev-server/issues/1592
this.listeningApp = require('spdy').createServer(options.https, app);
/* eslint-enable global-require */
}
So basically, user should still be allowed to use this: options.https.spdy
. It was never in the docs, but it was possible to use it before. The only breaking change now would be that they used to get http2 automatically if they were below Node 10.0.0. Now, they have to set http2
to true to get http2.
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.
👍
@@ -162,9 +162,6 @@ | |||
} | |||
] | |||
}, | |||
"spdy": { | |||
"type": "boolean" | |||
}, | |||
"http2": { |
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.
Don't remove spdy
, we just new output deprecation as i written above
@@ -327,8 +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)", | |||
"spdy": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-spdy)", | |||
"http2": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-spdy)", | |||
"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 comment
The 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)
if (argv.spdy) { | ||
options.spdy = true; | ||
if (argv.http2) { | ||
options.http2 = 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.
Same as above
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.
Suggestion for a later/separate cleanup: There are a lot of if (argv.foo) options.foo = true
. For the ones that are a simple copy:
const simpleOverrides = _.pick(argv, [
'http2',
// other simple if argv then set options
]);
_.merge(options, simpleOverrides);
|
||
expect(config).toMatchSnapshot(); | ||
}); | ||
|
||
it('http2 option', () => { |
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.
Don't remove this test, just move this in Http2
test
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.
I think this would stay in CreateConfig.test.js
since it is only confirming that the config option appears after passing through the config helper, right? Http2
test should be for actual functionality of the server, not just confirming that the config options get through to the server
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.
👍
test/Http2.test.js
Outdated
'fixtures/contentbase-config/public' | ||
); | ||
|
||
describe('HTTP2', () => { |
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.
http2
@@ -494,6 +494,7 @@ 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add spdy
and http2
@evilebottnawi It didn't pass CI for Node 6 because I used experimental |
…o run only within certain versions
Made a few changes. As I said before, |
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 comment
The reason will be displayed to describe this comment to others. Learn more.
spdy
should enable h2
(don't do braking change, even when option doesn't exists in docs)
Deprecation message is ok 👍
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.
This is not a breaking change, if options.https.spdy
is set by the user, it will not be changed at all currently: https://github.com/webpack/webpack-dev-server/blob/master/lib/Server.js#L650
lib/Server.js
Outdated
// options.https.spdy is here for slightly better backwards compatability, | ||
// since a user will probably provide this deprecated option if they | ||
// are expecting the spdy server to be used. | ||
const isHttp2 = options.http2 || options.https.spdy; |
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.
What do you think should we enable http2
be default when user use https: true
(without http2
option) for better DX?
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.
I would prefer this since h2 is the standard for most CDNs going forward (or should be). Would be good to mimic the greater movement across the web. I originally opened the issue because I wrongly assumed I was getting h2 by default.
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.
I changed it so that h2 is only prevented if http2 is explicitly set to false.
There should be no breaking changes at this point. I added another warning when someone tries to explicitly enable I think what you meant to say earlier is https !== http2. What I interpret as the misleading thing you are mentioning is that if a user wants to use |
Mhmm, putting the certs stuff inside const server = http2.createSecureServer({
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem')
}); {
http2: {
key: // …
cert: // …
}
} So then I think it's worth doing now. But then what if someone includes both? {
https: {
key: // …
cert: // …
},
http2: true,
} Throw saying use one or the other? In the above, you could detect that |
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.
Great work, thanks!
/cc @hiroppy |
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.
Thanks!
For Bugs and Features; did you add new tests?
Yes
Motivation / Use-Case
I decided to try solving this problem for my first PR: #1713
I added the spdy option for the node API and the CLI, along with an http2 alias option on both which does the same thing. When spdy is enabled, https is enabled, and node version is below 10.0.0 (since spdy is broken beyond that), spdy will be used to create the server.
Breaking Changes
The dev server will no longer automatically serve over HTTP/2 when HTTPS is enabled and Node version is below 10.0.0, as the spdy/http2 option must be true for this to happen.
Additional Info
Changes in docs that I made to reflect the new options: https://github.com/Loonride/webpack.js.org/blob/dev-server-spdy-http2/src/content/configuration/dev-server.md#devserverspdy