spa-server-gzip
Version:
Simple http server for developers of apps with client side routing
188 lines (165 loc) • 5.49 kB
JavaScript
var fs = require("fs");
var path = require("path");
var mime = require("mime");
var pem = require("pem");
var https = require("https");
var http = require("http");
var opn = require("opn");
var argv = require("minimist")(process.argv.slice(2));
const getFilePathFromUrl = require("./get-file-path-from-url");
const NO_PATH_FILE_ERROR_MESSAGE =
"Error: index.html could not be found in the specified path ";
// if using config file, load that first
if (argv.config) {
let configPath;
if (path.isAbsolute(argv.config)) {
configPath = argv.config;
} else {
configPath = path.join(process.cwd(), argv.config);
}
const getConfig = require(configPath);
let config;
if (typeof getConfig === "function") {
config = getConfig(argv);
} else {
config = getConfig;
}
// supplement argv with config, but CLI args take precedence
argv = Object.assign(config, argv);
}
// Pre-process arguments
const useHttps = argv.ssl || argv.https;
const basePath = argv.path ? path.resolve(argv.path) : process.cwd();
const baseHref = argv.baseHref ? argv.baseHref : "";
const port = getPort(argv.p);
// As a part of the startup - check to make sure we can access index.html
returnDistFile(true);
const { gzip } = require("node-gzip");
const textExtensions = new Set([".js", ".html", ".css", ".txt", ".json"]);
const gzipedFiles = new Map();
const fileNames = fs
.readdirSync(".")
.filter(f => textExtensions.has(path.extname(f)));
const fileContents = fileNames.map(f => fs.readFileSync(f, "utf8")).map(gzip);
let server;
Promise.all(fileContents).then(fileContents => {
for (let i = 0; i < fileNames.length; i++) {
gzipedFiles.set(`/${fileNames[i]}`, fileContents[i]);
}
// Start with/without https
if (useHttps) {
const startSSLCallback = (err, keys) => {
if (err) {
throw err;
}
const options = {
key: keys.serviceKey,
cert: keys.certificate,
rejectUnauthorized: false
};
server = https.createServer(options, requestListener);
start();
};
if (argv.key && argv.cert) {
const serviceKey = fs.readFileSync(argv.key);
const certificate = fs.readFileSync(argv.cert);
startSSLCallback(null, { serviceKey, certificate });
} else {
pem.createCertificate(
{ days: 1, selfSigned: true },
startSSLCallback
);
}
} else {
server = http.createServer(requestListener);
start();
}
});
function start() {
server.listen(port, function() {
if (argv.open == true || argv.o) {
opn((useHttps ? "https" : "http") + "://localhost:" + port);
}
return console.log("Listening on " + port);
});
}
// HELPERS
function requestListener(req, res) {
// Add CORS header if option chosen
if (argv.cors) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Request-Method", "*");
res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET");
res.setHeader(
"Access-Control-Allow-Headers",
"authorization, content-type"
);
// When the request is for CORS OPTIONS (rather than a page) return just the headers
if (req.method === "OPTIONS") {
res.writeHead(200);
res.end();
return;
}
}
const safeFullFileName = getFilePathFromUrl(req.url, basePath, {
baseHref
});
fs.stat(safeFullFileName, function(err, stats) {
var fileBuffer;
if (!err && stats.isFile()) {
let ct = mime.lookup(safeFullFileName);
const headers = { "Content-Type": ct };
if (gzipedFiles.has(req.url)) {
fileBuffer = gzipedFiles.get(req.url);
headers["Content-Encoding"] = "gzip";
} else {
fileBuffer = fs.readFileSync(safeFullFileName);
}
log(`Sending ${safeFullFileName} with Content-Type ${ct}`);
res.writeHead(200, headers);
} else {
log("Route %s, replacing with index.html", safeFullFileName);
fileBuffer = gzipedFiles.get("/index.html");
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Encoding": "gzip"
});
}
res.write(fileBuffer);
res.end();
});
}
function getPort(portNo) {
if (portNo) {
var portNum = parseInt(portNo);
if (!isNaN(portNum)) {
return portNum;
} else {
throw new Exception("Provided port number is not a number!");
}
} else {
return 8080;
}
}
function returnDistFile(displayFileMessages = false) {
var distPath;
try {
if (displayFileMessages) {
log("Serving from path: %s", basePath);
}
distPath = path.join(basePath, "index.html");
if (displayFileMessages) {
log("Using default file: %s", distPath);
}
return fs.readFileSync(distPath);
} catch (e) {
console.warn(NO_PATH_FILE_ERROR_MESSAGE + "%s", basePath);
process.exit(1);
}
}
function log() {
if (!argv.silent) {
console.log.apply(console, arguments);
}
}