Skip to content

Commit

Permalink
Added https development server support. (#161)
Browse files Browse the repository at this point in the history
* Added https development server support.

* From let to const cachedKey & cachedCert variable.

* Updated logger string to display correct url while serving with --https option.
  • Loading branch information
mbaranovski authored and devongovett committed Dec 24, 2017
1 parent a2aa64a commit 90b9684
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 8
"ecmaVersion": 8
},
"env": {
"node": true,
"es6": true
}
}
}
2 changes: 1 addition & 1 deletion bin/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"rules": {
"no-console": 0
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"json5": "^0.5.1",
"micromatch": "^3.0.4",
"mkdirp": "^0.5.1",
"node-forge": "^0.7.1",
"node-libs-browser": "^2.0.0",
"opn": "^5.1.0",
"physical-cpu-count": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,8 +475,8 @@ class Bundler extends EventEmitter {
return Server.middleware(this);
}

async serve(port = 1234) {
let server = await Server.serve(this, port);
async serve(port = 1234, https) {
let server = await Server.serve(this, port, https);
this.bundle();
return server;
}
Expand Down
16 changes: 13 additions & 3 deletions src/Server.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const http = require('http');
const https = require('https');
const serveStatic = require('serve-static');
const getPort = require('get-port');
const serverErrors = require('./utils/customErrors').serverErrors;

const generateCertificate = require('./utils/generateCertificate');

function middleware(bundler) {
const serve = serveStatic(bundler.options.outDir, {index: false});

Expand Down Expand Up @@ -54,9 +57,11 @@ function middleware(bundler) {
};
}

async function serve(bundler, port) {
async function serve(bundler, port, https) {
let freePort = await getPort({port});
let server = http.createServer(middleware(bundler)).listen(freePort);
let server = https
? serveHttps(bundler, freePort)
: http.createServer(middleware(bundler)).listen(freePort);

return new Promise((resolve, reject) => {
server.on('error', err => {
Expand All @@ -73,7 +78,7 @@ async function serve(bundler, port) {
: '';
bundler.logger.persistent(
`Server running at ${bundler.logger.chalk.cyan(
`http://localhost:${server.address().port}`
`${https ? 'https' : 'http'}://localhost:${server.address().port}`
)} ${addon}`
);

Expand All @@ -82,5 +87,10 @@ async function serve(bundler, port) {
});
}

function serveHttps(bundler, freePort) {
const {key, cert} = generateCertificate();
return https.createServer({key, cert}, middleware(bundler)).listen(freePort);
}

exports.middleware = middleware;
exports.serve = serve;
2 changes: 1 addition & 1 deletion src/assets/LESSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function urlPlugin(asset) {
return {
install: (less, pluginManager) => {
let visitor = new less.visitors.Visitor({
visitUrl: (node) => {
visitUrl: node => {
node.value.value = asset.addURLDependency(
node.value.value,
node.currentFileInfo.filename
Expand Down
9 changes: 7 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ program
'set the port to serve on. defaults to 1234',
parseInt
)
.option('--https', 'serves files over HTTPS')
.option('-o, --open', 'automatically open in default browser')
.option(
'-d, --out-dir <path>',
Expand Down Expand Up @@ -96,9 +97,13 @@ async function bundle(main, command) {
const bundler = new Bundler(main, command);

if (command.name() === 'serve') {
const server = await bundler.serve(command.port || 1234);
const server = await bundler.serve(command.port || 1234, command.https);
if (command.open) {
require('opn')(`http://localhost:${server.address().port}`);
require('opn')(
`${command.https ? 'https' : 'http'}://localhost:${
server.address().port
}`
);
}
} else {
bundler.bundle();
Expand Down
123 changes: 123 additions & 0 deletions src/utils/generateCertificate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const forge = require('node-forge');
const fs = require('fs');

const PRIVATE_KEY_PATH = './.cache/private.pem';
const PRIMARY_KEY_PATH = './.cache/primary.crt';

function generateCertificate() {
const cachedKey =
fs.existsSync(PRIVATE_KEY_PATH) && fs.readFileSync(PRIVATE_KEY_PATH);
const cachedCert =
fs.existsSync(PRIMARY_KEY_PATH) && fs.readFileSync(PRIMARY_KEY_PATH);

if (cachedKey && cachedCert)
return {
key: cachedKey,
cert: cachedCert
};

console.log('Generating SSL Certificate...');

const pki = forge.pki;
const keys = pki.rsa.generateKeyPair(2048);
const cert = pki.createCertificate();

cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);

const attrs = [
{
name: 'commonName',
value: 'parceljs.org'
},
{
name: 'countryName',
value: 'US'
},
{
shortName: 'ST',
value: 'Virginia'
},
{
name: 'localityName',
value: 'Blacksburg'
},
{
name: 'organizationName',
value: 'parcelBundler'
},
{
shortName: 'OU',
value: 'Test'
}
];

cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([
{
name: 'basicConstraints',
cA: true
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
},
{
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true
},
{
name: 'subjectAltName',
altNames: [
{
type: 6, // URI
value: 'http://example.org/webid#me'
},
{
type: 7, // IP
ip: '127.0.0.1'
}
]
},
{
name: 'subjectKeyIdentifier'
}
]);

cert.sign(keys.privateKey, forge.md.sha256.create());

const privPem = pki.privateKeyToPem(keys.privateKey);
const certPem = pki.certificateToPem(cert);

fs.writeFileSync(PRIVATE_KEY_PATH, privPem);
fs.writeFileSync(PRIMARY_KEY_PATH, certPem);

return {
key: privPem,
cert: certPem
};
}

module.exports = generateCertificate;
2 changes: 1 addition & 1 deletion test/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"env": {
"mocha": true
}
}
}
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3380,6 +3380,10 @@ nib@^1.1.2:
dependencies:
stylus "0.54.5"

node-forge@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"

node-gyp@^3.3.1:
version "3.6.2"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
Expand Down

0 comments on commit 90b9684

Please sign in to comment.