koa-files 3.0.7 → 3.1.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/README.md +5 -0
- package/cjs/Service.cjs +17 -86
- package/cjs/index.cjs +1 -1
- package/cjs/utils/fs.cjs +1 -1
- package/cjs/utils/hash.cjs +1 -1
- package/cjs/utils/http.cjs +18 -15
- package/cjs/utils/path.cjs +1 -1
- package/cjs/utils/stream.cjs +229 -0
- package/esm/Service.js +17 -85
- package/esm/index.js +1 -1
- package/esm/utils/fs.js +1 -1
- package/esm/utils/hash.js +1 -1
- package/esm/utils/http.js +18 -15
- package/esm/utils/path.js +1 -1
- package/esm/utils/stream.js +227 -0
- package/package.json +1 -2
- package/types/Service.d.ts +1 -19
- package/types/utils/fs.d.ts +7 -13
- package/types/utils/http.d.ts +1 -6
- package/types/utils/stream.d.ts +83 -0
package/README.md
CHANGED
|
@@ -89,6 +89,11 @@ export default function server(root: string, options?: Options): Middleware;
|
|
|
89
89
|
- Use the file system's last modified value.
|
|
90
90
|
- Can be overridden by the `headers`.
|
|
91
91
|
|
|
92
|
+
##### `highWaterMark`
|
|
93
|
+
|
|
94
|
+
- Defaults to `node:stream.Readable.highWaterMark`.
|
|
95
|
+
- Set the high water mark for the read stream.
|
|
96
|
+
|
|
92
97
|
##### `ignore`
|
|
93
98
|
|
|
94
99
|
- Defaults to `undefined`.
|
package/cjs/Service.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 3.0
|
|
4
|
+
* @version 3.1.0
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
12
|
const createETag = require('etag');
|
|
13
|
-
const destroy = require('destroy');
|
|
14
|
-
const stream = require('stream');
|
|
15
13
|
const fs = require('./utils/fs.cjs');
|
|
16
14
|
const path$1 = require('path');
|
|
15
|
+
const stream = require('./utils/stream.cjs');
|
|
17
16
|
const path = require('./utils/path.cjs');
|
|
18
17
|
const http = require('./utils/http.cjs');
|
|
19
18
|
|
|
@@ -22,7 +21,6 @@ function _interopDefault(e) {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const createETag__default = /*#__PURE__*/ _interopDefault(createETag);
|
|
25
|
-
const destroy__default = /*#__PURE__*/ _interopDefault(destroy);
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
26
|
* @module Service
|
|
@@ -46,6 +44,18 @@ class Service {
|
|
|
46
44
|
* @param options The file service options.
|
|
47
45
|
*/
|
|
48
46
|
constructor(root, options) {
|
|
47
|
+
Object.defineProperty(this, 'root', {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: void 0
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(this, 'options', {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: void 0
|
|
58
|
+
});
|
|
49
59
|
this.options = options;
|
|
50
60
|
this.root = path.unixify(path$1.resolve(root));
|
|
51
61
|
}
|
|
@@ -109,81 +119,6 @@ class Service {
|
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
|
-
/**
|
|
113
|
-
* @private
|
|
114
|
-
* @method read
|
|
115
|
-
* @description Read file with range.
|
|
116
|
-
* @param path The file path to read.
|
|
117
|
-
* @param range The range to read.
|
|
118
|
-
* @param stream The destination stream.
|
|
119
|
-
* @param end The end flag after pipe to stream.
|
|
120
|
-
*/
|
|
121
|
-
read(path, range, stream, end) {
|
|
122
|
-
const { fs } = this.options;
|
|
123
|
-
return new Promise(resolve => {
|
|
124
|
-
// Range prefix and suffix.
|
|
125
|
-
const { prefix, suffix } = range;
|
|
126
|
-
// Create file stream reader.
|
|
127
|
-
const reader = fs.createReadStream(path, range);
|
|
128
|
-
// File read stream open.
|
|
129
|
-
if (prefix) {
|
|
130
|
-
reader.once('open', () => {
|
|
131
|
-
// Write prefix boundary.
|
|
132
|
-
stream.write(prefix);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
// File read stream end.
|
|
136
|
-
if (suffix) {
|
|
137
|
-
reader.once('end', () => {
|
|
138
|
-
// Push suffix boundary.
|
|
139
|
-
stream.write(suffix);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
// File read stream error.
|
|
143
|
-
reader.once('error', () => {
|
|
144
|
-
// End stream.
|
|
145
|
-
stream.end();
|
|
146
|
-
// Unpipe.
|
|
147
|
-
reader.unpipe();
|
|
148
|
-
// Destroy.
|
|
149
|
-
destroy__default.default(reader);
|
|
150
|
-
// Resolve.
|
|
151
|
-
resolve(false);
|
|
152
|
-
});
|
|
153
|
-
// File read stream close.
|
|
154
|
-
reader.once('close', () => {
|
|
155
|
-
// Unpipe.
|
|
156
|
-
reader.unpipe();
|
|
157
|
-
// Destroy.
|
|
158
|
-
destroy__default.default(reader);
|
|
159
|
-
// Resolve.
|
|
160
|
-
resolve(true);
|
|
161
|
-
});
|
|
162
|
-
// Write data to buffer.
|
|
163
|
-
reader.pipe(stream, { end });
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* @private
|
|
168
|
-
* @method send
|
|
169
|
-
* @description Send file with ranges.
|
|
170
|
-
* @param path The file path to send.
|
|
171
|
-
* @param ranges The ranges to send.
|
|
172
|
-
* @param stream The destination stream.
|
|
173
|
-
*/
|
|
174
|
-
async send(path, ranges, stream) {
|
|
175
|
-
// Ranges length.
|
|
176
|
-
let { length } = ranges;
|
|
177
|
-
// Write ranges to stream.
|
|
178
|
-
for (const range of ranges) {
|
|
179
|
-
// Write range.
|
|
180
|
-
const passed = await this.read(path, range, stream, --length === 0);
|
|
181
|
-
// If not passed, break.
|
|
182
|
-
if (!passed) {
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
122
|
/**
|
|
188
123
|
* @public
|
|
189
124
|
* @method respond
|
|
@@ -264,14 +199,10 @@ class Service {
|
|
|
264
199
|
if (ranges === -2) {
|
|
265
200
|
return context.throw(400);
|
|
266
201
|
}
|
|
267
|
-
//
|
|
268
|
-
const
|
|
269
|
-
highWaterMark: 65536
|
|
270
|
-
});
|
|
271
|
-
// Send file with ranges.
|
|
272
|
-
this.send(path$2, ranges, stream$1);
|
|
202
|
+
// Ranges length.
|
|
203
|
+
const { fs: fs$1, highWaterMark } = this.options;
|
|
273
204
|
// Set response body.
|
|
274
|
-
context.body = stream$1;
|
|
205
|
+
context.body = new stream.FileReadStream(path$2, ranges, { fs: fs$1, highWaterMark });
|
|
275
206
|
// File found.
|
|
276
207
|
return true;
|
|
277
208
|
}
|
package/cjs/index.cjs
CHANGED
package/cjs/utils/fs.cjs
CHANGED
package/cjs/utils/hash.cjs
CHANGED
package/cjs/utils/http.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 3.0
|
|
4
|
+
* @version 3.1.0
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const buffer = require('buffer');
|
|
12
13
|
const hash = require('./hash.cjs');
|
|
13
14
|
const parseRange = require('range-parser');
|
|
14
15
|
|
|
@@ -175,42 +176,44 @@ function parseRanges(context, stats) {
|
|
|
175
176
|
let contentLength = 0;
|
|
176
177
|
// Ranges.
|
|
177
178
|
const ranges = [];
|
|
179
|
+
// Parsed entries.
|
|
180
|
+
const entries = parsed.entries();
|
|
178
181
|
// Range boundary.
|
|
179
182
|
const boundary = `<${hash.generate()}>`;
|
|
180
|
-
// Range suffix.
|
|
181
|
-
const suffix = `\r\n--${boundary}--\r\n`;
|
|
182
183
|
// Multipart Content-Type.
|
|
183
184
|
const contentType = `Content-Type: ${context.type}`;
|
|
185
|
+
// Range suffix.
|
|
186
|
+
const suffix = buffer.Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
184
187
|
// Override Content-Type.
|
|
185
188
|
context.type = `multipart/byteranges; boundary=${boundary}`;
|
|
186
189
|
// Map ranges.
|
|
187
|
-
for (
|
|
188
|
-
const
|
|
189
|
-
// The first prefix boundary no \r\n.
|
|
190
|
+
for (const [index, { start, end }] of entries) {
|
|
191
|
+
const length = end - start + 1;
|
|
190
192
|
const head = index > 0 ? '\r\n' : '';
|
|
191
193
|
const contentRange = `Content-Range: bytes ${start}-${end}/${size}`;
|
|
192
|
-
const prefix = `${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n
|
|
194
|
+
const prefix = buffer.Buffer.from(`${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n`);
|
|
193
195
|
// Compute Content-Length
|
|
194
|
-
contentLength +=
|
|
196
|
+
contentLength += length + prefix.length;
|
|
195
197
|
// Cache range.
|
|
196
|
-
ranges.push({ start,
|
|
198
|
+
ranges.push({ offset: start, length, prefix });
|
|
197
199
|
}
|
|
198
|
-
// The last add suffix boundary.
|
|
199
|
-
ranges[length - 1].suffix = suffix;
|
|
200
200
|
// Compute Content-Length.
|
|
201
|
-
contentLength +=
|
|
201
|
+
contentLength += suffix.length;
|
|
202
202
|
// Set Content-Length.
|
|
203
203
|
context.length = contentLength;
|
|
204
|
+
// The last add suffix boundary.
|
|
205
|
+
ranges[length - 1].suffix = suffix;
|
|
204
206
|
// Return ranges.
|
|
205
207
|
return ranges;
|
|
206
208
|
} else {
|
|
207
209
|
const [{ start, end }] = parsed;
|
|
210
|
+
const length = end - start + 1;
|
|
208
211
|
// Set Content-Length.
|
|
209
|
-
context.length =
|
|
212
|
+
context.length = length;
|
|
210
213
|
// Set Content-Range.
|
|
211
214
|
context.set('Content-Range', `bytes ${start}-${end}/${size}`);
|
|
212
215
|
// Return ranges.
|
|
213
|
-
return
|
|
216
|
+
return [{ offset: start, length }];
|
|
214
217
|
}
|
|
215
218
|
}
|
|
216
219
|
}
|
|
@@ -218,7 +221,7 @@ function parseRanges(context, stats) {
|
|
|
218
221
|
// Set Content-Length.
|
|
219
222
|
context.length = size;
|
|
220
223
|
// Return ranges.
|
|
221
|
-
return [{
|
|
224
|
+
return [{ offset: 0, length: size }];
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
exports.decodeURI = decodeURI;
|
package/cjs/utils/path.cjs
CHANGED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 3.1.0
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const buffer = require('buffer');
|
|
13
|
+
const stream = require('stream');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @module stream
|
|
17
|
+
*/
|
|
18
|
+
class FileReadStream extends stream.Readable {
|
|
19
|
+
/**
|
|
20
|
+
* @constructor
|
|
21
|
+
* @param path The file path.
|
|
22
|
+
* @param ranges The ranges to read.
|
|
23
|
+
* @param options The stream options.
|
|
24
|
+
*/
|
|
25
|
+
constructor(path, ranges, options) {
|
|
26
|
+
const { fs, encoding, highWaterMark } = options;
|
|
27
|
+
super({ encoding, highWaterMark });
|
|
28
|
+
Object.defineProperty(this, 'fd', {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, 'fs', {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: void 0
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, 'path', {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: void 0
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, 'ranges', {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(this, 'bytesRead', {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
configurable: true,
|
|
55
|
+
writable: true,
|
|
56
|
+
value: 0
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(this, 'reading', {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
writable: true,
|
|
62
|
+
value: false
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(this, 'currentRangeIndex', {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
value: 0
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, 'readState', {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
value: 0 /* ReadState.PREFIX */
|
|
75
|
+
});
|
|
76
|
+
this.fs = fs;
|
|
77
|
+
this.path = path;
|
|
78
|
+
this.ranges = ranges;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @override
|
|
82
|
+
* @method _construct
|
|
83
|
+
* @param callback The callback.
|
|
84
|
+
*/
|
|
85
|
+
_construct(callback) {
|
|
86
|
+
this.fs.open(this.path, 'r', (openError, fd) => {
|
|
87
|
+
if (openError === null) {
|
|
88
|
+
this.fd = fd;
|
|
89
|
+
}
|
|
90
|
+
callback(openError);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @private
|
|
95
|
+
* @method getRange
|
|
96
|
+
*/
|
|
97
|
+
getRange() {
|
|
98
|
+
return this.ranges[this.currentRangeIndex];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @private
|
|
102
|
+
* @method getPadding
|
|
103
|
+
* @param range The current range.
|
|
104
|
+
*/
|
|
105
|
+
getPadding(range) {
|
|
106
|
+
switch (this.readState) {
|
|
107
|
+
case 0 /* ReadState.PREFIX */:
|
|
108
|
+
return range.prefix;
|
|
109
|
+
case 2 /* ReadState.SUFFIX */:
|
|
110
|
+
return range.suffix;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @private
|
|
115
|
+
* @method readFilePadding
|
|
116
|
+
* @param fd The file descriptor.
|
|
117
|
+
* @param range The current range.
|
|
118
|
+
* @param size The number of bytes to read.
|
|
119
|
+
*/
|
|
120
|
+
readFilePadding(fd, range, size) {
|
|
121
|
+
let bytesRead = 0;
|
|
122
|
+
const padding = this.getPadding(range);
|
|
123
|
+
const hasRangePadding = padding != null;
|
|
124
|
+
const length = hasRangePadding ? padding.length : 0;
|
|
125
|
+
if (hasRangePadding && length > 0) {
|
|
126
|
+
const begin = this.bytesRead;
|
|
127
|
+
bytesRead = Math.min(size, length - begin);
|
|
128
|
+
const end = begin + bytesRead;
|
|
129
|
+
this.push(padding.subarray(begin, end));
|
|
130
|
+
this.bytesRead = end;
|
|
131
|
+
}
|
|
132
|
+
if (!hasRangePadding || this.bytesRead >= length) {
|
|
133
|
+
this.bytesRead = 0;
|
|
134
|
+
const hasBytesRead = bytesRead > 0;
|
|
135
|
+
switch (this.readState) {
|
|
136
|
+
case 0 /* ReadState.PREFIX */:
|
|
137
|
+
this.readState = 1 /* ReadState.RANGE */;
|
|
138
|
+
if (!hasBytesRead) {
|
|
139
|
+
this.readFileRange(fd, range, size);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case 2 /* ReadState.SUFFIX */:
|
|
143
|
+
this.currentRangeIndex++;
|
|
144
|
+
this.readState = 0 /* ReadState.PREFIX */;
|
|
145
|
+
if (!hasBytesRead) {
|
|
146
|
+
const range = this.getRange();
|
|
147
|
+
if (range == null) {
|
|
148
|
+
this.push(null);
|
|
149
|
+
} else {
|
|
150
|
+
this.readFilePadding(fd, range, size);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @private
|
|
159
|
+
* @method readFileRange
|
|
160
|
+
* @param fd The file descriptor.
|
|
161
|
+
* @param range The current range.
|
|
162
|
+
* @param size The number of bytes to read.
|
|
163
|
+
*/
|
|
164
|
+
readFileRange(fd, range, size) {
|
|
165
|
+
this.reading = true;
|
|
166
|
+
const { bytesRead } = this;
|
|
167
|
+
const position = range.offset + bytesRead;
|
|
168
|
+
const buffer$1 = buffer.Buffer.alloc(Math.min(size, range.length - bytesRead));
|
|
169
|
+
this.fs.read(fd, buffer$1, 0, buffer$1.length, position, (readError, bytesRead, buffer) => {
|
|
170
|
+
if (readError === null) {
|
|
171
|
+
if (bytesRead > 0) {
|
|
172
|
+
this.push(buffer.subarray(0, bytesRead));
|
|
173
|
+
this.bytesRead += bytesRead;
|
|
174
|
+
} else {
|
|
175
|
+
this.bytesRead = 0;
|
|
176
|
+
this.readState = 2 /* ReadState.SUFFIX */;
|
|
177
|
+
this.readFilePadding(fd, range, size);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
this.destroy(readError);
|
|
181
|
+
}
|
|
182
|
+
this.reading = false;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* @override
|
|
187
|
+
* @method _read
|
|
188
|
+
* @param size The number of bytes to read.
|
|
189
|
+
*/
|
|
190
|
+
_read(size) {
|
|
191
|
+
if (!this.reading) {
|
|
192
|
+
const { fd } = this;
|
|
193
|
+
const range = this.getRange();
|
|
194
|
+
if (fd != null && range != null) {
|
|
195
|
+
switch (this.readState) {
|
|
196
|
+
case 0 /* ReadState.PREFIX */:
|
|
197
|
+
case 2 /* ReadState.SUFFIX */:
|
|
198
|
+
this.readFilePadding(fd, range, size);
|
|
199
|
+
break;
|
|
200
|
+
case 1 /* ReadState.RANGE */:
|
|
201
|
+
this.readFileRange(fd, range, size);
|
|
202
|
+
break;
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`invalid read state: ${this.readState}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
this.push(null);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* @override
|
|
213
|
+
* @method _destroy
|
|
214
|
+
* @param error The error.
|
|
215
|
+
* @param callback The callback.
|
|
216
|
+
*/
|
|
217
|
+
_destroy(error, callback) {
|
|
218
|
+
const { fd } = this;
|
|
219
|
+
if (fd != null) {
|
|
220
|
+
this.fs.close(fd, closeError => {
|
|
221
|
+
callback(error !== null && error !== void 0 ? error : closeError);
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
callback(error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
exports.FileReadStream = FileReadStream;
|
package/esm/Service.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 3.0
|
|
4
|
+
* @version 3.1.0
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import createETag from 'etag';
|
|
11
|
-
import destroy from 'destroy';
|
|
12
|
-
import { PassThrough } from 'stream';
|
|
13
11
|
import { stat } from './utils/fs.js';
|
|
14
12
|
import { resolve, extname, join } from 'path';
|
|
13
|
+
import { FileReadStream } from './utils/stream.js';
|
|
15
14
|
import { unixify, isOutRoot, hasTrailingSlash } from './utils/path.js';
|
|
16
15
|
import { decodeURI, isConditionalGET, isPreconditionFailure, parseRanges } from './utils/http.js';
|
|
17
16
|
|
|
@@ -37,6 +36,18 @@ class Service {
|
|
|
37
36
|
* @param options The file service options.
|
|
38
37
|
*/
|
|
39
38
|
constructor(root, options) {
|
|
39
|
+
Object.defineProperty(this, 'root', {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: void 0
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(this, 'options', {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: void 0
|
|
50
|
+
});
|
|
40
51
|
this.options = options;
|
|
41
52
|
this.root = unixify(resolve(root));
|
|
42
53
|
}
|
|
@@ -100,81 +111,6 @@ class Service {
|
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
}
|
|
103
|
-
/**
|
|
104
|
-
* @private
|
|
105
|
-
* @method read
|
|
106
|
-
* @description Read file with range.
|
|
107
|
-
* @param path The file path to read.
|
|
108
|
-
* @param range The range to read.
|
|
109
|
-
* @param stream The destination stream.
|
|
110
|
-
* @param end The end flag after pipe to stream.
|
|
111
|
-
*/
|
|
112
|
-
read(path, range, stream, end) {
|
|
113
|
-
const { fs } = this.options;
|
|
114
|
-
return new Promise(resolve => {
|
|
115
|
-
// Range prefix and suffix.
|
|
116
|
-
const { prefix, suffix } = range;
|
|
117
|
-
// Create file stream reader.
|
|
118
|
-
const reader = fs.createReadStream(path, range);
|
|
119
|
-
// File read stream open.
|
|
120
|
-
if (prefix) {
|
|
121
|
-
reader.once('open', () => {
|
|
122
|
-
// Write prefix boundary.
|
|
123
|
-
stream.write(prefix);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
// File read stream end.
|
|
127
|
-
if (suffix) {
|
|
128
|
-
reader.once('end', () => {
|
|
129
|
-
// Push suffix boundary.
|
|
130
|
-
stream.write(suffix);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
// File read stream error.
|
|
134
|
-
reader.once('error', () => {
|
|
135
|
-
// End stream.
|
|
136
|
-
stream.end();
|
|
137
|
-
// Unpipe.
|
|
138
|
-
reader.unpipe();
|
|
139
|
-
// Destroy.
|
|
140
|
-
destroy(reader);
|
|
141
|
-
// Resolve.
|
|
142
|
-
resolve(false);
|
|
143
|
-
});
|
|
144
|
-
// File read stream close.
|
|
145
|
-
reader.once('close', () => {
|
|
146
|
-
// Unpipe.
|
|
147
|
-
reader.unpipe();
|
|
148
|
-
// Destroy.
|
|
149
|
-
destroy(reader);
|
|
150
|
-
// Resolve.
|
|
151
|
-
resolve(true);
|
|
152
|
-
});
|
|
153
|
-
// Write data to buffer.
|
|
154
|
-
reader.pipe(stream, { end });
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* @private
|
|
159
|
-
* @method send
|
|
160
|
-
* @description Send file with ranges.
|
|
161
|
-
* @param path The file path to send.
|
|
162
|
-
* @param ranges The ranges to send.
|
|
163
|
-
* @param stream The destination stream.
|
|
164
|
-
*/
|
|
165
|
-
async send(path, ranges, stream) {
|
|
166
|
-
// Ranges length.
|
|
167
|
-
let { length } = ranges;
|
|
168
|
-
// Write ranges to stream.
|
|
169
|
-
for (const range of ranges) {
|
|
170
|
-
// Write range.
|
|
171
|
-
const passed = await this.read(path, range, stream, --length === 0);
|
|
172
|
-
// If not passed, break.
|
|
173
|
-
if (!passed) {
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
114
|
/**
|
|
179
115
|
* @public
|
|
180
116
|
* @method respond
|
|
@@ -255,14 +191,10 @@ class Service {
|
|
|
255
191
|
if (ranges === -2) {
|
|
256
192
|
return context.throw(400);
|
|
257
193
|
}
|
|
258
|
-
//
|
|
259
|
-
const
|
|
260
|
-
highWaterMark: 65536
|
|
261
|
-
});
|
|
262
|
-
// Send file with ranges.
|
|
263
|
-
this.send(path, ranges, stream);
|
|
194
|
+
// Ranges length.
|
|
195
|
+
const { fs, highWaterMark } = this.options;
|
|
264
196
|
// Set response body.
|
|
265
|
-
context.body =
|
|
197
|
+
context.body = new FileReadStream(path, ranges, { fs, highWaterMark });
|
|
266
198
|
// File found.
|
|
267
199
|
return true;
|
|
268
200
|
}
|
package/esm/index.js
CHANGED
package/esm/utils/fs.js
CHANGED
package/esm/utils/hash.js
CHANGED
package/esm/utils/http.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version 3.0
|
|
4
|
+
* @version 3.1.0
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { Buffer } from 'buffer';
|
|
10
11
|
import { generate } from './hash.js';
|
|
11
12
|
import parseRange from 'range-parser';
|
|
12
13
|
|
|
@@ -167,42 +168,44 @@ function parseRanges(context, stats) {
|
|
|
167
168
|
let contentLength = 0;
|
|
168
169
|
// Ranges.
|
|
169
170
|
const ranges = [];
|
|
171
|
+
// Parsed entries.
|
|
172
|
+
const entries = parsed.entries();
|
|
170
173
|
// Range boundary.
|
|
171
174
|
const boundary = `<${generate()}>`;
|
|
172
|
-
// Range suffix.
|
|
173
|
-
const suffix = `\r\n--${boundary}--\r\n`;
|
|
174
175
|
// Multipart Content-Type.
|
|
175
176
|
const contentType = `Content-Type: ${context.type}`;
|
|
177
|
+
// Range suffix.
|
|
178
|
+
const suffix = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
176
179
|
// Override Content-Type.
|
|
177
180
|
context.type = `multipart/byteranges; boundary=${boundary}`;
|
|
178
181
|
// Map ranges.
|
|
179
|
-
for (
|
|
180
|
-
const
|
|
181
|
-
// The first prefix boundary no \r\n.
|
|
182
|
+
for (const [index, { start, end }] of entries) {
|
|
183
|
+
const length = end - start + 1;
|
|
182
184
|
const head = index > 0 ? '\r\n' : '';
|
|
183
185
|
const contentRange = `Content-Range: bytes ${start}-${end}/${size}`;
|
|
184
|
-
const prefix = `${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n
|
|
186
|
+
const prefix = Buffer.from(`${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n`);
|
|
185
187
|
// Compute Content-Length
|
|
186
|
-
contentLength +=
|
|
188
|
+
contentLength += length + prefix.length;
|
|
187
189
|
// Cache range.
|
|
188
|
-
ranges.push({ start,
|
|
190
|
+
ranges.push({ offset: start, length, prefix });
|
|
189
191
|
}
|
|
190
|
-
// The last add suffix boundary.
|
|
191
|
-
ranges[length - 1].suffix = suffix;
|
|
192
192
|
// Compute Content-Length.
|
|
193
|
-
contentLength +=
|
|
193
|
+
contentLength += suffix.length;
|
|
194
194
|
// Set Content-Length.
|
|
195
195
|
context.length = contentLength;
|
|
196
|
+
// The last add suffix boundary.
|
|
197
|
+
ranges[length - 1].suffix = suffix;
|
|
196
198
|
// Return ranges.
|
|
197
199
|
return ranges;
|
|
198
200
|
} else {
|
|
199
201
|
const [{ start, end }] = parsed;
|
|
202
|
+
const length = end - start + 1;
|
|
200
203
|
// Set Content-Length.
|
|
201
|
-
context.length =
|
|
204
|
+
context.length = length;
|
|
202
205
|
// Set Content-Range.
|
|
203
206
|
context.set('Content-Range', `bytes ${start}-${end}/${size}`);
|
|
204
207
|
// Return ranges.
|
|
205
|
-
return
|
|
208
|
+
return [{ offset: start, length }];
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
}
|
|
@@ -210,7 +213,7 @@ function parseRanges(context, stats) {
|
|
|
210
213
|
// Set Content-Length.
|
|
211
214
|
context.length = size;
|
|
212
215
|
// Return ranges.
|
|
213
|
-
return [{
|
|
216
|
+
return [{ offset: 0, length: size }];
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
export { decodeURI, isConditionalGET, isPreconditionFailure, parseRanges };
|
package/esm/utils/path.js
CHANGED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 3.1.0
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Buffer } from 'buffer';
|
|
11
|
+
import { Readable } from 'stream';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @module stream
|
|
15
|
+
*/
|
|
16
|
+
class FileReadStream extends Readable {
|
|
17
|
+
/**
|
|
18
|
+
* @constructor
|
|
19
|
+
* @param path The file path.
|
|
20
|
+
* @param ranges The ranges to read.
|
|
21
|
+
* @param options The stream options.
|
|
22
|
+
*/
|
|
23
|
+
constructor(path, ranges, options) {
|
|
24
|
+
const { fs, encoding, highWaterMark } = options;
|
|
25
|
+
super({ encoding, highWaterMark });
|
|
26
|
+
Object.defineProperty(this, 'fd', {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, 'fs', {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, 'path', {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, 'ranges', {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(this, 'bytesRead', {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
writable: true,
|
|
54
|
+
value: 0
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(this, 'reading', {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: false
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(this, 'currentRangeIndex', {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: 0
|
|
67
|
+
});
|
|
68
|
+
Object.defineProperty(this, 'readState', {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: true,
|
|
72
|
+
value: 0 /* ReadState.PREFIX */
|
|
73
|
+
});
|
|
74
|
+
this.fs = fs;
|
|
75
|
+
this.path = path;
|
|
76
|
+
this.ranges = ranges;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @override
|
|
80
|
+
* @method _construct
|
|
81
|
+
* @param callback The callback.
|
|
82
|
+
*/
|
|
83
|
+
_construct(callback) {
|
|
84
|
+
this.fs.open(this.path, 'r', (openError, fd) => {
|
|
85
|
+
if (openError === null) {
|
|
86
|
+
this.fd = fd;
|
|
87
|
+
}
|
|
88
|
+
callback(openError);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @private
|
|
93
|
+
* @method getRange
|
|
94
|
+
*/
|
|
95
|
+
getRange() {
|
|
96
|
+
return this.ranges[this.currentRangeIndex];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @private
|
|
100
|
+
* @method getPadding
|
|
101
|
+
* @param range The current range.
|
|
102
|
+
*/
|
|
103
|
+
getPadding(range) {
|
|
104
|
+
switch (this.readState) {
|
|
105
|
+
case 0 /* ReadState.PREFIX */:
|
|
106
|
+
return range.prefix;
|
|
107
|
+
case 2 /* ReadState.SUFFIX */:
|
|
108
|
+
return range.suffix;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* @private
|
|
113
|
+
* @method readFilePadding
|
|
114
|
+
* @param fd The file descriptor.
|
|
115
|
+
* @param range The current range.
|
|
116
|
+
* @param size The number of bytes to read.
|
|
117
|
+
*/
|
|
118
|
+
readFilePadding(fd, range, size) {
|
|
119
|
+
let bytesRead = 0;
|
|
120
|
+
const padding = this.getPadding(range);
|
|
121
|
+
const hasRangePadding = padding != null;
|
|
122
|
+
const length = hasRangePadding ? padding.length : 0;
|
|
123
|
+
if (hasRangePadding && length > 0) {
|
|
124
|
+
const begin = this.bytesRead;
|
|
125
|
+
bytesRead = Math.min(size, length - begin);
|
|
126
|
+
const end = begin + bytesRead;
|
|
127
|
+
this.push(padding.subarray(begin, end));
|
|
128
|
+
this.bytesRead = end;
|
|
129
|
+
}
|
|
130
|
+
if (!hasRangePadding || this.bytesRead >= length) {
|
|
131
|
+
this.bytesRead = 0;
|
|
132
|
+
const hasBytesRead = bytesRead > 0;
|
|
133
|
+
switch (this.readState) {
|
|
134
|
+
case 0 /* ReadState.PREFIX */:
|
|
135
|
+
this.readState = 1 /* ReadState.RANGE */;
|
|
136
|
+
if (!hasBytesRead) {
|
|
137
|
+
this.readFileRange(fd, range, size);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case 2 /* ReadState.SUFFIX */:
|
|
141
|
+
this.currentRangeIndex++;
|
|
142
|
+
this.readState = 0 /* ReadState.PREFIX */;
|
|
143
|
+
if (!hasBytesRead) {
|
|
144
|
+
const range = this.getRange();
|
|
145
|
+
if (range == null) {
|
|
146
|
+
this.push(null);
|
|
147
|
+
} else {
|
|
148
|
+
this.readFilePadding(fd, range, size);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @private
|
|
157
|
+
* @method readFileRange
|
|
158
|
+
* @param fd The file descriptor.
|
|
159
|
+
* @param range The current range.
|
|
160
|
+
* @param size The number of bytes to read.
|
|
161
|
+
*/
|
|
162
|
+
readFileRange(fd, range, size) {
|
|
163
|
+
this.reading = true;
|
|
164
|
+
const { bytesRead } = this;
|
|
165
|
+
const position = range.offset + bytesRead;
|
|
166
|
+
const buffer = Buffer.alloc(Math.min(size, range.length - bytesRead));
|
|
167
|
+
this.fs.read(fd, buffer, 0, buffer.length, position, (readError, bytesRead, buffer) => {
|
|
168
|
+
if (readError === null) {
|
|
169
|
+
if (bytesRead > 0) {
|
|
170
|
+
this.push(buffer.subarray(0, bytesRead));
|
|
171
|
+
this.bytesRead += bytesRead;
|
|
172
|
+
} else {
|
|
173
|
+
this.bytesRead = 0;
|
|
174
|
+
this.readState = 2 /* ReadState.SUFFIX */;
|
|
175
|
+
this.readFilePadding(fd, range, size);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
this.destroy(readError);
|
|
179
|
+
}
|
|
180
|
+
this.reading = false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @override
|
|
185
|
+
* @method _read
|
|
186
|
+
* @param size The number of bytes to read.
|
|
187
|
+
*/
|
|
188
|
+
_read(size) {
|
|
189
|
+
if (!this.reading) {
|
|
190
|
+
const { fd } = this;
|
|
191
|
+
const range = this.getRange();
|
|
192
|
+
if (fd != null && range != null) {
|
|
193
|
+
switch (this.readState) {
|
|
194
|
+
case 0 /* ReadState.PREFIX */:
|
|
195
|
+
case 2 /* ReadState.SUFFIX */:
|
|
196
|
+
this.readFilePadding(fd, range, size);
|
|
197
|
+
break;
|
|
198
|
+
case 1 /* ReadState.RANGE */:
|
|
199
|
+
this.readFileRange(fd, range, size);
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`invalid read state: ${this.readState}`);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.push(null);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* @override
|
|
211
|
+
* @method _destroy
|
|
212
|
+
* @param error The error.
|
|
213
|
+
* @param callback The callback.
|
|
214
|
+
*/
|
|
215
|
+
_destroy(error, callback) {
|
|
216
|
+
const { fd } = this;
|
|
217
|
+
if (fd != null) {
|
|
218
|
+
this.fs.close(fd, closeError => {
|
|
219
|
+
callback(error !== null && error !== void 0 ? error : closeError);
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
callback(error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { FileReadStream };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koa-files",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "A static files serving middleware for koa.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cjs/index.cjs",
|
|
@@ -54,7 +54,6 @@
|
|
|
54
54
|
"build:types": "tsc --declaration --emitDeclarationOnly --declarationDir types && node tools/fix-types.js"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"destroy": "^1.2.0",
|
|
58
57
|
"etag": "^1.8.1",
|
|
59
58
|
"range-parser": "^1.2.1",
|
|
60
59
|
"tslib": "^2.7.0"
|
package/types/Service.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface Options {
|
|
|
18
18
|
etag?: boolean;
|
|
19
19
|
acceptRanges?: boolean;
|
|
20
20
|
lastModified?: boolean;
|
|
21
|
+
highWaterMark?: number;
|
|
21
22
|
ignore?: IgnoreFunction;
|
|
22
23
|
headers?: Headers | HeaderFunction;
|
|
23
24
|
}
|
|
@@ -50,25 +51,6 @@ export default class Service {
|
|
|
50
51
|
* @param stats The file stats.
|
|
51
52
|
*/
|
|
52
53
|
private setupHeaders;
|
|
53
|
-
/**
|
|
54
|
-
* @private
|
|
55
|
-
* @method read
|
|
56
|
-
* @description Read file with range.
|
|
57
|
-
* @param path The file path to read.
|
|
58
|
-
* @param range The range to read.
|
|
59
|
-
* @param stream The destination stream.
|
|
60
|
-
* @param end The end flag after pipe to stream.
|
|
61
|
-
*/
|
|
62
|
-
private read;
|
|
63
|
-
/**
|
|
64
|
-
* @private
|
|
65
|
-
* @method send
|
|
66
|
-
* @description Send file with ranges.
|
|
67
|
-
* @param path The file path to send.
|
|
68
|
-
* @param ranges The ranges to send.
|
|
69
|
-
* @param stream The destination stream.
|
|
70
|
-
*/
|
|
71
|
-
private send;
|
|
72
54
|
/**
|
|
73
55
|
* @public
|
|
74
56
|
* @method respond
|
package/types/utils/fs.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module fs
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import fs, { Stats } from 'fs';
|
|
5
|
+
export interface FileSystem {
|
|
6
|
+
stat: typeof fs.stat;
|
|
7
|
+
open: typeof fs.open;
|
|
8
|
+
read: typeof fs.read;
|
|
9
|
+
close: typeof fs.close;
|
|
10
|
+
}
|
|
6
11
|
/**
|
|
7
12
|
* @function stat
|
|
8
13
|
* @description Get file stats.
|
|
@@ -10,14 +15,3 @@ type PathLike = string | Buffer | URL;
|
|
|
10
15
|
* @param path The file path.
|
|
11
16
|
*/
|
|
12
17
|
export declare function stat(fs: FileSystem, path: string): Promise<Stats | null>;
|
|
13
|
-
export interface FileSystem {
|
|
14
|
-
stat(path: PathLike, callback: (error: Error | null, stats: Stats) => void): void;
|
|
15
|
-
createReadStream(
|
|
16
|
-
path: PathLike,
|
|
17
|
-
options?: {
|
|
18
|
-
start?: number;
|
|
19
|
-
end?: number;
|
|
20
|
-
}
|
|
21
|
-
): ReadStream;
|
|
22
|
-
}
|
|
23
|
-
export {};
|
package/types/utils/http.d.ts
CHANGED
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Stats } from 'fs';
|
|
5
5
|
import { Context } from 'koa';
|
|
6
|
-
|
|
7
|
-
start: number;
|
|
8
|
-
end?: number;
|
|
9
|
-
prefix?: string;
|
|
10
|
-
suffix?: string;
|
|
11
|
-
}
|
|
6
|
+
import { Range } from './stream';
|
|
12
7
|
type Ranges = Range[] | -1 | -2;
|
|
13
8
|
/**
|
|
14
9
|
* @function decodeURI
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module stream
|
|
3
|
+
*/
|
|
4
|
+
import { PathLike } from 'fs';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
6
|
+
import { FileSystem } from './fs';
|
|
7
|
+
import { Readable, ReadableOptions } from 'stream';
|
|
8
|
+
export interface Options extends Pick<ReadableOptions, 'encoding' | 'highWaterMark'> {
|
|
9
|
+
fs: FileSystem;
|
|
10
|
+
}
|
|
11
|
+
export interface Range {
|
|
12
|
+
offset: number;
|
|
13
|
+
length: number;
|
|
14
|
+
prefix?: Buffer;
|
|
15
|
+
suffix?: Buffer;
|
|
16
|
+
}
|
|
17
|
+
interface Callback {
|
|
18
|
+
(error?: Error | null): void;
|
|
19
|
+
}
|
|
20
|
+
export declare class FileReadStream extends Readable {
|
|
21
|
+
private fd?;
|
|
22
|
+
private fs;
|
|
23
|
+
private path;
|
|
24
|
+
private ranges;
|
|
25
|
+
private bytesRead;
|
|
26
|
+
private reading;
|
|
27
|
+
private currentRangeIndex;
|
|
28
|
+
private readState;
|
|
29
|
+
/**
|
|
30
|
+
* @constructor
|
|
31
|
+
* @param path The file path.
|
|
32
|
+
* @param ranges The ranges to read.
|
|
33
|
+
* @param options The stream options.
|
|
34
|
+
*/
|
|
35
|
+
constructor(path: PathLike, ranges: Range[], options: Options);
|
|
36
|
+
/**
|
|
37
|
+
* @override
|
|
38
|
+
* @method _construct
|
|
39
|
+
* @param callback The callback.
|
|
40
|
+
*/
|
|
41
|
+
_construct(callback: Callback): void;
|
|
42
|
+
/**
|
|
43
|
+
* @private
|
|
44
|
+
* @method getRange
|
|
45
|
+
*/
|
|
46
|
+
private getRange;
|
|
47
|
+
/**
|
|
48
|
+
* @private
|
|
49
|
+
* @method getPadding
|
|
50
|
+
* @param range The current range.
|
|
51
|
+
*/
|
|
52
|
+
private getPadding;
|
|
53
|
+
/**
|
|
54
|
+
* @private
|
|
55
|
+
* @method readFilePadding
|
|
56
|
+
* @param fd The file descriptor.
|
|
57
|
+
* @param range The current range.
|
|
58
|
+
* @param size The number of bytes to read.
|
|
59
|
+
*/
|
|
60
|
+
private readFilePadding;
|
|
61
|
+
/**
|
|
62
|
+
* @private
|
|
63
|
+
* @method readFileRange
|
|
64
|
+
* @param fd The file descriptor.
|
|
65
|
+
* @param range The current range.
|
|
66
|
+
* @param size The number of bytes to read.
|
|
67
|
+
*/
|
|
68
|
+
private readFileRange;
|
|
69
|
+
/**
|
|
70
|
+
* @override
|
|
71
|
+
* @method _read
|
|
72
|
+
* @param size The number of bytes to read.
|
|
73
|
+
*/
|
|
74
|
+
_read(size: number): void;
|
|
75
|
+
/**
|
|
76
|
+
* @override
|
|
77
|
+
* @method _destroy
|
|
78
|
+
* @param error The error.
|
|
79
|
+
* @param callback The callback.
|
|
80
|
+
*/
|
|
81
|
+
_destroy(error: Error | null, callback: Callback): void;
|
|
82
|
+
}
|
|
83
|
+
export {};
|