@eggjs/koa-static-cache 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +144 -0
- package/dist/commonjs/index.d.ts +99 -0
- package/dist/commonjs/index.js +225 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/esm/index.d.ts +99 -0
- package/dist/esm/index.js +217 -0
- package/dist/esm/package.json +3 -0
- package/dist/package.json +4 -0
- package/package.json +90 -0
- package/src/index.ts +341 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 - present eggjs and the contributors.
|
|
4
|
+
Copyright (c) 2013 Jonathan Ong me@jongleberry.com
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Koa Static Cache
|
|
2
|
+
|
|
3
|
+
[![NPM version][npm-image]][npm-url]
|
|
4
|
+
[](https://github.com/eggjs/koa-static-cache/actions/workflows/nodejs.yml)
|
|
5
|
+
[![Test coverage][codecov-image]][codecov-url]
|
|
6
|
+
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
7
|
+
[![npm download][download-image]][download-url]
|
|
8
|
+
[](https://nodejs.org/en/download/)
|
|
9
|
+
[](https://makeapullrequest.com)
|
|
10
|
+
|
|
11
|
+
[npm-image]: https://img.shields.io/npm/v/@eggjs/koa-static-cache.svg?style=flat-square
|
|
12
|
+
[npm-url]: https://npmjs.org/package/@eggjs/koa-static-cache
|
|
13
|
+
[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/koa-static-cache.svg?style=flat-square
|
|
14
|
+
[codecov-url]: https://codecov.io/github/eggjs/koa-static-cache?branch=master
|
|
15
|
+
[snyk-image]: https://snyk.io/test/npm/@eggjs/koa-static-cache/badge.svg?style=flat-square
|
|
16
|
+
[snyk-url]: https://snyk.io/test/npm/@eggjs/koa-static-cache
|
|
17
|
+
[download-image]: https://img.shields.io/npm/dm/@eggjs/koa-static-cache.svg?style=flat-square
|
|
18
|
+
[download-url]: https://npmjs.org/package/@eggjs/koa-static-cache
|
|
19
|
+
|
|
20
|
+
Static cache middleware for koa.
|
|
21
|
+
|
|
22
|
+
Differences between this library and other libraries such as [static](https://github.com/koajs/static):
|
|
23
|
+
|
|
24
|
+
- There is no directory or `index.html` support.
|
|
25
|
+
- You may optionally store the data in memory - it streams by default.
|
|
26
|
+
- Caches the assets on initialization - you need to restart the process to update the assets.(can turn off with options.preload = false)
|
|
27
|
+
- Uses MD5 hash sum as an ETag.
|
|
28
|
+
- Uses `.gz` files if present on disk, like nginx gzip_static module
|
|
29
|
+
|
|
30
|
+
> Forked from https://github.com/koajs/static-cache, refactor with TypeScript to support CommonJS and ESM both.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @eggjs/koa-static-cache
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
### staticCache([options])
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const path = require('path');
|
|
44
|
+
const { staticCache } = require('@eggjs/koa-static-cache');
|
|
45
|
+
|
|
46
|
+
app.use(staticCache(path.join(__dirname, 'public'), {
|
|
47
|
+
maxAge: 365 * 24 * 60 * 60
|
|
48
|
+
}));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `options.dir` (str) - the directory you wish to serve, default to `process.cwd`.
|
|
52
|
+
- `options.maxAge` (int) - cache control max age for the files, `0` by default.
|
|
53
|
+
- `options.cacheControl` (str) - optional cache control header. Overrides `options.maxAge`.
|
|
54
|
+
- `options.buffer` (bool) - store the files in memory instead of streaming from the filesystem on each request.
|
|
55
|
+
- `options.gzip` (bool) - when request's accept-encoding include gzip, files will compressed by gzip.
|
|
56
|
+
- `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static
|
|
57
|
+
- `options.alias` (obj) - object map of aliases. See below.
|
|
58
|
+
- `options.prefix` (str) - the url prefix you wish to add, default to `''`.
|
|
59
|
+
- `options.dynamic` (bool) - dynamic load file which not cached on initialization.
|
|
60
|
+
- `options.filter` (function | array) - filter files at init dir, for example - skip non build (source) files. If array set - allow only listed files
|
|
61
|
+
- `options.preload` (bool) - caches the assets on initialization or not, default to `true`. always work together with `options.dynamic`.
|
|
62
|
+
- `options.files` (obj) - optional files object. See below.
|
|
63
|
+
|
|
64
|
+
### Aliases
|
|
65
|
+
|
|
66
|
+
For example, if you have this `alias` object:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
const options = {
|
|
70
|
+
alias: {
|
|
71
|
+
'/favicon.png': '/favicon-32.png'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then requests to `/favicon.png` will actually return `/favicon-32.png` without redirects or anything.
|
|
77
|
+
This is particularly important when serving [favicons](https://github.com/audreyr/favicon-cheat-sheet) as you don't want to store duplicate images.
|
|
78
|
+
|
|
79
|
+
### Files
|
|
80
|
+
|
|
81
|
+
You can pass in an optional files object.
|
|
82
|
+
This allows you to do two things:
|
|
83
|
+
|
|
84
|
+
#### Combining directories into a single middleware
|
|
85
|
+
|
|
86
|
+
Instead of doing:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
app.use(staticCache('/public/js'))
|
|
90
|
+
app.use(staticCache('/public/css'))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
You can do this:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const files = {};
|
|
97
|
+
|
|
98
|
+
// Mount the middleware
|
|
99
|
+
app.use(staticCache('/public/js', {}, files));
|
|
100
|
+
|
|
101
|
+
// Add additional files
|
|
102
|
+
staticCache('/public/css', {}, files);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The benefit is that you'll have one less function added to the stack as well as doing one hash lookup instead of two.
|
|
106
|
+
|
|
107
|
+
#### Editing the files object
|
|
108
|
+
|
|
109
|
+
For example, if you want to change the max age of `/package.json`, you can do the following:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const files = {};
|
|
113
|
+
|
|
114
|
+
app.use(staticCache('/public', {
|
|
115
|
+
maxAge: 60 * 60 * 24 * 365
|
|
116
|
+
}, files));
|
|
117
|
+
|
|
118
|
+
files['/package.json'].maxAge = 60 * 60 * 24 * 30;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Using a LRU cache to avoid OOM when dynamic mode enabled
|
|
122
|
+
|
|
123
|
+
You can pass in a lru cache instance which has tow methods: `get(key)` and `set(key, value)`.
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
const LRU = require('lru-cache');
|
|
127
|
+
const files = new LRU({ max: 1000 });
|
|
128
|
+
|
|
129
|
+
app.use(staticCache({
|
|
130
|
+
dir: '/public',
|
|
131
|
+
dynamic: true,
|
|
132
|
+
files,
|
|
133
|
+
}));
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
[MIT](LICENSE)
|
|
139
|
+
|
|
140
|
+
## Contributors
|
|
141
|
+
|
|
142
|
+
[](https://github.com/eggjs/koa-static-cache/graphs/contributors)
|
|
143
|
+
|
|
144
|
+
Made with [contributors-img](https://contrib.rocks).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type FileFilter = (path: string) => boolean;
|
|
2
|
+
export interface FileMeta {
|
|
3
|
+
maxAge?: number;
|
|
4
|
+
cacheControl?: string;
|
|
5
|
+
buffer?: Buffer;
|
|
6
|
+
zipBuffer?: Buffer;
|
|
7
|
+
type?: string;
|
|
8
|
+
mime?: string;
|
|
9
|
+
mtime?: Date;
|
|
10
|
+
path?: string;
|
|
11
|
+
md5?: string;
|
|
12
|
+
length?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FileMap {
|
|
15
|
+
[path: string]: FileMeta;
|
|
16
|
+
}
|
|
17
|
+
export interface FileStore {
|
|
18
|
+
get(key: string): unknown;
|
|
19
|
+
set(key: string, value: unknown): void;
|
|
20
|
+
}
|
|
21
|
+
export interface Options {
|
|
22
|
+
/**
|
|
23
|
+
* The root directory from which to serve static assets
|
|
24
|
+
* Default to `process.cwd`
|
|
25
|
+
*/
|
|
26
|
+
dir?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The max age for cache control
|
|
29
|
+
* Default to `0`
|
|
30
|
+
*/
|
|
31
|
+
maxAge?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The cache control header for static files
|
|
34
|
+
* Default to `undefined`
|
|
35
|
+
* Overrides `options.maxAge`
|
|
36
|
+
*/
|
|
37
|
+
cacheControl?: string;
|
|
38
|
+
/**
|
|
39
|
+
* store the files in memory instead of streaming from the filesystem on each request
|
|
40
|
+
*/
|
|
41
|
+
buffer?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* when request's accept-encoding include gzip, files will compressed by gzip
|
|
44
|
+
* Default to `false`
|
|
45
|
+
*/
|
|
46
|
+
gzip?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* try use gzip files, loaded from disk, like nginx gzip_static
|
|
49
|
+
* Default to `false`
|
|
50
|
+
*/
|
|
51
|
+
usePrecompiledGzip?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* object map of aliases
|
|
54
|
+
* Default to `{}`
|
|
55
|
+
*/
|
|
56
|
+
alias?: Record<string, string>;
|
|
57
|
+
/**
|
|
58
|
+
* the url prefix you wish to add
|
|
59
|
+
* Default to `''`
|
|
60
|
+
*/
|
|
61
|
+
prefix?: string;
|
|
62
|
+
/**
|
|
63
|
+
* filter files at init dir, for example - skip non build (source) files.
|
|
64
|
+
* If array set - allow only listed files
|
|
65
|
+
* Default to `undefined`
|
|
66
|
+
*/
|
|
67
|
+
filter?: FileFilter | string[];
|
|
68
|
+
/**
|
|
69
|
+
* dynamic load file which not cached on initialization
|
|
70
|
+
* Default to `false
|
|
71
|
+
*/
|
|
72
|
+
dynamic?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* caches the assets on initialization or not,
|
|
75
|
+
* always work together with `options.dynamic`
|
|
76
|
+
* Default to `true`
|
|
77
|
+
*/
|
|
78
|
+
preload?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* file store for caching
|
|
81
|
+
* Default to `undefined`
|
|
82
|
+
*/
|
|
83
|
+
files?: FileMap | FileStore;
|
|
84
|
+
}
|
|
85
|
+
type Next = () => Promise<void>;
|
|
86
|
+
export declare class FileManager {
|
|
87
|
+
store?: FileStore;
|
|
88
|
+
map?: FileMap;
|
|
89
|
+
constructor(store?: FileStore | FileMap);
|
|
90
|
+
get(key: string): unknown;
|
|
91
|
+
set(key: string, value: FileMeta): void;
|
|
92
|
+
}
|
|
93
|
+
type MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;
|
|
94
|
+
export declare function staticCache(): MiddlewareFunc;
|
|
95
|
+
export declare function staticCache(dir: string): MiddlewareFunc;
|
|
96
|
+
export declare function staticCache(options: Options): MiddlewareFunc;
|
|
97
|
+
export declare function staticCache(dir: string, options: Options): MiddlewareFunc;
|
|
98
|
+
export declare function staticCache(dir: string, options: Options, files: FileMap | FileStore): MiddlewareFunc;
|
|
99
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FileManager = void 0;
|
|
7
|
+
exports.staticCache = staticCache;
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const mime_types_1 = __importDefault(require("mime-types"));
|
|
15
|
+
const compressible_1 = __importDefault(require("compressible"));
|
|
16
|
+
const fs_readdir_recursive_1 = __importDefault(require("fs-readdir-recursive"));
|
|
17
|
+
const utility_1 = require("utility");
|
|
18
|
+
const debug = (0, node_util_1.debuglog)('@eggjs/koa-static-cache');
|
|
19
|
+
const gzip = (0, node_util_1.promisify)(node_zlib_1.default.gzip);
|
|
20
|
+
class FileManager {
|
|
21
|
+
store;
|
|
22
|
+
map;
|
|
23
|
+
constructor(store) {
|
|
24
|
+
if (store && typeof store.set === 'function' && typeof store.get === 'function') {
|
|
25
|
+
this.store = store;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.map = store || Object.create(null);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
return this.store ? this.store.get(key) : this.map[key];
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
if (this.store) {
|
|
36
|
+
return this.store.set(key, value);
|
|
37
|
+
}
|
|
38
|
+
this.map[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.FileManager = FileManager;
|
|
42
|
+
function staticCache(dirOrOptions, options = {}, filesStoreOrMap) {
|
|
43
|
+
let dir = '';
|
|
44
|
+
if (typeof dirOrOptions === 'string') {
|
|
45
|
+
// dir priority than options.dir
|
|
46
|
+
dir = dirOrOptions;
|
|
47
|
+
}
|
|
48
|
+
else if (dirOrOptions) {
|
|
49
|
+
options = dirOrOptions;
|
|
50
|
+
}
|
|
51
|
+
if (!dir && options.dir) {
|
|
52
|
+
dir = options.dir;
|
|
53
|
+
}
|
|
54
|
+
if (!dir) {
|
|
55
|
+
// default to process.cwd
|
|
56
|
+
dir = process.cwd();
|
|
57
|
+
}
|
|
58
|
+
dir = node_path_1.default.normalize(dir);
|
|
59
|
+
debug('staticCache dir: %s', dir);
|
|
60
|
+
// prefix must be ASCII code
|
|
61
|
+
options.prefix = (options.prefix ?? '').replace(/\/*$/, '/');
|
|
62
|
+
const files = new FileManager(filesStoreOrMap ?? options.files);
|
|
63
|
+
const enableGzip = !!options.gzip;
|
|
64
|
+
const filePrefix = node_path_1.default.normalize(options.prefix.replace(/^\//, ''));
|
|
65
|
+
// option.filter
|
|
66
|
+
let fileFilter = () => { return true; };
|
|
67
|
+
if (Array.isArray(options.filter)) {
|
|
68
|
+
fileFilter = (file) => {
|
|
69
|
+
return options.filter.includes(file);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (typeof options.filter === 'function') {
|
|
73
|
+
fileFilter = options.filter;
|
|
74
|
+
}
|
|
75
|
+
if (options.preload !== false) {
|
|
76
|
+
(0, fs_readdir_recursive_1.default)(dir).filter(fileFilter).forEach(name => {
|
|
77
|
+
loadFile(name, dir, options, files);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return async (ctx, next) => {
|
|
81
|
+
// only accept HEAD and GET
|
|
82
|
+
if (ctx.method !== 'HEAD' && ctx.method !== 'GET')
|
|
83
|
+
return await next();
|
|
84
|
+
// check prefix first to avoid calculate
|
|
85
|
+
if (!ctx.path.startsWith(options.prefix))
|
|
86
|
+
return await next();
|
|
87
|
+
// decode for `/%E4%B8%AD%E6%96%87`
|
|
88
|
+
// normalize for `//index`
|
|
89
|
+
let filename = node_path_1.default.normalize((0, utility_1.decodeURIComponent)(ctx.path));
|
|
90
|
+
// check alias
|
|
91
|
+
if (options.alias && options.alias[filename]) {
|
|
92
|
+
filename = options.alias[filename];
|
|
93
|
+
}
|
|
94
|
+
let file = files.get(filename);
|
|
95
|
+
// try to load file
|
|
96
|
+
if (!file) {
|
|
97
|
+
if (!options.dynamic)
|
|
98
|
+
return await next();
|
|
99
|
+
if (node_path_1.default.basename(filename)[0] === '.')
|
|
100
|
+
return await next();
|
|
101
|
+
if (filename.charAt(0) === node_path_1.default.sep) {
|
|
102
|
+
filename = filename.slice(1);
|
|
103
|
+
}
|
|
104
|
+
// trim prefix
|
|
105
|
+
if (options.prefix !== '/') {
|
|
106
|
+
if (filename.indexOf(filePrefix) !== 0) {
|
|
107
|
+
return await next();
|
|
108
|
+
}
|
|
109
|
+
filename = filename.slice(filePrefix.length);
|
|
110
|
+
}
|
|
111
|
+
const fullpath = node_path_1.default.join(dir, filename);
|
|
112
|
+
// files that can be accessed should be under options.dir
|
|
113
|
+
if (!fullpath.startsWith(dir)) {
|
|
114
|
+
return await next();
|
|
115
|
+
}
|
|
116
|
+
const stats = await (0, utility_1.exists)(fullpath);
|
|
117
|
+
if (!stats)
|
|
118
|
+
return await next();
|
|
119
|
+
if (!stats.isFile())
|
|
120
|
+
return await next();
|
|
121
|
+
file = loadFile(filename, dir, options, files);
|
|
122
|
+
}
|
|
123
|
+
ctx.status = 200;
|
|
124
|
+
if (enableGzip)
|
|
125
|
+
ctx.vary('Accept-Encoding');
|
|
126
|
+
if (!file.buffer) {
|
|
127
|
+
const stats = await promises_1.default.stat(file.path);
|
|
128
|
+
if (stats.mtime.getTime() !== file.mtime.getTime()) {
|
|
129
|
+
file.mtime = stats.mtime;
|
|
130
|
+
file.md5 = undefined;
|
|
131
|
+
file.length = stats.size;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ctx.response.lastModified = file.mtime;
|
|
135
|
+
if (file.md5) {
|
|
136
|
+
ctx.response.etag = file.md5;
|
|
137
|
+
}
|
|
138
|
+
if (ctx.fresh) {
|
|
139
|
+
ctx.status = 304;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
ctx.type = file.type;
|
|
143
|
+
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length;
|
|
144
|
+
ctx.set('cache-control', file.cacheControl ?? 'public, max-age=' + file.maxAge);
|
|
145
|
+
if (file.md5)
|
|
146
|
+
ctx.set('content-md5', file.md5);
|
|
147
|
+
if (ctx.method === 'HEAD') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';
|
|
151
|
+
if (file.zipBuffer) {
|
|
152
|
+
if (acceptGzip) {
|
|
153
|
+
ctx.set('content-encoding', 'gzip');
|
|
154
|
+
ctx.body = file.zipBuffer;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
ctx.body = file.buffer;
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const shouldGzip = enableGzip
|
|
162
|
+
&& file.length > 1024
|
|
163
|
+
&& acceptGzip
|
|
164
|
+
&& (0, compressible_1.default)(file.type);
|
|
165
|
+
if (file.buffer) {
|
|
166
|
+
if (shouldGzip) {
|
|
167
|
+
const gzFile = files.get(filename + '.gz');
|
|
168
|
+
if (options.usePrecompiledGzip && gzFile && gzFile.buffer) {
|
|
169
|
+
// if .gz file already read from disk
|
|
170
|
+
file.zipBuffer = gzFile.buffer;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
file.zipBuffer = await gzip(file.buffer);
|
|
174
|
+
}
|
|
175
|
+
ctx.set('content-encoding', 'gzip');
|
|
176
|
+
ctx.body = file.zipBuffer;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
ctx.body = file.buffer;
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const stream = (0, node_fs_1.createReadStream)(file.path);
|
|
184
|
+
// update file hash
|
|
185
|
+
if (!file.md5) {
|
|
186
|
+
const hash = node_crypto_1.default.createHash('md5');
|
|
187
|
+
stream.on('data', hash.update.bind(hash));
|
|
188
|
+
stream.on('end', () => {
|
|
189
|
+
file.md5 = hash.digest('base64');
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
ctx.body = stream;
|
|
193
|
+
// enable gzip will remove content length
|
|
194
|
+
if (shouldGzip) {
|
|
195
|
+
ctx.remove('content-length');
|
|
196
|
+
ctx.set('content-encoding', 'gzip');
|
|
197
|
+
ctx.body = stream.pipe(node_zlib_1.default.createGzip());
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* load file and add file content to cache
|
|
203
|
+
*/
|
|
204
|
+
function loadFile(name, dir, options, fileManager) {
|
|
205
|
+
const pathname = node_path_1.default.normalize(node_path_1.default.join(options.prefix, name));
|
|
206
|
+
if (!fileManager.get(pathname)) {
|
|
207
|
+
fileManager.set(pathname, {});
|
|
208
|
+
}
|
|
209
|
+
const obj = fileManager.get(pathname);
|
|
210
|
+
const filename = obj.path = node_path_1.default.join(dir, name);
|
|
211
|
+
const stats = (0, node_fs_1.statSync)(filename);
|
|
212
|
+
const buffer = (0, node_fs_1.readFileSync)(filename);
|
|
213
|
+
obj.cacheControl = options.cacheControl;
|
|
214
|
+
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0;
|
|
215
|
+
obj.type = obj.mime = mime_types_1.default.lookup(pathname) || 'application/octet-stream';
|
|
216
|
+
obj.mtime = stats.mtime;
|
|
217
|
+
obj.length = stats.size;
|
|
218
|
+
obj.md5 = node_crypto_1.default.createHash('md5').update(buffer).digest('base64');
|
|
219
|
+
debug('file: %s', JSON.stringify(obj, null, 2));
|
|
220
|
+
if (options.buffer) {
|
|
221
|
+
obj.buffer = buffer;
|
|
222
|
+
}
|
|
223
|
+
return obj;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBeUlBLGtDQWdMQztBQXpURCw4REFBaUM7QUFDakMseUNBQWdEO0FBQ2hELGdFQUFrQztBQUNsQyxxQ0FBbUU7QUFDbkUsMERBQTZCO0FBQzdCLDBEQUE2QjtBQUM3Qiw0REFBOEI7QUFDOUIsZ0VBQXdDO0FBQ3hDLGdGQUEyQztBQUMzQyxxQ0FBK0U7QUFFL0UsTUFBTSxLQUFLLEdBQUcsSUFBQSxvQkFBUSxFQUFDLHlCQUF5QixDQUFDLENBQUM7QUFFbEQsTUFBTSxJQUFJLEdBQUcsSUFBQSxxQkFBUyxFQUFDLG1CQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUE2RmxDLE1BQWEsV0FBVztJQUN0QixLQUFLLENBQWE7SUFDbEIsR0FBRyxDQUFXO0lBRWQsWUFBWSxLQUEyQjtRQUNyQyxJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssQ0FBQyxHQUFHLEtBQUssVUFBVSxJQUFJLE9BQU8sS0FBSyxDQUFDLEdBQUcsS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUNoRixJQUFJLENBQUMsS0FBSyxHQUFHLEtBQWtCLENBQUM7UUFDbEMsQ0FBQzthQUFNLENBQUM7WUFDTixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFDLENBQUM7SUFDSCxDQUFDO0lBRUQsR0FBRyxDQUFDLEdBQVc7UUFDYixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxHQUFHLENBQUMsR0FBVyxFQUFFLEtBQWU7UUFDOUIsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFF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
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type FileFilter = (path: string) => boolean;
|
|
2
|
+
export interface FileMeta {
|
|
3
|
+
maxAge?: number;
|
|
4
|
+
cacheControl?: string;
|
|
5
|
+
buffer?: Buffer;
|
|
6
|
+
zipBuffer?: Buffer;
|
|
7
|
+
type?: string;
|
|
8
|
+
mime?: string;
|
|
9
|
+
mtime?: Date;
|
|
10
|
+
path?: string;
|
|
11
|
+
md5?: string;
|
|
12
|
+
length?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FileMap {
|
|
15
|
+
[path: string]: FileMeta;
|
|
16
|
+
}
|
|
17
|
+
export interface FileStore {
|
|
18
|
+
get(key: string): unknown;
|
|
19
|
+
set(key: string, value: unknown): void;
|
|
20
|
+
}
|
|
21
|
+
export interface Options {
|
|
22
|
+
/**
|
|
23
|
+
* The root directory from which to serve static assets
|
|
24
|
+
* Default to `process.cwd`
|
|
25
|
+
*/
|
|
26
|
+
dir?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The max age for cache control
|
|
29
|
+
* Default to `0`
|
|
30
|
+
*/
|
|
31
|
+
maxAge?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The cache control header for static files
|
|
34
|
+
* Default to `undefined`
|
|
35
|
+
* Overrides `options.maxAge`
|
|
36
|
+
*/
|
|
37
|
+
cacheControl?: string;
|
|
38
|
+
/**
|
|
39
|
+
* store the files in memory instead of streaming from the filesystem on each request
|
|
40
|
+
*/
|
|
41
|
+
buffer?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* when request's accept-encoding include gzip, files will compressed by gzip
|
|
44
|
+
* Default to `false`
|
|
45
|
+
*/
|
|
46
|
+
gzip?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* try use gzip files, loaded from disk, like nginx gzip_static
|
|
49
|
+
* Default to `false`
|
|
50
|
+
*/
|
|
51
|
+
usePrecompiledGzip?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* object map of aliases
|
|
54
|
+
* Default to `{}`
|
|
55
|
+
*/
|
|
56
|
+
alias?: Record<string, string>;
|
|
57
|
+
/**
|
|
58
|
+
* the url prefix you wish to add
|
|
59
|
+
* Default to `''`
|
|
60
|
+
*/
|
|
61
|
+
prefix?: string;
|
|
62
|
+
/**
|
|
63
|
+
* filter files at init dir, for example - skip non build (source) files.
|
|
64
|
+
* If array set - allow only listed files
|
|
65
|
+
* Default to `undefined`
|
|
66
|
+
*/
|
|
67
|
+
filter?: FileFilter | string[];
|
|
68
|
+
/**
|
|
69
|
+
* dynamic load file which not cached on initialization
|
|
70
|
+
* Default to `false
|
|
71
|
+
*/
|
|
72
|
+
dynamic?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* caches the assets on initialization or not,
|
|
75
|
+
* always work together with `options.dynamic`
|
|
76
|
+
* Default to `true`
|
|
77
|
+
*/
|
|
78
|
+
preload?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* file store for caching
|
|
81
|
+
* Default to `undefined`
|
|
82
|
+
*/
|
|
83
|
+
files?: FileMap | FileStore;
|
|
84
|
+
}
|
|
85
|
+
type Next = () => Promise<void>;
|
|
86
|
+
export declare class FileManager {
|
|
87
|
+
store?: FileStore;
|
|
88
|
+
map?: FileMap;
|
|
89
|
+
constructor(store?: FileStore | FileMap);
|
|
90
|
+
get(key: string): unknown;
|
|
91
|
+
set(key: string, value: FileMeta): void;
|
|
92
|
+
}
|
|
93
|
+
type MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;
|
|
94
|
+
export declare function staticCache(): MiddlewareFunc;
|
|
95
|
+
export declare function staticCache(dir: string): MiddlewareFunc;
|
|
96
|
+
export declare function staticCache(options: Options): MiddlewareFunc;
|
|
97
|
+
export declare function staticCache(dir: string, options: Options): MiddlewareFunc;
|
|
98
|
+
export declare function staticCache(dir: string, options: Options, files: FileMap | FileStore): MiddlewareFunc;
|
|
99
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { debuglog, promisify } from 'node:util';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createReadStream, statSync, readFileSync } from 'node:fs';
|
|
5
|
+
import zlib from 'node:zlib';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import mime from 'mime-types';
|
|
8
|
+
import compressible from 'compressible';
|
|
9
|
+
import readDir from 'fs-readdir-recursive';
|
|
10
|
+
import { exists, decodeURIComponent as safeDecodeURIComponent } from 'utility';
|
|
11
|
+
const debug = debuglog('@eggjs/koa-static-cache');
|
|
12
|
+
const gzip = promisify(zlib.gzip);
|
|
13
|
+
export class FileManager {
|
|
14
|
+
store;
|
|
15
|
+
map;
|
|
16
|
+
constructor(store) {
|
|
17
|
+
if (store && typeof store.set === 'function' && typeof store.get === 'function') {
|
|
18
|
+
this.store = store;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.map = store || Object.create(null);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
get(key) {
|
|
25
|
+
return this.store ? this.store.get(key) : this.map[key];
|
|
26
|
+
}
|
|
27
|
+
set(key, value) {
|
|
28
|
+
if (this.store) {
|
|
29
|
+
return this.store.set(key, value);
|
|
30
|
+
}
|
|
31
|
+
this.map[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function staticCache(dirOrOptions, options = {}, filesStoreOrMap) {
|
|
35
|
+
let dir = '';
|
|
36
|
+
if (typeof dirOrOptions === 'string') {
|
|
37
|
+
// dir priority than options.dir
|
|
38
|
+
dir = dirOrOptions;
|
|
39
|
+
}
|
|
40
|
+
else if (dirOrOptions) {
|
|
41
|
+
options = dirOrOptions;
|
|
42
|
+
}
|
|
43
|
+
if (!dir && options.dir) {
|
|
44
|
+
dir = options.dir;
|
|
45
|
+
}
|
|
46
|
+
if (!dir) {
|
|
47
|
+
// default to process.cwd
|
|
48
|
+
dir = process.cwd();
|
|
49
|
+
}
|
|
50
|
+
dir = path.normalize(dir);
|
|
51
|
+
debug('staticCache dir: %s', dir);
|
|
52
|
+
// prefix must be ASCII code
|
|
53
|
+
options.prefix = (options.prefix ?? '').replace(/\/*$/, '/');
|
|
54
|
+
const files = new FileManager(filesStoreOrMap ?? options.files);
|
|
55
|
+
const enableGzip = !!options.gzip;
|
|
56
|
+
const filePrefix = path.normalize(options.prefix.replace(/^\//, ''));
|
|
57
|
+
// option.filter
|
|
58
|
+
let fileFilter = () => { return true; };
|
|
59
|
+
if (Array.isArray(options.filter)) {
|
|
60
|
+
fileFilter = (file) => {
|
|
61
|
+
return options.filter.includes(file);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (typeof options.filter === 'function') {
|
|
65
|
+
fileFilter = options.filter;
|
|
66
|
+
}
|
|
67
|
+
if (options.preload !== false) {
|
|
68
|
+
readDir(dir).filter(fileFilter).forEach(name => {
|
|
69
|
+
loadFile(name, dir, options, files);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return async (ctx, next) => {
|
|
73
|
+
// only accept HEAD and GET
|
|
74
|
+
if (ctx.method !== 'HEAD' && ctx.method !== 'GET')
|
|
75
|
+
return await next();
|
|
76
|
+
// check prefix first to avoid calculate
|
|
77
|
+
if (!ctx.path.startsWith(options.prefix))
|
|
78
|
+
return await next();
|
|
79
|
+
// decode for `/%E4%B8%AD%E6%96%87`
|
|
80
|
+
// normalize for `//index`
|
|
81
|
+
let filename = path.normalize(safeDecodeURIComponent(ctx.path));
|
|
82
|
+
// check alias
|
|
83
|
+
if (options.alias && options.alias[filename]) {
|
|
84
|
+
filename = options.alias[filename];
|
|
85
|
+
}
|
|
86
|
+
let file = files.get(filename);
|
|
87
|
+
// try to load file
|
|
88
|
+
if (!file) {
|
|
89
|
+
if (!options.dynamic)
|
|
90
|
+
return await next();
|
|
91
|
+
if (path.basename(filename)[0] === '.')
|
|
92
|
+
return await next();
|
|
93
|
+
if (filename.charAt(0) === path.sep) {
|
|
94
|
+
filename = filename.slice(1);
|
|
95
|
+
}
|
|
96
|
+
// trim prefix
|
|
97
|
+
if (options.prefix !== '/') {
|
|
98
|
+
if (filename.indexOf(filePrefix) !== 0) {
|
|
99
|
+
return await next();
|
|
100
|
+
}
|
|
101
|
+
filename = filename.slice(filePrefix.length);
|
|
102
|
+
}
|
|
103
|
+
const fullpath = path.join(dir, filename);
|
|
104
|
+
// files that can be accessed should be under options.dir
|
|
105
|
+
if (!fullpath.startsWith(dir)) {
|
|
106
|
+
return await next();
|
|
107
|
+
}
|
|
108
|
+
const stats = await exists(fullpath);
|
|
109
|
+
if (!stats)
|
|
110
|
+
return await next();
|
|
111
|
+
if (!stats.isFile())
|
|
112
|
+
return await next();
|
|
113
|
+
file = loadFile(filename, dir, options, files);
|
|
114
|
+
}
|
|
115
|
+
ctx.status = 200;
|
|
116
|
+
if (enableGzip)
|
|
117
|
+
ctx.vary('Accept-Encoding');
|
|
118
|
+
if (!file.buffer) {
|
|
119
|
+
const stats = await fs.stat(file.path);
|
|
120
|
+
if (stats.mtime.getTime() !== file.mtime.getTime()) {
|
|
121
|
+
file.mtime = stats.mtime;
|
|
122
|
+
file.md5 = undefined;
|
|
123
|
+
file.length = stats.size;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
ctx.response.lastModified = file.mtime;
|
|
127
|
+
if (file.md5) {
|
|
128
|
+
ctx.response.etag = file.md5;
|
|
129
|
+
}
|
|
130
|
+
if (ctx.fresh) {
|
|
131
|
+
ctx.status = 304;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
ctx.type = file.type;
|
|
135
|
+
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length;
|
|
136
|
+
ctx.set('cache-control', file.cacheControl ?? 'public, max-age=' + file.maxAge);
|
|
137
|
+
if (file.md5)
|
|
138
|
+
ctx.set('content-md5', file.md5);
|
|
139
|
+
if (ctx.method === 'HEAD') {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';
|
|
143
|
+
if (file.zipBuffer) {
|
|
144
|
+
if (acceptGzip) {
|
|
145
|
+
ctx.set('content-encoding', 'gzip');
|
|
146
|
+
ctx.body = file.zipBuffer;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
ctx.body = file.buffer;
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const shouldGzip = enableGzip
|
|
154
|
+
&& file.length > 1024
|
|
155
|
+
&& acceptGzip
|
|
156
|
+
&& compressible(file.type);
|
|
157
|
+
if (file.buffer) {
|
|
158
|
+
if (shouldGzip) {
|
|
159
|
+
const gzFile = files.get(filename + '.gz');
|
|
160
|
+
if (options.usePrecompiledGzip && gzFile && gzFile.buffer) {
|
|
161
|
+
// if .gz file already read from disk
|
|
162
|
+
file.zipBuffer = gzFile.buffer;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
file.zipBuffer = await gzip(file.buffer);
|
|
166
|
+
}
|
|
167
|
+
ctx.set('content-encoding', 'gzip');
|
|
168
|
+
ctx.body = file.zipBuffer;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
ctx.body = file.buffer;
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const stream = createReadStream(file.path);
|
|
176
|
+
// update file hash
|
|
177
|
+
if (!file.md5) {
|
|
178
|
+
const hash = crypto.createHash('md5');
|
|
179
|
+
stream.on('data', hash.update.bind(hash));
|
|
180
|
+
stream.on('end', () => {
|
|
181
|
+
file.md5 = hash.digest('base64');
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
ctx.body = stream;
|
|
185
|
+
// enable gzip will remove content length
|
|
186
|
+
if (shouldGzip) {
|
|
187
|
+
ctx.remove('content-length');
|
|
188
|
+
ctx.set('content-encoding', 'gzip');
|
|
189
|
+
ctx.body = stream.pipe(zlib.createGzip());
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* load file and add file content to cache
|
|
195
|
+
*/
|
|
196
|
+
function loadFile(name, dir, options, fileManager) {
|
|
197
|
+
const pathname = path.normalize(path.join(options.prefix, name));
|
|
198
|
+
if (!fileManager.get(pathname)) {
|
|
199
|
+
fileManager.set(pathname, {});
|
|
200
|
+
}
|
|
201
|
+
const obj = fileManager.get(pathname);
|
|
202
|
+
const filename = obj.path = path.join(dir, name);
|
|
203
|
+
const stats = statSync(filename);
|
|
204
|
+
const buffer = readFileSync(filename);
|
|
205
|
+
obj.cacheControl = options.cacheControl;
|
|
206
|
+
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0;
|
|
207
|
+
obj.type = obj.mime = mime.lookup(pathname) || 'application/octet-stream';
|
|
208
|
+
obj.mtime = stats.mtime;
|
|
209
|
+
obj.length = stats.size;
|
|
210
|
+
obj.md5 = crypto.createHash('md5').update(buffer).digest('base64');
|
|
211
|
+
debug('file: %s', JSON.stringify(obj, null, 2));
|
|
212
|
+
if (options.buffer) {
|
|
213
|
+
obj.buffer = buffer;
|
|
214
|
+
}
|
|
215
|
+
return obj;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2hELE9BQU8sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2xDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxRQUFRLEVBQUUsWUFBWSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ25FLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUM3QixPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFDN0IsT0FBTyxJQUFJLE1BQU0sWUFBWSxDQUFDO0FBQzlCLE9BQU8sWUFBWSxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLE9BQU8sTUFBTSxzQkFBc0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsTUFBTSxFQUFFLGtCQUFrQixJQUFJLHNCQUFzQixFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRS9FLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO0FBRWxELE1BQU0sSUFBSSxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUE2RmxDLE1BQU0sT0FBTyxXQUFXO0lBQ3RCLEtBQUssQ0FBYTtJQUNsQixHQUFHLENBQVc7SUFFZCxZQUFZLEtBQTJCO1FBQ3JDLElBQUksS0FBSyxJQUFJLE9BQU8sS0FBSyxDQUFDLEdBQUcsS0FBSyxVQUFVLElBQUksT0FBTyxLQUFLLENBQUMsR0FBRyxLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQ2hGLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBa0IsQ0FBQztRQUNsQyxDQUFDO2FBQU0sQ0FBQztZQUNOLElBQUksQ0FBQyxHQUFHLEdBQUcsS0FBSyxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUMsQ0FBQztJQUNILENBQUM7SUFFRCxHQUFHLENBQUMsR0FBVztRQUNiLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDM0QsQ0FBQztJQUVELEdBQUcsQ0FBQyxHQUFXLEVBQUUsS0FBZTtRQUM5QixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3BDLENBQUM7UUFDRCxJQUFJLENBQUMsR0FBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssQ0FBQztJQUN6QixDQUFDO0NBQ0Y7QUFTRCxNQUFNLFVBQVUsV0FBVyxDQUN6QixZQUErQixFQUMvQixVQUFtQixFQUFFLEVBQ3JCLGVBQXFDO0lBRXJDLElBQUksR0FBRyxHQUFHLEVBQUUsQ0FBQztJQUNiLElBQUksT0FBTyxZQUFZLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDckMsZ0NBQWdDO1FBQ2hDLEdBQUcsR0FBRyxZQUFZLENBQUM7SUFDckIsQ0FBQztTQUFNLElBQUksWUFBWSxFQUFFLENBQUM7UUFDeEIsT0FBTyxHQUFHLFlBQVksQ0FBQztJQUN6QixDQUFDO0lBQ0QsSUFBSSxDQUFDLEdBQUcsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDeEIsR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUM7SUFDcEIsQ0FBQztJQUNELElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNULHlCQUF5QjtRQUN6QixHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3RCLENBQUM7SUFDRCxHQUFHLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMxQixLQUFLLENBQUMscUJBQXFCLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFFbEMsNEJBQTRCO0lBQzVCLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDN0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxXQUFXLENBQUMsZUFBZSxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNoRSxNQUFNLFVBQVUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztJQUNsQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBRXJFLGdCQUFnQjtJQUNoQixJQUFJLFVBQVUsR0FBZSxHQUFHLEVBQUUsR0FBRyxPQUFPLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwRCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDbEMsVUFBVSxHQUFHLENBQUMsSUFBWSxFQUFFLEVBQUU7WUFDNUIsT0FBUSxPQUFPLENBQUMsTUFBbUIsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDckQsQ0FBQyxDQUFDO0lBQ0osQ0FBQztJQUNELElBQUksT0FBTyxPQUFPLENBQUMsTUFBTSxLQUFLLFVBQVUsRUFBRSxDQUFDO1FBQ3pDLFVBQVUsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQzlCLENBQUM7SUFFRCxJQUFJLE9BQU8sQ0FBQyxPQUFPLEtBQUssS0FBSyxFQUFFLENBQUM7UUFDOUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDN0MsUUFBUSxDQUFDLElBQUksRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3RDLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELE9BQU8sS0FBSyxFQUFFLEdBQVEsRUFBRSxJQUFVLEVBQUUsRUFBRTtRQUNwQywyQkFBMkI7UUFDM0IsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLE1BQU0sSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEtBQUs7WUFBRSxPQUFPLE1BQU0sSUFBSSxFQUFFLENBQUM7UUFDdkUsd0NBQXdDO1FBQ3hDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO1lBQUUsT0FBTyxNQUFNLElBQUksRUFBRSxDQUFDO1FBRTlELG1DQUFtQztRQUNuQywwQkFBMEI7UUFDMUIsSUFBSSxRQUFRLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUVoRSxjQUFjO1FBQ2QsSUFBSSxPQUFPLENBQUMsS0FBSyxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztZQUM3QyxRQUFRLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNyQyxDQUFDO1FBRUQsSUFBSSxJQUFJLEdBQUcsS0FBSyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQWEsQ0FBQztRQUMzQyxtQkFBbUI7UUFDbkIsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPO2dCQUFFLE9BQU8sTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUMxQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRztnQkFBRSxPQUFPLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFDNUQsSUFBSSxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDcEMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDL0IsQ0FBQztZQUVELGNBQWM7WUFDZCxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUM7Z0JBQzNCLElBQUksUUFBUSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztvQkFDdkMsT0FBTyxNQUFNLElBQUksRUFBRSxDQUFDO2dCQUN0QixDQUFDO2dCQUNELFFBQVEsR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUMvQyxDQUFDO1lBRUQsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDMUMseURBQXlEO1lBQ3pELElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQzlCLE9BQU8sTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUN0QixDQUFDO1lBRUQsTUFBTSxLQUFLLEdBQUcsTUFBTSxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDckMsSUFBSSxDQUFDLEtBQUs7Z0JBQUUsT0FBTyxNQUFNLElBQUksRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFO2dCQUFFLE9BQU8sTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUV6QyxJQUFJLEdBQUcsUUFBUSxDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ2pELENBQUM7UUFFRCxHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztRQUVqQixJQUFJLFVBQVU7WUFBRSxHQUFHLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFFNUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNqQixNQUFNLEtBQUssR0FBRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUssQ0FBQyxDQUFDO1lBQ3hDLElBQUksS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsS0FBSyxJQUFJLENBQUMsS0FBTSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7Z0JBQ3BELElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQztnQkFDekIsSUFBSSxDQUFDLEdBQUcsR0FBRyxTQUFTLENBQUM7Z0JBQ3JCLElBQUksQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQztZQUMzQixDQUFDO1FBQ0gsQ0FBQztRQUVELEdBQUcsQ0FBQyxRQUFRLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDdkMsSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDYixHQUFHLENBQUMsUUFBUSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDO1FBQy9CLENBQUM7UUFFRCxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNkLEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO1lBQ2pCLE9BQU87UUFDVCxDQUFDO1FBRUQsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDO1FBQ3JCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFPLENBQUM7UUFDbkUsR0FBRyxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLFlBQVksSUFBSSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDaEYsSUFBSSxJQUFJLENBQUMsR0FBRztZQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUUvQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDMUIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLEtBQUssTUFBTSxDQUFDO1FBRTNELElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ25CLElBQUksVUFBVSxFQUFFLENBQUM7Z0JBQ2YsR0FBRyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsRUFBRSxNQUFNLENBQUMsQ0FBQztnQkFDcEMsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO1lBQzVCLENBQUM7aUJBQU0sQ0FBQztnQkFDTixHQUFHLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7WUFDekIsQ0FBQztZQUNELE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxVQUFVLEdBQUcsVUFBVTtlQUN4QixJQUFJLENBQUMsTUFBTyxHQUFHLElBQUk7ZUFDbkIsVUFBVTtlQUNWLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSyxDQUFDLENBQUM7UUFFOUIsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDaEIsSUFBSSxVQUFVLEVBQUUsQ0FBQztnQkFFZixNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQWEsQ0FBQztnQkFDdkQsSUFBSSxPQUFPLENBQUMsa0JBQWtCLElBQUksTUFBTSxJQUFJLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQztvQkFDMUQscUNBQXFDO29CQUNyQyxJQUFJLENBQUMsU0FBUyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7Z0JBQ2pDLENBQUM7cUJBQU0sQ0FBQztvQkFDTixJQUFJLENBQUMsU0FBUyxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDM0MsQ0FBQztnQkFDRCxHQUFHLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLE1BQU0sQ0FBQyxDQUFDO2dCQUNwQyxHQUFHLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7WUFDNUIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztZQUN6QixDQUFDO1lBQ0QsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLE1BQU0sR0FBRyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSyxDQUFDLENBQUM7UUFFNUMsbUJBQW1CO1FBQ25CLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDZCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3RDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDMUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFO2dCQUNwQixJQUFJLENBQUMsR0FBRyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDbkMsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsR0FBRyxDQUFDLElBQUksR0FBRyxNQUFNLENBQUM7UUFDbEIseUNBQXlDO1FBQ3pDLElBQUksVUFBVSxFQUFFLENBQUM7WUFDZixHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUM7WUFDN0IsR0FBRyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNwQyxHQUFHLENBQUMsSUFBSSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7UUFDNUMsQ0FBQztJQUNILENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRDs7R0FFRztBQUNILFNBQVMsUUFBUSxDQUFDLElBQVksRUFBRSxHQUFXLEVBQUUsT0FBZ0IsRUFBRSxXQUF3QjtJQUNyRixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ2xFLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7UUFDL0IsV0FBVyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDaEMsQ0FBQztJQUNELE1BQU0sR0FBRyxHQUFHLFdBQVcsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFhLENBQUM7SUFDbEQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNqRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakMsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRXRDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQztJQUN4QyxHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLElBQUksQ0FBQyxDQUFDO0lBQzNELEdBQUcsQ0FBQyxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLDBCQUEwQixDQUFDO0lBQzFFLEdBQUcsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQztJQUN4QixHQUFHLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUM7SUFDeEIsR0FBRyxDQUFDLEdBQUcsR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFFbkUsS0FBSyxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNoRCxJQUFJLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNuQixHQUFHLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztJQUN0QixDQUFDO0lBQ0QsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDIn0=
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eggjs/koa-static-cache",
|
|
3
|
+
"description": "Static cache middleware for koa",
|
|
4
|
+
"version": "6.0.0",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"koa",
|
|
10
|
+
"middleware",
|
|
11
|
+
"file",
|
|
12
|
+
"static",
|
|
13
|
+
"cache",
|
|
14
|
+
"gzip",
|
|
15
|
+
"sendfile"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/eggjs/koa-static-cache.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/eggjs/koa-static-cache/issues"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">= 18.19.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"compressible": "^2.0.18",
|
|
30
|
+
"fs-readdir-recursive": "^1.1.0",
|
|
31
|
+
"mime-types": "^2.1.35",
|
|
32
|
+
"utility": "^2.4.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@arethetypeswrong/cli": "^0.17.1",
|
|
36
|
+
"@eggjs/bin": "7",
|
|
37
|
+
"@eggjs/koa": "^2.20.6",
|
|
38
|
+
"@eggjs/supertest": "^8.1.1",
|
|
39
|
+
"@eggjs/tsconfig": "1",
|
|
40
|
+
"@types/compressible": "^2.0.2",
|
|
41
|
+
"@types/fs-readdir-recursive": "^1.1.3",
|
|
42
|
+
"@types/mime-types": "^2.1.4",
|
|
43
|
+
"@types/mocha": "10",
|
|
44
|
+
"@types/node": "22",
|
|
45
|
+
"eslint": "8",
|
|
46
|
+
"eslint-config-egg": "14",
|
|
47
|
+
"rimraf": "6",
|
|
48
|
+
"tshy": "3",
|
|
49
|
+
"tshy-after": "1",
|
|
50
|
+
"typescript": "5",
|
|
51
|
+
"ylru": "^2.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"lint": "eslint --cache src test --ext .ts",
|
|
55
|
+
"pretest": "npm run clean && npm run lint -- --fix",
|
|
56
|
+
"test": "egg-bin test",
|
|
57
|
+
"preci": "npm run clean && npm run lint",
|
|
58
|
+
"ci": "egg-bin cov",
|
|
59
|
+
"postci": "npm run prepublishOnly && npm run clean",
|
|
60
|
+
"clean": "rimraf dist",
|
|
61
|
+
"prepublishOnly": "tshy && tshy-after && attw --pack"
|
|
62
|
+
},
|
|
63
|
+
"type": "module",
|
|
64
|
+
"tshy": {
|
|
65
|
+
"exports": {
|
|
66
|
+
".": "./src/index.ts",
|
|
67
|
+
"./package.json": "./package.json"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"exports": {
|
|
71
|
+
".": {
|
|
72
|
+
"import": {
|
|
73
|
+
"types": "./dist/esm/index.d.ts",
|
|
74
|
+
"default": "./dist/esm/index.js"
|
|
75
|
+
},
|
|
76
|
+
"require": {
|
|
77
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
78
|
+
"default": "./dist/commonjs/index.js"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"./package.json": "./package.json"
|
|
82
|
+
},
|
|
83
|
+
"files": [
|
|
84
|
+
"dist",
|
|
85
|
+
"src"
|
|
86
|
+
],
|
|
87
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
88
|
+
"main": "./dist/commonjs/index.js",
|
|
89
|
+
"module": "./dist/esm/index.js"
|
|
90
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { debuglog, promisify } from 'node:util';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createReadStream, statSync, readFileSync } from 'node:fs';
|
|
5
|
+
import zlib from 'node:zlib';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import mime from 'mime-types';
|
|
8
|
+
import compressible from 'compressible';
|
|
9
|
+
import readDir from 'fs-readdir-recursive';
|
|
10
|
+
import { exists, decodeURIComponent as safeDecodeURIComponent } from 'utility';
|
|
11
|
+
|
|
12
|
+
const debug = debuglog('@eggjs/koa-static-cache');
|
|
13
|
+
|
|
14
|
+
const gzip = promisify(zlib.gzip);
|
|
15
|
+
|
|
16
|
+
export type FileFilter = (path: string) => boolean;
|
|
17
|
+
|
|
18
|
+
export interface FileMeta {
|
|
19
|
+
maxAge?: number;
|
|
20
|
+
cacheControl?: string;
|
|
21
|
+
buffer?: Buffer;
|
|
22
|
+
zipBuffer?: Buffer;
|
|
23
|
+
type?: string;
|
|
24
|
+
mime?: string;
|
|
25
|
+
mtime?: Date;
|
|
26
|
+
path?: string;
|
|
27
|
+
md5?: string;
|
|
28
|
+
length?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FileMap {
|
|
32
|
+
[path: string]: FileMeta;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FileStore {
|
|
36
|
+
get(key: string): unknown;
|
|
37
|
+
set(key: string, value: unknown): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Options {
|
|
41
|
+
/**
|
|
42
|
+
* The root directory from which to serve static assets
|
|
43
|
+
* Default to `process.cwd`
|
|
44
|
+
*/
|
|
45
|
+
dir?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The max age for cache control
|
|
48
|
+
* Default to `0`
|
|
49
|
+
*/
|
|
50
|
+
maxAge?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The cache control header for static files
|
|
53
|
+
* Default to `undefined`
|
|
54
|
+
* Overrides `options.maxAge`
|
|
55
|
+
*/
|
|
56
|
+
cacheControl?: string;
|
|
57
|
+
/**
|
|
58
|
+
* store the files in memory instead of streaming from the filesystem on each request
|
|
59
|
+
*/
|
|
60
|
+
buffer?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* when request's accept-encoding include gzip, files will compressed by gzip
|
|
63
|
+
* Default to `false`
|
|
64
|
+
*/
|
|
65
|
+
gzip?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* try use gzip files, loaded from disk, like nginx gzip_static
|
|
68
|
+
* Default to `false`
|
|
69
|
+
*/
|
|
70
|
+
usePrecompiledGzip?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* object map of aliases
|
|
73
|
+
* Default to `{}`
|
|
74
|
+
*/
|
|
75
|
+
alias?: Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* the url prefix you wish to add
|
|
78
|
+
* Default to `''`
|
|
79
|
+
*/
|
|
80
|
+
prefix?: string;
|
|
81
|
+
/**
|
|
82
|
+
* filter files at init dir, for example - skip non build (source) files.
|
|
83
|
+
* If array set - allow only listed files
|
|
84
|
+
* Default to `undefined`
|
|
85
|
+
*/
|
|
86
|
+
filter?: FileFilter | string[];
|
|
87
|
+
/**
|
|
88
|
+
* dynamic load file which not cached on initialization
|
|
89
|
+
* Default to `false
|
|
90
|
+
*/
|
|
91
|
+
dynamic?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* caches the assets on initialization or not,
|
|
94
|
+
* always work together with `options.dynamic`
|
|
95
|
+
* Default to `true`
|
|
96
|
+
*/
|
|
97
|
+
preload?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* file store for caching
|
|
100
|
+
* Default to `undefined`
|
|
101
|
+
*/
|
|
102
|
+
files?: FileMap | FileStore;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type Next = () => Promise<void>;
|
|
106
|
+
|
|
107
|
+
export class FileManager {
|
|
108
|
+
store?: FileStore;
|
|
109
|
+
map?: FileMap;
|
|
110
|
+
|
|
111
|
+
constructor(store?: FileStore | FileMap) {
|
|
112
|
+
if (store && typeof store.set === 'function' && typeof store.get === 'function') {
|
|
113
|
+
this.store = store as FileStore;
|
|
114
|
+
} else {
|
|
115
|
+
this.map = store || Object.create(null);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get(key: string) {
|
|
120
|
+
return this.store ? this.store.get(key) : this.map![key];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
set(key: string, value: FileMeta) {
|
|
124
|
+
if (this.store) {
|
|
125
|
+
return this.store.set(key, value);
|
|
126
|
+
}
|
|
127
|
+
this.map![key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;
|
|
132
|
+
|
|
133
|
+
export function staticCache(): MiddlewareFunc;
|
|
134
|
+
export function staticCache(dir: string): MiddlewareFunc;
|
|
135
|
+
export function staticCache(options: Options): MiddlewareFunc;
|
|
136
|
+
export function staticCache(dir: string, options: Options): MiddlewareFunc;
|
|
137
|
+
export function staticCache(dir: string, options: Options, files: FileMap | FileStore): MiddlewareFunc;
|
|
138
|
+
export function staticCache(
|
|
139
|
+
dirOrOptions?: string | Options,
|
|
140
|
+
options: Options = {},
|
|
141
|
+
filesStoreOrMap?: FileMap | FileStore,
|
|
142
|
+
): MiddlewareFunc {
|
|
143
|
+
let dir = '';
|
|
144
|
+
if (typeof dirOrOptions === 'string') {
|
|
145
|
+
// dir priority than options.dir
|
|
146
|
+
dir = dirOrOptions;
|
|
147
|
+
} else if (dirOrOptions) {
|
|
148
|
+
options = dirOrOptions;
|
|
149
|
+
}
|
|
150
|
+
if (!dir && options.dir) {
|
|
151
|
+
dir = options.dir;
|
|
152
|
+
}
|
|
153
|
+
if (!dir) {
|
|
154
|
+
// default to process.cwd
|
|
155
|
+
dir = process.cwd();
|
|
156
|
+
}
|
|
157
|
+
dir = path.normalize(dir);
|
|
158
|
+
debug('staticCache dir: %s', dir);
|
|
159
|
+
|
|
160
|
+
// prefix must be ASCII code
|
|
161
|
+
options.prefix = (options.prefix ?? '').replace(/\/*$/, '/');
|
|
162
|
+
const files = new FileManager(filesStoreOrMap ?? options.files);
|
|
163
|
+
const enableGzip = !!options.gzip;
|
|
164
|
+
const filePrefix = path.normalize(options.prefix.replace(/^\//, ''));
|
|
165
|
+
|
|
166
|
+
// option.filter
|
|
167
|
+
let fileFilter: FileFilter = () => { return true; };
|
|
168
|
+
if (Array.isArray(options.filter)) {
|
|
169
|
+
fileFilter = (file: string) => {
|
|
170
|
+
return (options.filter as string[]).includes(file);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (typeof options.filter === 'function') {
|
|
174
|
+
fileFilter = options.filter;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (options.preload !== false) {
|
|
178
|
+
readDir(dir).filter(fileFilter).forEach(name => {
|
|
179
|
+
loadFile(name, dir, options, files);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return async (ctx: any, next: Next) => {
|
|
184
|
+
// only accept HEAD and GET
|
|
185
|
+
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return await next();
|
|
186
|
+
// check prefix first to avoid calculate
|
|
187
|
+
if (!ctx.path.startsWith(options.prefix)) return await next();
|
|
188
|
+
|
|
189
|
+
// decode for `/%E4%B8%AD%E6%96%87`
|
|
190
|
+
// normalize for `//index`
|
|
191
|
+
let filename = path.normalize(safeDecodeURIComponent(ctx.path));
|
|
192
|
+
|
|
193
|
+
// check alias
|
|
194
|
+
if (options.alias && options.alias[filename]) {
|
|
195
|
+
filename = options.alias[filename];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let file = files.get(filename) as FileMeta;
|
|
199
|
+
// try to load file
|
|
200
|
+
if (!file) {
|
|
201
|
+
if (!options.dynamic) return await next();
|
|
202
|
+
if (path.basename(filename)[0] === '.') return await next();
|
|
203
|
+
if (filename.charAt(0) === path.sep) {
|
|
204
|
+
filename = filename.slice(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// trim prefix
|
|
208
|
+
if (options.prefix !== '/') {
|
|
209
|
+
if (filename.indexOf(filePrefix) !== 0) {
|
|
210
|
+
return await next();
|
|
211
|
+
}
|
|
212
|
+
filename = filename.slice(filePrefix.length);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const fullpath = path.join(dir, filename);
|
|
216
|
+
// files that can be accessed should be under options.dir
|
|
217
|
+
if (!fullpath.startsWith(dir)) {
|
|
218
|
+
return await next();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const stats = await exists(fullpath);
|
|
222
|
+
if (!stats) return await next();
|
|
223
|
+
if (!stats.isFile()) return await next();
|
|
224
|
+
|
|
225
|
+
file = loadFile(filename, dir, options, files);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ctx.status = 200;
|
|
229
|
+
|
|
230
|
+
if (enableGzip) ctx.vary('Accept-Encoding');
|
|
231
|
+
|
|
232
|
+
if (!file.buffer) {
|
|
233
|
+
const stats = await fs.stat(file.path!);
|
|
234
|
+
if (stats.mtime.getTime() !== file.mtime!.getTime()) {
|
|
235
|
+
file.mtime = stats.mtime;
|
|
236
|
+
file.md5 = undefined;
|
|
237
|
+
file.length = stats.size;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
ctx.response.lastModified = file.mtime;
|
|
242
|
+
if (file.md5) {
|
|
243
|
+
ctx.response.etag = file.md5;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (ctx.fresh) {
|
|
247
|
+
ctx.status = 304;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
ctx.type = file.type;
|
|
252
|
+
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length!;
|
|
253
|
+
ctx.set('cache-control', file.cacheControl ?? 'public, max-age=' + file.maxAge);
|
|
254
|
+
if (file.md5) ctx.set('content-md5', file.md5);
|
|
255
|
+
|
|
256
|
+
if (ctx.method === 'HEAD') {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';
|
|
261
|
+
|
|
262
|
+
if (file.zipBuffer) {
|
|
263
|
+
if (acceptGzip) {
|
|
264
|
+
ctx.set('content-encoding', 'gzip');
|
|
265
|
+
ctx.body = file.zipBuffer;
|
|
266
|
+
} else {
|
|
267
|
+
ctx.body = file.buffer;
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const shouldGzip = enableGzip
|
|
273
|
+
&& file.length! > 1024
|
|
274
|
+
&& acceptGzip
|
|
275
|
+
&& compressible(file.type!);
|
|
276
|
+
|
|
277
|
+
if (file.buffer) {
|
|
278
|
+
if (shouldGzip) {
|
|
279
|
+
|
|
280
|
+
const gzFile = files.get(filename + '.gz') as FileMeta;
|
|
281
|
+
if (options.usePrecompiledGzip && gzFile && gzFile.buffer) {
|
|
282
|
+
// if .gz file already read from disk
|
|
283
|
+
file.zipBuffer = gzFile.buffer;
|
|
284
|
+
} else {
|
|
285
|
+
file.zipBuffer = await gzip(file.buffer);
|
|
286
|
+
}
|
|
287
|
+
ctx.set('content-encoding', 'gzip');
|
|
288
|
+
ctx.body = file.zipBuffer;
|
|
289
|
+
} else {
|
|
290
|
+
ctx.body = file.buffer;
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const stream = createReadStream(file.path!);
|
|
296
|
+
|
|
297
|
+
// update file hash
|
|
298
|
+
if (!file.md5) {
|
|
299
|
+
const hash = crypto.createHash('md5');
|
|
300
|
+
stream.on('data', hash.update.bind(hash));
|
|
301
|
+
stream.on('end', () => {
|
|
302
|
+
file.md5 = hash.digest('base64');
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ctx.body = stream;
|
|
307
|
+
// enable gzip will remove content length
|
|
308
|
+
if (shouldGzip) {
|
|
309
|
+
ctx.remove('content-length');
|
|
310
|
+
ctx.set('content-encoding', 'gzip');
|
|
311
|
+
ctx.body = stream.pipe(zlib.createGzip());
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* load file and add file content to cache
|
|
318
|
+
*/
|
|
319
|
+
function loadFile(name: string, dir: string, options: Options, fileManager: FileManager) {
|
|
320
|
+
const pathname = path.normalize(path.join(options.prefix!, name));
|
|
321
|
+
if (!fileManager.get(pathname)) {
|
|
322
|
+
fileManager.set(pathname, {});
|
|
323
|
+
}
|
|
324
|
+
const obj = fileManager.get(pathname) as FileMeta;
|
|
325
|
+
const filename = obj.path = path.join(dir, name);
|
|
326
|
+
const stats = statSync(filename);
|
|
327
|
+
const buffer = readFileSync(filename);
|
|
328
|
+
|
|
329
|
+
obj.cacheControl = options.cacheControl;
|
|
330
|
+
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0;
|
|
331
|
+
obj.type = obj.mime = mime.lookup(pathname) || 'application/octet-stream';
|
|
332
|
+
obj.mtime = stats.mtime;
|
|
333
|
+
obj.length = stats.size;
|
|
334
|
+
obj.md5 = crypto.createHash('md5').update(buffer).digest('base64');
|
|
335
|
+
|
|
336
|
+
debug('file: %s', JSON.stringify(obj, null, 2));
|
|
337
|
+
if (options.buffer) {
|
|
338
|
+
obj.buffer = buffer;
|
|
339
|
+
}
|
|
340
|
+
return obj;
|
|
341
|
+
}
|