-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
refactor: server (part 2) #1849
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
Changes from all commits
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 |
---|---|---|
@@ -1,44 +1,33 @@ | ||
'use strict'; | ||
|
||
/* eslint-disable | ||
import/order, | ||
no-shadow, | ||
no-undefined, | ||
func-names | ||
*/ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const ip = require('ip'); | ||
const tls = require('tls'); | ||
const url = require('url'); | ||
const http = require('http'); | ||
const https = require('https'); | ||
const ip = require('ip'); | ||
const sockjs = require('sockjs'); | ||
|
||
const semver = require('semver'); | ||
|
||
const killable = require('killable'); | ||
|
||
const del = require('del'); | ||
const chokidar = require('chokidar'); | ||
|
||
const express = require('express'); | ||
|
||
const httpProxyMiddleware = require('http-proxy-middleware'); | ||
const historyApiFallback = require('connect-history-api-fallback'); | ||
const compress = require('compression'); | ||
const serveIndex = require('serve-index'); | ||
|
||
const webpack = require('webpack'); | ||
const webpackDevMiddleware = require('webpack-dev-middleware'); | ||
|
||
const validateOptions = require('schema-utils'); | ||
const updateCompiler = require('./utils/updateCompiler'); | ||
const createLogger = require('./utils/createLogger'); | ||
const createCertificate = require('./utils/createCertificate'); | ||
const getCertificate = require('./utils/getCertificate'); | ||
const routes = require('./utils/routes'); | ||
|
||
const validateOptions = require('schema-utils'); | ||
const schema = require('./options.json'); | ||
|
||
// Workaround for sockjs@~0.3.19 | ||
|
@@ -96,43 +85,41 @@ class Server { | |
|
||
this.log = _log || createLogger(options); | ||
|
||
// if the user enables http2, we can safely enable https | ||
if (options.http2 && !options.https) { | ||
options.https = true; | ||
} | ||
|
||
this.originalStats = | ||
options.stats && Object.keys(options.stats).length ? options.stats : {}; | ||
this.options.stats && Object.keys(this.options.stats).length | ||
? this.options.stats | ||
: {}; | ||
|
||
this.hot = options.hot || options.hotOnly; | ||
this.headers = options.headers; | ||
this.progress = options.progress; | ||
this.sockets = []; | ||
this.contentBaseWatchers = []; | ||
|
||
this.serveIndex = options.serveIndex; | ||
// TODO this.<property> is deprecated (remove them in next major release.) in favor this.options.<property> | ||
this.hot = this.options.hot || this.options.hotOnly; | ||
this.headers = this.options.headers; | ||
this.progress = this.options.progress; | ||
|
||
this.clientOverlay = options.overlay; | ||
this.clientLogLevel = options.clientLogLevel; | ||
this.serveIndex = this.options.serveIndex; | ||
|
||
this.publicHost = options.public; | ||
this.allowedHosts = options.allowedHosts; | ||
this.disableHostCheck = !!options.disableHostCheck; | ||
this.clientOverlay = this.options.overlay; | ||
this.clientLogLevel = this.options.clientLogLevel; | ||
|
||
this.sockets = []; | ||
this.publicHost = this.options.public; | ||
this.allowedHosts = this.options.allowedHosts; | ||
this.disableHostCheck = !!this.options.disableHostCheck; | ||
|
||
if (!options.watchOptions) { | ||
options.watchOptions = {}; | ||
if (!this.options.watchOptions) { | ||
this.options.watchOptions = {}; | ||
} | ||
// ignoring node_modules folder by default | ||
options.watchOptions.ignored = options.watchOptions.ignored || [ | ||
// Ignoring node_modules folder by default | ||
this.options.watchOptions.ignored = this.options.watchOptions.ignored || [ | ||
/node_modules/, | ||
]; | ||
this.watchOptions = options.watchOptions; | ||
this.watchOptions = this.options.watchOptions; | ||
|
||
this.contentBaseWatchers = []; | ||
// Replace leading and trailing slashes to normalize path | ||
this.sockPath = `/${ | ||
options.sockPath | ||
? options.sockPath.replace(/^\/|\/$/g, '') | ||
this.options.sockPath | ||
? this.options.sockPath.replace(/^\/|\/$/g, '') | ||
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. Now we should use |
||
: 'sockjs-node' | ||
}`; | ||
|
||
|
@@ -146,28 +133,33 @@ class Server { | |
this.setupDevMiddleware(); | ||
|
||
// set express routes | ||
routes(this.app, this.middleware, options); | ||
routes(this.app, this.middleware, this.options); | ||
|
||
// Keep track of websocket proxies for external websocket upgrade. | ||
this.websocketProxies = []; | ||
|
||
this.setupFeatures(); | ||
|
||
if (options.https) { | ||
// if the user enables http2, we can safely enable https | ||
if (this.options.http2 && !this.options.https) { | ||
this.options.https = true; | ||
} | ||
|
||
if (this.options.https) { | ||
// for keep supporting CLI parameters | ||
if (typeof options.https === 'boolean') { | ||
options.https = { | ||
ca: options.ca, | ||
pfx: options.pfx, | ||
key: options.key, | ||
cert: options.cert, | ||
passphrase: options.pfxPassphrase, | ||
requestCert: options.requestCert || false, | ||
if (typeof this.options.https === 'boolean') { | ||
this.options.https = { | ||
ca: this.options.ca, | ||
pfx: this.options.pfx, | ||
key: this.options.key, | ||
cert: this.options.cert, | ||
passphrase: this.options.pfxPassphrase, | ||
requestCert: this.options.requestCert || false, | ||
}; | ||
} | ||
|
||
for (const property of ['ca', 'pfx', 'key', 'cert']) { | ||
const value = options.https[property]; | ||
const value = this.options.https[property]; | ||
const isBuffer = value instanceof Buffer; | ||
|
||
if (value && !isBuffer) { | ||
|
@@ -181,73 +173,38 @@ class Server { | |
|
||
if (stats) { | ||
// It is file | ||
options.https[property] = fs.readFileSync(path.resolve(value)); | ||
this.options.https[property] = fs.readFileSync(path.resolve(value)); | ||
} else { | ||
options.https[property] = value; | ||
this.options.https[property] = value; | ||
} | ||
} | ||
} | ||
|
||
let fakeCert; | ||
|
||
if (!options.https.key || !options.https.cert) { | ||
// Use a self-signed certificate if no certificate was configured. | ||
// Cycle certs every 24 hours | ||
const certPath = path.join(__dirname, '../ssl/server.pem'); | ||
|
||
let certExists = fs.existsSync(certPath); | ||
|
||
if (certExists) { | ||
const certTtl = 1000 * 60 * 60 * 24; | ||
const certStat = fs.statSync(certPath); | ||
|
||
const now = new Date(); | ||
|
||
// cert is more than 30 days old, kill it with fire | ||
if ((now - certStat.ctime) / certTtl > 30) { | ||
this.log.info( | ||
'SSL Certificate is more than 30 days old. Removing.' | ||
); | ||
|
||
del.sync([certPath], { force: true }); | ||
|
||
certExists = false; | ||
} | ||
} | ||
|
||
if (!certExists) { | ||
this.log.info('Generating SSL Certificate'); | ||
|
||
const attrs = [{ name: 'commonName', value: 'localhost' }]; | ||
const pems = createCertificate(attrs); | ||
|
||
fs.writeFileSync(certPath, pems.private + pems.cert, { | ||
encoding: 'utf8', | ||
}); | ||
} | ||
|
||
fakeCert = fs.readFileSync(certPath); | ||
if (!this.options.https.key || !this.options.https.cert) { | ||
fakeCert = getCertificate(this.log); | ||
} | ||
|
||
options.https.key = options.https.key || fakeCert; | ||
options.https.cert = options.https.cert || fakeCert; | ||
this.options.https.key = this.options.https.key || fakeCert; | ||
this.options.https.cert = this.options.https.cert || fakeCert; | ||
|
||
// Only prevent HTTP/2 if http2 is explicitly set to false | ||
const isHttp2 = options.http2 !== false; | ||
const isHttp2 = this.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) { | ||
if (this.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 { | ||
// if the normal https server gets this option, it will not affect it. | ||
options.https.spdy = { | ||
this.options.https.spdy = { | ||
protocols: ['h2', 'http/1.1'], | ||
}; | ||
} | ||
|
@@ -262,21 +219,21 @@ class Server { | |
// - https://github.com/webpack/webpack-dev-server/issues/1449 | ||
// - https://github.com/expressjs/express/issues/3388 | ||
if (semver.gte(process.version, '10.0.0') || !isHttp2) { | ||
if (options.http2) { | ||
if (this.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, this.app); | ||
this.listeningApp = https.createServer(this.options.https, this.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, | ||
this.options.https, | ||
this.app | ||
); | ||
/* eslint-enable global-require */ | ||
|
@@ -941,7 +898,7 @@ class Server { | |
? this.watchOptions.poll | ||
: undefined; | ||
|
||
const options = { | ||
const watchOptions = { | ||
ignoreInitial: true, | ||
persistent: true, | ||
followSymlinks: false, | ||
|
@@ -953,7 +910,7 @@ class Server { | |
interval, | ||
}; | ||
|
||
const watcher = chokidar.watch(watchPath, options); | ||
const watcher = chokidar.watch(watchPath, watchOptions); | ||
|
||
watcher.on('change', () => { | ||
this.sockWrite(this.sockets, 'content-changed'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const fs = require('fs'); | ||
const del = require('del'); | ||
const createCertificate = require('./createCertificate'); | ||
|
||
function getCertificate(logger) { | ||
// Use a self-signed certificate if no certificate was configured. | ||
// Cycle certs every 24 hours | ||
const certificatePath = path.join(__dirname, '../../ssl/server.pem'); | ||
|
||
let certificateExists = fs.existsSync(certificatePath); | ||
|
||
if (certificateExists) { | ||
const certificateTtl = 1000 * 60 * 60 * 24; | ||
const certificateStat = fs.statSync(certificatePath); | ||
|
||
const now = new Date(); | ||
|
||
// cert is more than 30 days old, kill it with fire | ||
if ((now - certificateStat.ctime) / certificateTtl > 30) { | ||
logger.info('SSL Certificate is more than 30 days old. Removing.'); | ||
|
||
del.sync([certificatePath], { force: true }); | ||
|
||
certificateExists = false; | ||
} | ||
} | ||
|
||
if (!certificateExists) { | ||
logger.info('Generating SSL Certificate'); | ||
|
||
const attributes = [{ name: 'commonName', value: 'localhost' }]; | ||
const pems = createCertificate(attributes); | ||
|
||
fs.writeFileSync(certificatePath, pems.private + pems.cert, { | ||
encoding: 'utf8', | ||
}); | ||
} | ||
|
||
return fs.readFileSync(certificatePath); | ||
} | ||
|
||
module.exports = getCertificate; | ||
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. New util |
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.
Fix order (
eslint
rule)