mm_https 1.6.6 → 1.6.7
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/eslint.config.js +67 -67
- package/index.js +259 -163
- package/package.json +4 -4
- package/test.js +214 -168
package/eslint.config.js
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
const jsdoc = require('eslint-plugin-jsdoc');
|
|
2
|
-
|
|
3
|
-
module.exports = [
|
|
4
|
-
{
|
|
5
|
-
files: ['lib/**/*.js'],
|
|
6
|
-
languageOptions: {
|
|
7
|
-
ecmaVersion: 2020,
|
|
8
|
-
sourceType: 'script'
|
|
9
|
-
},
|
|
10
|
-
plugins: {
|
|
11
|
-
jsdoc: jsdoc
|
|
12
|
-
},
|
|
13
|
-
rules: {
|
|
14
|
-
// JSDoc相关规则 - 设置为error级别强制要求
|
|
15
|
-
'jsdoc/require-jsdoc': ['error', {
|
|
16
|
-
require: {
|
|
17
|
-
FunctionDeclaration: true,
|
|
18
|
-
MethodDefinition: true,
|
|
19
|
-
ClassDeclaration: true,
|
|
20
|
-
ArrowFunctionExpression: true
|
|
21
|
-
}
|
|
22
|
-
}],
|
|
23
|
-
'jsdoc/check-param-names': 'off',
|
|
24
|
-
'jsdoc/check-tag-names': 'off',
|
|
25
|
-
'jsdoc/check-types': 'off',
|
|
26
|
-
'jsdoc/require-param': 'off',
|
|
27
|
-
'jsdoc/require-param-description': 'off',
|
|
28
|
-
'jsdoc/require-returns': 'off',
|
|
29
|
-
'jsdoc/require-returns-description': 'off',
|
|
30
|
-
|
|
31
|
-
// 基础语法规则
|
|
32
|
-
'no-unused-vars': ['error', { args: 'none', vars: 'all' }],
|
|
33
|
-
'no-console': 'warn',
|
|
34
|
-
|
|
35
|
-
// 代码风格规则
|
|
36
|
-
'indent': ['error', 2],
|
|
37
|
-
'quotes': ['error', 'single'],
|
|
38
|
-
'semi': ['error', 'always'],
|
|
39
|
-
'comma-dangle': ['error', 'never'],
|
|
40
|
-
'no-trailing-spaces': 'error',
|
|
41
|
-
'eol-last': 'error',
|
|
42
|
-
'max-len': ['error', { code: 100 }],
|
|
43
|
-
'max-lines-per-function': ['error', { max: 40 }],
|
|
44
|
-
'complexity': ['error', 12],
|
|
45
|
-
'max-depth': ['error', 4],
|
|
46
|
-
'max-params': ['error', 4],
|
|
47
|
-
|
|
48
|
-
// 命名规则
|
|
49
|
-
'id-length': ['error', {
|
|
50
|
-
min: 1,
|
|
51
|
-
max: 20,
|
|
52
|
-
properties: 'always'
|
|
53
|
-
}]
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
files: ['test/**/*.js'],
|
|
58
|
-
languageOptions: {
|
|
59
|
-
ecmaVersion: 2020,
|
|
60
|
-
sourceType: 'script'
|
|
61
|
-
},
|
|
62
|
-
rules: {
|
|
63
|
-
// 测试文件中允许使用console
|
|
64
|
-
'no-console': 'off',
|
|
65
|
-
'no-unused-vars': 'off'
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
const jsdoc = require('eslint-plugin-jsdoc');
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
{
|
|
5
|
+
files: ['index.js','lib/**/*.js'],
|
|
6
|
+
languageOptions: {
|
|
7
|
+
ecmaVersion: 2020,
|
|
8
|
+
sourceType: 'script'
|
|
9
|
+
},
|
|
10
|
+
plugins: {
|
|
11
|
+
jsdoc: jsdoc
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
// JSDoc相关规则 - 设置为error级别强制要求
|
|
15
|
+
'jsdoc/require-jsdoc': ['error', {
|
|
16
|
+
require: {
|
|
17
|
+
FunctionDeclaration: true,
|
|
18
|
+
MethodDefinition: true,
|
|
19
|
+
ClassDeclaration: true,
|
|
20
|
+
ArrowFunctionExpression: true
|
|
21
|
+
}
|
|
22
|
+
}],
|
|
23
|
+
'jsdoc/check-param-names': 'off',
|
|
24
|
+
'jsdoc/check-tag-names': 'off',
|
|
25
|
+
'jsdoc/check-types': 'off',
|
|
26
|
+
'jsdoc/require-param': 'off',
|
|
27
|
+
'jsdoc/require-param-description': 'off',
|
|
28
|
+
'jsdoc/require-returns': 'off',
|
|
29
|
+
'jsdoc/require-returns-description': 'off',
|
|
30
|
+
|
|
31
|
+
// 基础语法规则
|
|
32
|
+
'no-unused-vars': ['error', { args: 'none', vars: 'all' }],
|
|
33
|
+
'no-console': 'warn',
|
|
34
|
+
|
|
35
|
+
// 代码风格规则
|
|
36
|
+
'indent': ['error', 2],
|
|
37
|
+
'quotes': ['error', 'single'],
|
|
38
|
+
'semi': ['error', 'always'],
|
|
39
|
+
'comma-dangle': ['error', 'never'],
|
|
40
|
+
'no-trailing-spaces': 'error',
|
|
41
|
+
'eol-last': 'error',
|
|
42
|
+
'max-len': ['error', { code: 100 }],
|
|
43
|
+
'max-lines-per-function': ['error', { max: 40 }],
|
|
44
|
+
'complexity': ['error', 12],
|
|
45
|
+
'max-depth': ['error', 4],
|
|
46
|
+
'max-params': ['error', 4],
|
|
47
|
+
|
|
48
|
+
// 命名规则
|
|
49
|
+
'id-length': ['error', {
|
|
50
|
+
min: 1,
|
|
51
|
+
max: 20,
|
|
52
|
+
properties: 'always'
|
|
53
|
+
}]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
files: ['test/**/*.js'],
|
|
58
|
+
languageOptions: {
|
|
59
|
+
ecmaVersion: 2020,
|
|
60
|
+
sourceType: 'script'
|
|
61
|
+
},
|
|
62
|
+
rules: {
|
|
63
|
+
// 测试文件中允许使用console
|
|
64
|
+
'no-console': 'off',
|
|
65
|
+
'no-unused-vars': 'off'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
68
|
];
|
package/index.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const http = require('http');
|
|
7
7
|
const https = require('https');
|
|
8
|
-
const Url = require('url');
|
|
9
8
|
const HttpProxyAgent = require('https-proxy-agent');
|
|
10
9
|
const fs = require("fs");
|
|
11
10
|
const {
|
|
11
|
+
createGunzip,
|
|
12
|
+
createInflate,
|
|
12
13
|
unzipSync
|
|
13
14
|
} = require('zlib');
|
|
14
15
|
const iconv = require('iconv-lite');
|
|
@@ -36,6 +37,11 @@ class Http {
|
|
|
36
37
|
*/
|
|
37
38
|
this._cookies = {};
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* multipart表单边界值
|
|
42
|
+
*/
|
|
43
|
+
this._boundary = "";
|
|
44
|
+
|
|
39
45
|
/**
|
|
40
46
|
* headers协议头
|
|
41
47
|
*/
|
|
@@ -78,14 +84,9 @@ Http.prototype.log = function (level, message, ...args) {
|
|
|
78
84
|
Http.prototype.setCookie = function (cookies) {
|
|
79
85
|
if (typeof (cookies) == "string") {
|
|
80
86
|
var arr = cookies.split('; ');
|
|
81
|
-
var name;
|
|
82
87
|
if (arr.length > 0) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
var kvArr = kv.split("=");
|
|
86
|
-
name = kvArr[0];
|
|
87
|
-
this._cookies[name] = encodeURIComponent(kvArr[1]);
|
|
88
|
-
}
|
|
88
|
+
var kv_arr = arr[0].split("=");
|
|
89
|
+
this._cookies[kv_arr[0]] = encodeURIComponent(kv_arr[1]);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
else if (typeof (cookies) == "object") {
|
|
@@ -103,8 +104,8 @@ Http.prototype.getCookie = function () {
|
|
|
103
104
|
for (var key in cookies) {
|
|
104
105
|
str += "; " + key + "=" + cookies[key];
|
|
105
106
|
}
|
|
106
|
-
var
|
|
107
|
-
return
|
|
107
|
+
var cookie_str = str.replace("; ", "");
|
|
108
|
+
return cookie_str;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
/**
|
|
@@ -118,15 +119,15 @@ Http.prototype.changeHeaders = function (headers) {
|
|
|
118
119
|
if (headers.indexOf("{") === 0) {
|
|
119
120
|
return headers.toJson();
|
|
120
121
|
} else {
|
|
121
|
-
var
|
|
122
|
+
var header_dict = {};
|
|
122
123
|
var lines = headers.split('\n');
|
|
123
124
|
for (var i = 0; i < lines.length; i++) {
|
|
124
125
|
var line = lines[i].trim();
|
|
125
126
|
if (line) {
|
|
126
|
-
|
|
127
|
+
header_dict[line.left(":").trim()] = line.right(":").trim();
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
|
-
return
|
|
130
|
+
return header_dict;
|
|
130
131
|
}
|
|
131
132
|
} else {
|
|
132
133
|
return headers;
|
|
@@ -162,22 +163,48 @@ Http.prototype.httpOptions = function (options) {
|
|
|
162
163
|
headers,
|
|
163
164
|
method,
|
|
164
165
|
cookie,
|
|
165
|
-
type
|
|
166
|
+
type,
|
|
167
|
+
stream_callback
|
|
166
168
|
} = options;
|
|
167
|
-
var
|
|
169
|
+
var url_obj = new URL(href);
|
|
170
|
+
var query = {};
|
|
171
|
+
url_obj.searchParams.forEach(function (value, key) {
|
|
172
|
+
query[key] = value;
|
|
173
|
+
});
|
|
174
|
+
var port = url_obj.port ? parseInt(url_obj.port, 10) : (url_obj.protocol === "https:" ? 443 : 80);
|
|
175
|
+
url_obj = {
|
|
176
|
+
href: url_obj.href,
|
|
177
|
+
protocol: url_obj.protocol,
|
|
178
|
+
host: url_obj.host,
|
|
179
|
+
hostname: url_obj.hostname,
|
|
180
|
+
port: port,
|
|
181
|
+
pathname: url_obj.pathname,
|
|
182
|
+
search: url_obj.search,
|
|
183
|
+
path: url_obj.pathname + url_obj.search,
|
|
184
|
+
query: query,
|
|
185
|
+
hash: url_obj.hash
|
|
186
|
+
};
|
|
168
187
|
if (cookie) {
|
|
169
188
|
this.setCookie(cookie);
|
|
170
189
|
}
|
|
171
190
|
var protocol = href.left(":/") || 'http';
|
|
191
|
+
var http_headers = headers ? Object.assign({}, this._headers, headers) : Object.assign({}, this._headers);
|
|
192
|
+
var cookie_str = this.getCookie();
|
|
193
|
+
if (cookie_str) {
|
|
194
|
+
http_headers['Cookie'] = cookie_str;
|
|
195
|
+
}
|
|
172
196
|
return Object.assign({}, {
|
|
173
|
-
|
|
197
|
+
host_protocol: protocol,
|
|
174
198
|
body,
|
|
175
199
|
type,
|
|
176
200
|
method: method || "GET",
|
|
177
|
-
headers:
|
|
201
|
+
headers: http_headers,
|
|
178
202
|
agent: cfg.proxy ? new HttpProxyAgent(cfg.proxy) : null,
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
rejectUnauthorized: cfg.rejectUnauthorized,
|
|
204
|
+
timeout: options.timeout || cfg.timeout || 0,
|
|
205
|
+
stream_callback: stream_callback,
|
|
206
|
+
cookie: cookie_str
|
|
207
|
+
}, url_obj);
|
|
181
208
|
}
|
|
182
209
|
|
|
183
210
|
/**
|
|
@@ -186,47 +213,78 @@ Http.prototype.httpOptions = function (options) {
|
|
|
186
213
|
*/
|
|
187
214
|
Http.prototype.postOption = function (op) {
|
|
188
215
|
var param = op.body;
|
|
189
|
-
var
|
|
190
|
-
var
|
|
191
|
-
var
|
|
192
|
-
if (
|
|
193
|
-
if (!
|
|
194
|
-
|
|
195
|
-
} else if (contentType.indexOf('json') !== -1) {
|
|
196
|
-
contentType = "application/json; charset=UTF-8";
|
|
197
|
-
} else if (contentType.indexOf('html') !== -1) {
|
|
198
|
-
contentType = "text/html; charset=UTF-8";
|
|
199
|
-
} else if (contentType.indexOf('form') !== -1) {
|
|
200
|
-
contentType = "application/x-www-form-urlencoded";
|
|
201
|
-
} else {
|
|
202
|
-
contentType = "text/plain; charset=UTF-8";
|
|
216
|
+
var body_str = "";
|
|
217
|
+
var param_type = typeof (param);
|
|
218
|
+
var content_type = op.type;
|
|
219
|
+
if (param_type === "object") {
|
|
220
|
+
if (!content_type) {
|
|
221
|
+
content_type = 'application/json; charset=UTF-8';
|
|
203
222
|
}
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
if (content_type.indexOf('multipart') !== -1 || content_type.indexOf('form-data') !== -1) {
|
|
224
|
+
body_str = this.multipartBody(param);
|
|
225
|
+
content_type = "multipart/form-data; boundary=" + this._boundary;
|
|
226
|
+
} else if (content_type.indexOf('json') !== -1) {
|
|
227
|
+
body_str = JSON.stringify(param);
|
|
228
|
+
content_type = "application/json; charset=UTF-8";
|
|
229
|
+
} else if (content_type.indexOf('html') !== -1) {
|
|
230
|
+
body_str = $.toXml(param);
|
|
231
|
+
content_type = "text/html; charset=UTF-8";
|
|
232
|
+
} else if (content_type.indexOf('form') !== -1) {
|
|
233
|
+
body_str = $.toUrl(param);
|
|
234
|
+
content_type = "application/x-www-form-urlencoded";
|
|
208
235
|
} else {
|
|
209
|
-
|
|
236
|
+
body_str = $.toXml(param);
|
|
237
|
+
content_type = "text/plain; charset=UTF-8";
|
|
210
238
|
}
|
|
211
239
|
} else {
|
|
212
|
-
|
|
213
|
-
if (!
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
} else if (
|
|
217
|
-
|
|
218
|
-
} else if (
|
|
219
|
-
|
|
240
|
+
body_str = param.trim();
|
|
241
|
+
if (!content_type) {
|
|
242
|
+
if (body_str.startsWith('{') && body_str.endsWith('}')) {
|
|
243
|
+
content_type = "application/json; charset=UTF-8";
|
|
244
|
+
} else if (body_str.startsWith('[') && body_str.endsWith(']')) {
|
|
245
|
+
content_type = "application/json; charset=UTF-8";
|
|
246
|
+
} else if (body_str.indexOf("<html>") !== -1) {
|
|
247
|
+
content_type = "text/html";
|
|
220
248
|
} else {
|
|
221
|
-
|
|
249
|
+
content_type = "text/plain; charset=UTF-8";
|
|
222
250
|
}
|
|
223
251
|
}
|
|
224
252
|
}
|
|
225
|
-
op.body =
|
|
253
|
+
op.body = body_str;
|
|
226
254
|
if (!op.headers['Content-Type']) {
|
|
227
|
-
op.headers['Content-Type'] =
|
|
255
|
+
op.headers['Content-Type'] = content_type;
|
|
228
256
|
}
|
|
229
|
-
op.headers['Content-Length'] = Buffer.byteLength(
|
|
257
|
+
op.headers['Content-Length'] = Buffer.byteLength(body_str);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 生成multipart/form-data请求体
|
|
262
|
+
* @param {Object} param - 表单参数,文件字段值为 {value, filename, content_type}
|
|
263
|
+
* @returns {Buffer} multipart请求体
|
|
264
|
+
*/
|
|
265
|
+
Http.prototype.multipartBody = function (param) {
|
|
266
|
+
var boundary = "----mm_https_" + Date.now().toString(16);
|
|
267
|
+
this._boundary = boundary;
|
|
268
|
+
var chunks = [];
|
|
269
|
+
for (var key in param) {
|
|
270
|
+
var value = param[key];
|
|
271
|
+
var value_type = typeof (value);
|
|
272
|
+
if (value_type === "object" && value !== null) {
|
|
273
|
+
var filename = value.filename || key;
|
|
274
|
+
var file_type = value.content_type || "application/octet-stream";
|
|
275
|
+
var content = value.value;
|
|
276
|
+
if (!Buffer.isBuffer(content)) {
|
|
277
|
+
content = Buffer.from(String(content));
|
|
278
|
+
}
|
|
279
|
+
chunks.push(Buffer.from('--' + boundary + '\r\nContent-Disposition: form-data; name="' + key + '"; filename="' + filename + '"\r\nContent-Type: ' + file_type + '\r\n\r\n'));
|
|
280
|
+
chunks.push(content);
|
|
281
|
+
chunks.push(Buffer.from('\r\n'));
|
|
282
|
+
} else {
|
|
283
|
+
chunks.push(Buffer.from('--' + boundary + '\r\nContent-Disposition: form-data; name="' + key + '"\r\n\r\n' + value + '\r\n'));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
chunks.push(Buffer.from('--' + boundary + '--\r\n'));
|
|
287
|
+
return Buffer.concat(chunks);
|
|
230
288
|
}
|
|
231
289
|
|
|
232
290
|
/**
|
|
@@ -240,57 +298,49 @@ Http.prototype.req = async function (options) {
|
|
|
240
298
|
this.postOption(op);
|
|
241
299
|
}
|
|
242
300
|
var res = await this.request(op);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
for (var i = 0; i < len; i++) {
|
|
249
|
-
this.setCookie(cookieList[i]);
|
|
250
|
-
}
|
|
301
|
+
var redirects = [301, 302, 303, 307, 308];
|
|
302
|
+
var count = 0;
|
|
303
|
+
while (redirects.indexOf(res.status.code) !== -1) {
|
|
304
|
+
if (++count > 5) {
|
|
305
|
+
throw new Error("重定向次数超过上限(5次)");
|
|
251
306
|
}
|
|
252
|
-
|
|
307
|
+
var location = res.headers.location;
|
|
308
|
+
if (!location) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
var redirect_url = new URL(location, op.href).href;
|
|
312
|
+
options.href = redirect_url;
|
|
253
313
|
op = this.httpOptions(options);
|
|
254
|
-
op.referer =
|
|
314
|
+
op.referer = redirect_url;
|
|
255
315
|
res = await this.request(op);
|
|
256
316
|
}
|
|
257
317
|
return res;
|
|
258
318
|
}
|
|
259
319
|
|
|
260
320
|
/**
|
|
261
|
-
*
|
|
262
|
-
* @param {
|
|
321
|
+
* 解压数据流
|
|
322
|
+
* @param {Stream} stream - 输入数据流
|
|
323
|
+
* @param {string} encoding - 压缩方式,gzip使用gunzip,其他使用inflate
|
|
324
|
+
* @param {string} charset - 字符编码,指定时返回字符串
|
|
325
|
+
* @returns {Promise<Buffer|string>} 解压后的数据
|
|
263
326
|
*/
|
|
264
|
-
Http.prototype.undata = function (encoding) {
|
|
265
|
-
var
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
})
|
|
276
|
-
.on('end', function () {
|
|
277
|
-
if (errored) return;
|
|
278
|
-
errored = true;
|
|
279
|
-
cb(null, response, enc ? Buffer.concat(bufarr).toString(
|
|
280
|
-
enc) : Buffer.concat(bufarr));
|
|
281
|
-
})
|
|
282
|
-
.on('error', function (err) {
|
|
283
|
-
if (errored) return;
|
|
284
|
-
errored = true;
|
|
285
|
-
cb(err, response, null);
|
|
327
|
+
Http.prototype.undata = function (stream, encoding, charset) {
|
|
328
|
+
var buf_arr = [];
|
|
329
|
+
return new Promise(function (resolve, reject) {
|
|
330
|
+
var zpipe = encoding ? createGunzip() : createInflate();
|
|
331
|
+
zpipe.on('data', function (d) {
|
|
332
|
+
buf_arr.push(d);
|
|
333
|
+
}).on('end', function () {
|
|
334
|
+
var buffer = Buffer.concat(buf_arr);
|
|
335
|
+
resolve(charset ? buffer.toString(charset) : buffer);
|
|
336
|
+
}).on('error', function (err) {
|
|
337
|
+
reject(err);
|
|
286
338
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
.on('error', function (err) {
|
|
290
|
-
if (errored) return;
|
|
291
|
-
errored = true;
|
|
292
|
-
cb(err, response, null);
|
|
339
|
+
stream.on('error', function (err) {
|
|
340
|
+
reject(err);
|
|
293
341
|
});
|
|
342
|
+
stream.pipe(zpipe);
|
|
343
|
+
});
|
|
294
344
|
}
|
|
295
345
|
|
|
296
346
|
/**
|
|
@@ -299,7 +349,7 @@ Http.prototype.undata = function (encoding) {
|
|
|
299
349
|
* @returns {Promise<Object>} 请求结果
|
|
300
350
|
*/
|
|
301
351
|
Http.prototype.request = function (options) {
|
|
302
|
-
var protocol = options.
|
|
352
|
+
var protocol = options.host_protocol === "https" ? https : http;
|
|
303
353
|
var self = this;
|
|
304
354
|
return new Promise(function (resolve, reject) {
|
|
305
355
|
var body = '';
|
|
@@ -307,59 +357,82 @@ Http.prototype.request = function (options) {
|
|
|
307
357
|
var {
|
|
308
358
|
headers
|
|
309
359
|
} = res;
|
|
360
|
+
var status_code = res.statusCode;
|
|
361
|
+
var cookie_list = headers['set-cookie'];
|
|
362
|
+
if (cookie_list) {
|
|
363
|
+
var len = cookie_list.length;
|
|
364
|
+
for (var i = 0; i < len; i++) {
|
|
365
|
+
self.setCookie(cookie_list[i]);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
310
368
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
var
|
|
369
|
+
// 流式下载:仅对成功响应生效
|
|
370
|
+
if (status_code >= 200 && status_code < 300 && options.stream_callback) {
|
|
371
|
+
var stream = options.stream_callback(headers);
|
|
372
|
+
if (stream) {
|
|
373
|
+
res.on('error', function (err) {
|
|
374
|
+
reject(err);
|
|
375
|
+
});
|
|
376
|
+
res.pipe(stream);
|
|
377
|
+
stream.on('finish', function () {
|
|
378
|
+
resolve({
|
|
379
|
+
status: status_code,
|
|
380
|
+
headers
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
stream.on('error', function (err) {
|
|
384
|
+
reject(err);
|
|
385
|
+
});
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
314
389
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (cookieList) {
|
|
341
|
-
var len = cookieList.length;
|
|
342
|
-
for (var i = 0; i < len; i++) {
|
|
343
|
-
self.setCookie(cookieList[i]);
|
|
344
|
-
}
|
|
390
|
+
var chunks = [];
|
|
391
|
+
var encoding = headers['content-encoding'];
|
|
392
|
+
|
|
393
|
+
res.on('data', function (chunk) {
|
|
394
|
+
chunks.push(chunk);
|
|
395
|
+
}).on('end', function () {
|
|
396
|
+
var buffer = Buffer.concat(chunks);
|
|
397
|
+
switch (encoding) {
|
|
398
|
+
case 'br':
|
|
399
|
+
case 'gzip':
|
|
400
|
+
case 'deflate':
|
|
401
|
+
buffer = unzipSync(buffer);
|
|
402
|
+
body = buffer.toString();
|
|
403
|
+
break;
|
|
404
|
+
default:
|
|
405
|
+
body = buffer.toString();
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
var charset_match = body.match(/charset=gb2312"/i);
|
|
409
|
+
if (charset_match) {
|
|
410
|
+
body = iconv.decode(buffer, 'gb2312');
|
|
411
|
+
} else {
|
|
412
|
+
charset_match = body.match(/charset=GBK"/i);
|
|
413
|
+
if (charset_match) {
|
|
414
|
+
body = iconv.decode(buffer, 'GBK');
|
|
345
415
|
}
|
|
416
|
+
}
|
|
417
|
+
if (status_code >= 200 && status_code < 300) {
|
|
346
418
|
resolve({
|
|
347
|
-
status:
|
|
419
|
+
status: status_code,
|
|
348
420
|
headers,
|
|
349
421
|
buffer,
|
|
350
422
|
body
|
|
351
423
|
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
424
|
+
} else {
|
|
425
|
+
resolve({
|
|
426
|
+
headers,
|
|
427
|
+
status: {
|
|
428
|
+
code: status_code,
|
|
429
|
+
message: res.statusMessage
|
|
430
|
+
},
|
|
431
|
+
buffer,
|
|
432
|
+
body
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
363
436
|
});
|
|
364
437
|
|
|
365
438
|
req.on("error", function (err) {
|
|
@@ -368,8 +441,12 @@ Http.prototype.request = function (options) {
|
|
|
368
441
|
status: 0,
|
|
369
442
|
message: err.message
|
|
370
443
|
});
|
|
371
|
-
reject(err);
|
|
372
444
|
});
|
|
445
|
+
if (options.timeout) {
|
|
446
|
+
req.setTimeout(options.timeout, function () {
|
|
447
|
+
req.destroy(new Error("请求超时"));
|
|
448
|
+
});
|
|
449
|
+
}
|
|
373
450
|
if (options.method.toLocaleUpperCase() == "POST") {
|
|
374
451
|
req.write(options.body);
|
|
375
452
|
}
|
|
@@ -413,6 +490,25 @@ Http.prototype.post = async function (url, body, headers, type, cookie) {
|
|
|
413
490
|
});
|
|
414
491
|
};
|
|
415
492
|
|
|
493
|
+
/**
|
|
494
|
+
* 上传文件
|
|
495
|
+
* @param {string} url - 请求地址
|
|
496
|
+
* @param {Object} body - 表单参数,文件字段值为 {value: Buffer|string, filename, content_type}
|
|
497
|
+
* @param {Object} headers - 请求头
|
|
498
|
+
* @param {string} cookie - Cookie字符串
|
|
499
|
+
* @returns {Promise<Object>} 响应结果
|
|
500
|
+
*/
|
|
501
|
+
Http.prototype.upload = async function (url, body, headers, cookie) {
|
|
502
|
+
return await this.req({
|
|
503
|
+
method: "POST",
|
|
504
|
+
href: url,
|
|
505
|
+
body,
|
|
506
|
+
type: "form-data",
|
|
507
|
+
headers,
|
|
508
|
+
cookie
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
|
|
416
512
|
/**
|
|
417
513
|
* 下载文件
|
|
418
514
|
* @param {string} url - 下载地址
|
|
@@ -423,36 +519,36 @@ Http.prototype.post = async function (url, body, headers, type, cookie) {
|
|
|
423
519
|
* @returns {Promise<string>} 成功返回保存路径,失败返回null
|
|
424
520
|
*/
|
|
425
521
|
Http.prototype.download = async function (url, filename, auto, headers, cookie) {
|
|
426
|
-
var
|
|
427
|
-
var
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
522
|
+
var self = this;
|
|
523
|
+
var file_path = null;
|
|
524
|
+
await this.request(this.httpOptions({
|
|
525
|
+
method: "GET",
|
|
526
|
+
href: url,
|
|
527
|
+
headers,
|
|
528
|
+
cookie,
|
|
529
|
+
stream_callback: function (res_headers) {
|
|
530
|
+
var ext = "";
|
|
531
|
+
if (auto) {
|
|
532
|
+
var parts = url.split('/');
|
|
533
|
+
var full_name = parts[parts.length - 1];
|
|
534
|
+
if (full_name.indexOf('.') !== -1) {
|
|
535
|
+
ext = full_name.right('.');
|
|
536
|
+
}
|
|
537
|
+
if (!ext) {
|
|
538
|
+
var content_type = res_headers["content-type"] || "";
|
|
539
|
+
ext = content_type.replace("image/", "").replace("application/x-", "").replace("text/", "")
|
|
540
|
+
.replace("audio/", "").replace("jpeg", "jpg").split(";")[0].trim();
|
|
436
541
|
}
|
|
437
|
-
} else {
|
|
438
|
-
var contentType = res.headers["content-type"];
|
|
439
|
-
var ext = contentType.replace("image/", "").replace("application/x-", "").replace("text/", "")
|
|
440
|
-
.replace("audio/", "").replace("jpeg", "jpg");
|
|
441
|
-
filePath = filename + "." + ext;
|
|
442
542
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
var bytesWritten = await fs.writeFileSync(filePath, res.buffer, "binary");
|
|
448
|
-
if (bytesWritten == 0) {
|
|
449
|
-
filePath = null;
|
|
543
|
+
file_path = ext ? filename + "." + ext : filename;
|
|
544
|
+
if (!file_path) {
|
|
545
|
+
self.log("debug", '保存文件名(filename)不能为空!');
|
|
546
|
+
return null;
|
|
450
547
|
}
|
|
451
|
-
|
|
452
|
-
this.log("debug", '保存文件名(filename)不能为!');
|
|
548
|
+
return fs.createWriteStream(file_path);
|
|
453
549
|
}
|
|
454
|
-
}
|
|
455
|
-
return
|
|
550
|
+
}));
|
|
551
|
+
return file_path;
|
|
456
552
|
};
|
|
457
553
|
|
|
458
554
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_https",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "超级美眉HTTP请求模块 - 支持自动Cookie管理、代理设置、文件下载等功能",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"node": ">=12.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"https-proxy-agent": "^
|
|
37
|
-
"iconv-lite": "^0.7.
|
|
38
|
-
"mm_expand": "^2.
|
|
36
|
+
"https-proxy-agent": "^9.1.0",
|
|
37
|
+
"iconv-lite": "^0.7.3",
|
|
38
|
+
"mm_expand": "^2.5.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/test.js
CHANGED
|
@@ -1,168 +1,214 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mm_https
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log('
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log('
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
/**
|
|
2
|
+
* mm_https 模块功能测试
|
|
3
|
+
* @author 邱文武
|
|
4
|
+
* @description 启动本地服务器,测试 GET/POST/upload/download/重定向/超时/cookie/undata 等功能
|
|
5
|
+
*/
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { Readable } = require('stream');
|
|
10
|
+
const zlib = require('zlib');
|
|
11
|
+
const { Http } = require('./index.js');
|
|
12
|
+
|
|
13
|
+
let server_port;
|
|
14
|
+
let pass_count = 0;
|
|
15
|
+
let fail_count = 0;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 断言测试结果
|
|
19
|
+
* @param {string} name - 测试名称
|
|
20
|
+
* @param {boolean} cond - 断言条件
|
|
21
|
+
* @param {string} detail - 失败详情
|
|
22
|
+
*/
|
|
23
|
+
function assert(name, cond, detail) {
|
|
24
|
+
if (cond) {
|
|
25
|
+
pass_count++;
|
|
26
|
+
console.log(' [通过] ' + name);
|
|
27
|
+
} else {
|
|
28
|
+
fail_count++;
|
|
29
|
+
console.log(' [失败] ' + name + (detail ? ' => ' + detail : ''));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 启动本地测试服务器
|
|
35
|
+
* @returns {http.Server} 服务器实例
|
|
36
|
+
*/
|
|
37
|
+
function startServer() {
|
|
38
|
+
var server = http.createServer(function (req, res) {
|
|
39
|
+
var url = req.url;
|
|
40
|
+
if (url.startsWith('/get')) {
|
|
41
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
42
|
+
res.end(JSON.stringify({ ok: true, path: url }));
|
|
43
|
+
} else if (url.startsWith('/post')) {
|
|
44
|
+
var body = [];
|
|
45
|
+
req.on('data', function (c) { body.push(c); });
|
|
46
|
+
req.on('end', function () {
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
48
|
+
res.end(JSON.stringify({ type: req.headers['content-type'], body: Buffer.concat(body).toString() }));
|
|
49
|
+
});
|
|
50
|
+
} else if (url.startsWith('/upload')) {
|
|
51
|
+
var body = [];
|
|
52
|
+
req.on('data', function (c) { body.push(c); });
|
|
53
|
+
req.on('end', function () {
|
|
54
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
55
|
+
res.end(JSON.stringify({ type: req.headers['content-type'], length: req.headers['content-length'] }));
|
|
56
|
+
});
|
|
57
|
+
} else if (url.startsWith('/download-image')) {
|
|
58
|
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
|
59
|
+
res.end(Buffer.from([137, 80, 78, 71]));
|
|
60
|
+
} else if (url.startsWith('/download')) {
|
|
61
|
+
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
|
|
62
|
+
res.end(Buffer.from([1, 2, 3, 4, 5]));
|
|
63
|
+
} else if (url.startsWith('/redirect-relative')) {
|
|
64
|
+
res.writeHead(302, { 'Location': '/get' });
|
|
65
|
+
res.end();
|
|
66
|
+
} else if (url.startsWith('/redirect-absolute')) {
|
|
67
|
+
res.writeHead(301, { 'Location': 'http://127.0.0.1:' + server_port + '/get' });
|
|
68
|
+
res.end();
|
|
69
|
+
} else if (url.startsWith('/redirect-loop')) {
|
|
70
|
+
res.writeHead(302, { 'Location': '/redirect-loop' });
|
|
71
|
+
res.end();
|
|
72
|
+
} else if (url.startsWith('/redirect-empty')) {
|
|
73
|
+
res.writeHead(302, {});
|
|
74
|
+
res.end();
|
|
75
|
+
} else if (url.startsWith('/slow')) {
|
|
76
|
+
setTimeout(function () {
|
|
77
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
78
|
+
res.end('slow response');
|
|
79
|
+
}, 3000);
|
|
80
|
+
} else if (url.startsWith('/cookie')) {
|
|
81
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': 'test=abc; Path=/' });
|
|
82
|
+
res.end('{}');
|
|
83
|
+
} else if (url.startsWith('/check-cookie')) {
|
|
84
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
85
|
+
res.end(JSON.stringify({ cookie: req.headers['cookie'] || '' }));
|
|
86
|
+
} else {
|
|
87
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
88
|
+
res.end('not found');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return server;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 主测试流程
|
|
96
|
+
*/
|
|
97
|
+
async function main() {
|
|
98
|
+
console.log('== mm_https 模块功能测试 ==');
|
|
99
|
+
var server = startServer();
|
|
100
|
+
await new Promise(function (resolve) { server.listen(0, resolve); });
|
|
101
|
+
server_port = server.address().port;
|
|
102
|
+
var client = new Http({ proxy: null });
|
|
103
|
+
var base = 'http://127.0.0.1:' + server_port;
|
|
104
|
+
var tmp_dir = path.join(__dirname, 'tmp_test');
|
|
105
|
+
fs.mkdirSync(tmp_dir, { recursive: true });
|
|
106
|
+
try {
|
|
107
|
+
// 1. GET
|
|
108
|
+
console.log('\n[GET]');
|
|
109
|
+
var res = await client.get(base + '/get?page=1&size=10');
|
|
110
|
+
assert('GET 返回200', res.status === 200);
|
|
111
|
+
assert('GET 保留查询参数', res.body.indexOf('page=1') !== -1);
|
|
112
|
+
|
|
113
|
+
// 2. POST json
|
|
114
|
+
console.log('\n[POST json]');
|
|
115
|
+
res = await client.post(base + '/post', { a: 1, b: 'x' }, null, 'json');
|
|
116
|
+
var data = JSON.parse(res.body);
|
|
117
|
+
assert('POST json 类型正确', data.type.indexOf('application/json') === 0);
|
|
118
|
+
assert('POST json 内容正确', data.body === '{"a":1,"b":"x"}');
|
|
119
|
+
|
|
120
|
+
// 3. POST form
|
|
121
|
+
console.log('\n[POST form]');
|
|
122
|
+
res = await client.post(base + '/post', { a: 1, b: 'x' }, null, 'form');
|
|
123
|
+
data = JSON.parse(res.body);
|
|
124
|
+
assert('POST form 类型正确', data.type.indexOf('application/x-www-form-urlencoded') === 0);
|
|
125
|
+
assert('POST form 内容正确', data.body.indexOf('a=1') !== -1 && data.body.indexOf('b=x') !== -1);
|
|
126
|
+
|
|
127
|
+
// 4. POST text
|
|
128
|
+
console.log('\n[POST text]');
|
|
129
|
+
res = await client.post(base + '/post', 'plain text body', null, 'text');
|
|
130
|
+
data = JSON.parse(res.body);
|
|
131
|
+
assert('POST text 内容正确', data.body === 'plain text body');
|
|
132
|
+
|
|
133
|
+
// 5. upload
|
|
134
|
+
console.log('\n[UPLOAD]');
|
|
135
|
+
res = await client.upload(base + '/upload', {
|
|
136
|
+
name: '测试文件',
|
|
137
|
+
file: { value: Buffer.from([1, 2, 3]), filename: 'a.bin', contentType: 'application/octet-stream' }
|
|
138
|
+
});
|
|
139
|
+
data = JSON.parse(res.body);
|
|
140
|
+
assert('UPLOAD 类型为multipart', data.type.indexOf('multipart/form-data; boundary=') === 0);
|
|
141
|
+
assert('UPLOAD 携带Content-Length', parseInt(data.length, 10) > 0);
|
|
142
|
+
|
|
143
|
+
// 6. download
|
|
144
|
+
console.log('\n[DOWNLOAD]');
|
|
145
|
+
var file_path = await client.download(base + '/download', path.join(tmp_dir, 'dl'));
|
|
146
|
+
assert('DOWNLOAD 返回文件路径', !!file_path);
|
|
147
|
+
var buf = fs.readFileSync(file_path);
|
|
148
|
+
assert('DOWNLOAD 文件内容正确', buf.length === 5 && buf[0] === 1 && buf[4] === 5);
|
|
149
|
+
file_path = await client.download(base + '/download-image', path.join(tmp_dir, 'img'), true);
|
|
150
|
+
assert('DOWNLOAD auto扩展名', file_path === path.join(tmp_dir, 'img') + '.png');
|
|
151
|
+
|
|
152
|
+
// 7. 重定向
|
|
153
|
+
console.log('\n[REDIRECT]');
|
|
154
|
+
res = await client.get(base + '/redirect-relative');
|
|
155
|
+
assert('302 相对路径重定向', res.status === 200 && res.body.indexOf('ok') !== -1);
|
|
156
|
+
res = await client.get(base + '/redirect-absolute');
|
|
157
|
+
assert('301 绝对路径重定向', res.status === 200);
|
|
158
|
+
res = await client.get(base + '/redirect-empty');
|
|
159
|
+
assert('302 无Location不崩溃', res.status.code === 302);
|
|
160
|
+
var loop_error = null;
|
|
161
|
+
try {
|
|
162
|
+
await client.get(base + '/redirect-loop');
|
|
163
|
+
} catch (e) {
|
|
164
|
+
loop_error = e;
|
|
165
|
+
}
|
|
166
|
+
assert('重定向循环受限', loop_error && loop_error.message.indexOf('重定向次数超过上限') !== -1);
|
|
167
|
+
|
|
168
|
+
// 8. 非2xx状态码
|
|
169
|
+
console.log('\n[状态码]');
|
|
170
|
+
res = await client.get(base + '/status-404');
|
|
171
|
+
assert('404 返回状态对象', res.status && res.status.code === 404);
|
|
172
|
+
assert('404 携带响应体', res.body.indexOf('not found') !== -1);
|
|
173
|
+
|
|
174
|
+
// 9. cookie
|
|
175
|
+
console.log('\n[COOKIE]');
|
|
176
|
+
var cookie_client = new Http({ proxy: null });
|
|
177
|
+
await cookie_client.get(base + '/cookie');
|
|
178
|
+
res = await cookie_client.get(base + '/check-cookie');
|
|
179
|
+
data = JSON.parse(res.body);
|
|
180
|
+
assert('Cookie 自动保持', data.cookie.indexOf('test=abc') !== -1);
|
|
181
|
+
|
|
182
|
+
// 10. 超时
|
|
183
|
+
console.log('\n[TIMEOUT]');
|
|
184
|
+
var slow_client = new Http({ proxy: null, timeout: 500 });
|
|
185
|
+
var start = Date.now();
|
|
186
|
+
res = await slow_client.get(base + '/slow');
|
|
187
|
+
var elapsed = Date.now() - start;
|
|
188
|
+
assert('超时返回错误对象', res.status === 0 && res.message.indexOf('超时') !== -1);
|
|
189
|
+
assert('超时耗时合理(<2秒)', elapsed < 2000);
|
|
190
|
+
|
|
191
|
+
// 11. undata 解压
|
|
192
|
+
console.log('\n[UNDATA]');
|
|
193
|
+
var gz = zlib.gzipSync(Buffer.from('hello undata'));
|
|
194
|
+
var reader = new Readable({ read: function () { this.push(gz); this.push(null); } });
|
|
195
|
+
var out = await client.undata(reader, 'gzip', 'utf8');
|
|
196
|
+
assert('undata gzip解压', out === 'hello undata');
|
|
197
|
+
var def = zlib.deflateSync(Buffer.from('hello inflate'));
|
|
198
|
+
var reader2 = new Readable({ read: function () { this.push(def); this.push(null); } });
|
|
199
|
+
out = await client.undata(reader2, null, 'utf8');
|
|
200
|
+
assert('undata deflate解压', out === 'hello inflate');
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error('测试异常:', e);
|
|
203
|
+
fail_count++;
|
|
204
|
+
} finally {
|
|
205
|
+
server.close();
|
|
206
|
+
fs.rmSync(tmp_dir, { recursive: true, force: true });
|
|
207
|
+
console.log('\n== 测试结果: 通过 ' + pass_count + ', 失败 ' + fail_count + ' ==');
|
|
208
|
+
if (fail_count > 0) {
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
main();
|