Skip to content

Commit 0fca18b

Browse files
committed
feat: allow headers to be set before being served; fixes #169 ; closes #184
1 parent caba353 commit 0fca18b

File tree

2 files changed

+57
-23
lines changed

2 files changed

+57
-23
lines changed

lib/node-static.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import {mstat} from './node-static/util.js';
1111
/**
1212
* @typedef {{
1313
* status: number,
14-
* headers: Record<string, string>,
14+
* headers: http.OutgoingHttpHeaders,
1515
* message?: string
1616
* }} ResultInfo
1717
*/
1818

1919
/**
2020
* @typedef {(
2121
* status: number,
22-
* headers: Record<string, string>,
22+
* headers: http.OutgoingHttpHeaders,
2323
* streaming?: boolean
2424
* ) => void} Finish
2525
*/
@@ -49,7 +49,7 @@ function tryStat(p, callback) {
4949
* @typedef {{
5050
* indexFile?: string,
5151
* gzip?: boolean|RegExp,
52-
* headers?: Record<string, string>
52+
* headers?: http.OutgoingHttpHeaders
5353
* serverInfo?: string|null,
5454
* cache?: boolean|number|Record<string, number>,
5555
* defaultExtension?: string
@@ -76,7 +76,7 @@ class Server extends events.EventEmitter {
7676
/** @type {Record<string, number>} */
7777
this.cache = {'**': 3600};
7878

79-
/** @type {Record<string, string>} */
79+
/** @type {http.OutgoingHttpHeaders} */
8080
this.defaultHeaders = {};
8181
this.options.headers = this.options.headers || {};
8282

@@ -118,16 +118,16 @@ class Server extends events.EventEmitter {
118118
* @param {http.ServerResponse<http.IncomingMessage> & {
119119
* req: http.IncomingMessage;
120120
* }} res
121-
* @param {(status: number, headers: Record<string, string>) => void} finish
121+
* @param {(status: number, headers: http.OutgoingHttpHeaders) => void} finish
122122
*/
123123
serveDir (pathname, req, res, finish) {
124124
const htmlIndex = path.join(pathname, this.options.indexFile);
125125

126126
tryStat(htmlIndex, (e, stat) => {
127127
if (!e && stat) {
128128
const status = 200;
129-
/** @type {Record<string, string>} */
130-
const headers = {};
129+
/** @type {http.OutgoingHttpHeaders} */
130+
const headers = res.getHeaders();
131131
const originalPathname = decodeURIComponent(new URL(
132132
/* c8 ignore next -- TS */
133133
req.url ?? '',
@@ -154,15 +154,15 @@ class Server extends events.EventEmitter {
154154
const streamFiles = (files) => {
155155
mstat(pathname, files, (e, stat) => {
156156
if (e || !stat) { return finish(404, {}) }
157-
this.respond(pathname, 200, {}, files, stat, req, res, finish);
157+
this.respond(pathname, 200, res.getHeaders(), files, stat, req, res, finish);
158158
});
159159
};
160160
}
161161

162162
/**
163163
* @param {string} pathname
164164
* @param {number} status
165-
* @param {Record<string, string>} headers
165+
* @param {http.OutgoingHttpHeaders} headers
166166
* @param {http.IncomingMessage} req
167167
* @param {http.ServerResponse<http.IncomingMessage> & {
168168
* req: http.IncomingMessage;
@@ -186,7 +186,7 @@ class Server extends events.EventEmitter {
186186

187187
/**
188188
* @param {number} status
189-
* @param {Record<string, string>} headers
189+
* @param {http.OutgoingHttpHeaders} headers
190190
* @param {http.IncomingMessage} req
191191
* @param {http.ServerResponse<http.IncomingMessage> & {
192192
* req: http.IncomingMessage;
@@ -233,7 +233,7 @@ class Server extends events.EventEmitter {
233233
/**
234234
* @param {string} pathname
235235
* @param {number} status
236-
* @param {Record<string, string>} headers
236+
* @param {http.OutgoingHttpHeaders} headers
237237
* @param {http.IncomingMessage} req
238238
* @param {http.ServerResponse<http.IncomingMessage> & {
239239
* req: http.IncomingMessage;
@@ -303,7 +303,7 @@ class Server extends events.EventEmitter {
303303

304304
/**
305305
* @param {number} status
306-
* @param {Record<string, string>} headers
306+
* @param {http.OutgoingHttpHeaders} headers
307307
* @param {boolean} [streaming]
308308
*/
309309
const finish = (status, headers, streaming) => {
@@ -326,7 +326,7 @@ class Server extends events.EventEmitter {
326326
}
327327

328328
process.nextTick(() => {
329-
this.servePath(pathname, 200, {}, req, res, finish).on('success', function (result) {
329+
this.servePath(pathname, 200, res.getHeaders(), req, res, finish).on('success', function (result) {
330330
/* c8 ignore next -- How to cover? */
331331
promise.emit('success', result);
332332
}).on('error', function (err) {
@@ -362,7 +362,7 @@ class Server extends events.EventEmitter {
362362
* @param {string|null} pathname
363363
* @param {number} status
364364
* @param {string} contentType
365-
* @param {Record<string, string>} _headers
365+
* @param {http.OutgoingHttpHeaders} _headers
366366
* @param {string[]} files
367367
* @param {import('./node-static/util.js').StatInfo} stat
368368
* @param {http.IncomingMessage} req
@@ -436,7 +436,7 @@ class Server extends events.EventEmitter {
436436
* @param {string|null} pathname
437437
* @param {number} status
438438
* @param {string} contentType
439-
* @param {Record<string, string>} _headers
439+
* @param {http.OutgoingHttpHeaders} _headers
440440
* @param {string[]} files
441441
* @param {import('./node-static/util.js').StatInfo} stat
442442
* @param {http.IncomingMessage} req
@@ -448,7 +448,7 @@ class Server extends events.EventEmitter {
448448
respondNoGzip (pathname, status, contentType, _headers, files, stat, req, res, finish) {
449449
const mtime = Date.parse(stat.mtime.toString()),
450450
key = pathname || files[0],
451-
headers = /** @type {Record<string, string>} */ ({}),
451+
headers = /** @type {http.OutgoingHttpHeaders} */ ({}),
452452
clientETag = req.headers['if-none-match'],
453453
clientMTime = Date.parse(req.headers['if-modified-since'] ?? ''),
454454
byteRange = this.parseByteRange(req, stat);
@@ -523,7 +523,7 @@ class Server extends events.EventEmitter {
523523
/**
524524
* @param {string|null} pathname
525525
* @param {number} status
526-
* @param {Record<string, string>} _headers
526+
* @param {http.OutgoingHttpHeaders} _headers
527527
* @param {string[]} files
528528
* @param {import('./node-static/util.js').StatInfo} stat
529529
* @param {http.IncomingMessage} req
@@ -539,9 +539,9 @@ class Server extends events.EventEmitter {
539539
_headers = this.setCacheHeaders(_headers, req);
540540

541541
if(this.options.gzip) {
542-
this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
542+
this.respondGzip(pathname, status, /** @type {string} */ (contentType), _headers, files, stat, req, res, finish);
543543
} else {
544-
this.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
544+
this.respondNoGzip(pathname, status, /** @type {string} */ (contentType), _headers, files, stat, req, res, finish);
545545
}
546546
}
547547

@@ -589,7 +589,7 @@ class Server extends events.EventEmitter {
589589
}
590590

591591
/**
592-
* @param {Record<string, string>} _headers
592+
* @param {http.OutgoingHttpHeaders} _headers
593593
* @param {http.IncomingMessage} req
594594
*/
595595
setCacheHeaders (_headers, req) {

test/integration/node-static-test.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@ let testPort = 8151;
1717
* req: http.IncomingMessage;
1818
* }
1919
* ) => void} [cb]
20+
* @param {(
21+
* request: http.IncomingMessage,
22+
* response: http.ServerResponse<http.IncomingMessage> & {
23+
* req: http.IncomingMessage;
24+
* }
25+
* ) => void} [preprocessCb]
2026
*/
21-
async function setupStaticServer (obj, cb) {
27+
async function setupStaticServer (obj, cb, preprocessCb) {
2228
obj.port = ++testPort;
23-
obj.server = await startStaticServer(obj.port, cb);
29+
obj.server = await startStaticServer(obj.port, cb, preprocessCb);
2430
obj.getTestServer = () => {
2531
return 'http://localhost:' + obj.port;
2632
};
@@ -38,10 +44,19 @@ let fileServer = new statik.Server(__dirname + '/../fixtures');
3844
* req: http.IncomingMessage;
3945
* }
4046
* ) => void} [callback]
47+
* @param {(
48+
* request: http.IncomingMessage,
49+
* response: http.ServerResponse<http.IncomingMessage> & {
50+
* req: http.IncomingMessage;
51+
* }
52+
* ) => void} [preprocessCallback]
4153
*/
42-
function startStaticServer (port, callback) {
54+
function startStaticServer (port, callback, preprocessCallback) {
4355
return new Promise((resolve, reject) => {
4456
const server = http.createServer(function (request, response) {
57+
if (preprocessCallback) {
58+
preprocessCallback(request, response);
59+
}
4560
const serveProm = fileServer.serve(request, response);
4661
if (callback) {
4762
callback(serveProm, request, response);
@@ -616,6 +631,25 @@ describe('node-static', function () {
616631
assert.equal(response.status, 404, 'should respond with 404');
617632
});
618633
});
634+
635+
describe('once an http server is listening with a custom header', function () {
636+
beforeEach(async function () {
637+
await setupStaticServer(this, undefined, (_req, res) => {
638+
res.setHeader('Content-Type', 'text/html');
639+
});
640+
});
641+
afterEach(async function () {
642+
this.server.close();
643+
});
644+
it('requesting a text file as HTML', async function () {
645+
fileServer = new statik.Server(__dirname+'/../fixtures');
646+
const response = await fetch(this.getTestServer() + '/hello.txt');
647+
648+
assert.equal(response.headers.get('content-type'), 'text/html', 'should respond with text/html');
649+
assert.equal(response.status, 200, 'should respond with 200');
650+
});
651+
});
652+
619653
describe('once an http server is listening with custom index configuration', function () {
620654
before(function () {
621655
fileServer = new statik.Server(__dirname + '/../fixtures', { indexFile: "hello.txt" });

0 commit comments

Comments
 (0)