UNPKG

2.57 kBJavaScriptView Raw
1'use strict';
2
3const Crypto = require('crypto');
4
5const Boom = require('@hapi/boom');
6const Bounce = require('@hapi/bounce');
7const LruCache = require('lru-cache');
8
9
10const internals = {
11 pendings: Object.create(null)
12};
13
14
15internals.streamEnd = function (stream) {
16
17 return new Promise((resolve, reject) => {
18
19 stream.on('end', resolve);
20 stream.on('error', reject);
21 });
22};
23
24
25internals.computeHashed = async function (response, stat) {
26
27 const etags = response.request.server.plugins.inert._etags;
28 if (!etags) {
29 return null;
30 }
31
32 // Use stat info for an LRU cache key.
33
34 const path = response.source.path;
35 const cachekey = [path, stat.ino, stat.size, stat.mtime.getTime()].join('-');
36
37 // The etag hashes the file contents in order to be consistent across distributed deployments
38
39 const cachedEtag = etags.get(cachekey);
40 if (cachedEtag) {
41 return cachedEtag;
42 }
43
44 let promise = internals.pendings[cachekey];
45 if (promise) {
46 return await promise;
47 }
48
49 // Start hashing
50
51 const compute = async () => {
52
53 try {
54 const hash = await internals.hashFile(response);
55 etags.set(cachekey, hash);
56
57 return hash;
58 }
59 finally {
60 delete internals.pendings[cachekey];
61 }
62 };
63
64 internals.pendings[cachekey] = promise = compute();
65
66 return await promise;
67};
68
69
70internals.hashFile = async function (response) {
71
72 const hash = Crypto.createHash('sha1');
73 hash.setEncoding('hex');
74
75 const fileStream = response.source.file.createReadStream({ autoClose: false });
76 fileStream.pipe(hash);
77
78 try {
79 await internals.streamEnd(fileStream);
80 return hash.read();
81 }
82 catch (err) {
83 Bounce.rethrow(err, 'system');
84 throw Boom.boomify(err, { message: 'Failed to hash file', data: { path: response.source.path } });
85 }
86};
87
88
89internals.computeSimple = function (response, stat) {
90
91 const size = stat.size.toString(16);
92 const mtime = stat.mtime.getTime().toString(16);
93
94 return size + '-' + mtime;
95};
96
97
98exports.apply = async function (response, stat) {
99
100 const etagMethod = response.source.settings.etagMethod;
101 if (etagMethod === false) {
102 return;
103 }
104
105 let etag;
106 if (etagMethod === 'simple') {
107 etag = internals.computeSimple(response, stat);
108 }
109 else {
110 etag = await internals.computeHashed(response, stat);
111 }
112
113 if (etag !== null) {
114 response.etag(etag, { vary: true });
115 }
116};
117
118
119exports.Cache = LruCache;