Skip to content

Commit dcd2434

Browse files
knagaitsevevilebottnawi
authored andcommittedApr 4, 2019
feat: http2 option to enable/disable HTTP/2 with HTTPS (#1721)
1 parent 0984d4b commit dcd2434

File tree

7 files changed

+217
-3
lines changed

7 files changed

+217
-3
lines changed
 

‎bin/options.js

+5
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ const options = {
8787
group: SSL_GROUP,
8888
describe: 'HTTPS',
8989
},
90+
http2: {
91+
type: 'boolean',
92+
group: SSL_GROUP,
93+
describe: 'HTTP/2, must be used with HTTPS',
94+
},
9095
key: {
9196
type: 'string',
9297
describe: 'Path to a SSL key.',

‎lib/Server.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,19 @@ class Server {
8989
if (options.lazy && !options.filename) {
9090
throw new Error("'filename' option must be set in lazy mode.");
9191
}
92-
92+
93+
// if the user enables http2, we can safely enable https
94+
if (options.http2 && !options.https) {
95+
options.https = true;
96+
}
97+
9398
updateCompiler(compiler, options);
9499

95100
this.stats =
96101
options.stats && Object.keys(options.stats).length
97102
? options.stats
98103
: Server.DEFAULT_STATS;
104+
99105
this.hot = options.hot || options.hotOnly;
100106
this.headers = options.headers;
101107
this.progress = options.progress;
@@ -647,7 +653,21 @@ class Server {
647653
options.https.key = options.https.key || fakeCert;
648654
options.https.cert = options.https.cert || fakeCert;
649655

650-
if (!options.https.spdy) {
656+
// Only prevent HTTP/2 if http2 is explicitly set to false
657+
const isHttp2 = options.http2 !== false;
658+
659+
// note that options.spdy never existed. The user was able
660+
// to set options.https.spdy before, though it was not in the
661+
// docs. Keep options.https.spdy if the user sets it for
662+
// backwards compatability, but log a deprecation warning.
663+
if (options.https.spdy) {
664+
// for backwards compatability: if options.https.spdy was passed in before,
665+
// it was not altered in any way
666+
this.log.warn(
667+
'Providing custom spdy server options is deprecated and will be removed in the next major version.'
668+
);
669+
} else {
670+
// if the normal https server gets this option, it will not affect it.
651671
options.https.spdy = {
652672
protocols: ['h2', 'http/1.1'],
653673
};
@@ -662,7 +682,14 @@ class Server {
662682
// - https://github.com/nodejs/node/issues/21665
663683
// - https://github.com/webpack/webpack-dev-server/issues/1449
664684
// - https://github.com/expressjs/express/issues/3388
665-
if (semver.gte(process.version, '10.0.0')) {
685+
if (semver.gte(process.version, '10.0.0') || !isHttp2) {
686+
if (options.http2) {
687+
// the user explicitly requested http2 but is not getting it because
688+
// of the node version.
689+
this.log.warn(
690+
'HTTP/2 is currently unsupported for Node 10.0.0 and above, but will be supported once Express supports it'
691+
);
692+
}
666693
this.listeningApp = https.createServer(options.https, app);
667694
} else {
668695
/* eslint-disable global-require */

‎lib/options.json

+4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@
162162
}
163163
]
164164
},
165+
"http2": {
166+
"type": "boolean"
167+
},
165168
"contentBase": {
166169
"anyOf": [
167170
{
@@ -321,6 +324,7 @@
321324
"disableHostCheck": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-disablehostcheck)",
322325
"public": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserver-public)",
323326
"https": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-https)",
327+
"http2": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-http2)",
324328
"contentBase": "should be {Array} (https://webpack.js.org/configuration/dev-server/#devserver-contentbase)",
325329
"watchContentBase": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-watchcontentbase)",
326330
"open": "should be {String|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-open)",

‎lib/utils/createConfig.js

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ function createConfig(config, argv, { port }) {
136136
options.https = true;
137137
}
138138

139+
if (argv.http2) {
140+
options.http2 = true;
141+
}
142+
139143
if (argv.key) {
140144
options.key = argv.key;
141145
}

‎test/CreateConfig.test.js

+22
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,28 @@ describe('createConfig', () => {
560560
expect(config).toMatchSnapshot();
561561
});
562562

563+
it('http2 option', () => {
564+
const config = createConfig(
565+
webpackConfig,
566+
Object.assign({}, argv, { https: true, http2: true }),
567+
{ port: 8080 }
568+
);
569+
570+
expect(config).toMatchSnapshot();
571+
});
572+
573+
it('http2 option (in devServer config)', () => {
574+
const config = createConfig(
575+
Object.assign({}, webpackConfig, {
576+
devServer: { https: true, http2: true },
577+
}),
578+
argv,
579+
{ port: 8080 }
580+
);
581+
582+
expect(config).toMatchSnapshot();
583+
});
584+
563585
it('key option', () => {
564586
const config = createConfig(
565587
webpackConfig,

‎test/Http2.test.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const request = require('supertest');
5+
const semver = require('semver');
6+
const helper = require('./helper');
7+
const config = require('./fixtures/contentbase-config/webpack.config');
8+
9+
const contentBasePublic = path.join(
10+
__dirname,
11+
'fixtures/contentbase-config/public'
12+
);
13+
14+
describe('http2', () => {
15+
let server;
16+
let req;
17+
18+
// HTTP/2 will only work with node versions below 10.0.0
19+
// since spdy is broken past that point, and this test will only
20+
// work above Node 8.8.0, since it is the first version where the
21+
// built-in http2 module is exposed without need for a flag
22+
// (https://nodejs.org/en/blog/release/v8.8.0/)
23+
// if someone is testing below this Node version and breaks this,
24+
// their tests will not catch it, but CI will catch it.
25+
if (
26+
semver.gte(process.version, '8.8.0') &&
27+
semver.lt(process.version, '10.0.0')
28+
) {
29+
/* eslint-disable global-require */
30+
const http2 = require('http2');
31+
/* eslint-enable global-require */
32+
describe('http2 works with https', () => {
33+
beforeAll((done) => {
34+
server = helper.start(
35+
config,
36+
{
37+
contentBase: contentBasePublic,
38+
https: true,
39+
http2: true,
40+
},
41+
done
42+
);
43+
req = request(server.app);
44+
});
45+
46+
it('confirm http2 client can connect', (done) => {
47+
const client = http2.connect('https://localhost:8080', {
48+
rejectUnauthorized: false,
49+
});
50+
client.on('error', (err) => console.error(err));
51+
52+
const http2Req = client.request({ ':path': '/' });
53+
54+
http2Req.on('response', (headers) => {
55+
expect(headers[':status']).toEqual(200);
56+
});
57+
58+
http2Req.setEncoding('utf8');
59+
let data = '';
60+
http2Req.on('data', (chunk) => {
61+
data += chunk;
62+
});
63+
http2Req.on('end', () => {
64+
expect(data).toEqual(expect.stringMatching(/Heyo/));
65+
done();
66+
});
67+
http2Req.end();
68+
});
69+
70+
afterAll(helper.close);
71+
});
72+
}
73+
74+
describe('server works with http2 option, but without https option', () => {
75+
beforeAll((done) => {
76+
server = helper.start(
77+
config,
78+
{
79+
contentBase: contentBasePublic,
80+
http2: true,
81+
},
82+
done
83+
);
84+
req = request(server.app);
85+
});
86+
87+
it('Request to index', (done) => {
88+
req.get('/').expect(200, /Heyo/, done);
89+
});
90+
91+
afterAll(helper.close);
92+
});
93+
94+
describe('https without http2 disables HTTP/2', () => {
95+
beforeAll((done) => {
96+
server = helper.start(
97+
config,
98+
{
99+
contentBase: contentBasePublic,
100+
https: true,
101+
http2: false,
102+
},
103+
done
104+
);
105+
req = request(server.app);
106+
});
107+
108+
it('Request to index', (done) => {
109+
req
110+
.get('/')
111+
.expect(200, /Heyo/)
112+
.then(({ res }) => {
113+
expect(res.httpVersion).not.toEqual('2.0');
114+
done();
115+
});
116+
});
117+
118+
afterAll(helper.close);
119+
});
120+
});

‎test/__snapshots__/CreateConfig.test.js.snap

+32
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,38 @@ Object {
474474
}
475475
`;
476476

477+
exports[`createConfig http2 option (in devServer config) 1`] = `
478+
Object {
479+
"hot": true,
480+
"hotOnly": false,
481+
"http2": true,
482+
"https": true,
483+
"noInfo": true,
484+
"port": 8080,
485+
"publicPath": "/",
486+
"stats": Object {
487+
"cached": false,
488+
"cachedAssets": false,
489+
},
490+
}
491+
`;
492+
493+
exports[`createConfig http2 option 1`] = `
494+
Object {
495+
"hot": true,
496+
"hotOnly": false,
497+
"http2": true,
498+
"https": true,
499+
"noInfo": true,
500+
"port": 8080,
501+
"publicPath": "/",
502+
"stats": Object {
503+
"cached": false,
504+
"cachedAssets": false,
505+
},
506+
}
507+
`;
508+
477509
exports[`createConfig https option (in devServer config) 1`] = `
478510
Object {
479511
"hot": true,

0 commit comments

Comments
 (0)
Please sign in to comment.