Skip to content

Commit

Permalink
refactor: server (part 2) (#1849)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi authored May 7, 2019
1 parent a1cdd2a commit 34058a6
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 100 deletions.
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');

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, '')
: '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;

0 comments on commit 34058a6

Please sign in to comment.