Skip to content

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

Merged
merged 1 commit into from
May 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 55 additions & 98 deletions lib/Server.js
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');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix order (eslint rule)


const validateOptions = require('schema-utils');
const schema = require('./options.json');

// Workaround for sockjs@~0.3.19
Expand Down Expand Up @@ -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, '')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we should use this.options.<option> instead this.<option> because it is very uncomfortable and require extra code like: this.clientOverlay = options.clientOverlay so we should remove them in future major release

: 'sockjs-node'
}`;

Expand All @@ -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) {
Expand All @@ -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'],
};
}
Expand All @@ -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 */
Expand Down Expand Up @@ -941,7 +898,7 @@ class Server {
? this.watchOptions.poll
: undefined;

const options = {
const watchOptions = {
ignoreInitial: true,
persistent: true,
followSymlinks: false,
Expand All @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions lib/utils/createCertificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

const selfsigned = require('selfsigned');

function createCertificate(attrs) {
return selfsigned.generate(attrs, {
function createCertificate(attributes) {
return selfsigned.generate(attributes, {
algorithm: 'sha256',
days: 30,
keySize: 2048,
Expand Down
45 changes: 45 additions & 0 deletions lib/utils/getCertificate.js
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;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New util