@juzi/file-box 1.7.20 → 1.8.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 +4 -2
- package/dist/cjs/src/config.d.ts +5 -5
- package/dist/cjs/src/config.d.ts.map +1 -1
- package/dist/cjs/src/config.js +7 -9
- package/dist/cjs/src/config.js.map +1 -1
- package/dist/cjs/src/file-box.js +1 -1
- package/dist/cjs/src/file-box.js.map +1 -1
- package/dist/cjs/src/misc.d.ts.map +1 -1
- package/dist/cjs/src/misc.js +185 -69
- package/dist/cjs/src/misc.js.map +1 -1
- package/dist/cjs/src/misc.spec.js +26 -17
- package/dist/cjs/src/misc.spec.js.map +1 -1
- package/dist/cjs/src/version.d.ts.map +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/version.js.map +1 -1
- package/dist/cjs/tests/chunk-download.spec.js +62 -90
- package/dist/cjs/tests/chunk-download.spec.js.map +1 -1
- package/dist/cjs/tests/misc-error-handling.spec.js +134 -30
- package/dist/cjs/tests/misc-error-handling.spec.js.map +1 -1
- package/dist/cjs/tests/network-timeout.spec.js +101 -105
- package/dist/cjs/tests/network-timeout.spec.js.map +1 -1
- package/dist/esm/src/config.d.ts +5 -5
- package/dist/esm/src/config.d.ts.map +1 -1
- package/dist/esm/src/config.js +6 -8
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/file-box.js +2 -2
- package/dist/esm/src/file-box.js.map +1 -1
- package/dist/esm/src/misc.d.ts.map +1 -1
- package/dist/esm/src/misc.js +187 -71
- package/dist/esm/src/misc.js.map +1 -1
- package/dist/esm/src/misc.spec.js +26 -17
- package/dist/esm/src/misc.spec.js.map +1 -1
- package/dist/esm/src/version.d.ts.map +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/version.js.map +1 -1
- package/dist/esm/tests/chunk-download.spec.js +62 -90
- package/dist/esm/tests/chunk-download.spec.js.map +1 -1
- package/dist/esm/tests/misc-error-handling.spec.js +134 -30
- package/dist/esm/tests/misc-error-handling.spec.js.map +1 -1
- package/dist/esm/tests/network-timeout.spec.js +103 -107
- package/dist/esm/tests/network-timeout.spec.js.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +6 -12
- package/src/file-box.ts +2 -2
- package/src/misc.spec.ts +29 -14
- package/src/misc.ts +200 -75
- package/src/version.ts +1 -1
|
@@ -1,123 +1,119 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
2
|
import { createServer } from 'http';
|
|
3
3
|
import { setTimeout } from 'timers/promises';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { test } from 'tstest';
|
|
5
|
+
import { CONFIG } from '../src/config.js';
|
|
6
6
|
import { FileBox } from '../src/mod.js';
|
|
7
|
-
test('
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
test('HTTP timeout handling', async (t) => {
|
|
8
|
+
// 设置短超时用于快速测试
|
|
9
|
+
const originalRequestTimeout = CONFIG.HTTP_REQUEST_TIMEOUT;
|
|
10
|
+
const originalResponseTimeout = CONFIG.HTTP_RESPONSE_TIMEOUT;
|
|
11
|
+
CONFIG.HTTP_REQUEST_TIMEOUT = 200; // 200ms
|
|
12
|
+
CONFIG.HTTP_RESPONSE_TIMEOUT = 300; // 300ms
|
|
13
|
+
t.teardown(() => {
|
|
14
|
+
CONFIG.HTTP_REQUEST_TIMEOUT = originalRequestTimeout;
|
|
15
|
+
CONFIG.HTTP_RESPONSE_TIMEOUT = originalResponseTimeout;
|
|
14
16
|
});
|
|
15
|
-
t.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/* eslint @typescript-eslint/no-misused-promises:off */
|
|
23
|
-
const server = createServer(async (req, res) => {
|
|
24
|
-
res.write(Buffer.from('This is the first chunk of data.'));
|
|
25
|
-
if (req.url === URL.NOT_TIMEOUT) {
|
|
26
|
-
await setTimeout(HTTP_REQUEST_TIMEOUT * 0.5);
|
|
27
|
-
res.write(Buffer.from('This is the second chunk of data.'));
|
|
28
|
-
}
|
|
29
|
-
else if (req.url === URL.READY) {
|
|
30
|
-
await setTimeout(HTTP_REQUEST_TIMEOUT + 100);
|
|
31
|
-
}
|
|
32
|
-
else if (req.url === URL.TIMEOUT) {
|
|
33
|
-
if (req.method === 'GET') {
|
|
34
|
-
await setTimeout(HTTP_RESPONSE_TIMEOUT + 100);
|
|
17
|
+
await t.test('should complete download without timeout', async (t) => {
|
|
18
|
+
const testData = 'Test data for no timeout';
|
|
19
|
+
const server = createServer((req, res) => {
|
|
20
|
+
if (req.method === 'HEAD') {
|
|
21
|
+
res.writeHead(200, { 'Content-Length': String(testData.length) });
|
|
22
|
+
res.end();
|
|
23
|
+
return;
|
|
35
24
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const host = await new Promise((resolve) => {
|
|
41
|
-
server.listen(port, '127.0.0.1', () => {
|
|
42
|
-
const addr = server.address();
|
|
43
|
-
// console.debug(`Server is listening on port ${JSON.stringify(addr)}`)
|
|
44
|
-
resolve(`http://127.0.0.1:${addr.port}`);
|
|
25
|
+
// 服务器不支持 Range,直接返回 200
|
|
26
|
+
// 快速响应,不应该超时
|
|
27
|
+
res.writeHead(200, { 'Content-Length': String(testData.length) });
|
|
28
|
+
res.end(testData);
|
|
45
29
|
});
|
|
46
|
-
});
|
|
47
|
-
t.teardown(() => {
|
|
48
|
-
// console.debug('teardown')
|
|
49
|
-
server.close();
|
|
50
|
-
sandbox.restore();
|
|
51
|
-
});
|
|
52
|
-
/** eslint @typescript-eslint/no-floating-promises:off */
|
|
53
|
-
t.test('should not timeout', async (t) => {
|
|
54
|
-
const url = `${host}${URL.NOT_TIMEOUT}`;
|
|
55
|
-
const dataSpy = sandbox.spy();
|
|
56
|
-
const errorSpy = sandbox.spy();
|
|
57
|
-
// console.debug(`${new Date().toLocaleTimeString()} Start request "${url}" ...`)
|
|
58
|
-
const start = Date.now();
|
|
59
|
-
const stream = await FileBox.fromUrl(url).toStream();
|
|
60
|
-
stream.once('error', errorSpy).on('data', dataSpy);
|
|
61
|
-
await sandbox.clock.tickAsync(1);
|
|
62
|
-
t.ok(dataSpy.calledOnce, `should get chunk 1 (${Date.now() - start} passed)`);
|
|
63
|
-
t.ok(errorSpy.notCalled, `should not get error (${Date.now() - start} passed)`);
|
|
64
|
-
// FIXME: tickAsync does not work on socket timeout
|
|
65
30
|
await new Promise((resolve) => {
|
|
66
|
-
|
|
67
|
-
|
|
31
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
32
|
+
});
|
|
33
|
+
const port = server.address().port;
|
|
34
|
+
t.teardown(() => { server.close(); });
|
|
35
|
+
const url = `http://127.0.0.1:${port}/test`;
|
|
36
|
+
const fileBox = FileBox.fromUrl(url);
|
|
37
|
+
const stream = await fileBox.toStream();
|
|
38
|
+
const chunks = [];
|
|
39
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
stream.on('end', resolve);
|
|
42
|
+
stream.on('error', reject);
|
|
68
43
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
t.comment('recv data count:', dataSpy.callCount);
|
|
72
|
-
t.comment('recv error count:', errorSpy.callCount);
|
|
73
|
-
t.ok(dataSpy.calledThrice, `should get chunk 3 after TIMEOUT ${HTTP_REQUEST_TIMEOUT} (${Date.now() - start} passed)`);
|
|
74
|
-
t.ok(errorSpy.notCalled, `should not get error after TIMEOUT ${HTTP_REQUEST_TIMEOUT} (${Date.now() - start} passed)`);
|
|
44
|
+
const result = Buffer.concat(chunks).toString();
|
|
45
|
+
t.equal(result, testData, 'should receive complete data');
|
|
75
46
|
t.end();
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// console.error(`on data for req "${url}":`, d.toString())
|
|
91
|
-
// })
|
|
92
|
-
await sandbox.clock.tickAsync(1);
|
|
93
|
-
// t.comment('recv data count:', dataSpy.callCount)
|
|
94
|
-
// t.comment('recv error count:', errorSpy.callCount)
|
|
95
|
-
t.ok(dataSpy.calledOnce, `should get chunk 1 (${Date.now() - start} passed)`);
|
|
96
|
-
t.ok(errorSpy.notCalled, `should not get error (${Date.now() - start} passed)`);
|
|
97
|
-
// FIXME: tickAsync does not work on socket timeout
|
|
47
|
+
});
|
|
48
|
+
await t.test('should handle response timeout', async (t) => {
|
|
49
|
+
const server = createServer((req, res) => {
|
|
50
|
+
if (req.method === 'HEAD') {
|
|
51
|
+
res.writeHead(200, { 'Content-Length': '100' });
|
|
52
|
+
res.end();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// 发送部分数据后停止,不调用 res.end()
|
|
56
|
+
// 让连接挂起,Socket 会在 HTTP_RESPONSE_TIMEOUT 后超时
|
|
57
|
+
res.writeHead(200, { 'Content-Length': '100' });
|
|
58
|
+
res.write('Partial data...');
|
|
59
|
+
// 不调用 res.end()
|
|
60
|
+
});
|
|
98
61
|
await new Promise((resolve) => {
|
|
99
|
-
|
|
100
|
-
// resolve(setTimeout(HTTP_RESPONSE_TIMEOUT))
|
|
62
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
101
63
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
64
|
+
const port = server.address().port;
|
|
65
|
+
t.teardown(() => { server.close(); });
|
|
66
|
+
const url = `http://127.0.0.1:${port}/timeout`;
|
|
67
|
+
try {
|
|
68
|
+
const fileBox = FileBox.fromUrl(url);
|
|
69
|
+
const stream = await fileBox.toStream();
|
|
70
|
+
const chunks = [];
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
73
|
+
stream.on('end', resolve);
|
|
74
|
+
stream.on('error', reject);
|
|
75
|
+
});
|
|
76
|
+
t.fail('should have thrown timeout error');
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const err = error;
|
|
80
|
+
t.ok(err.message.includes('timeout'), `should timeout with error: ${err.message}`);
|
|
81
|
+
}
|
|
107
82
|
t.end();
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
83
|
+
});
|
|
84
|
+
await t.test('should handle request timeout', async (t) => {
|
|
85
|
+
let requestReceived = false;
|
|
86
|
+
/* eslint @typescript-eslint/no-misused-promises:off */
|
|
87
|
+
const server = createServer(async (req, res) => {
|
|
88
|
+
if (req.method === 'HEAD') {
|
|
89
|
+
res.writeHead(200, { 'Content-Length': '100' });
|
|
90
|
+
res.end();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
requestReceived = true;
|
|
94
|
+
// 延迟响应超过 HTTP_REQUEST_TIMEOUT
|
|
95
|
+
// 在发送任何数据之前延迟,触发 request timeout
|
|
96
|
+
await setTimeout(CONFIG.HTTP_REQUEST_TIMEOUT + 100);
|
|
97
|
+
res.writeHead(200, { 'Content-Length': '10' });
|
|
98
|
+
res.end('Too late');
|
|
99
|
+
});
|
|
100
|
+
await new Promise((resolve) => {
|
|
101
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
102
|
+
});
|
|
103
|
+
const port = server.address().port;
|
|
104
|
+
t.teardown(() => { server.close(); });
|
|
105
|
+
const url = `http://127.0.0.1:${port}/request-timeout`;
|
|
106
|
+
try {
|
|
107
|
+
const fileBox = FileBox.fromUrl(url);
|
|
108
|
+
await fileBox.toStream();
|
|
109
|
+
t.fail('should have thrown timeout error');
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const err = error;
|
|
113
|
+
t.ok(requestReceived, 'should have received request');
|
|
114
|
+
t.ok(err.message.includes('timeout'), `should timeout with error: ${err.message}`);
|
|
115
|
+
}
|
|
120
116
|
t.end();
|
|
121
|
-
})
|
|
117
|
+
});
|
|
122
118
|
});
|
|
123
119
|
//# sourceMappingURL=network-timeout.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-timeout.spec.js","sourceRoot":"","sources":["../../../tests/network-timeout.spec.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"network-timeout.spec.js","sourceRoot":"","sources":["../../../tests/network-timeout.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juzi/file-box",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Pack a File into Box for easy move/transfer between servers no matter of where it is.(local path, remote url, or cloud storage)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
],
|
|
88
88
|
"publishConfig": {
|
|
89
89
|
"access": "public",
|
|
90
|
-
"tag": "
|
|
90
|
+
"tag": "latest"
|
|
91
91
|
},
|
|
92
92
|
"git": {
|
|
93
93
|
"scripts": {
|
package/src/config.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/// <reference path="./typings.d.ts" />
|
|
2
2
|
export { VERSION } from './version.js'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export const NO_SLICE_DOWN = process.env['FILEBOX_NO_SLICE_DOWN'] === 'true'
|
|
11
|
-
|
|
12
|
-
export const HTTP_CHUNK_SIZE = Number(process.env['FILEBOX_HTTP_CHUNK_SIZE'])
|
|
13
|
-
|| 1024 * 512
|
|
14
|
-
|
|
15
|
-
export const READY_RETRY = Number(process.env['FILE_BOX_READY_RETRY']) || 3
|
|
4
|
+
// 导出可变配置对象,支持测试时动态修改
|
|
5
|
+
export const CONFIG = {
|
|
6
|
+
HTTP_REQUEST_TIMEOUT: Number(process.env['FILEBOX_HTTP_REQUEST_TIMEOUT']) || 10 * 1000,
|
|
7
|
+
HTTP_RESPONSE_TIMEOUT: Number(process.env['FILEBOX_HTTP_RESPONSE_TIMEOUT'] ?? process.env['FILEBOX_HTTP_TIMEOUT']) || 60 * 1000,
|
|
8
|
+
READY_RETRY: Number(process.env['FILEBOX_READY_RETRY'] ?? process.env['FILE_BOX_READY_RETRY']) || 3,
|
|
9
|
+
}
|
package/src/file-box.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from 'clone-class'
|
|
26
26
|
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
CONFIG,
|
|
29
29
|
VERSION,
|
|
30
30
|
} from './config.js'
|
|
31
31
|
import {
|
|
@@ -637,7 +637,7 @@ class FileBox implements Pipeable, FileBoxInterface {
|
|
|
637
637
|
break
|
|
638
638
|
} catch (e) {
|
|
639
639
|
tryCount++
|
|
640
|
-
if (tryCount >= READY_RETRY) {
|
|
640
|
+
if (tryCount >= CONFIG.READY_RETRY) {
|
|
641
641
|
throw e
|
|
642
642
|
}
|
|
643
643
|
}
|
package/src/misc.spec.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createServer } from 'http'
|
|
|
5
5
|
import type { AddressInfo } from 'net'
|
|
6
6
|
import { test } from 'tstest'
|
|
7
7
|
|
|
8
|
+
import { CONFIG } from './config.js'
|
|
8
9
|
import {
|
|
9
10
|
dataUrlToBase64,
|
|
10
11
|
httpHeaderToFileName,
|
|
@@ -13,6 +14,10 @@ import {
|
|
|
13
14
|
streamToBuffer,
|
|
14
15
|
} from './misc.js'
|
|
15
16
|
|
|
17
|
+
// 设置短超时用于测试
|
|
18
|
+
CONFIG.HTTP_REQUEST_TIMEOUT = 1000
|
|
19
|
+
CONFIG.HTTP_RESPONSE_TIMEOUT = 1000
|
|
20
|
+
|
|
16
21
|
test('dataUrl to base64', async t => {
|
|
17
22
|
const base64 = [
|
|
18
23
|
'R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl',
|
|
@@ -85,8 +90,25 @@ test('httpHeaderToFileName', async t => {
|
|
|
85
90
|
|
|
86
91
|
test('httpStream', async t => {
|
|
87
92
|
const server = createServer((req, res) => {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const content = JSON.stringify({ headers: req.headers })
|
|
94
|
+
|
|
95
|
+
// Handle HEAD requests
|
|
96
|
+
if (req.method === 'HEAD') {
|
|
97
|
+
res.writeHead(200, {
|
|
98
|
+
'Content-Length': String(content.length),
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
})
|
|
101
|
+
res.end()
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// This server doesn't support Range, always return 200 with full content
|
|
106
|
+
// (ignoring any Range header)
|
|
107
|
+
res.writeHead(200, {
|
|
108
|
+
'Content-Length': String(content.length),
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
})
|
|
111
|
+
res.end(content)
|
|
90
112
|
})
|
|
91
113
|
|
|
92
114
|
const host = await new Promise<string>((resolve) => {
|
|
@@ -128,14 +150,14 @@ test('httpStream in chunks', async (t) => {
|
|
|
128
150
|
|
|
129
151
|
const range = req.headers.range
|
|
130
152
|
if (range) {
|
|
131
|
-
const m = String(range).match(/bytes=(\d+)-(\d
|
|
153
|
+
const m = String(range).match(/bytes=(\d+)-(\d*)/)
|
|
132
154
|
if (!m) {
|
|
133
155
|
res.writeHead(416)
|
|
134
156
|
res.end()
|
|
135
157
|
return
|
|
136
158
|
}
|
|
137
159
|
const start = Number(m[1])
|
|
138
|
-
const end = Number(m[2])
|
|
160
|
+
const end = m[2] ? Number(m[2]) : FILE_SIZE - 1
|
|
139
161
|
const chunk = content.subarray(start, end + 1)
|
|
140
162
|
res.writeHead(206, {
|
|
141
163
|
'Accept-Ranges': 'bytes',
|
|
@@ -161,14 +183,7 @@ test('httpStream in chunks', async (t) => {
|
|
|
161
183
|
})
|
|
162
184
|
t.teardown(() => { server.close() })
|
|
163
185
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const res = await httpStream(`${host}/file`)
|
|
168
|
-
const buffer = await streamToBuffer(res)
|
|
169
|
-
t.equal(buffer.length, FILE_SIZE, 'should get data in chunks right')
|
|
170
|
-
} finally {
|
|
171
|
-
if (originalChunkSize) process.env['FILEBOX_HTTP_CHUNK_SIZE'] = originalChunkSize
|
|
172
|
-
else delete process.env['FILEBOX_HTTP_CHUNK_SIZE']
|
|
173
|
-
}
|
|
186
|
+
const res = await httpStream(`${host}/file`)
|
|
187
|
+
const buffer = await streamToBuffer(res)
|
|
188
|
+
t.equal(buffer.length, FILE_SIZE, 'should get data in chunks right')
|
|
174
189
|
})
|