mm_https 1.6.5 → 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 +68 -0
- package/index.js +272 -168
- package/package.json +4 -4
- package/test.js +214 -168
- package/npm-publish-checklist.md +0 -99
package/eslint.config.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
];
|
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
|
*/
|
|
@@ -56,9 +62,21 @@ class Http {
|
|
|
56
62
|
var headers = this.changeHeaders(config.headers);
|
|
57
63
|
Object.assign(this._headers, headers);
|
|
58
64
|
}
|
|
65
|
+
|
|
66
|
+
this._logger = $.log || console;
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 日志记录
|
|
72
|
+
* @param {string} level - 日志级别
|
|
73
|
+
* @param {string} message - 日志消息
|
|
74
|
+
* @param {...*} args - 日志参数
|
|
75
|
+
*/
|
|
76
|
+
Http.prototype.log = function (level, message, ...args) {
|
|
77
|
+
this._logger[level](`[${this.constructor.name}] ${message}`, ...args);
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
81
|
* 设置Cookie
|
|
64
82
|
* @param {Object|String} cookies - Cookie字符串
|
|
@@ -66,14 +84,9 @@ class Http {
|
|
|
66
84
|
Http.prototype.setCookie = function (cookies) {
|
|
67
85
|
if (typeof (cookies) == "string") {
|
|
68
86
|
var arr = cookies.split('; ');
|
|
69
|
-
var name;
|
|
70
87
|
if (arr.length > 0) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
var kvArr = kv.split("=");
|
|
74
|
-
name = kvArr[0];
|
|
75
|
-
this._cookies[name] = encodeURIComponent(kvArr[1]);
|
|
76
|
-
}
|
|
88
|
+
var kv_arr = arr[0].split("=");
|
|
89
|
+
this._cookies[kv_arr[0]] = encodeURIComponent(kv_arr[1]);
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
else if (typeof (cookies) == "object") {
|
|
@@ -91,8 +104,8 @@ Http.prototype.getCookie = function () {
|
|
|
91
104
|
for (var key in cookies) {
|
|
92
105
|
str += "; " + key + "=" + cookies[key];
|
|
93
106
|
}
|
|
94
|
-
var
|
|
95
|
-
return
|
|
107
|
+
var cookie_str = str.replace("; ", "");
|
|
108
|
+
return cookie_str;
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
/**
|
|
@@ -106,15 +119,15 @@ Http.prototype.changeHeaders = function (headers) {
|
|
|
106
119
|
if (headers.indexOf("{") === 0) {
|
|
107
120
|
return headers.toJson();
|
|
108
121
|
} else {
|
|
109
|
-
var
|
|
122
|
+
var header_dict = {};
|
|
110
123
|
var lines = headers.split('\n');
|
|
111
124
|
for (var i = 0; i < lines.length; i++) {
|
|
112
125
|
var line = lines[i].trim();
|
|
113
126
|
if (line) {
|
|
114
|
-
|
|
127
|
+
header_dict[line.left(":").trim()] = line.right(":").trim();
|
|
115
128
|
}
|
|
116
129
|
}
|
|
117
|
-
return
|
|
130
|
+
return header_dict;
|
|
118
131
|
}
|
|
119
132
|
} else {
|
|
120
133
|
return headers;
|
|
@@ -150,22 +163,48 @@ Http.prototype.httpOptions = function (options) {
|
|
|
150
163
|
headers,
|
|
151
164
|
method,
|
|
152
165
|
cookie,
|
|
153
|
-
type
|
|
166
|
+
type,
|
|
167
|
+
stream_callback
|
|
154
168
|
} = options;
|
|
155
|
-
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
|
+
};
|
|
156
187
|
if (cookie) {
|
|
157
188
|
this.setCookie(cookie);
|
|
158
189
|
}
|
|
159
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
|
+
}
|
|
160
196
|
return Object.assign({}, {
|
|
161
|
-
|
|
197
|
+
host_protocol: protocol,
|
|
162
198
|
body,
|
|
163
199
|
type,
|
|
164
200
|
method: method || "GET",
|
|
165
|
-
headers:
|
|
201
|
+
headers: http_headers,
|
|
166
202
|
agent: cfg.proxy ? new HttpProxyAgent(cfg.proxy) : null,
|
|
167
|
-
|
|
168
|
-
|
|
203
|
+
rejectUnauthorized: cfg.rejectUnauthorized,
|
|
204
|
+
timeout: options.timeout || cfg.timeout || 0,
|
|
205
|
+
stream_callback: stream_callback,
|
|
206
|
+
cookie: cookie_str
|
|
207
|
+
}, url_obj);
|
|
169
208
|
}
|
|
170
209
|
|
|
171
210
|
/**
|
|
@@ -174,47 +213,78 @@ Http.prototype.httpOptions = function (options) {
|
|
|
174
213
|
*/
|
|
175
214
|
Http.prototype.postOption = function (op) {
|
|
176
215
|
var param = op.body;
|
|
177
|
-
var
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
if (
|
|
181
|
-
if (!
|
|
182
|
-
|
|
183
|
-
} else if (contentType.indexOf('json') !== -1) {
|
|
184
|
-
contentType = "application/json; charset=UTF-8";
|
|
185
|
-
} else if (contentType.indexOf('html') !== -1) {
|
|
186
|
-
contentType = "text/html; charset=UTF-8";
|
|
187
|
-
} else if (contentType.indexOf('form') !== -1) {
|
|
188
|
-
contentType = "application/x-www-form-urlencoded";
|
|
189
|
-
} else {
|
|
190
|
-
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';
|
|
191
222
|
}
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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";
|
|
196
235
|
} else {
|
|
197
|
-
|
|
236
|
+
body_str = $.toXml(param);
|
|
237
|
+
content_type = "text/plain; charset=UTF-8";
|
|
198
238
|
}
|
|
199
239
|
} else {
|
|
200
|
-
|
|
201
|
-
if (!
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
} else if (
|
|
205
|
-
|
|
206
|
-
} else if (
|
|
207
|
-
|
|
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";
|
|
208
248
|
} else {
|
|
209
|
-
|
|
249
|
+
content_type = "text/plain; charset=UTF-8";
|
|
210
250
|
}
|
|
211
251
|
}
|
|
212
252
|
}
|
|
213
|
-
op.body =
|
|
253
|
+
op.body = body_str;
|
|
214
254
|
if (!op.headers['Content-Type']) {
|
|
215
|
-
op.headers['Content-Type'] =
|
|
255
|
+
op.headers['Content-Type'] = content_type;
|
|
256
|
+
}
|
|
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
|
+
}
|
|
216
285
|
}
|
|
217
|
-
|
|
286
|
+
chunks.push(Buffer.from('--' + boundary + '--\r\n'));
|
|
287
|
+
return Buffer.concat(chunks);
|
|
218
288
|
}
|
|
219
289
|
|
|
220
290
|
/**
|
|
@@ -228,57 +298,49 @@ Http.prototype.req = async function (options) {
|
|
|
228
298
|
this.postOption(op);
|
|
229
299
|
}
|
|
230
300
|
var res = await this.request(op);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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次)");
|
|
306
|
+
}
|
|
307
|
+
var location = res.headers.location;
|
|
308
|
+
if (!location) {
|
|
309
|
+
break;
|
|
239
310
|
}
|
|
240
|
-
|
|
311
|
+
var redirect_url = new URL(location, op.href).href;
|
|
312
|
+
options.href = redirect_url;
|
|
241
313
|
op = this.httpOptions(options);
|
|
242
|
-
op.referer =
|
|
314
|
+
op.referer = redirect_url;
|
|
243
315
|
res = await this.request(op);
|
|
244
316
|
}
|
|
245
317
|
return res;
|
|
246
318
|
}
|
|
247
319
|
|
|
248
320
|
/**
|
|
249
|
-
*
|
|
250
|
-
* @param {
|
|
321
|
+
* 解压数据流
|
|
322
|
+
* @param {Stream} stream - 输入数据流
|
|
323
|
+
* @param {string} encoding - 压缩方式,gzip使用gunzip,其他使用inflate
|
|
324
|
+
* @param {string} charset - 字符编码,指定时返回字符串
|
|
325
|
+
* @returns {Promise<Buffer|string>} 解压后的数据
|
|
251
326
|
*/
|
|
252
|
-
Http.prototype.undata = function (encoding) {
|
|
253
|
-
var
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
})
|
|
264
|
-
.on('end', function () {
|
|
265
|
-
if (errored) return;
|
|
266
|
-
errored = true;
|
|
267
|
-
cb(null, response, enc ? Buffer.concat(bufarr).toString(
|
|
268
|
-
enc) : Buffer.concat(bufarr));
|
|
269
|
-
})
|
|
270
|
-
.on('error', function (err) {
|
|
271
|
-
if (errored) return;
|
|
272
|
-
errored = true;
|
|
273
|
-
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);
|
|
274
338
|
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.on('error', function (err) {
|
|
278
|
-
if (errored) return;
|
|
279
|
-
errored = true;
|
|
280
|
-
cb(err, response, null);
|
|
339
|
+
stream.on('error', function (err) {
|
|
340
|
+
reject(err);
|
|
281
341
|
});
|
|
342
|
+
stream.pipe(zpipe);
|
|
343
|
+
});
|
|
282
344
|
}
|
|
283
345
|
|
|
284
346
|
/**
|
|
@@ -287,7 +349,7 @@ Http.prototype.undata = function (encoding) {
|
|
|
287
349
|
* @returns {Promise<Object>} 请求结果
|
|
288
350
|
*/
|
|
289
351
|
Http.prototype.request = function (options) {
|
|
290
|
-
var protocol = options.
|
|
352
|
+
var protocol = options.host_protocol === "https" ? https : http;
|
|
291
353
|
var self = this;
|
|
292
354
|
return new Promise(function (resolve, reject) {
|
|
293
355
|
var body = '';
|
|
@@ -295,69 +357,96 @@ Http.prototype.request = function (options) {
|
|
|
295
357
|
var {
|
|
296
358
|
headers
|
|
297
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
|
+
}
|
|
298
368
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
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
|
+
}
|
|
302
389
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (cookieList) {
|
|
329
|
-
var len = cookieList.length;
|
|
330
|
-
for (var i = 0; i < len; i++) {
|
|
331
|
-
self.setCookie(cookieList[i]);
|
|
332
|
-
}
|
|
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');
|
|
333
415
|
}
|
|
416
|
+
}
|
|
417
|
+
if (status_code >= 200 && status_code < 300) {
|
|
334
418
|
resolve({
|
|
335
|
-
status:
|
|
419
|
+
status: status_code,
|
|
336
420
|
headers,
|
|
337
421
|
buffer,
|
|
338
422
|
body
|
|
339
423
|
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
424
|
+
} else {
|
|
425
|
+
resolve({
|
|
426
|
+
headers,
|
|
427
|
+
status: {
|
|
428
|
+
code: status_code,
|
|
429
|
+
message: res.statusMessage
|
|
430
|
+
},
|
|
431
|
+
buffer,
|
|
432
|
+
body
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
351
436
|
});
|
|
352
437
|
|
|
353
438
|
req.on("error", function (err) {
|
|
354
|
-
|
|
439
|
+
self.log("debug", "错误", err);
|
|
355
440
|
resolve({
|
|
356
441
|
status: 0,
|
|
357
442
|
message: err.message
|
|
358
443
|
});
|
|
359
|
-
reject(err);
|
|
360
444
|
});
|
|
445
|
+
if (options.timeout) {
|
|
446
|
+
req.setTimeout(options.timeout, function () {
|
|
447
|
+
req.destroy(new Error("请求超时"));
|
|
448
|
+
});
|
|
449
|
+
}
|
|
361
450
|
if (options.method.toLocaleUpperCase() == "POST") {
|
|
362
451
|
req.write(options.body);
|
|
363
452
|
}
|
|
@@ -401,6 +490,25 @@ Http.prototype.post = async function (url, body, headers, type, cookie) {
|
|
|
401
490
|
});
|
|
402
491
|
};
|
|
403
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
|
+
|
|
404
512
|
/**
|
|
405
513
|
* 下载文件
|
|
406
514
|
* @param {string} url - 下载地址
|
|
@@ -411,36 +519,36 @@ Http.prototype.post = async function (url, body, headers, type, cookie) {
|
|
|
411
519
|
* @returns {Promise<string>} 成功返回保存路径,失败返回null
|
|
412
520
|
*/
|
|
413
521
|
Http.prototype.download = async function (url, filename, auto, headers, cookie) {
|
|
414
|
-
var
|
|
415
|
-
var
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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();
|
|
424
541
|
}
|
|
425
|
-
} else {
|
|
426
|
-
var contentType = res.headers["content-type"];
|
|
427
|
-
var ext = contentType.replace("image/", "").replace("application/x-", "").replace("text/", "")
|
|
428
|
-
.replace("audio/", "").replace("jpeg", "jpg");
|
|
429
|
-
filePath = filename + "." + ext;
|
|
430
542
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
var bytesWritten = await fs.writeFileSync(filePath, res.buffer, "binary");
|
|
436
|
-
if (bytesWritten == 0) {
|
|
437
|
-
filePath = null;
|
|
543
|
+
file_path = ext ? filename + "." + ext : filename;
|
|
544
|
+
if (!file_path) {
|
|
545
|
+
self.log("debug", '保存文件名(filename)不能为空!');
|
|
546
|
+
return null;
|
|
438
547
|
}
|
|
439
|
-
|
|
440
|
-
$.log.debug('保存文件名(filename)不能为!');
|
|
548
|
+
return fs.createWriteStream(file_path);
|
|
441
549
|
}
|
|
442
|
-
}
|
|
443
|
-
return
|
|
550
|
+
}));
|
|
551
|
+
return file_path;
|
|
444
552
|
};
|
|
445
553
|
|
|
446
554
|
/**
|
|
@@ -450,10 +558,6 @@ Http.prototype.clear = function () {
|
|
|
450
558
|
this._cookies = {};
|
|
451
559
|
};
|
|
452
560
|
|
|
453
|
-
if (global.$ && !$.Http) {
|
|
454
|
-
$.Http = Http;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
561
|
/**
|
|
458
562
|
* 导出构造函数
|
|
459
563
|
*/
|
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": "^
|
|
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();
|
package/npm-publish-checklist.md
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# mm_https模块 npm发布检查清单
|
|
2
|
-
|
|
3
|
-
## ✅ 已完成的项目
|
|
4
|
-
|
|
5
|
-
### 1. 代码重构和规范
|
|
6
|
-
- [x] 类名和方法名使用小驼峰命名法
|
|
7
|
-
- [x] 私有成员使用下划线前缀
|
|
8
|
-
- [x] 变量名使用小写蛇形命名法
|
|
9
|
-
- [x] 完整的JSDoc注释
|
|
10
|
-
- [x] 符合ES6类和原型方法范式
|
|
11
|
-
|
|
12
|
-
### 2. 测试文件优化
|
|
13
|
-
- [x] 创建适合npm发布的测试文件
|
|
14
|
-
- [x] 包含基础功能测试
|
|
15
|
-
- [x] 包含高级功能测试
|
|
16
|
-
- [x] 包含错误处理测试
|
|
17
|
-
- [x] 所有测试通过
|
|
18
|
-
|
|
19
|
-
### 3. 配置文件优化
|
|
20
|
-
- [x] 更新package.json描述和关键词
|
|
21
|
-
- [x] 添加Node.js版本要求
|
|
22
|
-
- [x] 优化依赖管理
|
|
23
|
-
|
|
24
|
-
### 4. 功能验证
|
|
25
|
-
- [x] 语法检查通过
|
|
26
|
-
- [x] 依赖关系正常
|
|
27
|
-
- [x] 模块功能完整
|
|
28
|
-
|
|
29
|
-
## 📋 npm发布前检查
|
|
30
|
-
|
|
31
|
-
### 版本管理
|
|
32
|
-
- [ ] 确认当前版本号:1.6.4
|
|
33
|
-
- [ ] 是否需要更新版本号
|
|
34
|
-
- [ ] 更新CHANGELOG.md(如有)
|
|
35
|
-
|
|
36
|
-
### 文档检查
|
|
37
|
-
- [ ] README.md内容完整且准确
|
|
38
|
-
- [ ] 示例代码正确
|
|
39
|
-
- [ ] 安装说明清晰
|
|
40
|
-
|
|
41
|
-
### 发布准备
|
|
42
|
-
- [ ] 登录npm账号:`npm login`
|
|
43
|
-
- [ ] 检查包名可用性:`npm search mm_https`
|
|
44
|
-
- [ ] 执行发布命令:`npm publish`
|
|
45
|
-
|
|
46
|
-
## 🚀 发布命令
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# 登录npm(如果未登录)
|
|
50
|
-
npm login
|
|
51
|
-
|
|
52
|
-
# 检查包名是否可用
|
|
53
|
-
npm search mm_https
|
|
54
|
-
|
|
55
|
-
# 发布到npm
|
|
56
|
-
npm publish
|
|
57
|
-
|
|
58
|
-
# 或者发布为公开包(如果需要)
|
|
59
|
-
npm publish --access public
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 📦 安装使用
|
|
63
|
-
|
|
64
|
-
发布后,用户可以通过以下方式安装:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
npm install mm_https
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
使用示例:
|
|
71
|
-
```javascript
|
|
72
|
-
const { Http } = require('mm_https');
|
|
73
|
-
|
|
74
|
-
async function example() {
|
|
75
|
-
const http = new Http();
|
|
76
|
-
const res = await http.get('https://httpbin.org/get');
|
|
77
|
-
console.log(res.body);
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## 🔧 开发命令
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
# 运行测试
|
|
85
|
-
npm test
|
|
86
|
-
|
|
87
|
-
# 开发模式
|
|
88
|
-
npm run dev
|
|
89
|
-
|
|
90
|
-
# 安装依赖
|
|
91
|
-
npm install
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## 📞 支持信息
|
|
95
|
-
|
|
96
|
-
- 作者:邱文武
|
|
97
|
-
- 许可证:ISC
|
|
98
|
-
- 问题反馈:https://gitee.com/qiuwenwu91/mm_https/issues
|
|
99
|
-
- 主页:https://gitee.com/qiuwenwu91/mm_https#readme
|