Skip to content
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

Add support for deflate and gzip compression #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
63 changes: 53 additions & 10 deletions lib/angular-http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ var pem = require("pem");
var https = require("https");
var http = require("http");
var opn = require("opn");
var zlib = require('zlib');

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 ";

const COMPRESSIBLE_CONTENT_TYPES = ['application/javascript', 'application/json', 'text/html', 'text/css'];

// if using config file, load that first
if (argv.config) {
let configPath;
Expand Down Expand Up @@ -72,7 +75,7 @@ if (useHttps) {
}

function start() {
server.listen(port, function() {
server.listen(port, function () {
if (argv.open == true || argv.o) {
opn((useHttps ? "https" : "http") + "://localhost:" + port);
}
Expand Down Expand Up @@ -102,24 +105,64 @@ function requestListener(req, res) {

const safeFullFileName = getFilePathFromUrl(req.url, basePath, { baseHref });

fs.stat(safeFullFileName, function(err, stats) {
var fileBuffer;
fs.stat(safeFullFileName, function (err, stats) {
if (!err && stats.isFile()) {
fileBuffer = fs.readFileSync(safeFullFileName);
let ct = mime.lookup(safeFullFileName);
const ct = mime.lookup(safeFullFileName);
const acceptEncoding = req.headers['accept-encoding'] || '';

log(`Sending ${safeFullFileName} with Content-Type ${ct}`);
res.writeHead(200, { "Content-Type": ct });

if (contentIsCompressible(ct, acceptEncoding)) {
sendCompressedResponse(res, ct, acceptEncoding, safeFullFileName);
} else {
const raw = fs.createReadStream(safeFullFileName);
res.writeHead(200, {
"Content-Type": ct
});
raw.pipe(res);
Copy link
Owner

Choose a reason for hiding this comment

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

can you explain what raw.pipe does relative the existing code that uses res.write?

Choose a reason for hiding this comment

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

@simonh1000, the pipe here allows the file that is being read as a stream to be streamed directly to the response without the entire file being in memory at any point. It is a more efficient approach to serving files, especially if any of the static assets is large.

}
} else {
log("Route %s, replacing with index.html", safeFullFileName);
fileBuffer = returnDistFile();
res.writeHead(200, { "Content-Type": "text/html" });
res.write(returnDistFile());
res.end();
}

res.write(fileBuffer);
res.end();
});
}

function contentIsCompressible(contentType, acceptEncoding) {
// Note: This is not a conformant accept-encoding parser.
// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (COMPRESSIBLE_CONTENT_TYPES.includes(contentType)
&& (
/\bdeflate\b/.test(acceptEncoding) ||
/\bgzip\b/.test(acceptEncoding)
)) {
return true;
}
return false;
}

function sendCompressedResponse(res, contentType, encoding, safeFullFileName) {
const raw = fs.createReadStream(safeFullFileName);

if (/\bgzip\b/.test(encoding)) {
res.writeHead(200, {
"Content-Encoding": "gzip",
"Content-Type": contentType
});
raw.pipe(zlib.createGzip()).pipe(res);
} else if (/\bdeflate\b/.test(encoding)) {
res.writeHead(200, {
"Content-Type": "deflate",
"Content-Type": contentType
});
raw.pipe(zlib.createDeflate()).pipe(res);
} else {
log("Unsupported encoding: ", encoding);
Copy link
Owner

Choose a reason for hiding this comment

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

if we hit this will a server request being going unanswered?

Choose a reason for hiding this comment

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

@simonh1000, that is my read.

If you'd like, I can pick this up and carry it to the finish line, including handling unsupported encodings.

Copy link
Owner

Choose a reason for hiding this comment

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

I'd first like to understand why this feature is useful for people on this development-only server.

Choose a reason for hiding this comment

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

That's a fair question. I'm not entirely sure myself.

Choose a reason for hiding this comment

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

It looks like @oderayi's use case was Lighthouse auditing before deploying to production.

Copy link
Owner

Choose a reason for hiding this comment

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

hmm. auditing like this does not help you know what score you will get in production. I'm concerned that this is increasing the sense that this is a production server, which it absolutely is not. I'm inclined to drop this.

}
}

function getPort(portNo) {
if (portNo) {
var portNum = parseInt(portNo);
Expand Down