hyperttp 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Hyperttp/Core/CacheManager.d.ts +13 -54
- package/dist/Hyperttp/Core/CacheManager.d.ts.map +1 -1
- package/dist/Hyperttp/Core/CacheManager.js +28 -50
- package/dist/Hyperttp/Core/CacheManager.js.map +1 -1
- package/dist/Hyperttp/Core/HttpClientImproved.d.ts +64 -285
- package/dist/Hyperttp/Core/HttpClientImproved.d.ts.map +1 -1
- package/dist/Hyperttp/Core/HttpClientImproved.js +282 -584
- package/dist/Hyperttp/Core/HttpClientImproved.js.map +1 -1
- package/dist/Hyperttp/Core/QueueManager.d.ts.map +1 -1
- package/dist/Hyperttp/Core/QueueManager.js.map +1 -1
- package/dist/Hyperttp/Core/RateLimiter.d.ts.map +1 -1
- package/dist/Hyperttp/Core/RateLimiter.js.map +1 -1
- package/dist/Hyperttp/Core/RequestBuilder.d.ts +97 -0
- package/dist/Hyperttp/Core/RequestBuilder.d.ts.map +1 -0
- package/dist/Hyperttp/Core/RequestBuilder.js +174 -0
- package/dist/Hyperttp/Core/RequestBuilder.js.map +1 -0
- package/dist/Hyperttp/Core/index.d.ts +1 -0
- package/dist/Hyperttp/Core/index.d.ts.map +1 -1
- package/dist/Hyperttp/Core/index.js +3 -1
- package/dist/Hyperttp/Core/index.js.map +1 -1
- package/dist/Hyperttp/Request.d.ts.map +1 -1
- package/dist/Hyperttp/Request.js.map +1 -1
- package/dist/Hyperttp/UrlExtractor.d.ts.map +1 -1
- package/dist/Hyperttp/UrlExtractor.js.map +1 -1
- package/dist/Hyperttp/index.d.ts +1 -1
- package/dist/Hyperttp/index.d.ts.map +1 -1
- package/dist/Hyperttp/index.js +2 -1
- package/dist/Hyperttp/index.js.map +1 -1
- package/dist/Types/index.d.ts.map +1 -1
- package/dist/Types/index.js.map +1 -1
- package/dist/Types/request.d.ts.map +1 -1
- package/dist/Types/request.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,69 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
const tough_cookie_1 = require("tough-cookie");
|
|
4
37
|
const undici_1 = require("undici");
|
|
5
38
|
const undici_2 = require("http-cookie-agent/undici");
|
|
39
|
+
const zlib = __importStar(require("zlib"));
|
|
40
|
+
const util_1 = require("util");
|
|
6
41
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
7
|
-
const url_1 = require("url");
|
|
8
|
-
const crypto_1 = require("crypto");
|
|
9
42
|
const CacheManager_1 = require("./CacheManager");
|
|
10
43
|
const QueueManager_1 = require("./QueueManager");
|
|
11
44
|
const RateLimiter_1 = require("./RateLimiter");
|
|
12
45
|
const Types_1 = require("../../Types");
|
|
13
|
-
|
|
46
|
+
const RequestBuilder_1 = require("./RequestBuilder");
|
|
47
|
+
const gunzip = (0, util_1.promisify)(zlib.gunzip);
|
|
48
|
+
const inflate = (0, util_1.promisify)(zlib.inflate);
|
|
49
|
+
const brotliDecompress = (0, util_1.promisify)(zlib.brotliDecompress);
|
|
14
50
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Предоставляет надежный интерфейс для выполнения HTTP-запросов с автоматической обработкой
|
|
18
|
-
* распространенных паттернов, таких как повторные попытки, кэширование и перехват запросов/ответов.
|
|
19
|
-
* @en
|
|
20
|
-
* Enhanced HTTP client with caching, rate limiting, retry logic, and advanced features.
|
|
21
|
-
* Provides a robust interface for making HTTP requests with automatic handling of
|
|
22
|
-
* common patterns like retries, caching, and request/response interception.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* const client = new HttpClientImproved({
|
|
27
|
-
* timeout: 10000,
|
|
28
|
-
* maxRetries: 3,
|
|
29
|
-
* cacheTTL: 300000,
|
|
30
|
-
* rateLimit: { maxRequests: 100, windowMs: 60000 }
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* const response = await client.get('https://api.example.com/data');
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* // Using the fluent request builder
|
|
39
|
-
* const client = new HttpClientImproved();
|
|
40
|
-
* const response = await client.request('https://api.example.com/data')
|
|
41
|
-
* .headers({ 'Authorization': 'Bearer token' })
|
|
42
|
-
* .json()
|
|
43
|
-
* .send();
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* // Using RequestInterface for complex requests
|
|
49
|
-
* import { RequestInterface } from './src';
|
|
50
|
-
*
|
|
51
|
-
* class ApiRequest implements RequestInterface {
|
|
52
|
-
* constructor(
|
|
53
|
-
* private url: string,
|
|
54
|
-
* private headers: Record<string, string> = {},
|
|
55
|
-
* private body?: any
|
|
56
|
-
* ) {}
|
|
57
|
-
*
|
|
58
|
-
* getURL(): string { return this.url; }
|
|
59
|
-
* getHeaders(): Record<string, string> { return this.headers; }
|
|
60
|
-
* getBodyData(): any { return this.body; }
|
|
61
|
-
* }
|
|
62
|
-
*
|
|
63
|
-
* const client = new HttpClientImproved();
|
|
64
|
-
* const request = new ApiRequest('https://api.example.com/data');
|
|
65
|
-
* const response = await client.get(request);
|
|
66
|
-
* ```
|
|
51
|
+
* Advanced HTTP client with built-in caching, rate limiting, request queuing,
|
|
52
|
+
* automatic retries, cookie management, and response decompression.
|
|
67
53
|
*/
|
|
68
54
|
class HttpClientImproved {
|
|
69
55
|
cookieJar = new tough_cookie_1.CookieJar();
|
|
@@ -78,6 +64,10 @@ class HttpClientImproved {
|
|
|
78
64
|
requestInterceptors = [];
|
|
79
65
|
responseInterceptors = [];
|
|
80
66
|
requestMetrics = new Map();
|
|
67
|
+
/**
|
|
68
|
+
* Creates a new instance of HttpClientImproved.
|
|
69
|
+
* @param options Optional configuration options for the HTTP client
|
|
70
|
+
*/
|
|
81
71
|
constructor(options) {
|
|
82
72
|
this.options = {
|
|
83
73
|
timeout: options?.timeout ?? 15000,
|
|
@@ -106,11 +96,10 @@ class HttpClientImproved {
|
|
|
106
96
|
retryOptions: options?.retryOptions ?? {},
|
|
107
97
|
maxResponseBytes: options?.maxResponseBytes ?? 1024 * 1024,
|
|
108
98
|
verbose: false,
|
|
109
|
-
enableQueue: options?.enableQueue ??
|
|
110
|
-
enableRateLimit: options?.enableRateLimit ??
|
|
99
|
+
enableQueue: options?.enableQueue ?? false,
|
|
100
|
+
enableRateLimit: options?.enableRateLimit ?? false,
|
|
111
101
|
enableCache: options?.enableCache ?? true,
|
|
112
102
|
};
|
|
113
|
-
// Lazy initialization - only create components when needed
|
|
114
103
|
if (this.options.enableCache) {
|
|
115
104
|
this.cache = new CacheManager_1.CacheManager({
|
|
116
105
|
cacheTTL: this.options.cacheTTL,
|
|
@@ -124,7 +113,7 @@ class HttpClientImproved {
|
|
|
124
113
|
this.limiter = new RateLimiter_1.RateLimiter(this.options.rateLimit);
|
|
125
114
|
}
|
|
126
115
|
this.retryOptions = {
|
|
127
|
-
maxRetries: this.options.maxRetries,
|
|
116
|
+
maxRetries: this.options.maxRetries ?? 5,
|
|
128
117
|
baseDelay: this.options.retryOptions?.baseDelay ?? 1000,
|
|
129
118
|
maxDelay: this.options.retryOptions?.maxDelay ?? 30000,
|
|
130
119
|
retryStatusCodes: this.options.retryOptions?.retryStatusCodes ?? [
|
|
@@ -134,6 +123,7 @@ class HttpClientImproved {
|
|
|
134
123
|
};
|
|
135
124
|
this.defaultHeaders = {
|
|
136
125
|
Accept: "application/json, text/plain, */*",
|
|
126
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
137
127
|
"User-Agent": this.options.userAgent ?? "Hyperttp/0.1.0 Node.js",
|
|
138
128
|
};
|
|
139
129
|
this.agent = new undici_1.Agent({
|
|
@@ -146,97 +136,85 @@ class HttpClientImproved {
|
|
|
146
136
|
},
|
|
147
137
|
});
|
|
148
138
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Sets default headers that will be applied to all outgoing requests.
|
|
141
|
+
* @param headers An object containing header names and values
|
|
142
|
+
*/
|
|
143
|
+
setDefaultHeaders(headers) {
|
|
144
|
+
Object.assign(this.defaultHeaders, headers);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns the cookie jar used for managing HTTP cookies.
|
|
148
|
+
* @returns The CookieJar instance
|
|
149
|
+
*/
|
|
150
|
+
getCookieJar() {
|
|
151
|
+
return this.cookieJar;
|
|
161
152
|
}
|
|
162
153
|
/**
|
|
163
|
-
*
|
|
164
|
-
* @param
|
|
165
|
-
* @returns SHA1 hash of the body, truncated to 8 characters
|
|
154
|
+
* Adds a request interceptor to modify requests before they are sent.
|
|
155
|
+
* @param interceptor The interceptor function to add
|
|
166
156
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return "";
|
|
170
|
-
if (typeof body === "string")
|
|
171
|
-
body = Buffer.from(body, "utf-8");
|
|
172
|
-
return (0, crypto_1.createHash)("sha1").update(body).digest("hex").slice(0, 8);
|
|
157
|
+
addRequestInterceptor(interceptor) {
|
|
158
|
+
this.requestInterceptors.push(interceptor);
|
|
173
159
|
}
|
|
174
160
|
/**
|
|
175
|
-
*
|
|
176
|
-
* @param
|
|
177
|
-
* @returns Delay in milliseconds
|
|
161
|
+
* Adds a response interceptor to modify responses after they are received.
|
|
162
|
+
* @param interceptor The interceptor function to add
|
|
178
163
|
*/
|
|
164
|
+
addResponseInterceptor(interceptor) {
|
|
165
|
+
this.responseInterceptors.push(interceptor);
|
|
166
|
+
}
|
|
167
|
+
/** Closes the HTTP agent to properly terminate keep-alive connections. */
|
|
168
|
+
close() {
|
|
169
|
+
this.agent.close();
|
|
170
|
+
}
|
|
171
|
+
log(level, msg, meta) {
|
|
172
|
+
if (this.options.verbose) {
|
|
173
|
+
if (this.options.logger)
|
|
174
|
+
this.options.logger(level, msg, meta);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async decompress(buf, enc, charset = "utf-8") {
|
|
178
|
+
if (!enc)
|
|
179
|
+
return buf.toString(charset);
|
|
180
|
+
try {
|
|
181
|
+
switch (enc.toLowerCase()) {
|
|
182
|
+
case "gzip":
|
|
183
|
+
return (await gunzip(buf)).toString(charset);
|
|
184
|
+
case "deflate":
|
|
185
|
+
return (await inflate(buf)).toString(charset);
|
|
186
|
+
case "br":
|
|
187
|
+
return (await brotliDecompress(buf)).toString(charset);
|
|
188
|
+
default:
|
|
189
|
+
return buf.toString(charset);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
this.log("error", `Decompression failed for encoding ${enc}`, error);
|
|
194
|
+
return buf.toString(charset);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
179
197
|
calcDelay(attempt) {
|
|
180
198
|
const base = Math.min(this.retryOptions.baseDelay * 2 ** attempt, this.retryOptions.maxDelay);
|
|
181
199
|
return this.retryOptions.jitter
|
|
182
200
|
? base * (0.75 + Math.random() * 0.5)
|
|
183
201
|
: base;
|
|
184
202
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Creates a promise that resolves after the specified delay.
|
|
187
|
-
* @param ms - Delay in milliseconds
|
|
188
|
-
* @returns Promise that resolves after the delay
|
|
189
|
-
*/
|
|
190
203
|
sleep(ms) {
|
|
191
204
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
192
205
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Applies all registered request interceptors to modify the request configuration.
|
|
195
|
-
* Interceptors are executed in sequence, with each one receiving the output of the previous.
|
|
196
|
-
* @param config - Original request configuration
|
|
197
|
-
* @returns Modified request configuration
|
|
198
|
-
*/
|
|
199
206
|
async applyRequestInterceptors(config) {
|
|
200
|
-
if (!this.requestInterceptors.length)
|
|
201
|
-
return config;
|
|
202
207
|
let result = config;
|
|
203
|
-
for (const interceptor of this.requestInterceptors)
|
|
204
|
-
|
|
205
|
-
result = await interceptor(result);
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
this.log("error", "Request interceptor failed", { error });
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
208
|
+
for (const interceptor of this.requestInterceptors)
|
|
209
|
+
result = await interceptor(result);
|
|
212
210
|
return result;
|
|
213
211
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Applies all registered response interceptors to modify the response data.
|
|
216
|
-
* Interceptors are executed in sequence, with each one receiving the output of the previous.
|
|
217
|
-
* @param response - Original response data
|
|
218
|
-
* @returns Modified response data
|
|
219
|
-
*/
|
|
220
212
|
async applyResponseInterceptors(response) {
|
|
221
213
|
let result = response;
|
|
222
|
-
for (const interceptor of this.responseInterceptors)
|
|
223
|
-
|
|
224
|
-
result = await interceptor(result);
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
this.log("error", "Response interceptor failed", { error });
|
|
228
|
-
throw error;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
214
|
+
for (const interceptor of this.responseInterceptors)
|
|
215
|
+
result = await interceptor(result);
|
|
231
216
|
return result;
|
|
232
217
|
}
|
|
233
|
-
/**
|
|
234
|
-
* Resolves a redirect location relative to the base URL.
|
|
235
|
-
* Handles both absolute and relative redirect URLs.
|
|
236
|
-
* @param location - The redirect location from the response
|
|
237
|
-
* @param baseUrl - The original request URL
|
|
238
|
-
* @returns The resolved absolute URL
|
|
239
|
-
*/
|
|
240
218
|
resolveRedirect(location, baseUrl) {
|
|
241
219
|
try {
|
|
242
220
|
return new URL(location, baseUrl).toString();
|
|
@@ -245,12 +223,6 @@ class HttpClientImproved {
|
|
|
245
223
|
return location;
|
|
246
224
|
}
|
|
247
225
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Parses the Retry-After header to determine when to retry a request.
|
|
250
|
-
* Supports both seconds and HTTP date formats.
|
|
251
|
-
* @param retryAfterHeader - The Retry-After header value
|
|
252
|
-
* @returns Delay in milliseconds, or undefined if not parseable
|
|
253
|
-
*/
|
|
254
226
|
parseRetryAfterMs(retryAfterHeader) {
|
|
255
227
|
if (!retryAfterHeader)
|
|
256
228
|
return undefined;
|
|
@@ -265,69 +237,19 @@ class HttpClientImproved {
|
|
|
265
237
|
return Math.max(0, asDate - Date.now());
|
|
266
238
|
return undefined;
|
|
267
239
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
*/
|
|
274
|
-
async readBodyWithLimit(body, maxBytes = this.options.maxResponseBytes) {
|
|
275
|
-
if (!body)
|
|
276
|
-
return Buffer.alloc(0);
|
|
277
|
-
if (body[Symbol.asyncIterator]) {
|
|
278
|
-
const chunks = [];
|
|
279
|
-
let total = 0;
|
|
280
|
-
for await (const chunk of body) {
|
|
281
|
-
chunks.push(Buffer.from(chunk));
|
|
282
|
-
total += chunk.length;
|
|
283
|
-
if (total > maxBytes)
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
return Buffer.concat(chunks);
|
|
287
|
-
}
|
|
288
|
-
if (body?.arrayBuffer) {
|
|
289
|
-
const arrayBuffer = await body.arrayBuffer();
|
|
290
|
-
return Buffer.from(arrayBuffer);
|
|
240
|
+
async readBodyWithLimit(body) {
|
|
241
|
+
const buf = Buffer.from(await body.arrayBuffer());
|
|
242
|
+
const limit = this.options.maxResponseBytes;
|
|
243
|
+
if (typeof limit === "number" && limit > 0 && buf.length > limit) {
|
|
244
|
+
throw new Types_1.HttpClientError(`Response too large (${buf.length} bytes), limit is ${limit}`, 0);
|
|
291
245
|
}
|
|
292
|
-
|
|
293
|
-
return body.subarray(0, maxBytes);
|
|
294
|
-
if (typeof body === "string")
|
|
295
|
-
return Buffer.from(body);
|
|
296
|
-
throw new TypeError(`Unsupported body type: ${typeof body}`);
|
|
246
|
+
return buf;
|
|
297
247
|
}
|
|
298
|
-
/**
|
|
299
|
-
* Removes old metrics entries to prevent memory leaks.
|
|
300
|
-
* Keeps only metrics from the last 24 hours.
|
|
301
|
-
*/
|
|
302
|
-
trimMetrics() {
|
|
303
|
-
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
304
|
-
for (const [key, metrics] of this.requestMetrics) {
|
|
305
|
-
if (metrics.endTime && metrics.endTime < cutoff) {
|
|
306
|
-
this.requestMetrics.delete(key);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
for (const key of this.inflight.keys()) {
|
|
310
|
-
if (this.inflight.size > 1000) {
|
|
311
|
-
this.inflight.delete(key);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Sends an HTTP request with retry logic and rate limiting.
|
|
317
|
-
* Handles timeouts, redirects, and various retry scenarios.
|
|
318
|
-
* @param method - HTTP method (GET, POST, etc.)
|
|
319
|
-
* @param url - Target URL
|
|
320
|
-
* @param headers - HTTP headers
|
|
321
|
-
* @param body - Request body (optional)
|
|
322
|
-
* @param metrics - Optional metrics object to track request details
|
|
323
|
-
* @param redirects - Number of redirects followed so far
|
|
324
|
-
* @returns Promise resolving to the response data
|
|
325
|
-
*/
|
|
326
248
|
async sendWithRetry(method, url, headers, body, metrics, redirects = 0) {
|
|
327
249
|
let lastError;
|
|
328
250
|
for (let attempt = 0; attempt <= this.retryOptions.maxRetries; attempt++) {
|
|
329
251
|
try {
|
|
330
|
-
if (this.limiter) {
|
|
252
|
+
if (this.limiter && this.options.enableRateLimit) {
|
|
331
253
|
await this.limiter.wait();
|
|
332
254
|
}
|
|
333
255
|
const finalConfig = await this.applyRequestInterceptors({
|
|
@@ -337,7 +259,7 @@ class HttpClientImproved {
|
|
|
337
259
|
body,
|
|
338
260
|
});
|
|
339
261
|
const controller = new AbortController();
|
|
340
|
-
const timeout = this.options.timeout;
|
|
262
|
+
const timeout = this.options.timeout ?? 15000;
|
|
341
263
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
342
264
|
try {
|
|
343
265
|
const res = await (0, undici_1.request)(finalConfig.url, {
|
|
@@ -347,15 +269,6 @@ class HttpClientImproved {
|
|
|
347
269
|
dispatcher: this.agent,
|
|
348
270
|
signal: controller.signal,
|
|
349
271
|
});
|
|
350
|
-
if (method === "HEAD") {
|
|
351
|
-
clearTimeout(timer);
|
|
352
|
-
return {
|
|
353
|
-
status: res.statusCode,
|
|
354
|
-
headers: res.headers,
|
|
355
|
-
body: Buffer.alloc(0),
|
|
356
|
-
url: finalConfig.url,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
272
|
clearTimeout(timer);
|
|
360
273
|
const buf = await this.readBodyWithLimit(res.body);
|
|
361
274
|
let response = await this.applyResponseInterceptors({
|
|
@@ -364,9 +277,7 @@ class HttpClientImproved {
|
|
|
364
277
|
body: buf,
|
|
365
278
|
url: finalConfig.url,
|
|
366
279
|
});
|
|
367
|
-
|
|
368
|
-
throw new Types_1.HttpClientError(`Request failed with status ${response.status}`, response.status, undefined, finalConfig.url, finalConfig.method);
|
|
369
|
-
}
|
|
280
|
+
// Redirects
|
|
370
281
|
if (this.options.followRedirects &&
|
|
371
282
|
[301, 302, 303, 307, 308].includes(response.status) &&
|
|
372
283
|
redirects < (this.options.maxRedirects ?? 5)) {
|
|
@@ -376,6 +287,7 @@ class HttpClientImproved {
|
|
|
376
287
|
const redirectMethod = response.status === 303 ? "GET" : method;
|
|
377
288
|
const nextHeaders = { ...headers };
|
|
378
289
|
let nextBody = body;
|
|
290
|
+
// If switching to GET, drop body-related headers.
|
|
379
291
|
if (redirectMethod === "GET") {
|
|
380
292
|
nextBody = undefined;
|
|
381
293
|
delete nextHeaders["content-type"];
|
|
@@ -390,6 +302,7 @@ class HttpClientImproved {
|
|
|
390
302
|
return this.sendWithRetry(redirectMethod, nextUrl, nextHeaders, nextBody, metrics, redirects + 1);
|
|
391
303
|
}
|
|
392
304
|
}
|
|
305
|
+
// Retry by status
|
|
393
306
|
if (this.retryOptions.retryStatusCodes.includes(response.status)) {
|
|
394
307
|
metrics && (metrics.retries += 1);
|
|
395
308
|
if (response.status === 429) {
|
|
@@ -441,11 +354,6 @@ class HttpClientImproved {
|
|
|
441
354
|
throw lastError;
|
|
442
355
|
throw new Types_1.HttpClientError(`Request failed after ${this.retryOptions.maxRetries + 1} attempts`, undefined, lastError instanceof Error ? lastError : undefined, url, method);
|
|
443
356
|
}
|
|
444
|
-
/**
|
|
445
|
-
* Parses the Content-Type header to extract MIME type and character encoding.
|
|
446
|
-
* @param contentType - Content-Type header value
|
|
447
|
-
* @returns Object containing type and charset information
|
|
448
|
-
*/
|
|
449
357
|
parseContentType(contentType) {
|
|
450
358
|
if (!contentType)
|
|
451
359
|
return { type: "text/plain", charset: "utf-8" };
|
|
@@ -469,35 +377,41 @@ class HttpClientImproved {
|
|
|
469
377
|
"base64",
|
|
470
378
|
"hex",
|
|
471
379
|
];
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
* Handles JSON, XML, text, and buffer responses with fallback parsing.
|
|
482
|
-
* @param res - HTTP response object
|
|
483
|
-
* @param responseType - Desired response type
|
|
484
|
-
* @returns Parsed response data
|
|
485
|
-
*/
|
|
380
|
+
const charset = (allowed.includes(normalized)
|
|
381
|
+
? normalized
|
|
382
|
+
: "utf-8");
|
|
383
|
+
return { type, charset };
|
|
384
|
+
}
|
|
385
|
+
xmlParser = new fast_xml_parser_1.XMLParser({
|
|
386
|
+
ignoreAttributes: false,
|
|
387
|
+
allowBooleanAttributes: true,
|
|
388
|
+
});
|
|
486
389
|
async parseResponse(res, responseType) {
|
|
487
|
-
const { type, charset } = this.parseContentType(res.headers["content-type"]);
|
|
488
|
-
const text = res.body.toString(charset);
|
|
489
|
-
const finalType = responseType ?? (type.includes("application/json") ? "json" : "text");
|
|
490
390
|
try {
|
|
391
|
+
const contentType = res.headers["content-type"] || "";
|
|
392
|
+
const text = await this.decompress(res.body, res.headers["content-encoding"]);
|
|
393
|
+
const finalType = responseType ?? "json";
|
|
491
394
|
switch (finalType) {
|
|
492
|
-
case "json":
|
|
493
|
-
|
|
395
|
+
case "json": {
|
|
396
|
+
if (contentType.includes("json")) {
|
|
397
|
+
return JSON.parse(text);
|
|
398
|
+
}
|
|
399
|
+
if (text.trim().startsWith("<")) {
|
|
400
|
+
return this.xmlParser.parse(text);
|
|
401
|
+
}
|
|
402
|
+
return { text };
|
|
403
|
+
}
|
|
494
404
|
case "xml": {
|
|
405
|
+
const text = await this.decompress(res.body, res.headers["content-encoding"]);
|
|
406
|
+
if (text.trim().startsWith("<")) {
|
|
407
|
+
return text;
|
|
408
|
+
}
|
|
495
409
|
try {
|
|
496
|
-
const
|
|
497
|
-
return new fast_xml_parser_1.XMLBuilder({ format: true }).build({ root:
|
|
410
|
+
const json = JSON.parse(text);
|
|
411
|
+
return new fast_xml_parser_1.XMLBuilder({ format: true }).build({ root: json });
|
|
498
412
|
}
|
|
499
413
|
catch {
|
|
500
|
-
return text;
|
|
414
|
+
return String(text);
|
|
501
415
|
}
|
|
502
416
|
}
|
|
503
417
|
case "text":
|
|
@@ -505,91 +419,21 @@ class HttpClientImproved {
|
|
|
505
419
|
case "buffer":
|
|
506
420
|
return res.body;
|
|
507
421
|
case "stream":
|
|
508
|
-
|
|
422
|
+
return res.body;
|
|
509
423
|
default:
|
|
510
424
|
return text;
|
|
511
425
|
}
|
|
512
426
|
}
|
|
513
|
-
catch (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* @param method - HTTP method
|
|
521
|
-
* @param req - Request configuration
|
|
522
|
-
* @param responseType - Expected response type
|
|
523
|
-
* @returns Promise resolving to the response data
|
|
524
|
-
*/
|
|
525
|
-
async requestInternalWithoutCache(method, req, responseType) {
|
|
526
|
-
const url = req.getURL();
|
|
527
|
-
const rawBody = req.getBodyData();
|
|
528
|
-
const headers = {
|
|
529
|
-
...this.defaultHeaders,
|
|
530
|
-
...req.getHeaders(),
|
|
531
|
-
};
|
|
532
|
-
const isBodyAllowed = ["POST", "PUT", "PATCH", "DELETE"].includes(method);
|
|
533
|
-
let body;
|
|
534
|
-
const contentType = headers["content-type"] || headers["Content-Type"] || "";
|
|
535
|
-
if (isBodyAllowed && rawBody !== undefined && rawBody !== null) {
|
|
536
|
-
if (Buffer.isBuffer(rawBody)) {
|
|
537
|
-
body = rawBody;
|
|
538
|
-
}
|
|
539
|
-
else if (typeof rawBody === "string") {
|
|
540
|
-
body = rawBody;
|
|
541
|
-
}
|
|
542
|
-
else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
543
|
-
body = new url_1.URLSearchParams(rawBody).toString();
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
body = JSON.stringify(rawBody);
|
|
547
|
-
if (!contentType)
|
|
548
|
-
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
549
|
-
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
this.log("error", "Failed to parse response", {
|
|
429
|
+
error,
|
|
430
|
+
status: res.status,
|
|
431
|
+
contentType: res.headers["content-type"],
|
|
432
|
+
});
|
|
433
|
+
throw new Types_1.HttpClientError(`Response parsing failed: ${error instanceof Error ? error.message : String(error)}`, res.status);
|
|
550
434
|
}
|
|
551
|
-
const metrics = {
|
|
552
|
-
startTime: Date.now(),
|
|
553
|
-
endTime: 0,
|
|
554
|
-
duration: 0,
|
|
555
|
-
bytesReceived: 0,
|
|
556
|
-
bytesSent: 0,
|
|
557
|
-
retries: 0,
|
|
558
|
-
cached: false,
|
|
559
|
-
url,
|
|
560
|
-
method,
|
|
561
|
-
};
|
|
562
|
-
const result = await (this.queue.enqueue(async () => {
|
|
563
|
-
const res = await this.sendWithRetry(method, url, headers, body, metrics);
|
|
564
|
-
metrics.statusCode = res.status;
|
|
565
|
-
metrics.bytesReceived = res.body.length;
|
|
566
|
-
metrics.bytesSent =
|
|
567
|
-
body instanceof Buffer ? body.length : Buffer.byteLength(body || "");
|
|
568
|
-
if (method === "HEAD") {
|
|
569
|
-
return { status: res.status, headers: res.headers };
|
|
570
|
-
}
|
|
571
|
-
const parsed = await this.parseResponse(res, responseType);
|
|
572
|
-
return parsed;
|
|
573
|
-
}) ?? Promise.resolve(undefined));
|
|
574
|
-
metrics.endTime = Date.now();
|
|
575
|
-
metrics.duration = metrics.endTime - metrics.startTime;
|
|
576
|
-
this.requestMetrics.set(url, metrics);
|
|
577
|
-
this.trimMetrics();
|
|
578
|
-
return result;
|
|
579
435
|
}
|
|
580
|
-
/**
|
|
581
|
-
* Makes an HTTP request with caching support.
|
|
582
|
-
* Handles cache lookups, request deduplication, and automatic cache storage.
|
|
583
|
-
* @param method - HTTP method
|
|
584
|
-
* @param req - Request configuration
|
|
585
|
-
* @param useCache - Whether to use caching (default: true)
|
|
586
|
-
* @param responseType - Expected response type
|
|
587
|
-
* @returns Promise resolving to the response data
|
|
588
|
-
*/
|
|
589
436
|
async requestInternal(method, req, useCache = true, responseType) {
|
|
590
|
-
if (this.options.cacheTTL === 0) {
|
|
591
|
-
return this.requestInternalWithoutCache(method, req, responseType);
|
|
592
|
-
}
|
|
593
437
|
const url = req.getURL();
|
|
594
438
|
const rawBody = req.getBodyData();
|
|
595
439
|
const headers = {
|
|
@@ -607,28 +451,26 @@ class HttpClientImproved {
|
|
|
607
451
|
body = rawBody;
|
|
608
452
|
}
|
|
609
453
|
else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
610
|
-
body = new
|
|
454
|
+
body = new URLSearchParams(rawBody).toString();
|
|
611
455
|
}
|
|
612
456
|
else {
|
|
613
457
|
body = JSON.stringify(rawBody);
|
|
614
|
-
if (!contentType)
|
|
458
|
+
if (!contentType) {
|
|
615
459
|
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
460
|
+
}
|
|
616
461
|
}
|
|
617
462
|
}
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
.digest("hex");
|
|
622
|
-
if (this.options.cacheMethods.includes(method) && useCache && this.cache) {
|
|
623
|
-
const cached = this.cache.get(cacheKey);
|
|
463
|
+
const key = `${method}:${url}:${body ?? ""}`;
|
|
464
|
+
if (method === "GET" && useCache && this.cache) {
|
|
465
|
+
const cached = await this.cache.get(key);
|
|
624
466
|
if (cached) {
|
|
625
467
|
this.log("debug", `Cache hit for ${url}`);
|
|
626
468
|
return cached;
|
|
627
469
|
}
|
|
628
470
|
}
|
|
629
|
-
if (this.inflight.has(
|
|
471
|
+
if (this.inflight.has(key)) {
|
|
630
472
|
this.log("debug", `Deduplicating request for ${url}`);
|
|
631
|
-
return this.inflight.get(
|
|
473
|
+
return this.inflight.get(key);
|
|
632
474
|
}
|
|
633
475
|
const promise = (async () => {
|
|
634
476
|
const metrics = {
|
|
@@ -641,58 +483,55 @@ class HttpClientImproved {
|
|
|
641
483
|
cached: false,
|
|
642
484
|
url,
|
|
643
485
|
method,
|
|
644
|
-
bodyHash,
|
|
645
486
|
};
|
|
646
487
|
try {
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
488
|
+
let result;
|
|
489
|
+
const res = await this.sendWithRetry(method, url, headers, body, metrics);
|
|
490
|
+
metrics.statusCode = res.status;
|
|
491
|
+
metrics.bytesReceived = res.body.length;
|
|
492
|
+
metrics.bytesSent =
|
|
493
|
+
body instanceof Buffer ? body.length : Buffer.byteLength(body || "");
|
|
494
|
+
if (method === "HEAD") {
|
|
495
|
+
metrics.endTime = Date.now();
|
|
496
|
+
metrics.duration = metrics.endTime - metrics.startTime;
|
|
497
|
+
this.requestMetrics.set(key, metrics);
|
|
498
|
+
this.log("info", `${method} ${url} completed in ${metrics.duration}ms`, metrics);
|
|
499
|
+
return {
|
|
500
|
+
status: res.status,
|
|
501
|
+
headers: res.headers,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const parsed = await this.parseResponse(res, responseType);
|
|
505
|
+
if (method === "GET" && useCache && this.cache) {
|
|
506
|
+
this.cache.set(key, parsed);
|
|
507
|
+
metrics.cached = true;
|
|
508
|
+
}
|
|
509
|
+
result = parsed;
|
|
668
510
|
metrics.endTime = Date.now();
|
|
669
511
|
metrics.duration = metrics.endTime - metrics.startTime;
|
|
670
|
-
this.requestMetrics.set(
|
|
671
|
-
this.trimMetrics();
|
|
512
|
+
this.requestMetrics.set(key, metrics);
|
|
672
513
|
this.log("info", `${method} ${url} completed in ${metrics.duration}ms`, metrics);
|
|
673
514
|
return result;
|
|
674
515
|
}
|
|
675
516
|
catch (error) {
|
|
676
517
|
metrics.endTime = Date.now();
|
|
677
518
|
metrics.duration = metrics.endTime - metrics.startTime;
|
|
678
|
-
this.requestMetrics.set(
|
|
679
|
-
this.trimMetrics();
|
|
519
|
+
this.requestMetrics.set(key, metrics);
|
|
680
520
|
throw error;
|
|
681
521
|
}
|
|
682
522
|
finally {
|
|
683
|
-
this.inflight.delete(
|
|
523
|
+
this.inflight.delete(key);
|
|
684
524
|
}
|
|
685
525
|
})();
|
|
686
|
-
this.inflight.set(
|
|
526
|
+
this.inflight.set(key, promise);
|
|
687
527
|
return promise;
|
|
688
528
|
}
|
|
689
529
|
/**
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
* @
|
|
694
|
-
* @
|
|
695
|
-
* @returns Promise resolving to the response data
|
|
530
|
+
* Performs an HTTP GET request.
|
|
531
|
+
* @param req The request object containing URL and headers
|
|
532
|
+
* @param responseType Optional response parsing type
|
|
533
|
+
* @returns A promise that resolves to the parsed response
|
|
534
|
+
* @template T The expected response type
|
|
696
535
|
*/
|
|
697
536
|
get(req, responseType = "json") {
|
|
698
537
|
if (typeof req === "string") {
|
|
@@ -708,13 +547,11 @@ class HttpClientImproved {
|
|
|
708
547
|
}
|
|
709
548
|
}
|
|
710
549
|
/**
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
* @
|
|
715
|
-
* @
|
|
716
|
-
* @param responseType - Expected response type (default: "json")
|
|
717
|
-
* @returns Promise resolving to the response data
|
|
550
|
+
* Performs an HTTP POST request.
|
|
551
|
+
* @param req The request object containing URL, body, and headers
|
|
552
|
+
* @param responseType Optional response parsing type
|
|
553
|
+
* @returns A promise that resolves to the parsed response
|
|
554
|
+
* @template T The expected response type
|
|
718
555
|
*/
|
|
719
556
|
post(req, body, responseType = "json") {
|
|
720
557
|
if (typeof req === "string") {
|
|
@@ -730,13 +567,11 @@ class HttpClientImproved {
|
|
|
730
567
|
}
|
|
731
568
|
}
|
|
732
569
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
* @
|
|
737
|
-
* @
|
|
738
|
-
* @param responseType - Expected response type (default: "json")
|
|
739
|
-
* @returns Promise resolving to the response data
|
|
570
|
+
* Performs an HTTP PUT request.
|
|
571
|
+
* @param req The request object containing URL, body, and headers
|
|
572
|
+
* @param responseType Optional response parsing type
|
|
573
|
+
* @returns A promise that resolves to the parsed response
|
|
574
|
+
* @template T The expected response type
|
|
740
575
|
*/
|
|
741
576
|
put(req, body, responseType = "json") {
|
|
742
577
|
if (typeof req === "string") {
|
|
@@ -750,70 +585,94 @@ class HttpClientImproved {
|
|
|
750
585
|
return this.requestInternal("PUT", req, false, responseType);
|
|
751
586
|
}
|
|
752
587
|
/**
|
|
753
|
-
*
|
|
754
|
-
* @
|
|
588
|
+
* Performs an HTTP DELETE request.
|
|
589
|
+
* @param req The request object containing URL and headers
|
|
590
|
+
* @param responseType Optional response parsing type
|
|
591
|
+
* @returns A promise that resolves to the parsed response
|
|
592
|
+
* @template T The expected response type
|
|
755
593
|
*/
|
|
756
|
-
|
|
594
|
+
delete(req, responseType = "json") {
|
|
757
595
|
if (typeof req === "string") {
|
|
596
|
+
const client = new HttpClientImproved();
|
|
758
597
|
const simpleReq = {
|
|
759
598
|
getURL: () => req,
|
|
760
599
|
getBodyData: () => undefined,
|
|
761
600
|
getHeaders: () => ({}),
|
|
762
601
|
};
|
|
763
|
-
return
|
|
602
|
+
return client.delete(simpleReq, responseType);
|
|
764
603
|
}
|
|
765
|
-
return this.
|
|
766
|
-
const url = req.getURL();
|
|
767
|
-
const headers = { ...this.defaultHeaders, ...req.getHeaders() };
|
|
768
|
-
const response = await (0, undici_1.request)(url, {
|
|
769
|
-
method: "GET",
|
|
770
|
-
headers,
|
|
771
|
-
dispatcher: this.agent,
|
|
772
|
-
});
|
|
773
|
-
return {
|
|
774
|
-
status: response.statusCode,
|
|
775
|
-
headers: response.headers,
|
|
776
|
-
body: response.body,
|
|
777
|
-
url,
|
|
778
|
-
};
|
|
779
|
-
}.bind(this));
|
|
604
|
+
return this.requestInternal("DELETE", req, false, responseType);
|
|
780
605
|
}
|
|
781
606
|
/**
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
* @
|
|
786
|
-
* @
|
|
787
|
-
* @returns Promise resolving to the response data
|
|
607
|
+
* Performs an HTTP PATCH request.
|
|
608
|
+
* @param req The request object containing URL, body, and headers
|
|
609
|
+
* @param responseType Optional response parsing type
|
|
610
|
+
* @returns A promise that resolves to the parsed response
|
|
611
|
+
* @template T The expected response type
|
|
788
612
|
*/
|
|
789
|
-
|
|
613
|
+
patch(req, body, responseType = "json") {
|
|
790
614
|
if (typeof req === "string") {
|
|
791
|
-
const client = defaultClient ?? (defaultClient = new HttpClientImproved());
|
|
792
615
|
const simpleReq = {
|
|
793
616
|
getURL: () => req,
|
|
794
|
-
getBodyData: () =>
|
|
795
|
-
getHeaders: () => ({}),
|
|
617
|
+
getBodyData: () => body,
|
|
618
|
+
getHeaders: () => ({ "Content-Type": "application/json" }),
|
|
796
619
|
};
|
|
797
|
-
return
|
|
620
|
+
return this.requestInternal("PATCH", simpleReq, false, responseType);
|
|
798
621
|
}
|
|
799
|
-
return this.requestInternal("
|
|
622
|
+
return this.requestInternal("PATCH", req, false, responseType);
|
|
800
623
|
}
|
|
801
624
|
/**
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
* @param req - Request configuration
|
|
805
|
-
* @param responseType - Expected response type (default: "json")
|
|
806
|
-
* @returns Promise resolving to the response data
|
|
625
|
+
* @ru Получает потоковый ответ (для SSE, больших файлов).
|
|
626
|
+
* @en Gets streaming response (for SSE, large files).
|
|
807
627
|
*/
|
|
808
|
-
|
|
809
|
-
|
|
628
|
+
stream(req) {
|
|
629
|
+
if (typeof req === "string") {
|
|
630
|
+
const simpleReq = {
|
|
631
|
+
getURL: () => req,
|
|
632
|
+
getBodyData: () => undefined,
|
|
633
|
+
getHeaders: () => ({}),
|
|
634
|
+
};
|
|
635
|
+
return this.stream(simpleReq);
|
|
636
|
+
}
|
|
637
|
+
return (this.queue && this.options.enableQueue
|
|
638
|
+
? this.queue.enqueue(async function () {
|
|
639
|
+
const url = req.getURL();
|
|
640
|
+
const headers = {
|
|
641
|
+
...this.defaultHeaders,
|
|
642
|
+
...req.getHeaders(),
|
|
643
|
+
};
|
|
644
|
+
const response = await (0, undici_1.request)(url, {
|
|
645
|
+
method: "GET",
|
|
646
|
+
headers,
|
|
647
|
+
dispatcher: this.agent,
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
status: response.statusCode,
|
|
651
|
+
headers: response.headers,
|
|
652
|
+
body: response.body,
|
|
653
|
+
url,
|
|
654
|
+
};
|
|
655
|
+
}.bind(this))
|
|
656
|
+
: async function () {
|
|
657
|
+
const url = req.getURL();
|
|
658
|
+
const headers = Object.assign({}, this.defaultHeaders, req.getHeaders());
|
|
659
|
+
const response = await (0, undici_1.request)(url, {
|
|
660
|
+
method: "GET",
|
|
661
|
+
headers,
|
|
662
|
+
dispatcher: this.agent,
|
|
663
|
+
});
|
|
664
|
+
return {
|
|
665
|
+
status: response.statusCode,
|
|
666
|
+
headers: response.headers,
|
|
667
|
+
body: response.body,
|
|
668
|
+
url,
|
|
669
|
+
};
|
|
670
|
+
}.bind(this)());
|
|
810
671
|
}
|
|
811
672
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
* @param req - Request configuration or URL string
|
|
816
|
-
* @returns Promise resolving to status and headers
|
|
673
|
+
* Performs an HTTP HEAD request.
|
|
674
|
+
* @param req The request object containing URL and headers
|
|
675
|
+
* @returns A promise that resolves when the request completes
|
|
817
676
|
*/
|
|
818
677
|
async head(req) {
|
|
819
678
|
if (typeof req === "string") {
|
|
@@ -827,12 +686,11 @@ class HttpClientImproved {
|
|
|
827
686
|
return this.requestInternal("HEAD", req, false);
|
|
828
687
|
}
|
|
829
688
|
/**
|
|
830
|
-
* Clears the
|
|
831
|
-
* Removes all cached responses and resets the cache state.
|
|
689
|
+
* Clears the request cache.
|
|
832
690
|
*/
|
|
833
|
-
clearCache() {
|
|
691
|
+
async clearCache() {
|
|
834
692
|
if (this.cache) {
|
|
835
|
-
this.cache.clear();
|
|
693
|
+
await this.cache.clear();
|
|
836
694
|
this.log("info", "Cache cleared");
|
|
837
695
|
}
|
|
838
696
|
}
|
|
@@ -866,187 +724,27 @@ class HttpClientImproved {
|
|
|
866
724
|
* @returns RequestBuilder instance for chaining
|
|
867
725
|
*/
|
|
868
726
|
request(url) {
|
|
869
|
-
return new RequestBuilder(url);
|
|
727
|
+
return new RequestBuilder_1.RequestBuilder(url);
|
|
870
728
|
}
|
|
871
729
|
/**
|
|
872
730
|
* Returns current statistics about the HTTP client's state.
|
|
873
|
-
*
|
|
874
|
-
* @returns Object containing various client statistics
|
|
731
|
+
* @returns An object containing cache size, request counts, and rate limit information
|
|
875
732
|
*/
|
|
876
733
|
getStats() {
|
|
877
734
|
return {
|
|
878
735
|
cacheSize: this.cache?.size ?? 0,
|
|
879
736
|
inflightRequests: this.inflight.size,
|
|
880
|
-
queuedRequests: this.queue
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
737
|
+
queuedRequests: this.queue && this.options.enableQueue
|
|
738
|
+
? (this.queue.queuedCount ?? 0)
|
|
739
|
+
: 0,
|
|
740
|
+
activeRequests: this.queue && this.options.enableQueue
|
|
741
|
+
? (this.queue.activeCount ?? 0)
|
|
742
|
+
: 0,
|
|
743
|
+
currentRateLimit: this.limiter && this.options.enableRateLimit
|
|
744
|
+
? (this.limiter.currentCount ?? 0)
|
|
745
|
+
: 0,
|
|
884
746
|
};
|
|
885
747
|
}
|
|
886
748
|
}
|
|
887
749
|
exports.default = HttpClientImproved;
|
|
888
|
-
/**
|
|
889
|
-
* Fluent request builder for making HTTP requests with a chainable API.
|
|
890
|
-
* Provides a convenient way to build and send HTTP requests with various options.
|
|
891
|
-
*
|
|
892
|
-
* @example
|
|
893
|
-
* ```ts
|
|
894
|
-
* const client = new HttpClientImproved();
|
|
895
|
-
* const response = await client.request('https://api.example.com/data')
|
|
896
|
-
* .headers({ 'Authorization': 'Bearer token' })
|
|
897
|
-
* .query({ limit: 10, offset: 0 })
|
|
898
|
-
* .json()
|
|
899
|
-
* .send();
|
|
900
|
-
* ```
|
|
901
|
-
*/
|
|
902
|
-
class RequestBuilder {
|
|
903
|
-
_url;
|
|
904
|
-
_method = "GET";
|
|
905
|
-
_headers = {};
|
|
906
|
-
_body;
|
|
907
|
-
_responseType = "json";
|
|
908
|
-
/**
|
|
909
|
-
* Creates a new request builder for the specified URL.
|
|
910
|
-
* @param url - The target URL for the request
|
|
911
|
-
*/
|
|
912
|
-
constructor(url) {
|
|
913
|
-
this._url = url;
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* Sets HTTP headers for the request.
|
|
917
|
-
* @param headers - Object containing header key-value pairs
|
|
918
|
-
* @returns The builder instance for chaining
|
|
919
|
-
*/
|
|
920
|
-
headers(headers) {
|
|
921
|
-
this._headers = headers;
|
|
922
|
-
return this;
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Sets the request body data.
|
|
926
|
-
* @param bodyData - The body data to send with the request
|
|
927
|
-
* @returns The builder instance for chaining
|
|
928
|
-
*/
|
|
929
|
-
body(bodyData) {
|
|
930
|
-
this._body = bodyData;
|
|
931
|
-
return this;
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Sets the response type to JSON.
|
|
935
|
-
* @returns The builder instance for chaining
|
|
936
|
-
*/
|
|
937
|
-
json() {
|
|
938
|
-
this._responseType = "json";
|
|
939
|
-
return this;
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Sets the response type to plain text.
|
|
943
|
-
* @returns The builder instance for chaining
|
|
944
|
-
*/
|
|
945
|
-
text() {
|
|
946
|
-
this._responseType = "text";
|
|
947
|
-
return this;
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Sets the response type to XML.
|
|
951
|
-
* @returns The builder instance for chaining
|
|
952
|
-
*/
|
|
953
|
-
xml() {
|
|
954
|
-
this._responseType = "xml";
|
|
955
|
-
return this;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Sets the HTTP method to POST.
|
|
959
|
-
* @returns The builder instance for chaining
|
|
960
|
-
*/
|
|
961
|
-
post() {
|
|
962
|
-
this._method = "POST";
|
|
963
|
-
return this;
|
|
964
|
-
}
|
|
965
|
-
/**
|
|
966
|
-
* @ru Устанавливает потоковый режим ответа.
|
|
967
|
-
* @en Sets streaming response mode.
|
|
968
|
-
*/
|
|
969
|
-
stream() {
|
|
970
|
-
this._responseType = "stream";
|
|
971
|
-
return this;
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Sets the HTTP method to PUT.
|
|
975
|
-
* @returns The builder instance for chaining
|
|
976
|
-
*/
|
|
977
|
-
put() {
|
|
978
|
-
this._method = "PUT";
|
|
979
|
-
return this;
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Sets the HTTP method to PATCH.
|
|
983
|
-
* @returns The builder instance for chaining
|
|
984
|
-
*/
|
|
985
|
-
patch() {
|
|
986
|
-
this._method = "PATCH";
|
|
987
|
-
return this;
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Sets the HTTP method to DELETE.
|
|
991
|
-
* @returns The builder instance for chaining
|
|
992
|
-
*/
|
|
993
|
-
delete() {
|
|
994
|
-
this._method = "DELETE";
|
|
995
|
-
return this;
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* Adds query parameters to the URL.
|
|
999
|
-
* @param params - Object containing query parameter key-value pairs
|
|
1000
|
-
* @returns The builder instance for chaining
|
|
1001
|
-
*/
|
|
1002
|
-
query(params) {
|
|
1003
|
-
const urlObj = new URL(this._url);
|
|
1004
|
-
Object.entries(params).forEach(([k, v]) => urlObj.searchParams.set(k, String(v)));
|
|
1005
|
-
this._url = urlObj.toString();
|
|
1006
|
-
return this;
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Sets a JSON body for the request.
|
|
1010
|
-
* Automatically sets the Content-Type header to application/json.
|
|
1011
|
-
* @param body - The JSON body data
|
|
1012
|
-
* @returns The builder instance for chaining
|
|
1013
|
-
*/
|
|
1014
|
-
jsonBody(body) {
|
|
1015
|
-
this._body = body;
|
|
1016
|
-
this._headers["Content-Type"] = "application/json; charset=utf-8";
|
|
1017
|
-
return this;
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Sends the HTTP request and returns the response.
|
|
1021
|
-
* @returns Promise resolving to the response data
|
|
1022
|
-
*/
|
|
1023
|
-
async send() {
|
|
1024
|
-
const client = defaultClient ?? (defaultClient = new HttpClientImproved());
|
|
1025
|
-
const req = {
|
|
1026
|
-
getURL: () => this._url,
|
|
1027
|
-
getBodyData: () => this._body,
|
|
1028
|
-
getHeaders: () => this._headers,
|
|
1029
|
-
};
|
|
1030
|
-
switch (this._method) {
|
|
1031
|
-
case "GET":
|
|
1032
|
-
if (this._responseType === "stream") {
|
|
1033
|
-
return client.stream(req);
|
|
1034
|
-
}
|
|
1035
|
-
return client.get(req, this._responseType);
|
|
1036
|
-
case "POST":
|
|
1037
|
-
return client.post(req, undefined, this._responseType);
|
|
1038
|
-
case "PUT":
|
|
1039
|
-
return client.put(req, this._responseType);
|
|
1040
|
-
case "DELETE":
|
|
1041
|
-
return client.delete(req, this._responseType);
|
|
1042
|
-
case "PATCH":
|
|
1043
|
-
return client.patch(req, this._responseType);
|
|
1044
|
-
default:
|
|
1045
|
-
if (this._responseType === "stream") {
|
|
1046
|
-
return client.stream(req);
|
|
1047
|
-
}
|
|
1048
|
-
return client.get(req, this._responseType);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
750
|
//# sourceMappingURL=HttpClientImproved.js.map
|