Skip to content

Commit f265197

Browse files
Include changes form PR #923 with additional changes (#927)
* feat: add encoding even if .br/.gz specified (#923) * Add handling of brotli/gzip pre-compressed files Serving pre-compressed brotli/gzip files was not working before, because a file.br.br against the pre-compressed file.br check always returned false. * Bump version from 14.1.1 to 14.1.2 * fix: always check the dot This fixes an issue recently introduced where file extensions ending with `br` are handled even when the dot does not immediately preceded. This introduces erroneous an error when trying to access files with other extensions ending with `br`, such as `.cbr`. I meant to merge the previous PR with this fix included but forgot to commit the suggestion I added in the PR review. This was merged to a feature branch so the main branch was not affected. * fix: put new behavior for .br/.gz behind a flag Adds --force-content-encoding flag which must be specified to enable the behavior of including the Content-Encoding header in the response when a .br or .gz file is directly requested in the URL. * test: --force-content-encoding flag Tests whether or not Content-Encoding is present under different cases with and without the --force-content-encoding flag set. * doc: update manpage * sync: package-lock.json --------- Co-authored-by: Andreas Zeitler <andreas.zeitler@protonmail.com>
1 parent 1d8972f commit f265197

File tree

9 files changed

+137
-5
lines changed

9 files changed

+137
-5
lines changed

bin/http-server

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ if (argv.h || argv.help) {
3333
' -g --gzip Serve gzip files when possible [false]',
3434
' -b --brotli Serve brotli files when possible [false]',
3535
' If both brotli and gzip are enabled, brotli takes precedence',
36+
'',
37+
' --force-content-encoding',
38+
' When using --gzip or --brotli, includes the content encoding',
39+
' header even when the extension for the compressed file is',
40+
' specified in the URL. "test.png.br" will be served the same',
41+
' way as "test.png".',
42+
'',
3643
' -e --ext Default file extension if none supplied [none]',
3744
' -s --silent Suppress log messages from output',
3845
' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header',
@@ -180,6 +187,10 @@ function listen(port) {
180187
options.corsHeaders = argv.cors;
181188
}
182189
}
190+
191+
if ( argv['force-content-encoding'] ) {
192+
options.forceContentEncoding = true;
193+
}
183194

184195
if (argv.header) {
185196
if (Array.isArray(argv.header)) {

doc/http-server.1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ Serve brotli files when possible.
4949
If both brotli and gzip are enabled, brotli takes precedence.
5050
Default is false.
5151

52+
.TP
53+
.BI \-\-force\-content\-encoding
54+
When using --gzip or --brotli, includes the content encoding
55+
header even when the extension for the compressed file is
56+
specified in the URL. "test.png.br" will be served the same
57+
way as "test.png".
58+
5259
.TP
5360
.BI \-e ", " \-\-ext " " \fIEXTENSION\fR
5461
Default file extension is none is provided.

lib/core/defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"cors": false,
1010
"gzip": true,
1111
"brotli": false,
12+
"forceContentEncoding": false,
1213
"defaultExt": ".html",
1314
"handleError": true,
1415
"contentType": "application/octet-stream",

lib/core/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,14 @@ module.exports = function createMiddleware(_dir, _options) {
200200
path.relative(path.join('/', baseDir), pathname)
201201
)
202202
);
203-
// determine compressed forms if they were to exist
203+
// determine compressed forms if they were to exist, make sure to handle pre-compressed files, i.e. files with .br/.gz extension. we will serve them "as-is"
204204
gzippedFile = `${file}.gz`;
205205
brotliFile = `${file}.br`;
206+
207+
if ( opts.forceContentEncoding ) {
208+
if ( file.endsWith('.gz') ) gzippedFile = file;
209+
if ( file.endsWith('.br') ) brotliFile = file;
210+
}
206211

207212
Object.keys(headers).forEach((key) => {
208213
res.setHeader(key, headers[key]);

lib/core/opts.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = (opts) => {
1515
let cache = defaults.cache;
1616
let gzip = defaults.gzip;
1717
let brotli = defaults.brotli;
18+
let forceContentEncoding = defaults.forceContentEncoding;
1819
let defaultExt = defaults.defaultExt;
1920
let handleError = defaults.handleError;
2021
const headers = {};
@@ -108,6 +109,9 @@ module.exports = (opts) => {
108109
if (typeof opts.brotli !== 'undefined' && opts.brotli !== null) {
109110
brotli = opts.brotli;
110111
}
112+
if (typeof opts.forceContentEncoding !== 'undefined' && opts.forceContentEncoding !== null) {
113+
forceContentEncoding = opts.forceContentEncoding;
114+
}
111115

112116
aliases.handleError.some((k) => {
113117
if (isDeclared(k)) {
@@ -192,6 +196,7 @@ module.exports = (opts) => {
192196
baseDir: (opts && opts.baseDir) || '/',
193197
gzip,
194198
brotli,
199+
forceContentEncoding,
195200
handleError,
196201
headers,
197202
contentType,

lib/http-server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function HttpServer(options) {
5858
this.showDotfiles = options.showDotfiles;
5959
this.gzip = options.gzip === true;
6060
this.brotli = options.brotli === true;
61+
this.forceContentEncoding = options.forceContentEncoding === true;
6162
if (options.ext) {
6263
this.ext = options.ext === true
6364
? 'html'
@@ -138,6 +139,7 @@ function HttpServer(options) {
138139
defaultExt: this.ext,
139140
gzip: this.gzip,
140141
brotli: this.brotli,
142+
forceContentEncoding: this.forceContentEncoding,
141143
contentType: this.contentType,
142144
mimetypes: options.mimetypes,
143145
handleError: typeof options.proxy !== 'string'

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "http-server",
3-
"version": "14.1.1",
3+
"version": "14.1.2",
44
"description": "A simple zero-configuration command-line http server",
55
"main": "./lib/http-server",
66
"repository": {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
'use strict';
2+
3+
const test = require('tap').test;
4+
const ecstatic = require('../lib/core');
5+
const http = require('http');
6+
const request = require('request');
7+
8+
const root = `${__dirname}/public`;
9+
10+
test('--force-content-encoding flag: .br files served without Content-Encoding header when flag not set', (t) => {
11+
t.plan(3);
12+
13+
const server = http.createServer(ecstatic({
14+
root,
15+
brotli: true,
16+
autoIndex: true,
17+
forceContentEncoding: false
18+
}));
19+
20+
server.listen(() => {
21+
const port = server.address().port;
22+
const options = {
23+
uri: `http://localhost:${port}/brotli/index.html.br`,
24+
headers: {
25+
'accept-encoding': 'gzip, deflate, br'
26+
}
27+
};
28+
29+
request.get(options, (err, res) => {
30+
t.error(err);
31+
t.equal(res.statusCode, 200);
32+
t.notOk(res.headers['content-encoding'], 'should not have content-encoding header when flag not set');
33+
});
34+
});
35+
36+
t.once('end', () => {
37+
server.close();
38+
});
39+
});
40+
41+
test('--force-content-encoding flag: .br files served with Content-Encoding header when flag is set', (t) => {
42+
t.plan(3);
43+
44+
const server = http.createServer(ecstatic({
45+
root,
46+
brotli: true,
47+
autoIndex: true,
48+
forceContentEncoding: true
49+
}));
50+
51+
server.listen(() => {
52+
const port = server.address().port;
53+
const options = {
54+
uri: `http://localhost:${port}/brotli/index.html.br`,
55+
headers: {
56+
'accept-encoding': 'gzip, deflate, br'
57+
}
58+
};
59+
60+
request.get(options, (err, res) => {
61+
t.error(err);
62+
t.equal(res.statusCode, 200);
63+
t.equal(res.headers['content-encoding'], 'br', 'should have content-encoding: br header when flag is set');
64+
});
65+
});
66+
67+
t.once('end', () => {
68+
server.close();
69+
});
70+
});
71+
72+
test('--force-content-encoding flag: regular files served with Content-Encoding header when flag is set', (t) => {
73+
t.plan(3);
74+
75+
const server = http.createServer(ecstatic({
76+
root,
77+
brotli: true,
78+
autoIndex: true,
79+
forceContentEncoding: true
80+
}));
81+
82+
server.listen(() => {
83+
const port = server.address().port;
84+
const options = {
85+
uri: `http://localhost:${port}/brotli/index.html`,
86+
headers: {
87+
'accept-encoding': 'gzip, deflate, br'
88+
}
89+
};
90+
91+
request.get(options, (err, res) => {
92+
t.error(err);
93+
t.equal(res.statusCode, 200);
94+
t.ok(res.headers['content-encoding'], 'regular files should have content-encoding header');
95+
});
96+
});
97+
98+
t.once('end', () => {
99+
server.close();
100+
});
101+
});

0 commit comments

Comments
 (0)