@faasjs/request 8.0.0-beta.5 → 8.0.0-beta.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/README.md +1 -0
- package/dist/index.cjs +225 -211
- package/dist/index.d.ts +80 -109
- package/dist/index.mjs +195 -204
- package/package.json +4 -4
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,224 +1,238 @@
|
|
|
1
|
-
'
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
//#endregion
|
|
29
|
+
let node_crypto = require("node:crypto");
|
|
30
|
+
let node_fs = require("node:fs");
|
|
31
|
+
let node_http = require("node:http");
|
|
32
|
+
node_http = __toESM(node_http);
|
|
33
|
+
let node_https = require("node:https");
|
|
34
|
+
node_https = __toESM(node_https);
|
|
35
|
+
let node_path = require("node:path");
|
|
36
|
+
let node_url = require("node:url");
|
|
37
|
+
let node_zlib = require("node:zlib");
|
|
38
|
+
let _faasjs_logger = require("@faasjs/logger");
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
40
|
+
//#region src/index.ts
|
|
41
|
+
/**
|
|
42
|
+
* FaasJS's request module.
|
|
43
|
+
*
|
|
44
|
+
* [](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
|
|
45
|
+
* [](https://www.npmjs.com/package/@faasjs/request)
|
|
46
|
+
*
|
|
47
|
+
* ## Install
|
|
48
|
+
*
|
|
49
|
+
* ```sh
|
|
50
|
+
* npm install @faasjs/request
|
|
51
|
+
* ```
|
|
52
|
+
* @packageDocumentation
|
|
53
|
+
*/
|
|
54
|
+
let mock = null;
|
|
55
|
+
/**
|
|
56
|
+
* Mock requests
|
|
57
|
+
* @param handler {function | null} null to disable mock
|
|
58
|
+
* @example setMock(async (url, options) => Promise.resolve({ headers: {}, statusCode: 200, body: { data: 'ok' } }))
|
|
59
|
+
*/
|
|
19
60
|
function setMock(handler) {
|
|
20
|
-
|
|
61
|
+
mock = handler;
|
|
21
62
|
}
|
|
22
63
|
function querystringify(obj) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
pairs.push(`${key}=${value}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return pairs.length ? pairs.join("&") : "";
|
|
64
|
+
const pairs = [];
|
|
65
|
+
let value;
|
|
66
|
+
let key;
|
|
67
|
+
for (key in obj) if (Object.hasOwn(obj, key)) {
|
|
68
|
+
value = obj[key];
|
|
69
|
+
if (!value && (value === null || value === void 0 || Number.isNaN(value))) value = "";
|
|
70
|
+
key = encodeURIComponent(key);
|
|
71
|
+
value = encodeURIComponent(value);
|
|
72
|
+
if (key === null || value === null) continue;
|
|
73
|
+
pairs.push(`${key}=${value}`);
|
|
74
|
+
}
|
|
75
|
+
return pairs.length ? pairs.join("&") : "";
|
|
39
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* ResponseError class
|
|
79
|
+
*/
|
|
40
80
|
var ResponseError = class extends Error {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
response;
|
|
82
|
+
request;
|
|
83
|
+
statusCode;
|
|
84
|
+
statusMessage;
|
|
85
|
+
headers;
|
|
86
|
+
body;
|
|
87
|
+
constructor(message, response) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.response = response;
|
|
90
|
+
this.request = response.request;
|
|
91
|
+
this.statusCode = response.statusCode || 500;
|
|
92
|
+
this.statusMessage = response.statusMessage || "Error";
|
|
93
|
+
this.headers = response.headers;
|
|
94
|
+
this.body = response.body;
|
|
95
|
+
}
|
|
56
96
|
};
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const delimiter = `${crlf}--${boundary}`;
|
|
193
|
-
const headers = [
|
|
194
|
-
`Content-Disposition: form-data; name="file"; filename="${path.basename(
|
|
195
|
-
options.file
|
|
196
|
-
)}"${crlf}`
|
|
197
|
-
];
|
|
198
|
-
const multipartBody = Buffer.concat([
|
|
199
|
-
Buffer.from(delimiter + crlf + headers.join("") + crlf),
|
|
200
|
-
fs.readFileSync(options.file),
|
|
201
|
-
Buffer.from(`${delimiter}--`)
|
|
202
|
-
]);
|
|
203
|
-
req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
|
|
204
|
-
req.setHeader("Content-Length", multipartBody.length);
|
|
205
|
-
req.write(multipartBody);
|
|
206
|
-
}
|
|
207
|
-
req.on("error", (e) => {
|
|
208
|
-
logger$1.timeEnd(requestId, "response.error %j", e);
|
|
209
|
-
reject(e);
|
|
210
|
-
});
|
|
211
|
-
req.on("timeout", () => {
|
|
212
|
-
logger$1.timeEnd(requestId, "response.timeout");
|
|
213
|
-
req.destroy();
|
|
214
|
-
reject(Error(`Timeout ${url$1}`));
|
|
215
|
-
});
|
|
216
|
-
logger$1.time(requestId);
|
|
217
|
-
req.end();
|
|
218
|
-
});
|
|
97
|
+
/**
|
|
98
|
+
* Request
|
|
99
|
+
*
|
|
100
|
+
* @param url - Request target URL.
|
|
101
|
+
* @param options - Request options.
|
|
102
|
+
* @returns Request response.
|
|
103
|
+
* @see https://faasjs.com/doc/request.html
|
|
104
|
+
*/
|
|
105
|
+
async function request(url, options = { headers: {} }) {
|
|
106
|
+
const requestId = (0, node_crypto.randomUUID)();
|
|
107
|
+
const logger = options.logger || new _faasjs_logger.Logger(`request][${requestId}`);
|
|
108
|
+
if (mock) {
|
|
109
|
+
logger.debug("mock %s %j", url, options);
|
|
110
|
+
return mock(url, options);
|
|
111
|
+
}
|
|
112
|
+
if (options.query) {
|
|
113
|
+
if (!url.includes("?")) url += "?";
|
|
114
|
+
else if (!url.endsWith("?")) url += "&";
|
|
115
|
+
url += querystringify(options.query);
|
|
116
|
+
}
|
|
117
|
+
if (!options.headers) options.headers = {};
|
|
118
|
+
const uri = new node_url.URL(url);
|
|
119
|
+
const protocol = uri.protocol === "https:" ? node_https.default : node_http.default;
|
|
120
|
+
if (!uri.protocol) throw Error("Unknown protocol");
|
|
121
|
+
const requestOptions = {
|
|
122
|
+
headers: {},
|
|
123
|
+
host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
|
|
124
|
+
method: options.method ? options.method.toUpperCase() : "GET",
|
|
125
|
+
path: uri.pathname + uri.search,
|
|
126
|
+
port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
|
|
127
|
+
timeout: options.timeout || 5e3,
|
|
128
|
+
auth: options.auth,
|
|
129
|
+
pfx: options.pfx,
|
|
130
|
+
passphrase: options.passphrase,
|
|
131
|
+
agent: options.agent
|
|
132
|
+
};
|
|
133
|
+
if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream) options.headers["Accept-Encoding"] = "br,gzip";
|
|
134
|
+
for (const key in options.headers) if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null) requestOptions.headers[key] = options.headers[key];
|
|
135
|
+
let body = options.body;
|
|
136
|
+
if (body && typeof body !== "string") if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded")) body = querystringify(body);
|
|
137
|
+
else body = JSON.stringify(body);
|
|
138
|
+
if (body && !options.headers["Content-Length"]) requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
|
|
139
|
+
return await new Promise((resolve, reject) => {
|
|
140
|
+
logger.debug("request %j", {
|
|
141
|
+
...options,
|
|
142
|
+
body
|
|
143
|
+
});
|
|
144
|
+
const req = protocol.request(requestOptions, (res) => {
|
|
145
|
+
if (options.downloadStream) {
|
|
146
|
+
options.downloadStream.on("error", (error) => {
|
|
147
|
+
logger.timeEnd(requestId, "response.error %j", error);
|
|
148
|
+
reject(error);
|
|
149
|
+
}).on("finish", () => {
|
|
150
|
+
logger.timeEnd(requestId, "response %s %s", res.statusCode, res.headers["content-type"]);
|
|
151
|
+
options.downloadStream?.end();
|
|
152
|
+
resolve(void 0);
|
|
153
|
+
});
|
|
154
|
+
res.pipe(options.downloadStream, { end: true });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (options.downloadFile) {
|
|
158
|
+
const stream = (0, node_fs.createWriteStream)(options.downloadFile).on("error", (error) => {
|
|
159
|
+
logger.timeEnd(requestId, "response.error %j", error);
|
|
160
|
+
stream.destroy();
|
|
161
|
+
reject(error);
|
|
162
|
+
}).on("finish", () => {
|
|
163
|
+
logger.timeEnd(requestId, "response %s %s %s", res.statusCode, res.headers["content-type"], stream.bytesWritten);
|
|
164
|
+
resolve(void 0);
|
|
165
|
+
});
|
|
166
|
+
res.pipe(stream, { end: true });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let stream = res;
|
|
170
|
+
switch (res.headers["content-encoding"]) {
|
|
171
|
+
case "br":
|
|
172
|
+
stream = res.pipe((0, node_zlib.createBrotliDecompress)());
|
|
173
|
+
break;
|
|
174
|
+
case "gzip":
|
|
175
|
+
stream = res.pipe((0, node_zlib.createGunzip)());
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
let raw = "";
|
|
179
|
+
stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
|
|
180
|
+
logger.timeEnd(requestId, "response.error %j", e);
|
|
181
|
+
reject(e);
|
|
182
|
+
}).on("end", () => {
|
|
183
|
+
logger.timeEnd(requestId, "response %s %s %s %j", res.statusCode, res.headers["content-type"], res.headers["content-encoding"], raw);
|
|
184
|
+
const response = Object.create(null);
|
|
185
|
+
response.request = requestOptions;
|
|
186
|
+
response.request.body = body;
|
|
187
|
+
response.statusCode = res.statusCode;
|
|
188
|
+
response.statusMessage = res.statusMessage;
|
|
189
|
+
response.headers = res.headers;
|
|
190
|
+
response.body = raw;
|
|
191
|
+
if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("["))) try {
|
|
192
|
+
response.body = (options.parse || JSON.parse)(response.body);
|
|
193
|
+
logger.debug("response.parse JSON");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.warn("response plain body", response.body);
|
|
196
|
+
logger.error(error);
|
|
197
|
+
}
|
|
198
|
+
if (response.statusCode >= 200 && response.statusCode < 400) resolve(response);
|
|
199
|
+
else {
|
|
200
|
+
logger.debug("response.error %j", response);
|
|
201
|
+
reject(new ResponseError(`${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`, response));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
if (body) req.write(body);
|
|
206
|
+
if (options.file) {
|
|
207
|
+
const crlf = "\r\n";
|
|
208
|
+
const boundary = `--${Math.random().toString(16)}`;
|
|
209
|
+
const delimiter = `${crlf}--${boundary}`;
|
|
210
|
+
const headers = [`Content-Disposition: form-data; name="file"; filename="${(0, node_path.basename)(options.file)}"${crlf}`];
|
|
211
|
+
const multipartBody = Buffer.concat([
|
|
212
|
+
Buffer.from(delimiter + crlf + headers.join("") + crlf),
|
|
213
|
+
(0, node_fs.readFileSync)(options.file),
|
|
214
|
+
Buffer.from(`${delimiter}--`)
|
|
215
|
+
]);
|
|
216
|
+
req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
|
|
217
|
+
req.setHeader("Content-Length", multipartBody.length);
|
|
218
|
+
req.write(multipartBody);
|
|
219
|
+
}
|
|
220
|
+
req.on("error", (e) => {
|
|
221
|
+
logger.timeEnd(requestId, "response.error %j", e);
|
|
222
|
+
reject(e);
|
|
223
|
+
});
|
|
224
|
+
req.on("timeout", () => {
|
|
225
|
+
logger.timeEnd(requestId, "response.timeout");
|
|
226
|
+
req.destroy();
|
|
227
|
+
reject(Error(`Timeout ${url}`));
|
|
228
|
+
});
|
|
229
|
+
logger.time(requestId);
|
|
230
|
+
req.end();
|
|
231
|
+
});
|
|
219
232
|
}
|
|
220
233
|
|
|
234
|
+
//#endregion
|
|
221
235
|
exports.ResponseError = ResponseError;
|
|
222
236
|
exports.querystringify = querystringify;
|
|
223
237
|
exports.request = request;
|
|
224
|
-
exports.setMock = setMock;
|
|
238
|
+
exports.setMock = setMock;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,90 +1,76 @@
|
|
|
1
|
-
import { OutgoingHttpHeaders } from
|
|
2
|
-
import https from
|
|
3
|
-
import { Logger } from
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* FaasJS's request module.
|
|
7
|
-
*
|
|
8
|
-
* [](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
|
|
9
|
-
* [](https://www.npmjs.com/package/@faasjs/request)
|
|
10
|
-
*
|
|
11
|
-
* ## Install
|
|
12
|
-
*
|
|
13
|
-
* ```sh
|
|
14
|
-
* npm install @faasjs/request
|
|
15
|
-
* ```
|
|
16
|
-
* @packageDocumentation
|
|
17
|
-
*/
|
|
1
|
+
import { OutgoingHttpHeaders } from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import { Logger } from "@faasjs/logger";
|
|
18
4
|
|
|
5
|
+
//#region src/index.d.ts
|
|
19
6
|
type Request = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
headers?: OutgoingHttpHeaders;
|
|
8
|
+
method?: string;
|
|
9
|
+
host?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
query?: OutgoingHttpHeaders;
|
|
12
|
+
body?: {
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
};
|
|
28
15
|
};
|
|
29
16
|
type Response<T = any> = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
request?: Request;
|
|
18
|
+
statusCode?: number;
|
|
19
|
+
statusMessage?: string;
|
|
20
|
+
headers: OutgoingHttpHeaders;
|
|
21
|
+
body: T;
|
|
35
22
|
};
|
|
36
23
|
type RequestOptions = {
|
|
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
|
-
logger?: Logger;
|
|
24
|
+
headers?: OutgoingHttpHeaders;
|
|
25
|
+
/**
|
|
26
|
+
* The HTTP method to use when making the request. Defaults to GET.
|
|
27
|
+
*/
|
|
28
|
+
method?: string;
|
|
29
|
+
query?: {
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
};
|
|
32
|
+
body?: {
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
} | string; /** Timeout in milliseconds, @default 5000 */
|
|
35
|
+
timeout?: number;
|
|
36
|
+
/**
|
|
37
|
+
* The authentication credentials to use for the request.
|
|
38
|
+
*
|
|
39
|
+
* Format: `username:password`
|
|
40
|
+
*/
|
|
41
|
+
auth?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Path of uploading a file to the server.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* await request('https://example.com', { file: 'filepath' })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
file?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Create a write stream to download a file.
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { createWriteStream } from 'fs'
|
|
55
|
+
*
|
|
56
|
+
* const stream = createWriteStream('filepath')
|
|
57
|
+
* await request('https://example.com', { downloadStream: stream })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
downloadStream?: NodeJS.WritableStream;
|
|
61
|
+
/**
|
|
62
|
+
* Path of downloading a file from the server.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* await request('https://example.com', { downloadFile: 'filepath' })
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
downloadFile?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Body parser. Defaults to `JSON.parse`.
|
|
71
|
+
*/
|
|
72
|
+
parse?: (body: string) => any;
|
|
73
|
+
logger?: Logger;
|
|
88
74
|
} & Pick<https.RequestOptions, 'pfx' | 'passphrase' | 'agent'>;
|
|
89
75
|
type Mock = (url: string, options: RequestOptions) => Promise<Response>;
|
|
90
76
|
/**
|
|
@@ -98,37 +84,22 @@ declare function querystringify(obj: any): string;
|
|
|
98
84
|
* ResponseError class
|
|
99
85
|
*/
|
|
100
86
|
declare class ResponseError extends Error {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
response: Response;
|
|
88
|
+
request: Request | undefined;
|
|
89
|
+
statusCode: number;
|
|
90
|
+
statusMessage: string;
|
|
91
|
+
headers: OutgoingHttpHeaders;
|
|
92
|
+
body: any;
|
|
93
|
+
constructor(message: string, response: Response<any>);
|
|
108
94
|
}
|
|
109
95
|
/**
|
|
110
96
|
* Request
|
|
111
97
|
*
|
|
112
|
-
* @param
|
|
113
|
-
* @param
|
|
114
|
-
* @
|
|
115
|
-
* @
|
|
116
|
-
* @param {object} [options.headers={}] Headers
|
|
117
|
-
* @param {object=} options.body Body
|
|
118
|
-
* @param {number=} options.timeout Timeout
|
|
119
|
-
* @param {string=} options.auth Auth, format: user:password
|
|
120
|
-
* @param {string=} options.file Upload file path
|
|
121
|
-
* @param {WritableStream=} options.downloadStream Download stream
|
|
122
|
-
* @param {string=} options.downloadFile Download to file
|
|
123
|
-
* @param {Buffer=} options.pfx pfx
|
|
124
|
-
* @param {string=} options.passphrase passphrase
|
|
125
|
-
* @param {boolean=} options.agent agent
|
|
126
|
-
* @param {parse=} options.parse body parser, default is JSON.parse
|
|
127
|
-
*
|
|
128
|
-
* @returns {promise}
|
|
129
|
-
*
|
|
130
|
-
* @url https://faasjs.com/doc/request.html
|
|
98
|
+
* @param url - Request target URL.
|
|
99
|
+
* @param options - Request options.
|
|
100
|
+
* @returns Request response.
|
|
101
|
+
* @see https://faasjs.com/doc/request.html
|
|
131
102
|
*/
|
|
132
103
|
declare function request<T = any>(url: string, options?: RequestOptions): Promise<Response<T>>;
|
|
133
|
-
|
|
134
|
-
export {
|
|
104
|
+
//#endregion
|
|
105
|
+
export { Mock, Request, RequestOptions, Response, ResponseError, querystringify, request, setMock };
|
package/dist/index.mjs
CHANGED
|
@@ -1,214 +1,205 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
2
|
-
import { createWriteStream, readFileSync } from
|
|
3
|
-
import http from
|
|
4
|
-
import https from
|
|
5
|
-
import { basename } from
|
|
6
|
-
import { URL } from
|
|
7
|
-
import {
|
|
8
|
-
import { Logger } from
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createWriteStream, readFileSync } from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import https from "node:https";
|
|
5
|
+
import { basename } from "node:path";
|
|
6
|
+
import { URL } from "node:url";
|
|
7
|
+
import { createBrotliDecompress, createGunzip } from "node:zlib";
|
|
8
|
+
import { Logger } from "@faasjs/logger";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
//#region src/index.ts
|
|
11
|
+
/**
|
|
12
|
+
* FaasJS's request module.
|
|
13
|
+
*
|
|
14
|
+
* [](https://github.com/faasjs/faasjs/blob/main/packages/request/LICENSE)
|
|
15
|
+
* [](https://www.npmjs.com/package/@faasjs/request)
|
|
16
|
+
*
|
|
17
|
+
* ## Install
|
|
18
|
+
*
|
|
19
|
+
* ```sh
|
|
20
|
+
* npm install @faasjs/request
|
|
21
|
+
* ```
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
let mock = null;
|
|
25
|
+
/**
|
|
26
|
+
* Mock requests
|
|
27
|
+
* @param handler {function | null} null to disable mock
|
|
28
|
+
* @example setMock(async (url, options) => Promise.resolve({ headers: {}, statusCode: 200, body: { data: 'ok' } }))
|
|
29
|
+
*/
|
|
12
30
|
function setMock(handler) {
|
|
13
|
-
|
|
31
|
+
mock = handler;
|
|
14
32
|
}
|
|
15
33
|
function querystringify(obj) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
pairs.push(`${key}=${value}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return pairs.length ? pairs.join("&") : "";
|
|
34
|
+
const pairs = [];
|
|
35
|
+
let value;
|
|
36
|
+
let key;
|
|
37
|
+
for (key in obj) if (Object.hasOwn(obj, key)) {
|
|
38
|
+
value = obj[key];
|
|
39
|
+
if (!value && (value === null || value === void 0 || Number.isNaN(value))) value = "";
|
|
40
|
+
key = encodeURIComponent(key);
|
|
41
|
+
value = encodeURIComponent(value);
|
|
42
|
+
if (key === null || value === null) continue;
|
|
43
|
+
pairs.push(`${key}=${value}`);
|
|
44
|
+
}
|
|
45
|
+
return pairs.length ? pairs.join("&") : "";
|
|
32
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* ResponseError class
|
|
49
|
+
*/
|
|
33
50
|
var ResponseError = class extends Error {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
response;
|
|
52
|
+
request;
|
|
53
|
+
statusCode;
|
|
54
|
+
statusMessage;
|
|
55
|
+
headers;
|
|
56
|
+
body;
|
|
57
|
+
constructor(message, response) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.response = response;
|
|
60
|
+
this.request = response.request;
|
|
61
|
+
this.statusCode = response.statusCode || 500;
|
|
62
|
+
this.statusMessage = response.statusMessage || "Error";
|
|
63
|
+
this.headers = response.headers;
|
|
64
|
+
this.body = response.body;
|
|
65
|
+
}
|
|
49
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Request
|
|
69
|
+
*
|
|
70
|
+
* @param url - Request target URL.
|
|
71
|
+
* @param options - Request options.
|
|
72
|
+
* @returns Request response.
|
|
73
|
+
* @see https://faasjs.com/doc/request.html
|
|
74
|
+
*/
|
|
50
75
|
async function request(url, options = { headers: {} }) {
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
if (body) req.write(body);
|
|
182
|
-
if (options.file) {
|
|
183
|
-
const crlf = "\r\n";
|
|
184
|
-
const boundary = `--${Math.random().toString(16)}`;
|
|
185
|
-
const delimiter = `${crlf}--${boundary}`;
|
|
186
|
-
const headers = [
|
|
187
|
-
`Content-Disposition: form-data; name="file"; filename="${basename(
|
|
188
|
-
options.file
|
|
189
|
-
)}"${crlf}`
|
|
190
|
-
];
|
|
191
|
-
const multipartBody = Buffer.concat([
|
|
192
|
-
Buffer.from(delimiter + crlf + headers.join("") + crlf),
|
|
193
|
-
readFileSync(options.file),
|
|
194
|
-
Buffer.from(`${delimiter}--`)
|
|
195
|
-
]);
|
|
196
|
-
req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
|
|
197
|
-
req.setHeader("Content-Length", multipartBody.length);
|
|
198
|
-
req.write(multipartBody);
|
|
199
|
-
}
|
|
200
|
-
req.on("error", (e) => {
|
|
201
|
-
logger.timeEnd(requestId, "response.error %j", e);
|
|
202
|
-
reject(e);
|
|
203
|
-
});
|
|
204
|
-
req.on("timeout", () => {
|
|
205
|
-
logger.timeEnd(requestId, "response.timeout");
|
|
206
|
-
req.destroy();
|
|
207
|
-
reject(Error(`Timeout ${url}`));
|
|
208
|
-
});
|
|
209
|
-
logger.time(requestId);
|
|
210
|
-
req.end();
|
|
211
|
-
});
|
|
76
|
+
const requestId = randomUUID();
|
|
77
|
+
const logger = options.logger || new Logger(`request][${requestId}`);
|
|
78
|
+
if (mock) {
|
|
79
|
+
logger.debug("mock %s %j", url, options);
|
|
80
|
+
return mock(url, options);
|
|
81
|
+
}
|
|
82
|
+
if (options.query) {
|
|
83
|
+
if (!url.includes("?")) url += "?";
|
|
84
|
+
else if (!url.endsWith("?")) url += "&";
|
|
85
|
+
url += querystringify(options.query);
|
|
86
|
+
}
|
|
87
|
+
if (!options.headers) options.headers = {};
|
|
88
|
+
const uri = new URL(url);
|
|
89
|
+
const protocol = uri.protocol === "https:" ? https : http;
|
|
90
|
+
if (!uri.protocol) throw Error("Unknown protocol");
|
|
91
|
+
const requestOptions = {
|
|
92
|
+
headers: {},
|
|
93
|
+
host: uri.host ? uri.host.replace(/:[0-9]+$/, "") : uri.host,
|
|
94
|
+
method: options.method ? options.method.toUpperCase() : "GET",
|
|
95
|
+
path: uri.pathname + uri.search,
|
|
96
|
+
port: uri.port || (uri.protocol === "https:" ? "443" : "80"),
|
|
97
|
+
timeout: options.timeout || 5e3,
|
|
98
|
+
auth: options.auth,
|
|
99
|
+
pfx: options.pfx,
|
|
100
|
+
passphrase: options.passphrase,
|
|
101
|
+
agent: options.agent
|
|
102
|
+
};
|
|
103
|
+
if (!options.headers["Accept-Encoding"] && !options.downloadFile && !options.downloadStream) options.headers["Accept-Encoding"] = "br,gzip";
|
|
104
|
+
for (const key in options.headers) if (typeof options.headers[key] !== "undefined" && options.headers[key] !== null) requestOptions.headers[key] = options.headers[key];
|
|
105
|
+
let body = options.body;
|
|
106
|
+
if (body && typeof body !== "string") if (options.headers["Content-Type"]?.toString().includes("application/x-www-form-urlencoded")) body = querystringify(body);
|
|
107
|
+
else body = JSON.stringify(body);
|
|
108
|
+
if (body && !options.headers["Content-Length"]) requestOptions.headers["Content-Length"] = Buffer.byteLength(body);
|
|
109
|
+
return await new Promise((resolve, reject) => {
|
|
110
|
+
logger.debug("request %j", {
|
|
111
|
+
...options,
|
|
112
|
+
body
|
|
113
|
+
});
|
|
114
|
+
const req = protocol.request(requestOptions, (res) => {
|
|
115
|
+
if (options.downloadStream) {
|
|
116
|
+
options.downloadStream.on("error", (error) => {
|
|
117
|
+
logger.timeEnd(requestId, "response.error %j", error);
|
|
118
|
+
reject(error);
|
|
119
|
+
}).on("finish", () => {
|
|
120
|
+
logger.timeEnd(requestId, "response %s %s", res.statusCode, res.headers["content-type"]);
|
|
121
|
+
options.downloadStream?.end();
|
|
122
|
+
resolve(void 0);
|
|
123
|
+
});
|
|
124
|
+
res.pipe(options.downloadStream, { end: true });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (options.downloadFile) {
|
|
128
|
+
const stream = createWriteStream(options.downloadFile).on("error", (error) => {
|
|
129
|
+
logger.timeEnd(requestId, "response.error %j", error);
|
|
130
|
+
stream.destroy();
|
|
131
|
+
reject(error);
|
|
132
|
+
}).on("finish", () => {
|
|
133
|
+
logger.timeEnd(requestId, "response %s %s %s", res.statusCode, res.headers["content-type"], stream.bytesWritten);
|
|
134
|
+
resolve(void 0);
|
|
135
|
+
});
|
|
136
|
+
res.pipe(stream, { end: true });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let stream = res;
|
|
140
|
+
switch (res.headers["content-encoding"]) {
|
|
141
|
+
case "br":
|
|
142
|
+
stream = res.pipe(createBrotliDecompress());
|
|
143
|
+
break;
|
|
144
|
+
case "gzip":
|
|
145
|
+
stream = res.pipe(createGunzip());
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
let raw = "";
|
|
149
|
+
stream.on("data", (chunk) => raw += chunk).on("error", (e) => {
|
|
150
|
+
logger.timeEnd(requestId, "response.error %j", e);
|
|
151
|
+
reject(e);
|
|
152
|
+
}).on("end", () => {
|
|
153
|
+
logger.timeEnd(requestId, "response %s %s %s %j", res.statusCode, res.headers["content-type"], res.headers["content-encoding"], raw);
|
|
154
|
+
const response = Object.create(null);
|
|
155
|
+
response.request = requestOptions;
|
|
156
|
+
response.request.body = body;
|
|
157
|
+
response.statusCode = res.statusCode;
|
|
158
|
+
response.statusMessage = res.statusMessage;
|
|
159
|
+
response.headers = res.headers;
|
|
160
|
+
response.body = raw;
|
|
161
|
+
if (response.body && response.headers["content-type"] && response.headers["content-type"].includes("application/json") && typeof response.body === "string" && (response.body.startsWith("{") || response.body.startsWith("["))) try {
|
|
162
|
+
response.body = (options.parse || JSON.parse)(response.body);
|
|
163
|
+
logger.debug("response.parse JSON");
|
|
164
|
+
} catch (error) {
|
|
165
|
+
logger.warn("response plain body", response.body);
|
|
166
|
+
logger.error(error);
|
|
167
|
+
}
|
|
168
|
+
if (response.statusCode >= 200 && response.statusCode < 400) resolve(response);
|
|
169
|
+
else {
|
|
170
|
+
logger.debug("response.error %j", response);
|
|
171
|
+
reject(new ResponseError(`${res.statusMessage || res.statusCode} ${requestOptions.host}${requestOptions.path}`, response));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
if (body) req.write(body);
|
|
176
|
+
if (options.file) {
|
|
177
|
+
const crlf = "\r\n";
|
|
178
|
+
const boundary = `--${Math.random().toString(16)}`;
|
|
179
|
+
const delimiter = `${crlf}--${boundary}`;
|
|
180
|
+
const headers = [`Content-Disposition: form-data; name="file"; filename="${basename(options.file)}"${crlf}`];
|
|
181
|
+
const multipartBody = Buffer.concat([
|
|
182
|
+
Buffer.from(delimiter + crlf + headers.join("") + crlf),
|
|
183
|
+
readFileSync(options.file),
|
|
184
|
+
Buffer.from(`${delimiter}--`)
|
|
185
|
+
]);
|
|
186
|
+
req.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
|
|
187
|
+
req.setHeader("Content-Length", multipartBody.length);
|
|
188
|
+
req.write(multipartBody);
|
|
189
|
+
}
|
|
190
|
+
req.on("error", (e) => {
|
|
191
|
+
logger.timeEnd(requestId, "response.error %j", e);
|
|
192
|
+
reject(e);
|
|
193
|
+
});
|
|
194
|
+
req.on("timeout", () => {
|
|
195
|
+
logger.timeEnd(requestId, "response.timeout");
|
|
196
|
+
req.destroy();
|
|
197
|
+
reject(Error(`Timeout ${url}`));
|
|
198
|
+
});
|
|
199
|
+
logger.time(requestId);
|
|
200
|
+
req.end();
|
|
201
|
+
});
|
|
212
202
|
}
|
|
213
203
|
|
|
214
|
-
|
|
204
|
+
//#endregion
|
|
205
|
+
export { ResponseError, querystringify, request, setMock };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faasjs/request",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-beta.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -24,16 +24,16 @@
|
|
|
24
24
|
},
|
|
25
25
|
"funding": "https://github.com/sponsors/faasjs",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "
|
|
27
|
+
"build": "tsdown src/index.ts --config ../../tsdown.config.ts"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@faasjs/logger": ">=
|
|
33
|
+
"@faasjs/logger": ">=8.0.0-beta.7"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@faasjs/logger": ">=
|
|
36
|
+
"@faasjs/logger": ">=8.0.0-beta.7"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=24.0.0",
|