forked from tkoenig89/express-static-gzip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
207 lines (182 loc) · 7.34 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
var serveStatic = require("serve-static");
var mime = serveStatic.mime;
module.exports = expressStaticGzip;
/**
* Generates a middleware function to serve static files. It is build on top of the express.static middleware.
* It extends the express.static middleware with the capability to serve (previously) gziped files. For this
* it asumes, the gziped files are next to the original files.
* @param {string} rootFolder: folder to staticly serve files from
* @param {{enableBrotli:boolean, customCompressions:[{encodingName:string,fileExtension:string}], indexFromEmptyFile:boolean}} options: options to change module behaviour
* @returns express middleware function
*/
function expressStaticGzip(rootFolder, options) {
options = options || {};
if (typeof (options.indexFromEmptyFile) === "undefined") options.indexFromEmptyFile = true;
//create a express.static middleware to handle serving files
var defaultStatic = serveStatic(rootFolder, options),
compressions = [],
files = {};
//read compressions from options
setupCompressions();
//if at least one compression has been added, lookup files
if (compressions.length > 0) {
findAllCompressionFiles(require("fs"), rootFolder);
}
return function middleware(req, res, next) {
changeUrlFromEmptyToIndexHtml(req);
//get browser's' supported encodings
var acceptEncoding = req.header("accept-encoding");
//test if any compression is available
var matchedFile = files[req.path];
if (matchedFile) {
//as long as there is any compression available for this file, add the Vary Header (used for caching proxies)
res.setHeader("Vary", "Accept-Encoding");
//use the first matching compression to serve a compresed file
var compression = findAvailableCompressionForFile(matchedFile.compressions, acceptEncoding);
if (compression) {
convertToCompressedRequest(req, res, compression);
}
}
//allways call the default static file provider
defaultStatic(req, res, next);
};
/**
* Reads the options into a list of available compressions.
*/
function setupCompressions() {
//register all provided compressions
if (options.customCompressions && options.customCompressions.length > 0) {
for (var i = 0; i < options.customCompressions.length; i++) {
var customCompression = options.customCompressions[i];
registerCompression(customCompression.encodingName, customCompression.fileExtension);
}
}
//enable brotli compression
if (options.enableBrotli) {
registerCompression("br", "br");
}
//gzip compression is enabled by default
registerCompression("gzip", "gz");
}
/**
* Changes the url and adds required headers to serve a compressed file.
* @param {Object} req
* @param {Object} res
*/
function convertToCompressedRequest(req, res, compression) {
var type = mime.lookup(req.path);
var charset = mime.charsets.lookup(type);
var search = req.url.split('?').splice(1).join('?');
if (search !== "") {
search = "?" + search;
}
req.url = req.path + compression.fileExtension + search;
res.setHeader("Content-Encoding", compression.encodingName);
res.setHeader("Content-Type", type + (charset ? "; charset=" + charset : ""));
}
/**
* In case it's enabled in the options and the requested url does not request a specific file, "index.html" will be appended.
* @param {Object} req
*/
function changeUrlFromEmptyToIndexHtml(req) {
if (options.indexFromEmptyFile && req.url.endsWith("/")) {
req.url += "index.html";
}
}
/**
* Searches for the first matching compression available from the given compressions.
* @param {[Compression]} compressionList
* @param {string} acceptedEncoding
* @returns
*/
function findAvailableCompressionForFile(compressionList, acceptedEncoding) {
if (acceptedEncoding) {
for (var i = 0; i < compressionList.length; i++) {
if (acceptedEncoding.indexOf(compressionList[i].encodingName) >= 0) {
return compressionList[i];
}
}
}
return null;
}
/**
* Picks all files into the matching compression's file list. Search is done recursively!
* @param {Object} fs: node.fs
* @param {string} folderPath
*/
function findAllCompressionFiles(fs, folderPath) {
// check if folder exists
if (!fs.existsSync(folderPath)) return;
var files = fs.readdirSync(folderPath);
//iterate all files in the current folder
for (var i = 0; i < files.length; i++) {
var filePath = folderPath + "/" + files[i];
var stats = fs.statSync(filePath);
if (stats.isDirectory()) {
//recursively search folders and append the matching files
findAllCompressionFiles(fs, filePath);
} else {
addAllMatchingCompressionsToFile(files[i], filePath);
}
}
}
/**
* Takes a filename and checks if there is any compression type matching the file extension.
* Adds all matching compressions to the file.
* @param {string} fileName
* @param {string} fillFilePath
*/
function addAllMatchingCompressionsToFile(fileName, fullFilePath) {
for (var i = 0; i < compressions.length; i++) {
if (fileName.endsWith(compressions[i].fileExtension)) {
addCompressionToFile(fullFilePath, compressions[i]);
return;
}
}
}
/**
* Adds the compression to the file's list of available compressions
* @param {string} filePath
* @param {Compression} compression
*/
function addCompressionToFile(filePath, compression) {
var srcFilePath = filePath.replace(compression.fileExtension, "").replace(rootFolder, "");
var existingFile = files[srcFilePath];
if (!existingFile) {
files[srcFilePath] = { compressions: [compression] };
} else {
existingFile.compressions.push(compression);
}
}
/**
* Registers a new compression to the module.
* @param {string} encodingName
* @param {string} fileExtension
*/
function registerCompression(encodingName, fileExtension) {
if (!findCompressionByName(encodingName))
compressions.push(new Compression(encodingName, fileExtension));
}
/**
* Constructor
* @param {string} encodingName
* @param {string} fileExtension
* @returns {encodingName:string, fileExtension:string,files:[Object]}
*/
function Compression(encodingName, fileExtension) {
this.encodingName = encodingName;
this.fileExtension = "." + fileExtension;
}
/**
* Compression lookup by name.
* @param {string} encodingName
* @returns {Compression}
*/
function findCompressionByName(encodingName) {
for (var i = 0; i < compressions.length; i++) {
if (compressions[i].encodingName === encodingName)
return compressions[i];
}
return null;
}
}