koa-files 4.1.7 → 5.0.1
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/README.md +91 -7
- package/cjs/ReadStream.cjs +66 -62
- package/cjs/ReadStream.d.cts +3 -45
- package/cjs/Service.cjs +25 -22
- package/cjs/Service.d.cts +2 -12
- package/cjs/index.cjs +3 -2
- package/cjs/utils/fs.cjs +2 -1
- package/cjs/utils/fs.d.cts +18 -5
- package/cjs/utils/hash.cjs +4 -3
- package/cjs/utils/http.cjs +8 -5
- package/cjs/utils/http.d.cts +2 -2
- package/cjs/utils/path.cjs +6 -5
- package/cjs/utils/type.cjs +2 -1
- package/esm/ReadStream.d.ts +3 -45
- package/esm/ReadStream.js +64 -60
- package/esm/Service.d.ts +2 -12
- package/esm/Service.js +17 -14
- package/esm/index.js +3 -2
- package/esm/utils/fs.d.ts +18 -5
- package/esm/utils/fs.js +2 -1
- package/esm/utils/hash.js +3 -2
- package/esm/utils/http.d.ts +2 -2
- package/esm/utils/http.js +7 -4
- package/esm/utils/path.js +3 -2
- package/esm/utils/type.js +2 -1
- package/package.json +13 -14
package/README.md
CHANGED
|
@@ -9,17 +9,38 @@
|
|
|
9
9
|
> ![Node Version][node-image]
|
|
10
10
|
> [![License][license-image]][license-url]
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
$ npm install koa-files
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import Koa from 'koa';
|
|
22
|
+
import { server } from 'koa-files';
|
|
23
|
+
|
|
24
|
+
const app = new Koa();
|
|
25
|
+
|
|
26
|
+
app.use(
|
|
27
|
+
server('public', {
|
|
28
|
+
headers: {
|
|
29
|
+
'Cache-Control': 'public, max-age=31536000, immutable'
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
app.listen(3000);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then visit `http://127.0.0.1:3000/app.js` to serve `public/app.js`.
|
|
38
|
+
|
|
18
39
|
## API
|
|
19
40
|
|
|
20
41
|
```ts
|
|
21
42
|
import { Middleware } from 'koa';
|
|
22
|
-
import
|
|
43
|
+
import { PathLike, Stats } from 'node:fs';
|
|
23
44
|
|
|
24
45
|
interface Headers {
|
|
25
46
|
[key: string]: string | string[];
|
|
@@ -38,10 +59,21 @@ interface HeadersFunction {
|
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
export interface FileSystem {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
close(fd: number, callback?: (error?: Error | null) => void): void;
|
|
63
|
+
read<T extends ArrayBufferView>(
|
|
64
|
+
fd: number,
|
|
65
|
+
buffer: T,
|
|
66
|
+
offset: number,
|
|
67
|
+
length: number,
|
|
68
|
+
position: number | bigint | null,
|
|
69
|
+
callback: (error: Error | null | undefined, bytesRead: number, buffer: T) => void
|
|
70
|
+
): void;
|
|
71
|
+
stat(path: PathLike, callback: (error: Error | null | undefined, stats: Stats) => void): void;
|
|
72
|
+
open(
|
|
73
|
+
path: PathLike,
|
|
74
|
+
flags: string | number | undefined,
|
|
75
|
+
callback: (error: Error | null | undefined, fd: number) => void
|
|
76
|
+
): void;
|
|
45
77
|
}
|
|
46
78
|
|
|
47
79
|
export interface Options {
|
|
@@ -62,6 +94,7 @@ export function server(root: string, options?: Options): Middleware;
|
|
|
62
94
|
|
|
63
95
|
- Root directory string.
|
|
64
96
|
- Nothing above this root directory can be served.
|
|
97
|
+
- `root` is resolved with `path.resolve`, so relative paths are based on the process working directory.
|
|
65
98
|
|
|
66
99
|
### Options
|
|
67
100
|
|
|
@@ -75,6 +108,7 @@ export function server(root: string, options?: Options): Middleware;
|
|
|
75
108
|
- Defaults to `false`.
|
|
76
109
|
- If true, serves after `await next()`.
|
|
77
110
|
- Allowing any downstream middleware to respond first.
|
|
111
|
+
- Useful when you want route handlers to take priority over static files.
|
|
78
112
|
|
|
79
113
|
##### `etag`
|
|
80
114
|
|
|
@@ -101,17 +135,28 @@ export function server(root: string, options?: Options): Middleware;
|
|
|
101
135
|
|
|
102
136
|
- Defaults to `65536` (64 KiB).
|
|
103
137
|
- Set the high water mark for the read stream.
|
|
138
|
+
- Supports function form to customize by file path and stats.
|
|
104
139
|
|
|
105
140
|
##### `ignore`
|
|
106
141
|
|
|
107
142
|
- Defaults to `undefined`.
|
|
108
143
|
- Function that determines if a file should be ignored.
|
|
144
|
+
- Return `true` to skip static serving for the current file.
|
|
109
145
|
|
|
110
146
|
##### `headers`
|
|
111
147
|
|
|
112
148
|
- Defaults to `undefined`.
|
|
113
149
|
- Set headers to be sent.
|
|
114
150
|
- See docs: [Headers in MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
|
|
151
|
+
- Supports function form to compute headers dynamically.
|
|
152
|
+
|
|
153
|
+
### Behavior notes
|
|
154
|
+
|
|
155
|
+
- Only `GET` and `HEAD` requests are handled.
|
|
156
|
+
- `Range` requests are supported (including multipart range responses).
|
|
157
|
+
- Conditional requests are supported via `ETag` and `Last-Modified`.
|
|
158
|
+
- Requests containing invalid paths (e.g. null bytes) respond with `400`.
|
|
159
|
+
- Files outside `root` are never served.
|
|
115
160
|
|
|
116
161
|
## Example
|
|
117
162
|
|
|
@@ -157,9 +202,48 @@ app.listen(port, () => {
|
|
|
157
202
|
});
|
|
158
203
|
```
|
|
159
204
|
|
|
205
|
+
## More examples
|
|
206
|
+
|
|
207
|
+
### Prefer application routes first (`defer: true`)
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
app.use(async (ctx, next) => {
|
|
211
|
+
if (ctx.path === '/healthz') {
|
|
212
|
+
ctx.body = 'ok';
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await next();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
app.use(server('public', { defer: true }));
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Dynamic ignore rule
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
app.use(
|
|
226
|
+
server('public', {
|
|
227
|
+
ignore: path => path.endsWith('.map')
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Dynamic stream buffer
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
app.use(
|
|
236
|
+
server('public', {
|
|
237
|
+
highWaterMark: (path, stats) => (stats.size > 10 * 1024 * 1024 ? 256 * 1024 : 64 * 1024)
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
160
242
|
## Features
|
|
161
243
|
|
|
162
|
-
|
|
244
|
+
- Static file middleware for Koa.
|
|
245
|
+
- Supports multipart range and download resumption.
|
|
246
|
+
- Supports conditional requests (`ETag`, `Last-Modified`).
|
|
163
247
|
|
|
164
248
|
## License
|
|
165
249
|
|
package/cjs/ReadStream.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.1
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,14 +10,22 @@
|
|
|
9
10
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const node_buffer = require('node:buffer');
|
|
14
|
+
const node_stream = require('node:stream');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @module ReadStream
|
|
17
18
|
*/
|
|
18
19
|
const DISPOSE_EVENT = Symbol('dispose');
|
|
19
|
-
class ReadStream extends
|
|
20
|
+
class ReadStream extends node_stream.Readable {
|
|
21
|
+
#path;
|
|
22
|
+
#fs;
|
|
23
|
+
#ranges;
|
|
24
|
+
#bytesRead = 0;
|
|
25
|
+
#fd = null;
|
|
26
|
+
#reading = false;
|
|
27
|
+
#currentRangeIndex = 0;
|
|
28
|
+
#readState = 0 /* ReadState.PREFIX */;
|
|
20
29
|
/**
|
|
21
30
|
* @constructor
|
|
22
31
|
* @param path The file path.
|
|
@@ -26,14 +35,9 @@ class ReadStream extends stream.Readable {
|
|
|
26
35
|
constructor(path, ranges, options) {
|
|
27
36
|
const { fs, highWaterMark } = options;
|
|
28
37
|
super({ highWaterMark });
|
|
29
|
-
this
|
|
30
|
-
this
|
|
31
|
-
this
|
|
32
|
-
this.currentRangeIndex = 0;
|
|
33
|
-
this.readState = 0 /* ReadState.PREFIX */;
|
|
34
|
-
this.fs = fs;
|
|
35
|
-
this.path = path;
|
|
36
|
-
this.ranges = ranges;
|
|
38
|
+
this.#fs = fs;
|
|
39
|
+
this.#path = path;
|
|
40
|
+
this.#ranges = ranges;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* @override
|
|
@@ -41,27 +45,27 @@ class ReadStream extends stream.Readable {
|
|
|
41
45
|
* @param callback The callback.
|
|
42
46
|
*/
|
|
43
47
|
_construct(callback) {
|
|
44
|
-
this
|
|
48
|
+
this.#fs.open(this.#path, 'r', (openError, fd) => {
|
|
45
49
|
if (openError == null) {
|
|
46
|
-
this
|
|
50
|
+
this.#fd = fd;
|
|
47
51
|
}
|
|
48
52
|
callback(openError);
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
56
|
* @private
|
|
53
|
-
* @method getRange
|
|
57
|
+
* @method #getRange
|
|
54
58
|
*/
|
|
55
|
-
getRange() {
|
|
56
|
-
return this
|
|
59
|
+
#getRange() {
|
|
60
|
+
return this.#ranges[this.#currentRangeIndex];
|
|
57
61
|
}
|
|
58
62
|
/**
|
|
59
63
|
* @private
|
|
60
|
-
* @method getPadding
|
|
64
|
+
* @method #getPadding
|
|
61
65
|
* @param range The current range.
|
|
62
66
|
*/
|
|
63
|
-
getPadding(range) {
|
|
64
|
-
switch (this
|
|
67
|
+
#getPadding(range) {
|
|
68
|
+
switch (this.#readState) {
|
|
65
69
|
case 0 /* ReadState.PREFIX */:
|
|
66
70
|
return range.prefix;
|
|
67
71
|
case 2 /* ReadState.SUFFIX */:
|
|
@@ -70,42 +74,42 @@ class ReadStream extends stream.Readable {
|
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* @private
|
|
73
|
-
* @method readPadding
|
|
77
|
+
* @method #readPadding
|
|
74
78
|
* @param fd The file descriptor.
|
|
75
79
|
* @param range The current range.
|
|
76
80
|
* @param size The number of bytes to read.
|
|
77
81
|
*/
|
|
78
|
-
readPadding(fd, range, size) {
|
|
82
|
+
#readPadding(fd, range, size) {
|
|
79
83
|
let bytesRead = 0;
|
|
80
|
-
const padding = this
|
|
84
|
+
const padding = this.#getPadding(range);
|
|
81
85
|
// If padding exists.
|
|
82
86
|
if (padding != null) {
|
|
83
87
|
const { length } = padding;
|
|
84
|
-
const begin = this
|
|
88
|
+
const begin = this.#bytesRead;
|
|
85
89
|
if (begin < length) {
|
|
86
90
|
bytesRead = Math.min(size, length - begin);
|
|
87
91
|
const end = begin + bytesRead;
|
|
88
92
|
this.push(padding.subarray(begin, end));
|
|
89
|
-
this
|
|
93
|
+
this.#bytesRead = end;
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
// If no padding or read completed.
|
|
93
97
|
if (bytesRead < size) {
|
|
94
|
-
this
|
|
98
|
+
this.#bytesRead = 0;
|
|
95
99
|
// Change read state.
|
|
96
|
-
switch (this
|
|
100
|
+
switch (this.#readState) {
|
|
97
101
|
case 0 /* ReadState.PREFIX */:
|
|
98
|
-
this
|
|
99
|
-
this
|
|
102
|
+
this.#readState = 1 /* ReadState.RANGE */;
|
|
103
|
+
this.#readFileRange(fd, range, size - bytesRead);
|
|
100
104
|
break;
|
|
101
105
|
case 2 /* ReadState.SUFFIX */:
|
|
102
|
-
this
|
|
103
|
-
this
|
|
104
|
-
const nextRange = this
|
|
106
|
+
this.#currentRangeIndex++;
|
|
107
|
+
this.#readState = 0 /* ReadState.PREFIX */;
|
|
108
|
+
const nextRange = this.#getRange();
|
|
105
109
|
if (nextRange == null) {
|
|
106
110
|
this.push(null);
|
|
107
111
|
} else {
|
|
108
|
-
this
|
|
112
|
+
this.#readPadding(fd, nextRange, size - bytesRead);
|
|
109
113
|
}
|
|
110
114
|
break;
|
|
111
115
|
}
|
|
@@ -113,23 +117,23 @@ class ReadStream extends stream.Readable {
|
|
|
113
117
|
}
|
|
114
118
|
/**
|
|
115
119
|
* @private
|
|
116
|
-
* @method readFileRange
|
|
120
|
+
* @method #readFileRange
|
|
117
121
|
* @param fd The file descriptor.
|
|
118
122
|
* @param range The current range.
|
|
119
123
|
* @param size The number of bytes to read.
|
|
120
124
|
*/
|
|
121
|
-
readFileRange(fd, range, size) {
|
|
125
|
+
#readFileRange(fd, range, size) {
|
|
122
126
|
const { length } = range;
|
|
123
|
-
const
|
|
127
|
+
const bytesRead = this.#bytesRead;
|
|
124
128
|
// File range not finished.
|
|
125
129
|
if (bytesRead < length) {
|
|
126
|
-
this
|
|
130
|
+
this.#reading = true;
|
|
127
131
|
const bytesToRead = Math.min(size, length - bytesRead);
|
|
128
|
-
const buffer
|
|
132
|
+
const buffer = node_buffer.Buffer.allocUnsafeSlow(bytesToRead);
|
|
129
133
|
const position = range.offset + bytesRead;
|
|
130
134
|
// Read file range.
|
|
131
|
-
this
|
|
132
|
-
this
|
|
135
|
+
this.#fs.read(fd, buffer, 0, bytesToRead, position, (readError, bytesRead, buffer) => {
|
|
136
|
+
this.#reading = false;
|
|
133
137
|
// Tell ._destroy() that it's safe to close the fd now.
|
|
134
138
|
if (this.destroyed) {
|
|
135
139
|
this.emit(DISPOSE_EVENT, readError);
|
|
@@ -139,19 +143,19 @@ class ReadStream extends stream.Readable {
|
|
|
139
143
|
this.destroy(new RangeError('invalid read operation'));
|
|
140
144
|
} else {
|
|
141
145
|
this.push(buffer);
|
|
142
|
-
this
|
|
146
|
+
this.#bytesRead += bytesRead;
|
|
143
147
|
// file range read completed.
|
|
144
148
|
if (bytesRead < size) {
|
|
145
|
-
this
|
|
146
|
-
this
|
|
147
|
-
this
|
|
149
|
+
this.#bytesRead = 0;
|
|
150
|
+
this.#readState = 2 /* ReadState.SUFFIX */;
|
|
151
|
+
this.#readPadding(fd, range, size - bytesRead);
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
});
|
|
151
155
|
} else {
|
|
152
|
-
this
|
|
153
|
-
this
|
|
154
|
-
this
|
|
156
|
+
this.#bytesRead = 0;
|
|
157
|
+
this.#readState = 2 /* ReadState.SUFFIX */;
|
|
158
|
+
this.#readPadding(fd, range, size);
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
/**
|
|
@@ -160,21 +164,21 @@ class ReadStream extends stream.Readable {
|
|
|
160
164
|
* @param size The number of bytes to read.
|
|
161
165
|
*/
|
|
162
166
|
_read(size) {
|
|
163
|
-
if (!this
|
|
164
|
-
const
|
|
165
|
-
const range = this
|
|
167
|
+
if (!this.#reading) {
|
|
168
|
+
const fd = this.#fd;
|
|
169
|
+
const range = this.#getRange();
|
|
166
170
|
// If fd or range is null, finish stream.
|
|
167
171
|
if (fd == null || range == null) {
|
|
168
172
|
this.push(null);
|
|
169
173
|
} else {
|
|
170
174
|
// Read bytes from range.
|
|
171
|
-
switch (this
|
|
175
|
+
switch (this.#readState) {
|
|
172
176
|
case 0 /* ReadState.PREFIX */:
|
|
173
177
|
case 2 /* ReadState.SUFFIX */:
|
|
174
|
-
this
|
|
178
|
+
this.#readPadding(fd, range, size);
|
|
175
179
|
break;
|
|
176
180
|
case 1 /* ReadState.RANGE */:
|
|
177
|
-
this
|
|
181
|
+
this.#readFileRange(fd, range, size);
|
|
178
182
|
break;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
@@ -182,17 +186,17 @@ class ReadStream extends stream.Readable {
|
|
|
182
186
|
}
|
|
183
187
|
/**
|
|
184
188
|
* @private
|
|
185
|
-
* @method dispose
|
|
189
|
+
* @method #dispose
|
|
186
190
|
* @param error The error.
|
|
187
191
|
* @param callback The callback.
|
|
188
192
|
*/
|
|
189
|
-
dispose(error, callback) {
|
|
190
|
-
const
|
|
193
|
+
#dispose(error, callback) {
|
|
194
|
+
const fd = this.#fd;
|
|
191
195
|
if (fd != null) {
|
|
192
|
-
this
|
|
196
|
+
this.#fd = null;
|
|
193
197
|
// Close the fd.
|
|
194
|
-
this
|
|
195
|
-
callback(error
|
|
198
|
+
this.#fs.close(fd, closeError => {
|
|
199
|
+
callback(error ?? closeError);
|
|
196
200
|
});
|
|
197
201
|
} else {
|
|
198
202
|
callback(error);
|
|
@@ -206,12 +210,12 @@ class ReadStream extends stream.Readable {
|
|
|
206
210
|
*/
|
|
207
211
|
_destroy(error, callback) {
|
|
208
212
|
// Wait I/O completion.
|
|
209
|
-
if (this
|
|
213
|
+
if (this.#reading) {
|
|
210
214
|
this.once(DISPOSE_EVENT, disposeError => {
|
|
211
|
-
this
|
|
215
|
+
this.#dispose(error ?? disposeError, callback);
|
|
212
216
|
});
|
|
213
217
|
} else {
|
|
214
|
-
this
|
|
218
|
+
this.#dispose(error, callback);
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
}
|
package/cjs/ReadStream.d.cts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ReadStream
|
|
3
3
|
*/
|
|
4
|
-
import { PathLike } from 'fs';
|
|
5
|
-
import { Readable } from 'stream';
|
|
6
4
|
import { Range } from './utils/http.cjs';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
7
6
|
import { FileSystem } from './utils/fs.cjs';
|
|
8
7
|
export interface Options {
|
|
9
8
|
fs: FileSystem;
|
|
@@ -13,67 +12,26 @@ interface Callback {
|
|
|
13
12
|
(error?: Error | null): void;
|
|
14
13
|
}
|
|
15
14
|
export declare class ReadStream extends Readable {
|
|
16
|
-
private
|
|
17
|
-
private readonly path;
|
|
18
|
-
private readonly ranges;
|
|
19
|
-
private bytesRead;
|
|
20
|
-
private fd;
|
|
21
|
-
private reading;
|
|
22
|
-
private currentRangeIndex;
|
|
23
|
-
private readState;
|
|
15
|
+
#private;
|
|
24
16
|
/**
|
|
25
17
|
* @constructor
|
|
26
18
|
* @param path The file path.
|
|
27
19
|
* @param ranges The ranges to read.
|
|
28
20
|
* @param options The stream options.
|
|
29
21
|
*/
|
|
30
|
-
constructor(path:
|
|
22
|
+
constructor(path: string, ranges: Range[], options: Options);
|
|
31
23
|
/**
|
|
32
24
|
* @override
|
|
33
25
|
* @method _construct
|
|
34
26
|
* @param callback The callback.
|
|
35
27
|
*/
|
|
36
28
|
_construct(callback: Callback): void;
|
|
37
|
-
/**
|
|
38
|
-
* @private
|
|
39
|
-
* @method getRange
|
|
40
|
-
*/
|
|
41
|
-
private getRange;
|
|
42
|
-
/**
|
|
43
|
-
* @private
|
|
44
|
-
* @method getPadding
|
|
45
|
-
* @param range The current range.
|
|
46
|
-
*/
|
|
47
|
-
private getPadding;
|
|
48
|
-
/**
|
|
49
|
-
* @private
|
|
50
|
-
* @method readPadding
|
|
51
|
-
* @param fd The file descriptor.
|
|
52
|
-
* @param range The current range.
|
|
53
|
-
* @param size The number of bytes to read.
|
|
54
|
-
*/
|
|
55
|
-
private readPadding;
|
|
56
|
-
/**
|
|
57
|
-
* @private
|
|
58
|
-
* @method readFileRange
|
|
59
|
-
* @param fd The file descriptor.
|
|
60
|
-
* @param range The current range.
|
|
61
|
-
* @param size The number of bytes to read.
|
|
62
|
-
*/
|
|
63
|
-
private readFileRange;
|
|
64
29
|
/**
|
|
65
30
|
* @override
|
|
66
31
|
* @method _read
|
|
67
32
|
* @param size The number of bytes to read.
|
|
68
33
|
*/
|
|
69
34
|
_read(size: number): void;
|
|
70
|
-
/**
|
|
71
|
-
* @private
|
|
72
|
-
* @method dispose
|
|
73
|
-
* @param error The error.
|
|
74
|
-
* @param callback The callback.
|
|
75
|
-
*/
|
|
76
|
-
private dispose;
|
|
77
35
|
/**
|
|
78
36
|
* @override
|
|
79
37
|
* @method _destroy
|
package/cjs/Service.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.1
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -10,11 +11,11 @@
|
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
13
|
const createETag = require('etag');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
+
const fs = require('node:fs');
|
|
14
15
|
const ReadStream = require('./ReadStream.cjs');
|
|
15
16
|
const type = require('./utils/type.cjs');
|
|
16
17
|
const fs$1 = require('./utils/fs.cjs');
|
|
17
|
-
const
|
|
18
|
+
const node_path = require('node:path');
|
|
18
19
|
const path = require('./utils/path.cjs');
|
|
19
20
|
const http = require('./utils/http.cjs');
|
|
20
21
|
|
|
@@ -32,6 +33,8 @@ const fs__default = /*#__PURE__*/ _interopDefault(fs);
|
|
|
32
33
|
* @class Service
|
|
33
34
|
*/
|
|
34
35
|
class Service {
|
|
36
|
+
#root;
|
|
37
|
+
#options;
|
|
35
38
|
/**
|
|
36
39
|
* @constructor
|
|
37
40
|
* @description Create file service.
|
|
@@ -39,30 +42,30 @@ class Service {
|
|
|
39
42
|
* @param options The file service options.
|
|
40
43
|
*/
|
|
41
44
|
constructor(root, options = {}) {
|
|
42
|
-
|
|
43
|
-
this.root = path.unixify(path$1.resolve(root));
|
|
45
|
+
this.#root = path.unixify(node_path.resolve(root));
|
|
44
46
|
const { ignore, headers, highWaterMark = 65536 } = options;
|
|
45
|
-
this
|
|
46
|
-
|
|
47
|
+
this.#options = {
|
|
48
|
+
...options,
|
|
49
|
+
fs: options.fs ?? fs__default.default,
|
|
47
50
|
ignore: type.isFunction(ignore) ? ignore : () => false,
|
|
48
51
|
headers: type.isFunction(headers) ? headers : () => headers,
|
|
49
52
|
highWaterMark: type.isFunction(highWaterMark) ? highWaterMark : () => highWaterMark
|
|
50
|
-
}
|
|
53
|
+
};
|
|
51
54
|
}
|
|
52
55
|
/**
|
|
53
56
|
* @private
|
|
54
|
-
* @method setupHeaders
|
|
57
|
+
* @method #setupHeaders
|
|
55
58
|
* @description Setup headers.
|
|
56
59
|
* @param context The koa context.
|
|
57
60
|
* @param path The file path.
|
|
58
61
|
* @param stats The file stats.
|
|
59
62
|
*/
|
|
60
|
-
async setupHeaders({ response }, path, stats) {
|
|
61
|
-
const
|
|
63
|
+
async #setupHeaders({ response }, path, stats) {
|
|
64
|
+
const options = this.#options;
|
|
62
65
|
// Set status.
|
|
63
66
|
response.status = 200;
|
|
64
67
|
// Set Content-Type.
|
|
65
|
-
response.type =
|
|
68
|
+
response.type = node_path.extname(path);
|
|
66
69
|
// Get headers.
|
|
67
70
|
const headers = await options.headers(path, stats);
|
|
68
71
|
// If headers not empty, set headers.
|
|
@@ -114,21 +117,21 @@ class Service {
|
|
|
114
117
|
return context.throw(400);
|
|
115
118
|
}
|
|
116
119
|
// Get service root.
|
|
117
|
-
const
|
|
120
|
+
const root = this.#root;
|
|
118
121
|
// Get file path.
|
|
119
|
-
const path$
|
|
122
|
+
const path$1 = path.unixify(node_path.join(root, pathname));
|
|
120
123
|
// Malicious path (403).
|
|
121
|
-
if (path.isOutRoot(path$
|
|
124
|
+
if (path.isOutRoot(path$1, root)) {
|
|
122
125
|
return false;
|
|
123
126
|
}
|
|
124
127
|
// Get options.
|
|
125
|
-
const
|
|
128
|
+
const options = this.#options;
|
|
126
129
|
// Is ignore path or file (403).
|
|
127
|
-
if (await options.ignore(path$
|
|
130
|
+
if (await options.ignore(path$1)) {
|
|
128
131
|
return false;
|
|
129
132
|
}
|
|
130
133
|
// File stats.
|
|
131
|
-
const stats = await fs$1.stat(options.fs, path$
|
|
134
|
+
const stats = await fs$1.stat(options.fs, path$1);
|
|
132
135
|
// Check file stats.
|
|
133
136
|
if (
|
|
134
137
|
// File not exist (404 | 500).
|
|
@@ -136,14 +139,14 @@ class Service {
|
|
|
136
139
|
// Is directory (403).
|
|
137
140
|
stats.isDirectory() ||
|
|
138
141
|
// Not a directory but has trailing slash (404).
|
|
139
|
-
path.hasTrailingSlash(path$
|
|
142
|
+
path.hasTrailingSlash(path$1)
|
|
140
143
|
) {
|
|
141
144
|
return false;
|
|
142
145
|
}
|
|
143
146
|
// Koa response.
|
|
144
147
|
const { response } = context;
|
|
145
148
|
// Setup headers.
|
|
146
|
-
await this
|
|
149
|
+
await this.#setupHeaders(context, path$1, stats);
|
|
147
150
|
// Conditional get support.
|
|
148
151
|
if (http.isConditionalGET(context)) {
|
|
149
152
|
// Request precondition failed.
|
|
@@ -183,9 +186,9 @@ class Service {
|
|
|
183
186
|
return context.throw(400);
|
|
184
187
|
}
|
|
185
188
|
// Set response body.
|
|
186
|
-
response.body = new ReadStream.ReadStream(path$
|
|
189
|
+
response.body = new ReadStream.ReadStream(path$1, ranges, {
|
|
187
190
|
fs: options.fs,
|
|
188
|
-
highWaterMark: await options.highWaterMark(path$
|
|
191
|
+
highWaterMark: await options.highWaterMark(path$1, stats)
|
|
189
192
|
});
|
|
190
193
|
// File found.
|
|
191
194
|
return true;
|
package/cjs/Service.d.cts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module Service
|
|
3
3
|
*/
|
|
4
4
|
import { Context } from 'koa';
|
|
5
|
-
import { Stats } from 'fs';
|
|
5
|
+
import { Stats } from 'node:fs';
|
|
6
6
|
import { FileSystem } from './utils/fs.cjs';
|
|
7
7
|
interface Headers {
|
|
8
8
|
[key: string]: string | string[];
|
|
@@ -29,8 +29,7 @@ export interface Options {
|
|
|
29
29
|
* @class Service
|
|
30
30
|
*/
|
|
31
31
|
export declare class Service {
|
|
32
|
-
private
|
|
33
|
-
private readonly options;
|
|
32
|
+
#private;
|
|
34
33
|
/**
|
|
35
34
|
* @constructor
|
|
36
35
|
* @description Create file service.
|
|
@@ -38,15 +37,6 @@ export declare class Service {
|
|
|
38
37
|
* @param options The file service options.
|
|
39
38
|
*/
|
|
40
39
|
constructor(root: string, options?: Options);
|
|
41
|
-
/**
|
|
42
|
-
* @private
|
|
43
|
-
* @method setupHeaders
|
|
44
|
-
* @description Setup headers.
|
|
45
|
-
* @param context The koa context.
|
|
46
|
-
* @param path The file path.
|
|
47
|
-
* @param stats The file stats.
|
|
48
|
-
*/
|
|
49
|
-
private setupHeaders;
|
|
50
40
|
/**
|
|
51
41
|
* @public
|
|
52
42
|
* @method respond
|
package/cjs/index.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @module koa-files
|
|
2
3
|
* @package koa-files
|
|
3
4
|
* @license MIT
|
|
4
|
-
* @version
|
|
5
|
+
* @version 5.0.1
|
|
5
6
|
* @author nuintun <nuintun@qq.com>
|
|
6
7
|
* @description A static files serving middleware for koa.
|
|
7
8
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -22,7 +23,7 @@ const Service = require('./Service.cjs');
|
|
|
22
23
|
*/
|
|
23
24
|
function server(root, options) {
|
|
24
25
|
const service = new Service.Service(root, options);
|
|
25
|
-
if (options
|
|
26
|
+
if (options?.defer) {
|
|
26
27
|
return async (context, next) => {
|
|
27
28
|
await next();
|
|
28
29
|
await service.respond(context);
|