@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.2
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/CHANGELOG.md +5 -0
- package/dist/runtime/http.js +28 -338
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/stealth.js +57 -203
- package/dist/server/serve.js +5 -2
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +1 -0
- package/package.json +1 -1
- package/src/runtime/http.ts +60 -547
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/stealth.ts +100 -361
- package/src/server/serve.ts +8 -2
- package/src/server/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.2
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit ceefad020a1038eade542fd3b128667b39625f6f.
|
|
6
|
+
|
|
3
7
|
## 2.2.0-beta.1
|
|
4
8
|
|
|
5
9
|
- Release candidate for main commit 5056b8c89fe0fa8f10bafcd30f83bcc421d4b5c5.
|
|
@@ -35,6 +39,7 @@
|
|
|
35
39
|
## Unreleased
|
|
36
40
|
|
|
37
41
|
- Add `arrayBuffer()` and `bytes()` to `HttpResponse` so `ctx.http` consumers can read binary-safe upstream bodies; internal response handling is now byte-first.
|
|
42
|
+
- Preserve identity-only operation `connectionId` values in `ProviderContext` without requiring credential material.
|
|
38
43
|
|
|
39
44
|
## 2.1.0-beta.15
|
|
40
45
|
|
package/dist/runtime/http.js
CHANGED
|
@@ -1,317 +1,12 @@
|
|
|
1
1
|
import { resolveProxyConfigAsync } from "../config/loader";
|
|
2
2
|
import { ProviderError, TransportError } from "../errors";
|
|
3
|
-
import { parseSseStream, readableBytes, readableLines, readableTextChunks
|
|
4
|
-
import {
|
|
3
|
+
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream";
|
|
4
|
+
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy";
|
|
5
5
|
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
|
|
6
6
|
const DEFAULT_HTTP_BASE_URL = "http://localhost";
|
|
7
7
|
function isHttpStatusOutcome(outcome) {
|
|
8
8
|
return "kind" in outcome && outcome.kind === "http-status";
|
|
9
9
|
}
|
|
10
|
-
const DEFAULT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS"];
|
|
11
|
-
const DEFAULT_RETRY_ERROR_CODES = [
|
|
12
|
-
"transport_network_error",
|
|
13
|
-
"transport_timeout",
|
|
14
|
-
];
|
|
15
|
-
const SAFE_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
16
|
-
const RATE_LIMIT_RETRY_STATUS_CODES = [429, 503];
|
|
17
|
-
const KNOWN_RETRY_METHODS = new Set([
|
|
18
|
-
"GET",
|
|
19
|
-
"HEAD",
|
|
20
|
-
"POST",
|
|
21
|
-
"PUT",
|
|
22
|
-
"DELETE",
|
|
23
|
-
"OPTIONS",
|
|
24
|
-
"TRACE",
|
|
25
|
-
"PATCH",
|
|
26
|
-
]);
|
|
27
|
-
const UNSAFE_RETRY_METHODS = new Set([
|
|
28
|
-
"POST",
|
|
29
|
-
"PUT",
|
|
30
|
-
"PATCH",
|
|
31
|
-
"DELETE",
|
|
32
|
-
"TRACE",
|
|
33
|
-
]);
|
|
34
|
-
const MAX_RETRY_ATTEMPTS = 8;
|
|
35
|
-
const MAX_RETRY_DELAY_MS = 30_000;
|
|
36
|
-
function hasOwnValue(values, value) {
|
|
37
|
-
if (typeof value !== "string")
|
|
38
|
-
return false;
|
|
39
|
-
return Object.values(values).some((candidate) => candidate === value);
|
|
40
|
-
}
|
|
41
|
-
function createInvalidRetryPolicyError(message) {
|
|
42
|
-
return new ProviderError(message, { code: "retry_invalid_policy" });
|
|
43
|
-
}
|
|
44
|
-
function createRetryOptions(preset) {
|
|
45
|
-
switch (preset) {
|
|
46
|
-
case HttpRetryPreset.Off:
|
|
47
|
-
return {
|
|
48
|
-
preset,
|
|
49
|
-
attempts: 1,
|
|
50
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
51
|
-
statusCodes: [],
|
|
52
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
53
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
54
|
-
baseDelayMs: 100,
|
|
55
|
-
maxDelayMs: 1_000,
|
|
56
|
-
jitter: HttpRetryJitter.Full,
|
|
57
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
58
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
59
|
-
};
|
|
60
|
-
case HttpRetryPreset.SafeRead:
|
|
61
|
-
return {
|
|
62
|
-
preset,
|
|
63
|
-
attempts: 3,
|
|
64
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
65
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
66
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
67
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
68
|
-
baseDelayMs: 100,
|
|
69
|
-
maxDelayMs: 2_000,
|
|
70
|
-
jitter: HttpRetryJitter.Full,
|
|
71
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
72
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
73
|
-
};
|
|
74
|
-
case HttpRetryPreset.AggressiveRead:
|
|
75
|
-
return {
|
|
76
|
-
preset,
|
|
77
|
-
attempts: 4,
|
|
78
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
79
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
80
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
81
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
82
|
-
baseDelayMs: 150,
|
|
83
|
-
maxDelayMs: 5_000,
|
|
84
|
-
jitter: HttpRetryJitter.Full,
|
|
85
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
86
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
87
|
-
};
|
|
88
|
-
case HttpRetryPreset.RateLimitAware:
|
|
89
|
-
return {
|
|
90
|
-
preset,
|
|
91
|
-
attempts: 3,
|
|
92
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
93
|
-
statusCodes: RATE_LIMIT_RETRY_STATUS_CODES,
|
|
94
|
-
errorCodes: ["transport_timeout"],
|
|
95
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
96
|
-
baseDelayMs: 250,
|
|
97
|
-
maxDelayMs: 5_000,
|
|
98
|
-
jitter: HttpRetryJitter.Equal,
|
|
99
|
-
retryAfter: HttpRetryAfterPolicy.Respect,
|
|
100
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
101
|
-
};
|
|
102
|
-
case HttpRetryPreset.TransportTransient:
|
|
103
|
-
return {
|
|
104
|
-
preset,
|
|
105
|
-
attempts: 3,
|
|
106
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
107
|
-
statusCodes: [],
|
|
108
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
109
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
110
|
-
baseDelayMs: 100,
|
|
111
|
-
maxDelayMs: 1_000,
|
|
112
|
-
jitter: HttpRetryJitter.Full,
|
|
113
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
114
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${preset}`);
|
|
118
|
-
}
|
|
119
|
-
function clampPositiveInteger(value, fallback, max) {
|
|
120
|
-
if (value === undefined)
|
|
121
|
-
return fallback;
|
|
122
|
-
if (!Number.isFinite(value) || value < 1)
|
|
123
|
-
return fallback;
|
|
124
|
-
return Math.min(Math.floor(value), max);
|
|
125
|
-
}
|
|
126
|
-
function clampDelay(value, fallback) {
|
|
127
|
-
if (value === undefined)
|
|
128
|
-
return fallback;
|
|
129
|
-
if (!Number.isFinite(value) || value < 0)
|
|
130
|
-
return fallback;
|
|
131
|
-
return Math.min(Math.floor(value), MAX_RETRY_DELAY_MS);
|
|
132
|
-
}
|
|
133
|
-
function normalizeRetryOptions(retry) {
|
|
134
|
-
if (retry === undefined || retry === false) {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
if (retry === true) {
|
|
138
|
-
return createRetryOptions(HttpRetryPreset.TransportTransient);
|
|
139
|
-
}
|
|
140
|
-
if (typeof retry === "string") {
|
|
141
|
-
if (!hasOwnValue(HttpRetryPreset, retry)) {
|
|
142
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${retry}`);
|
|
143
|
-
}
|
|
144
|
-
return createRetryOptions(retry);
|
|
145
|
-
}
|
|
146
|
-
if (typeof retry !== "object" || retry === null) {
|
|
147
|
-
throw createInvalidRetryPolicyError("HTTP retry policy must be an object");
|
|
148
|
-
}
|
|
149
|
-
if (Array.isArray(retry)) {
|
|
150
|
-
throw createInvalidRetryPolicyError("HTTP retry policy must be a plain object");
|
|
151
|
-
}
|
|
152
|
-
validateRetryOptionsShape(retry);
|
|
153
|
-
const base = createRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient);
|
|
154
|
-
const maxDelayMs = clampDelay(retry.maxDelayMs, base.maxDelayMs);
|
|
155
|
-
const normalized = {
|
|
156
|
-
preset: retry.preset ?? base.preset,
|
|
157
|
-
attempts: clampPositiveInteger(retry.attempts, base.attempts, MAX_RETRY_ATTEMPTS),
|
|
158
|
-
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
159
|
-
statusCodes: retry.statusCodes?.filter((status) => Number.isInteger(status)) ??
|
|
160
|
-
base.statusCodes,
|
|
161
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
162
|
-
delayStrategy: retry.delayStrategy ?? base.delayStrategy,
|
|
163
|
-
baseDelayMs: clampDelay(retry.baseDelayMs, base.baseDelayMs),
|
|
164
|
-
maxDelayMs,
|
|
165
|
-
jitter: retry.jitter ?? base.jitter,
|
|
166
|
-
retryAfter: retry.retryAfter ?? base.retryAfter,
|
|
167
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
168
|
-
};
|
|
169
|
-
return normalized;
|
|
170
|
-
}
|
|
171
|
-
function validateRetryOptionsShape(retry) {
|
|
172
|
-
if (retry.preset !== undefined &&
|
|
173
|
-
!hasOwnValue(HttpRetryPreset, retry.preset)) {
|
|
174
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${String(retry.preset)}`);
|
|
175
|
-
}
|
|
176
|
-
if (retry.delayStrategy !== undefined &&
|
|
177
|
-
!hasOwnValue(HttpRetryDelayStrategy, retry.delayStrategy)) {
|
|
178
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry delay strategy: ${String(retry.delayStrategy)}`);
|
|
179
|
-
}
|
|
180
|
-
if (retry.jitter !== undefined &&
|
|
181
|
-
!hasOwnValue(HttpRetryJitter, retry.jitter)) {
|
|
182
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry jitter policy: ${String(retry.jitter)}`);
|
|
183
|
-
}
|
|
184
|
-
if (retry.retryAfter !== undefined &&
|
|
185
|
-
!hasOwnValue(HttpRetryAfterPolicy, retry.retryAfter)) {
|
|
186
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry-after policy: ${String(retry.retryAfter)}`);
|
|
187
|
-
}
|
|
188
|
-
if (retry.unsafeMethodPolicy !== undefined &&
|
|
189
|
-
!hasOwnValue(HttpRetryUnsafeMethodPolicy, retry.unsafeMethodPolicy)) {
|
|
190
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`);
|
|
191
|
-
}
|
|
192
|
-
if (retry.methods !== undefined) {
|
|
193
|
-
if (!Array.isArray(retry.methods)) {
|
|
194
|
-
throw createInvalidRetryPolicyError("HTTP retry methods must be an array");
|
|
195
|
-
}
|
|
196
|
-
const nonStringMethods = retry.methods.filter((method) => typeof method !== "string");
|
|
197
|
-
if (nonStringMethods.length > 0) {
|
|
198
|
-
throw createInvalidRetryPolicyError("HTTP retry methods must contain only strings");
|
|
199
|
-
}
|
|
200
|
-
const unknownMethods = retry.methods
|
|
201
|
-
.map((method) => method.toUpperCase())
|
|
202
|
-
.filter((method) => !KNOWN_RETRY_METHODS.has(method));
|
|
203
|
-
if (unknownMethods.length > 0) {
|
|
204
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry method(s): ${unknownMethods.join(", ")}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (retry.statusCodes !== undefined) {
|
|
208
|
-
if (!Array.isArray(retry.statusCodes)) {
|
|
209
|
-
throw createInvalidRetryPolicyError("HTTP retry statusCodes must be an array");
|
|
210
|
-
}
|
|
211
|
-
const invalidStatusCodes = retry.statusCodes.filter((status) => !Number.isInteger(status) ||
|
|
212
|
-
Number(status) < 100 ||
|
|
213
|
-
Number(status) > 599);
|
|
214
|
-
if (invalidStatusCodes.length > 0) {
|
|
215
|
-
throw createInvalidRetryPolicyError("HTTP retry statusCodes must contain HTTP status integers in [100, 599]");
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (retry.errorCodes !== undefined) {
|
|
219
|
-
if (!Array.isArray(retry.errorCodes)) {
|
|
220
|
-
throw createInvalidRetryPolicyError("HTTP retry errorCodes must be an array");
|
|
221
|
-
}
|
|
222
|
-
const nonStringErrorCodes = retry.errorCodes.filter((errorCode) => typeof errorCode !== "string");
|
|
223
|
-
if (nonStringErrorCodes.length > 0) {
|
|
224
|
-
throw createInvalidRetryPolicyError("HTTP retry errorCodes must contain only strings");
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (retry.preset === HttpRetryPreset.Off &&
|
|
228
|
-
((retry.attempts !== undefined && retry.attempts > 1) ||
|
|
229
|
-
(retry.statusCodes !== undefined && retry.statusCodes.length > 0))) {
|
|
230
|
-
throw createInvalidRetryPolicyError("HTTP retry preset off cannot be combined with retry-enabling overrides");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function validateUnsafeRetryMethods(options) {
|
|
234
|
-
if (options.unsafeMethodPolicy ===
|
|
235
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const unsafeMethods = options.methods.filter((method) => UNSAFE_RETRY_METHODS.has(method.toUpperCase()));
|
|
239
|
-
if (unsafeMethods.length === 0)
|
|
240
|
-
return;
|
|
241
|
-
throw new ProviderError(`HTTP retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
242
|
-
}
|
|
243
|
-
function isMethodRetryable(method, options) {
|
|
244
|
-
return options.methods
|
|
245
|
-
.map((allowedMethod) => allowedMethod.toUpperCase())
|
|
246
|
-
.includes(method.toUpperCase());
|
|
247
|
-
}
|
|
248
|
-
function retryErrorCode(error) {
|
|
249
|
-
if (error instanceof TransportError) {
|
|
250
|
-
return error.code;
|
|
251
|
-
}
|
|
252
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
253
|
-
const code = Reflect.get(error, "code");
|
|
254
|
-
return typeof code === "string" ? code : undefined;
|
|
255
|
-
}
|
|
256
|
-
return undefined;
|
|
257
|
-
}
|
|
258
|
-
function retryErrorStatus(error) {
|
|
259
|
-
if (error instanceof TransportError) {
|
|
260
|
-
return error.status ?? error.upstreamStatus;
|
|
261
|
-
}
|
|
262
|
-
return undefined;
|
|
263
|
-
}
|
|
264
|
-
function shouldRetryTransportError(error, options) {
|
|
265
|
-
const code = retryErrorCode(error);
|
|
266
|
-
return Boolean(code && options.errorCodes.includes(code));
|
|
267
|
-
}
|
|
268
|
-
function retryAfterHeader(headers) {
|
|
269
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
270
|
-
if (name.toLowerCase() === "retry-after")
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
function parseRetryAfterMs(headers, now = Date.now()) {
|
|
276
|
-
const value = retryAfterHeader(headers);
|
|
277
|
-
if (!value)
|
|
278
|
-
return undefined;
|
|
279
|
-
const seconds = Number(value);
|
|
280
|
-
if (Number.isFinite(seconds)) {
|
|
281
|
-
return Math.max(0, Math.floor(seconds * 1_000));
|
|
282
|
-
}
|
|
283
|
-
const dateMs = Date.parse(value);
|
|
284
|
-
if (!Number.isNaN(dateMs)) {
|
|
285
|
-
return Math.max(0, dateMs - now);
|
|
286
|
-
}
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
function computeRetryDelayMs(options, attemptIndex, headers) {
|
|
290
|
-
const multiplier = options.delayStrategy === HttpRetryDelayStrategy.Exponential
|
|
291
|
-
? 2 ** Math.max(0, attemptIndex - 1)
|
|
292
|
-
: 1;
|
|
293
|
-
const configuredDelay = Math.min(options.baseDelayMs * multiplier, options.maxDelayMs);
|
|
294
|
-
const retryAfterMs = options.retryAfter === HttpRetryAfterPolicy.Ignore
|
|
295
|
-
? undefined
|
|
296
|
-
: headers
|
|
297
|
-
? parseRetryAfterMs(headers)
|
|
298
|
-
: undefined;
|
|
299
|
-
if (retryAfterMs !== undefined) {
|
|
300
|
-
const boundedRetryAfterMs = Math.min(retryAfterMs, options.maxDelayMs);
|
|
301
|
-
if (options.retryAfter === HttpRetryAfterPolicy.Cap) {
|
|
302
|
-
return Math.min(boundedRetryAfterMs, configuredDelay);
|
|
303
|
-
}
|
|
304
|
-
return boundedRetryAfterMs;
|
|
305
|
-
}
|
|
306
|
-
switch (options.jitter) {
|
|
307
|
-
case HttpRetryJitter.None:
|
|
308
|
-
return configuredDelay;
|
|
309
|
-
case HttpRetryJitter.Equal:
|
|
310
|
-
return Math.floor(configuredDelay / 2 + Math.random() * (configuredDelay / 2));
|
|
311
|
-
case HttpRetryJitter.Full:
|
|
312
|
-
return Math.floor(Math.random() * configuredDelay);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
10
|
async function sleep(ms) {
|
|
316
11
|
if (ms <= 0)
|
|
317
12
|
return;
|
|
@@ -330,9 +25,7 @@ function hasHeader(headers, name) {
|
|
|
330
25
|
}
|
|
331
26
|
function withClientHeaders(options, clientOptions, body) {
|
|
332
27
|
const headers = {
|
|
333
|
-
...(clientOptions.userAgent
|
|
334
|
-
? { "User-Agent": clientOptions.userAgent }
|
|
335
|
-
: {}),
|
|
28
|
+
...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
|
|
336
29
|
...options?.headers,
|
|
337
30
|
};
|
|
338
31
|
if (body !== undefined && !hasHeader(headers, "Content-Type")) {
|
|
@@ -344,9 +37,7 @@ function withClientHeaders(options, clientOptions, body) {
|
|
|
344
37
|
};
|
|
345
38
|
}
|
|
346
39
|
function parseHttpData(body, headers) {
|
|
347
|
-
const contentType = headers["content-type"] ??
|
|
348
|
-
headers["Content-Type"] ??
|
|
349
|
-
headers["CONTENT-TYPE"];
|
|
40
|
+
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
350
41
|
if (contentType?.includes("application/json")) {
|
|
351
42
|
return body ? JSON.parse(body) : null;
|
|
352
43
|
}
|
|
@@ -403,12 +94,8 @@ async function toNativeHttpResponse(response) {
|
|
|
403
94
|
data,
|
|
404
95
|
headers,
|
|
405
96
|
json: async () => {
|
|
406
|
-
const contentType = headers["content-type"] ??
|
|
407
|
-
|
|
408
|
-
headers["CONTENT-TYPE"];
|
|
409
|
-
return parseJson(contentType?.includes("application/json") && !rawText
|
|
410
|
-
? "null"
|
|
411
|
-
: rawText);
|
|
97
|
+
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
98
|
+
return parseJson(contentType?.includes("application/json") && !rawText ? "null" : rawText);
|
|
412
99
|
},
|
|
413
100
|
ok: response.status >= 200 && response.status < 300,
|
|
414
101
|
status: response.status,
|
|
@@ -478,17 +165,16 @@ function resolveHttpUrl(baseUrl, url) {
|
|
|
478
165
|
return new URL(url, baseUrl ?? DEFAULT_HTTP_BASE_URL).toString();
|
|
479
166
|
}
|
|
480
167
|
async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset = 0) {
|
|
481
|
-
const baseProxyAttempt = clientOptions.proxyAttempt === undefined ||
|
|
482
|
-
!Number.isFinite(clientOptions.proxyAttempt)
|
|
483
|
-
? 0
|
|
484
|
-
: Math.max(0, Math.floor(clientOptions.proxyAttempt));
|
|
485
168
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
486
169
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
487
170
|
upstream: clientOptions.upstream,
|
|
488
171
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
489
172
|
proxyPolicy: clientOptions.proxyPolicy,
|
|
490
173
|
affinityKey: clientOptions.affinityKey,
|
|
491
|
-
proxyAttempt:
|
|
174
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
175
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
176
|
+
retryAttemptOffset: proxyAttemptOffset,
|
|
177
|
+
}),
|
|
492
178
|
telemetry: clientOptions.telemetry,
|
|
493
179
|
});
|
|
494
180
|
if (resolvedProxy.shouldWarn) {
|
|
@@ -622,15 +308,15 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
622
308
|
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
623
309
|
const methodName = normalizeHttpMethod(method);
|
|
624
310
|
const explicitRetry = headersOptions.retry !== undefined;
|
|
625
|
-
const retryOptions =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
311
|
+
const retryOptions = normalizeProxyTransportRetryOptions(headersOptions.retry, {
|
|
312
|
+
label: "HTTP",
|
|
313
|
+
}) ??
|
|
314
|
+
(explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
|
|
629
315
|
if (retryOptions)
|
|
630
|
-
|
|
316
|
+
validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
|
|
631
317
|
const retryEnabled = Boolean(retryOptions &&
|
|
632
318
|
retryOptions.attempts > 1 &&
|
|
633
|
-
|
|
319
|
+
isProxyTransportRetryMethod(methodName, retryOptions));
|
|
634
320
|
const statusRetryEnabled = Boolean(retryEnabled &&
|
|
635
321
|
explicitRetry &&
|
|
636
322
|
retryOptions &&
|
|
@@ -655,14 +341,13 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
655
341
|
if (isHttpStatusOutcome(outcome)) {
|
|
656
342
|
lastStatus = outcome.status;
|
|
657
343
|
if (outcome.retryable && attempt < retryOptions.attempts) {
|
|
658
|
-
await sleep(
|
|
344
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
|
|
659
345
|
continue;
|
|
660
346
|
}
|
|
661
347
|
throw toUpstreamHttpError(outcome.status);
|
|
662
348
|
}
|
|
663
349
|
const response = outcome;
|
|
664
|
-
if (response.status >= 400 &&
|
|
665
|
-
headersOptions.throwOnHttpError !== false) {
|
|
350
|
+
if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
|
|
666
351
|
throw toUpstreamHttpError(response.status);
|
|
667
352
|
}
|
|
668
353
|
if (attempt > 1) {
|
|
@@ -679,13 +364,18 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
679
364
|
return response;
|
|
680
365
|
}
|
|
681
366
|
catch (error) {
|
|
682
|
-
lastErrorCode =
|
|
683
|
-
lastStatus =
|
|
367
|
+
lastErrorCode = proxyTransportRetryErrorCode(error);
|
|
368
|
+
lastStatus = proxyTransportRetryErrorStatus(error);
|
|
684
369
|
const proxyUsed = Boolean(error.proxyUsed);
|
|
685
370
|
if (attempt < retryOptions.attempts &&
|
|
686
|
-
(
|
|
687
|
-
|
|
688
|
-
|
|
371
|
+
shouldRetryProxyTransportAttempt({
|
|
372
|
+
error,
|
|
373
|
+
explicitRetry,
|
|
374
|
+
method: methodName,
|
|
375
|
+
options: retryOptions,
|
|
376
|
+
proxyUsed,
|
|
377
|
+
})) {
|
|
378
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
|
|
689
379
|
continue;
|
|
690
380
|
}
|
|
691
381
|
throw error;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HttpMethod, HttpRetryOptions, RequestOptions } from "../types";
|
|
2
|
+
import { HttpRetryPreset } from "../types";
|
|
3
|
+
export type NormalizedProxyTransportRetryOptions = Required<Pick<HttpRetryOptions, "attempts" | "delayStrategy" | "baseDelayMs" | "maxDelayMs" | "jitter" | "retryAfter" | "unsafeMethodPolicy">> & {
|
|
4
|
+
preset?: HttpRetryPreset;
|
|
5
|
+
methods: readonly string[];
|
|
6
|
+
statusCodes: readonly number[];
|
|
7
|
+
errorCodes: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_PROXY_TRANSPORT_RETRY_METHODS: readonly ["GET", "HEAD", "OPTIONS"];
|
|
10
|
+
export declare const DEFAULT_PROXY_TRANSPORT_RETRY_ERROR_CODES: readonly ["transport_network_error", "transport_timeout"];
|
|
11
|
+
export declare const MAX_PROXY_TRANSPORT_RETRY_ATTEMPTS = 8;
|
|
12
|
+
type RetryPolicyLabel = "HTTP" | "Stealth" | "Proxy transport";
|
|
13
|
+
export declare function proxyTransportRetryErrorCode(error: unknown): string | undefined;
|
|
14
|
+
export declare function proxyTransportRetryErrorStatus(error: unknown): number | undefined;
|
|
15
|
+
export declare function createDefaultProxyTransportRetryOptions(options?: {
|
|
16
|
+
extraErrorCodes?: readonly string[];
|
|
17
|
+
label?: RetryPolicyLabel;
|
|
18
|
+
}): NormalizedProxyTransportRetryOptions;
|
|
19
|
+
export declare function normalizeProxyTransportRetryOptions(retry: RequestOptions["retry"], options?: {
|
|
20
|
+
extraErrorCodes?: readonly string[];
|
|
21
|
+
label?: RetryPolicyLabel;
|
|
22
|
+
}): NormalizedProxyTransportRetryOptions | undefined;
|
|
23
|
+
export declare function validateUnsafeProxyTransportRetryMethods(options: NormalizedProxyTransportRetryOptions, label?: RetryPolicyLabel): void;
|
|
24
|
+
export declare function isProxyTransportRetryMethod(method: HttpMethod | string, options: NormalizedProxyTransportRetryOptions): boolean;
|
|
25
|
+
export declare function shouldRetryProxyTransportError(error: unknown, options: NormalizedProxyTransportRetryOptions): boolean;
|
|
26
|
+
export declare function shouldRetryProxyTransportAttempt(input: {
|
|
27
|
+
error: unknown;
|
|
28
|
+
explicitRetry: boolean;
|
|
29
|
+
method: HttpMethod | string;
|
|
30
|
+
options: NormalizedProxyTransportRetryOptions | undefined;
|
|
31
|
+
proxyUsed: boolean;
|
|
32
|
+
}): boolean;
|
|
33
|
+
export declare function computeProxyTransportRetryDelayMs(options: NormalizedProxyTransportRetryOptions, attemptIndex: number, headers?: Record<string, string>): number;
|
|
34
|
+
export declare function normalizeProxyAttemptIndex(value: number | undefined): number;
|
|
35
|
+
export declare function computeProxyAttemptIndex(options: {
|
|
36
|
+
baseProxyAttempt?: number;
|
|
37
|
+
proxyAttemptOffset?: number;
|
|
38
|
+
retryAttemptOffset?: number;
|
|
39
|
+
}): number;
|
|
40
|
+
export {};
|