rezo 1.0.41 → 1.0.42
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/adapters/curl.cjs +12 -3
- package/dist/adapters/curl.js +12 -3
- package/dist/adapters/fetch.cjs +98 -12
- package/dist/adapters/fetch.js +98 -12
- package/dist/adapters/http.cjs +26 -14
- package/dist/adapters/http.js +26 -14
- package/dist/adapters/http2.cjs +553 -211
- package/dist/adapters/http2.js +553 -211
- package/dist/adapters/index.cjs +6 -6
- package/dist/adapters/xhr.cjs +94 -2
- package/dist/adapters/xhr.js +94 -2
- package/dist/cache/dns-cache.cjs +5 -3
- package/dist/cache/dns-cache.js +5 -3
- package/dist/cache/index.cjs +13 -13
- package/dist/entries/crawler.cjs +5 -5
- package/dist/index.cjs +24 -24
- package/dist/plugin/index.cjs +36 -36
- package/dist/proxy/index.cjs +4 -4
- package/dist/queue/index.cjs +8 -8
- package/dist/responses/universal/index.cjs +11 -11
- package/dist/utils/agent-pool.cjs +1 -17
- package/dist/utils/agent-pool.js +1 -17
- package/package.json +1 -1
package/dist/adapters/http2.js
CHANGED
|
@@ -17,6 +17,100 @@ import { isSameDomain, RezoPerformance } from '../utils/tools.js';
|
|
|
17
17
|
import { ResponseCache } from '../cache/response-cache.js';
|
|
18
18
|
let zstdDecompressSync = null;
|
|
19
19
|
let zstdChecked = false;
|
|
20
|
+
const debugLog = {
|
|
21
|
+
requestStart: (config, url, method) => {
|
|
22
|
+
if (config.debug) {
|
|
23
|
+
console.log(`
|
|
24
|
+
[Rezo Debug] ─────────────────────────────────────`);
|
|
25
|
+
console.log(`[Rezo Debug] ${method} ${url}`);
|
|
26
|
+
console.log(`[Rezo Debug] Request ID: ${config.requestId}`);
|
|
27
|
+
if (config.originalRequest?.headers) {
|
|
28
|
+
const headers = config.originalRequest.headers instanceof RezoHeaders ? config.originalRequest.headers.toObject() : config.originalRequest.headers;
|
|
29
|
+
console.log(`[Rezo Debug] Request Headers:`, JSON.stringify(headers, null, 2));
|
|
30
|
+
}
|
|
31
|
+
if (config.proxy && typeof config.proxy === "object") {
|
|
32
|
+
console.log(`[Rezo Debug] Proxy: ${config.proxy.protocol}://${config.proxy.host}:${config.proxy.port}`);
|
|
33
|
+
} else if (config.proxy && typeof config.proxy === "string") {
|
|
34
|
+
console.log(`[Rezo Debug] Proxy: ${config.proxy}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (config.trackUrl) {
|
|
38
|
+
console.log(`[Rezo Track] → ${method} ${url}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
redirect: (config, fromUrl, toUrl, statusCode, method) => {
|
|
42
|
+
if (config.debug) {
|
|
43
|
+
console.log(`[Rezo Debug] Redirect ${statusCode}: ${fromUrl}`);
|
|
44
|
+
console.log(`[Rezo Debug] → ${toUrl} (${method})`);
|
|
45
|
+
}
|
|
46
|
+
if (config.trackUrl) {
|
|
47
|
+
console.log(`[Rezo Track] ↳ ${statusCode} → ${toUrl}`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
retry: (config, attempt, maxRetries, statusCode, delay) => {
|
|
51
|
+
if (config.debug) {
|
|
52
|
+
console.log(`[Rezo Debug] Retry ${attempt}/${maxRetries} after status ${statusCode}${delay > 0 ? ` (waiting ${delay}ms)` : ""}`);
|
|
53
|
+
}
|
|
54
|
+
if (config.trackUrl) {
|
|
55
|
+
console.log(`[Rezo Track] ⟳ Retry ${attempt}/${maxRetries} (status ${statusCode})`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
maxRetries: (config, maxRetries) => {
|
|
59
|
+
if (config.debug) {
|
|
60
|
+
console.log(`[Rezo Debug] Max retries (${maxRetries}) reached, throwing error`);
|
|
61
|
+
}
|
|
62
|
+
if (config.trackUrl) {
|
|
63
|
+
console.log(`[Rezo Track] ✗ Max retries reached`);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
response: (config, status, statusText, duration) => {
|
|
67
|
+
if (config.debug) {
|
|
68
|
+
console.log(`[Rezo Debug] Response: ${status} ${statusText} (${duration.toFixed(2)}ms)`);
|
|
69
|
+
}
|
|
70
|
+
if (config.trackUrl) {
|
|
71
|
+
console.log(`[Rezo Track] ✓ ${status} ${statusText}`);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
responseHeaders: (config, headers) => {
|
|
75
|
+
if (config.debug) {
|
|
76
|
+
console.log(`[Rezo Debug] Response Headers:`, JSON.stringify(headers, null, 2));
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
cookies: (config, cookieCount) => {
|
|
80
|
+
if (config.debug && cookieCount > 0) {
|
|
81
|
+
console.log(`[Rezo Debug] Cookies received: ${cookieCount}`);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
timing: (config, timing) => {
|
|
85
|
+
if (config.debug) {
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (timing.dns)
|
|
88
|
+
parts.push(`DNS: ${timing.dns.toFixed(2)}ms`);
|
|
89
|
+
if (timing.connect)
|
|
90
|
+
parts.push(`Connect: ${timing.connect.toFixed(2)}ms`);
|
|
91
|
+
if (timing.tls)
|
|
92
|
+
parts.push(`TLS: ${timing.tls.toFixed(2)}ms`);
|
|
93
|
+
if (timing.ttfb)
|
|
94
|
+
parts.push(`TTFB: ${timing.ttfb.toFixed(2)}ms`);
|
|
95
|
+
if (timing.total)
|
|
96
|
+
parts.push(`Total: ${timing.total.toFixed(2)}ms`);
|
|
97
|
+
if (parts.length > 0) {
|
|
98
|
+
console.log(`[Rezo Debug] Timing: ${parts.join(" | ")}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
complete: (config, finalUrl, redirectCount, duration) => {
|
|
103
|
+
if (config.debug) {
|
|
104
|
+
console.log(`[Rezo Debug] Complete: ${finalUrl}`);
|
|
105
|
+
if (redirectCount > 0) {
|
|
106
|
+
console.log(`[Rezo Debug] Redirects: ${redirectCount}`);
|
|
107
|
+
}
|
|
108
|
+
console.log(`[Rezo Debug] Total Duration: ${duration.toFixed(2)}ms`);
|
|
109
|
+
console.log(`[Rezo Debug] ─────────────────────────────────────
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
20
114
|
async function decompressBuffer(buffer, contentEncoding) {
|
|
21
115
|
const encoding = contentEncoding.toLowerCase();
|
|
22
116
|
switch (encoding) {
|
|
@@ -103,26 +197,47 @@ class Http2SessionPool {
|
|
|
103
197
|
getSessionKey(url, options) {
|
|
104
198
|
return `${url.protocol}//${url.host}`;
|
|
105
199
|
}
|
|
106
|
-
|
|
200
|
+
isSessionHealthy(session, entry) {
|
|
201
|
+
if (session.closed || session.destroyed)
|
|
202
|
+
return false;
|
|
203
|
+
if (entry.goawayReceived)
|
|
204
|
+
return false;
|
|
205
|
+
const socket = session.socket;
|
|
206
|
+
if (socket && (socket.destroyed || socket.closed || !socket.writable))
|
|
207
|
+
return false;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
async getSession(url, options, timeout, forceNew = false) {
|
|
107
211
|
const key = this.getSessionKey(url, options);
|
|
108
212
|
const existing = this.sessions.get(key);
|
|
109
|
-
if (
|
|
213
|
+
if (!forceNew && existing && this.isSessionHealthy(existing.session, existing)) {
|
|
110
214
|
existing.lastUsed = Date.now();
|
|
111
215
|
existing.refCount++;
|
|
112
216
|
return existing.session;
|
|
113
217
|
}
|
|
218
|
+
if (existing && !this.isSessionHealthy(existing.session, existing)) {
|
|
219
|
+
try {
|
|
220
|
+
existing.session.close();
|
|
221
|
+
} catch {}
|
|
222
|
+
this.sessions.delete(key);
|
|
223
|
+
}
|
|
114
224
|
const session = await this.createSession(url, options, timeout);
|
|
115
|
-
|
|
225
|
+
const entry = {
|
|
116
226
|
session,
|
|
117
227
|
lastUsed: Date.now(),
|
|
118
|
-
refCount: 1
|
|
119
|
-
|
|
228
|
+
refCount: 1,
|
|
229
|
+
goawayReceived: false
|
|
230
|
+
};
|
|
231
|
+
this.sessions.set(key, entry);
|
|
120
232
|
session.on("close", () => {
|
|
121
233
|
this.sessions.delete(key);
|
|
122
234
|
});
|
|
123
235
|
session.on("error", () => {
|
|
124
236
|
this.sessions.delete(key);
|
|
125
237
|
});
|
|
238
|
+
session.on("goaway", () => {
|
|
239
|
+
entry.goawayReceived = true;
|
|
240
|
+
});
|
|
126
241
|
return session;
|
|
127
242
|
}
|
|
128
243
|
createSession(url, options, timeout) {
|
|
@@ -308,7 +423,7 @@ function sanitizeConfig(config) {
|
|
|
308
423
|
const { data: _data, ...sanitized } = config;
|
|
309
424
|
return sanitized;
|
|
310
425
|
}
|
|
311
|
-
async function updateCookies(config, headers, url) {
|
|
426
|
+
async function updateCookies(config, headers, url, rootJar) {
|
|
312
427
|
const setCookieHeaders = headers["set-cookie"];
|
|
313
428
|
if (!setCookieHeaders)
|
|
314
429
|
return;
|
|
@@ -352,8 +467,9 @@ async function updateCookies(config, headers, url) {
|
|
|
352
467
|
const acceptedCookieStrings = acceptedCookies.map((c) => c.toSetCookieString());
|
|
353
468
|
const jar = new RezoCookieJar;
|
|
354
469
|
jar.setCookiesSync(acceptedCookieStrings, url);
|
|
355
|
-
|
|
356
|
-
|
|
470
|
+
const jarToSync = rootJar || config.cookieJar;
|
|
471
|
+
if (!config.disableCookieJar && jarToSync) {
|
|
472
|
+
jarToSync.setCookiesSync(acceptedCookieStrings, url);
|
|
357
473
|
}
|
|
358
474
|
const cookies = jar.cookies();
|
|
359
475
|
cookies.setCookiesString = cookieHeaderArray;
|
|
@@ -525,7 +641,7 @@ export async function executeRequest(options, defaultOptions, jar) {
|
|
|
525
641
|
}
|
|
526
642
|
}
|
|
527
643
|
try {
|
|
528
|
-
const res = executeHttp2Request(fetchOptions, mainConfig, options, perform, fs, streamResponse, downloadResponse, uploadResponse);
|
|
644
|
+
const res = executeHttp2Request(fetchOptions, mainConfig, options, perform, fs, streamResponse, downloadResponse, uploadResponse, jar);
|
|
529
645
|
if (streamResponse) {
|
|
530
646
|
return streamResponse;
|
|
531
647
|
} else if (downloadResponse) {
|
|
@@ -558,7 +674,7 @@ export async function executeRequest(options, defaultOptions, jar) {
|
|
|
558
674
|
throw error;
|
|
559
675
|
}
|
|
560
676
|
}
|
|
561
|
-
async function executeHttp2Request(fetchOptions, config, options, perform, fs, streamResult, downloadResult, uploadResult) {
|
|
677
|
+
async function executeHttp2Request(fetchOptions, config, options, perform, fs, streamResult, downloadResult, uploadResult, rootJar) {
|
|
562
678
|
let requestCount = 0;
|
|
563
679
|
const _stats = { statusOnNext: "abort" };
|
|
564
680
|
let responseStatusCode;
|
|
@@ -578,6 +694,11 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
578
694
|
config.setSignal();
|
|
579
695
|
const timeoutClearInstance = config.timeoutClearInstanse;
|
|
580
696
|
delete config.timeoutClearInstanse;
|
|
697
|
+
if (!config.requestId) {
|
|
698
|
+
config.requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
699
|
+
}
|
|
700
|
+
const requestUrl = fetchOptions.fullUrl ? String(fetchOptions.fullUrl) : "";
|
|
701
|
+
debugLog.requestStart(config, requestUrl, fetchOptions.method || "GET");
|
|
581
702
|
const eventEmitter = streamResult || downloadResult || uploadResult;
|
|
582
703
|
if (eventEmitter) {
|
|
583
704
|
eventEmitter.emit("initiated");
|
|
@@ -590,7 +711,7 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
590
711
|
throw error;
|
|
591
712
|
}
|
|
592
713
|
try {
|
|
593
|
-
const response = await executeHttp2Stream(config, fetchOptions, requestCount, timing, _stats, responseStatusCode, fs, streamResult, downloadResult, uploadResult, sessionPool);
|
|
714
|
+
const response = await executeHttp2Stream(config, fetchOptions, requestCount, timing, _stats, responseStatusCode, fs, streamResult, downloadResult, uploadResult, sessionPool, rootJar);
|
|
594
715
|
const statusOnNext = _stats.statusOnNext;
|
|
595
716
|
if (response instanceof RezoError) {
|
|
596
717
|
const fileName = config.fileName;
|
|
@@ -619,16 +740,12 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
619
740
|
throw response;
|
|
620
741
|
}
|
|
621
742
|
if (maxRetries <= retries) {
|
|
622
|
-
|
|
623
|
-
console.log(`Max retries (${maxRetries}) reached`);
|
|
624
|
-
}
|
|
743
|
+
debugLog.maxRetries(config, maxRetries);
|
|
625
744
|
throw response;
|
|
626
745
|
}
|
|
627
746
|
retries++;
|
|
628
747
|
const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
|
|
629
|
-
|
|
630
|
-
console.log(`Retrying... ${retryDelay > 0 ? "in " + currentDelay + "ms" : ""}`);
|
|
631
|
-
}
|
|
748
|
+
debugLog.retry(config, retries, maxRetries, responseStatusCode || 0, currentDelay);
|
|
632
749
|
if (config.hooks?.beforeRetry && config.hooks.beforeRetry.length > 0) {
|
|
633
750
|
for (const hook of config.hooks.beforeRetry) {
|
|
634
751
|
await hook(config, response, retries);
|
|
@@ -643,6 +760,16 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
643
760
|
continue;
|
|
644
761
|
}
|
|
645
762
|
if (statusOnNext === "success") {
|
|
763
|
+
const totalDuration = performance.now() - timing.startTime;
|
|
764
|
+
debugLog.response(config, response.status, response.statusText, totalDuration);
|
|
765
|
+
if (response.headers) {
|
|
766
|
+
const headersObj = response.headers instanceof RezoHeaders ? response.headers.toObject() : response.headers;
|
|
767
|
+
debugLog.responseHeaders(config, headersObj);
|
|
768
|
+
}
|
|
769
|
+
if (response.cookies?.array) {
|
|
770
|
+
debugLog.cookies(config, response.cookies.array.length);
|
|
771
|
+
}
|
|
772
|
+
debugLog.complete(config, response.finalUrl || requestUrl, config.redirectCount, totalDuration);
|
|
646
773
|
return response;
|
|
647
774
|
}
|
|
648
775
|
if (statusOnNext === "error") {
|
|
@@ -686,18 +813,20 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
686
813
|
visitedUrls.add(normalizedRedirectUrl);
|
|
687
814
|
}
|
|
688
815
|
const redirectCode = response.status;
|
|
816
|
+
const fromUrl = fetchOptions.fullUrl;
|
|
689
817
|
const redirectCallback = config.beforeRedirect || config.onRedirect;
|
|
690
818
|
const onRedirect = redirectCallback ? redirectCallback({
|
|
691
819
|
url: new URL(location),
|
|
692
820
|
status: response.status,
|
|
693
821
|
headers: response.headers,
|
|
694
822
|
sameDomain: isSameDomain(fetchOptions.fullUrl, location),
|
|
695
|
-
method: fetchOptions.method.toUpperCase()
|
|
823
|
+
method: fetchOptions.method.toUpperCase(),
|
|
824
|
+
body: config.originalBody
|
|
696
825
|
}) : undefined;
|
|
697
826
|
if (typeof onRedirect !== "undefined") {
|
|
698
827
|
if (typeof onRedirect === "boolean" && !onRedirect) {
|
|
699
828
|
throw builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
|
|
700
|
-
} else if (typeof onRedirect === "object" && !onRedirect.redirect) {
|
|
829
|
+
} else if (typeof onRedirect === "object" && !onRedirect.redirect && !onRedirect.withoutBody && !("body" in onRedirect)) {
|
|
701
830
|
throw builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
|
|
702
831
|
}
|
|
703
832
|
}
|
|
@@ -717,14 +846,88 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
717
846
|
});
|
|
718
847
|
perform.reset();
|
|
719
848
|
config.redirectCount++;
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
849
|
+
fetchOptions.fullUrl = location;
|
|
850
|
+
delete options.params;
|
|
851
|
+
const normalizedRedirect = typeof onRedirect === "object" ? onRedirect.redirect || onRedirect.withoutBody || "body" in onRedirect : undefined;
|
|
852
|
+
if (typeof onRedirect === "object" && normalizedRedirect) {
|
|
853
|
+
let method;
|
|
854
|
+
const userMethod = onRedirect.method;
|
|
855
|
+
if (redirectCode === 301 || redirectCode === 302 || redirectCode === 303) {
|
|
856
|
+
method = userMethod || "GET";
|
|
857
|
+
} else {
|
|
858
|
+
method = userMethod || fetchOptions.method;
|
|
859
|
+
}
|
|
860
|
+
config.method = method;
|
|
861
|
+
options.method = method;
|
|
862
|
+
fetchOptions.method = method;
|
|
863
|
+
if (onRedirect.redirect && onRedirect.url) {
|
|
864
|
+
options.fullUrl = onRedirect.url;
|
|
865
|
+
fetchOptions.fullUrl = onRedirect.url;
|
|
866
|
+
}
|
|
867
|
+
if (onRedirect.withoutBody) {
|
|
723
868
|
delete options.body;
|
|
869
|
+
delete fetchOptions.body;
|
|
870
|
+
config.originalBody = undefined;
|
|
871
|
+
if (fetchOptions.headers instanceof RezoHeaders) {
|
|
872
|
+
fetchOptions.headers.delete("Content-Type");
|
|
873
|
+
fetchOptions.headers.delete("Content-Length");
|
|
874
|
+
}
|
|
875
|
+
} else if ("body" in onRedirect) {
|
|
876
|
+
options.body = onRedirect.body;
|
|
877
|
+
fetchOptions.body = onRedirect.body;
|
|
878
|
+
config.originalBody = onRedirect.body;
|
|
879
|
+
} else if (redirectCode === 307 || redirectCode === 308) {
|
|
880
|
+
const methodUpper = method.toUpperCase();
|
|
881
|
+
if ((methodUpper === "POST" || methodUpper === "PUT" || methodUpper === "PATCH") && config.originalBody !== undefined) {
|
|
882
|
+
options.body = config.originalBody;
|
|
883
|
+
fetchOptions.body = config.originalBody;
|
|
884
|
+
}
|
|
885
|
+
} else {
|
|
886
|
+
delete options.body;
|
|
887
|
+
delete fetchOptions.body;
|
|
888
|
+
if (fetchOptions.headers instanceof RezoHeaders) {
|
|
889
|
+
fetchOptions.headers.delete("Content-Type");
|
|
890
|
+
fetchOptions.headers.delete("Content-Length");
|
|
891
|
+
}
|
|
724
892
|
}
|
|
893
|
+
debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, method);
|
|
894
|
+
} else if (response.status === 301 || response.status === 302 || response.status === 303) {
|
|
895
|
+
debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, "GET");
|
|
896
|
+
options.method = "GET";
|
|
897
|
+
fetchOptions.method = "GET";
|
|
898
|
+
config.method = "GET";
|
|
899
|
+
delete options.body;
|
|
900
|
+
delete fetchOptions.body;
|
|
901
|
+
if (fetchOptions.headers instanceof RezoHeaders) {
|
|
902
|
+
fetchOptions.headers.delete("Content-Type");
|
|
903
|
+
fetchOptions.headers.delete("Content-Length");
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, fetchOptions.method);
|
|
907
|
+
}
|
|
908
|
+
const jarToSync = rootJar || config.cookieJar;
|
|
909
|
+
if (response.cookies?.setCookiesString?.length > 0 && jarToSync) {
|
|
910
|
+
try {
|
|
911
|
+
jarToSync.setCookiesSync(response.cookies.setCookiesString, fromUrl);
|
|
912
|
+
} catch (e) {}
|
|
913
|
+
}
|
|
914
|
+
if (jarToSync && !config.disableCookieJar) {
|
|
915
|
+
try {
|
|
916
|
+
const cookieString = jarToSync.getCookieStringSync(fetchOptions.fullUrl);
|
|
917
|
+
if (cookieString) {
|
|
918
|
+
if (fetchOptions.headers instanceof RezoHeaders) {
|
|
919
|
+
fetchOptions.headers.set("cookie", cookieString);
|
|
920
|
+
} else if (fetchOptions.headers) {
|
|
921
|
+
fetchOptions.headers["cookie"] = cookieString;
|
|
922
|
+
} else {
|
|
923
|
+
fetchOptions.headers = new RezoHeaders({ cookie: cookieString });
|
|
924
|
+
}
|
|
925
|
+
if (config.debug) {
|
|
926
|
+
console.log(`[Rezo Debug] HTTP/2: Updated Cookie header for redirect: ${cookieString.substring(0, 100)}...`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
} catch (e) {}
|
|
725
930
|
}
|
|
726
|
-
fetchOptions.fullUrl = location;
|
|
727
|
-
delete options.params;
|
|
728
931
|
requestCount++;
|
|
729
932
|
continue;
|
|
730
933
|
}
|
|
@@ -737,7 +940,7 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
|
|
|
737
940
|
}
|
|
738
941
|
}
|
|
739
942
|
}
|
|
740
|
-
async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _stats, responseStatusCode, fs, streamResult, downloadResult, uploadResult, sessionPool) {
|
|
943
|
+
async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _stats, responseStatusCode, fs, streamResult, downloadResult, uploadResult, sessionPool, rootJar) {
|
|
741
944
|
return new Promise(async (resolve) => {
|
|
742
945
|
try {
|
|
743
946
|
const { fullUrl, body } = fetchOptions;
|
|
@@ -764,6 +967,11 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
764
967
|
if (!headers["accept-encoding"]) {
|
|
765
968
|
headers["accept-encoding"] = "gzip, deflate, br";
|
|
766
969
|
}
|
|
970
|
+
if (config.debug && headers["cookie"]) {
|
|
971
|
+
console.log(`[Rezo Debug] HTTP/2: Sending Cookie header: ${String(headers["cookie"]).substring(0, 100)}...`);
|
|
972
|
+
} else if (config.debug) {
|
|
973
|
+
console.log(`[Rezo Debug] HTTP/2: No Cookie header in request`);
|
|
974
|
+
}
|
|
767
975
|
if (body instanceof RezoFormData) {
|
|
768
976
|
headers["content-type"] = `multipart/form-data; boundary=${body.getBoundary()}`;
|
|
769
977
|
} else if (body instanceof FormData) {
|
|
@@ -797,30 +1005,136 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
797
1005
|
sessionOptions.pfx = securityContext.pfx;
|
|
798
1006
|
if (securityContext?.passphrase)
|
|
799
1007
|
sessionOptions.passphrase = securityContext.passphrase;
|
|
1008
|
+
const forceNewSession = requestCount > 0;
|
|
800
1009
|
let session;
|
|
1010
|
+
if (config.debug) {
|
|
1011
|
+
console.log(`[Rezo Debug] HTTP/2: Acquiring session for ${url.host}${forceNewSession ? " (forcing new for redirect)" : ""}...`);
|
|
1012
|
+
}
|
|
801
1013
|
try {
|
|
802
|
-
session = await (sessionPool || Http2SessionPool.getInstance()).getSession(url, sessionOptions, config.timeout !== null ? config.timeout : undefined);
|
|
1014
|
+
session = await (sessionPool || Http2SessionPool.getInstance()).getSession(url, sessionOptions, config.timeout !== null ? config.timeout : undefined, forceNewSession);
|
|
1015
|
+
if (config.debug) {
|
|
1016
|
+
console.log(`[Rezo Debug] HTTP/2: Session acquired successfully`);
|
|
1017
|
+
}
|
|
803
1018
|
} catch (err) {
|
|
1019
|
+
if (config.debug) {
|
|
1020
|
+
console.log(`[Rezo Debug] HTTP/2: Session failed:`, err.message);
|
|
1021
|
+
}
|
|
804
1022
|
const error = buildSmartError(config, fetchOptions, err);
|
|
805
1023
|
_stats.statusOnNext = "error";
|
|
806
1024
|
resolve(error);
|
|
807
1025
|
return;
|
|
808
1026
|
}
|
|
809
|
-
const req = session.request(headers);
|
|
810
|
-
if (config.timeout) {
|
|
811
|
-
req.setTimeout(config.timeout, () => {
|
|
812
|
-
req.close(http2.constants.NGHTTP2_CANCEL);
|
|
813
|
-
const error = buildSmartError(config, fetchOptions, new Error(`Request timeout after ${config.timeout}ms`));
|
|
814
|
-
_stats.statusOnNext = "error";
|
|
815
|
-
resolve(error);
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
1027
|
let chunks = [];
|
|
819
1028
|
let contentLengthCounter = 0;
|
|
820
1029
|
let responseHeaders = {};
|
|
821
1030
|
let status = 0;
|
|
822
1031
|
let statusText = "";
|
|
1032
|
+
let resolved = false;
|
|
1033
|
+
let isRedirect = false;
|
|
1034
|
+
let timeoutId = null;
|
|
1035
|
+
const sessionErrorHandler = (err) => {
|
|
1036
|
+
if (config.debug) {
|
|
1037
|
+
console.log(`[Rezo Debug] HTTP/2: Session error:`, err.message);
|
|
1038
|
+
}
|
|
1039
|
+
if (!resolved) {
|
|
1040
|
+
resolved = true;
|
|
1041
|
+
if (timeoutId)
|
|
1042
|
+
clearTimeout(timeoutId);
|
|
1043
|
+
const error = buildSmartError(config, fetchOptions, err);
|
|
1044
|
+
_stats.statusOnNext = "error";
|
|
1045
|
+
resolve(error);
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
session.on("error", sessionErrorHandler);
|
|
1049
|
+
session.on("goaway", (errorCode, lastStreamID) => {
|
|
1050
|
+
if (config.debug) {
|
|
1051
|
+
console.log(`[Rezo Debug] HTTP/2: Session GOAWAY received (errorCode: ${errorCode}, lastStreamID: ${lastStreamID})`);
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
if (config.debug) {
|
|
1055
|
+
console.log(`[Rezo Debug] HTTP/2: Creating request stream...`);
|
|
1056
|
+
}
|
|
1057
|
+
let req;
|
|
1058
|
+
try {
|
|
1059
|
+
req = session.request(headers);
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
if (config.debug) {
|
|
1062
|
+
console.log(`[Rezo Debug] HTTP/2: Failed to create request stream:`, err.message);
|
|
1063
|
+
}
|
|
1064
|
+
session.removeListener("error", sessionErrorHandler);
|
|
1065
|
+
const error = buildSmartError(config, fetchOptions, err);
|
|
1066
|
+
_stats.statusOnNext = "error";
|
|
1067
|
+
resolve(error);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (config.debug) {
|
|
1071
|
+
console.log(`[Rezo Debug] HTTP/2: Request stream created`);
|
|
1072
|
+
}
|
|
1073
|
+
const requestTimeout = config.timeout || 30000;
|
|
1074
|
+
timeoutId = setTimeout(() => {
|
|
1075
|
+
if (!resolved) {
|
|
1076
|
+
resolved = true;
|
|
1077
|
+
if (config.debug) {
|
|
1078
|
+
console.log(`[Rezo Debug] HTTP/2: Request timeout after ${requestTimeout}ms (no response received)`);
|
|
1079
|
+
}
|
|
1080
|
+
req.close(http2.constants.NGHTTP2_CANCEL);
|
|
1081
|
+
const error = buildSmartError(config, fetchOptions, new Error(`Request timeout after ${requestTimeout}ms`));
|
|
1082
|
+
_stats.statusOnNext = "error";
|
|
1083
|
+
resolve(error);
|
|
1084
|
+
}
|
|
1085
|
+
}, requestTimeout);
|
|
1086
|
+
const sessionSocket = session.socket;
|
|
1087
|
+
if (sessionSocket && typeof sessionSocket.ref === "function") {
|
|
1088
|
+
sessionSocket.ref();
|
|
1089
|
+
}
|
|
1090
|
+
req.on("close", () => {
|
|
1091
|
+
if (config.debug && !resolved) {
|
|
1092
|
+
console.log(`[Rezo Debug] HTTP/2: Stream closed (status: ${status}, resolved: ${resolved})`);
|
|
1093
|
+
}
|
|
1094
|
+
if (!resolved && status === 0) {
|
|
1095
|
+
resolved = true;
|
|
1096
|
+
clearTimeout(timeoutId);
|
|
1097
|
+
if (config.debug) {
|
|
1098
|
+
console.log(`[Rezo Debug] HTTP/2: Stream closed without response - retrying with new session`);
|
|
1099
|
+
}
|
|
1100
|
+
const error = buildSmartError(config, fetchOptions, new Error("HTTP/2 stream closed without response"));
|
|
1101
|
+
_stats.statusOnNext = "error";
|
|
1102
|
+
resolve(error);
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
req.on("aborted", () => {
|
|
1106
|
+
if (config.debug) {
|
|
1107
|
+
console.log(`[Rezo Debug] HTTP/2: Stream aborted`);
|
|
1108
|
+
}
|
|
1109
|
+
if (!resolved) {
|
|
1110
|
+
resolved = true;
|
|
1111
|
+
clearTimeout(timeoutId);
|
|
1112
|
+
const error = buildSmartError(config, fetchOptions, new Error("HTTP/2 stream aborted"));
|
|
1113
|
+
_stats.statusOnNext = "error";
|
|
1114
|
+
resolve(error);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
req.on("error", (err) => {
|
|
1118
|
+
if (config.debug) {
|
|
1119
|
+
console.log(`[Rezo Debug] HTTP/2: Stream error:`, err.message);
|
|
1120
|
+
}
|
|
1121
|
+
if (!resolved) {
|
|
1122
|
+
resolved = true;
|
|
1123
|
+
clearTimeout(timeoutId);
|
|
1124
|
+
const error = buildSmartError(config, fetchOptions, err);
|
|
1125
|
+
_stats.statusOnNext = "error";
|
|
1126
|
+
resolve(error);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
req.on("frameError", (type, code, id) => {
|
|
1130
|
+
if (config.debug) {
|
|
1131
|
+
console.log(`[Rezo Debug] HTTP/2: Frame error - type: ${type}, code: ${code}, id: ${id}`);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
823
1134
|
req.on("response", (headers) => {
|
|
1135
|
+
if (config.debug) {
|
|
1136
|
+
console.log(`[Rezo Debug] HTTP/2: Response received, status: ${headers[":status"]}`);
|
|
1137
|
+
}
|
|
824
1138
|
responseHeaders = headers;
|
|
825
1139
|
status = Number(headers[http2.constants.HTTP2_HEADER_STATUS]) || 200;
|
|
826
1140
|
statusText = getStatusText(status);
|
|
@@ -829,7 +1143,7 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
829
1143
|
config.timing.responseStart = timing.firstByteTime;
|
|
830
1144
|
}
|
|
831
1145
|
const location = headers["location"];
|
|
832
|
-
|
|
1146
|
+
isRedirect = status >= 300 && status < 400 && !!location;
|
|
833
1147
|
if (isRedirect) {
|
|
834
1148
|
_stats.statusOnNext = "redirect";
|
|
835
1149
|
const redirectUrlObj = new URL(location, url);
|
|
@@ -841,7 +1155,7 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
841
1155
|
config.network.httpVersion = "h2";
|
|
842
1156
|
(async () => {
|
|
843
1157
|
try {
|
|
844
|
-
await updateCookies(config, headers, url.href);
|
|
1158
|
+
await updateCookies(config, headers, url.href, rootJar);
|
|
845
1159
|
} catch (err) {
|
|
846
1160
|
if (config.debug) {
|
|
847
1161
|
console.log("[Rezo Debug] Cookie hook error:", err);
|
|
@@ -874,6 +1188,9 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
874
1188
|
}
|
|
875
1189
|
});
|
|
876
1190
|
req.on("data", (chunk) => {
|
|
1191
|
+
if (config.debug) {
|
|
1192
|
+
console.log(`[Rezo Debug] HTTP/2: Received data chunk: ${chunk.length} bytes (total: ${contentLengthCounter + chunk.length})`);
|
|
1193
|
+
}
|
|
877
1194
|
chunks.push(chunk);
|
|
878
1195
|
contentLengthCounter += chunk.length;
|
|
879
1196
|
if (streamResult) {
|
|
@@ -895,209 +1212,221 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
895
1212
|
}
|
|
896
1213
|
});
|
|
897
1214
|
req.on("end", async () => {
|
|
898
|
-
|
|
899
|
-
if (!config.transfer) {
|
|
900
|
-
config.transfer = { requestSize: 0, responseSize: 0, headerSize: 0, bodySize: 0 };
|
|
901
|
-
}
|
|
902
|
-
if (config.transfer.requestSize === undefined) {
|
|
903
|
-
config.transfer.requestSize = 0;
|
|
904
|
-
}
|
|
905
|
-
if (config.transfer.requestSize === 0 && body) {
|
|
906
|
-
if (typeof body === "string") {
|
|
907
|
-
config.transfer.requestSize = Buffer.byteLength(body, "utf8");
|
|
908
|
-
} else if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
909
|
-
config.transfer.requestSize = body.length;
|
|
910
|
-
} else if (body instanceof URLSearchParams || body instanceof RezoURLSearchParams) {
|
|
911
|
-
config.transfer.requestSize = Buffer.byteLength(body.toString(), "utf8");
|
|
912
|
-
} else if (body instanceof RezoFormData) {
|
|
913
|
-
config.transfer.requestSize = await body.getLength() || 0;
|
|
914
|
-
} else if (typeof body === "object") {
|
|
915
|
-
config.transfer.requestSize = Buffer.byteLength(JSON.stringify(body), "utf8");
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
(sessionPool || Http2SessionPool.getInstance()).releaseSession(url);
|
|
919
|
-
if (_stats.statusOnNext === "redirect") {
|
|
920
|
-
const partialResponse = {
|
|
921
|
-
data: "",
|
|
922
|
-
status,
|
|
923
|
-
statusText,
|
|
924
|
-
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
925
|
-
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
926
|
-
config,
|
|
927
|
-
contentType: responseHeaders["content-type"],
|
|
928
|
-
contentLength: contentLengthCounter,
|
|
929
|
-
finalUrl: url.href,
|
|
930
|
-
urls: buildUrlTree(config, url.href)
|
|
931
|
-
};
|
|
932
|
-
resolve(partialResponse);
|
|
1215
|
+
if (resolved)
|
|
933
1216
|
return;
|
|
1217
|
+
if (config.debug) {
|
|
1218
|
+
console.log(`[Rezo Debug] HTTP/2: Stream 'end' event fired (status: ${status}, chunks: ${chunks.length}, bytes: ${contentLengthCounter})`);
|
|
934
1219
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1220
|
+
resolved = true;
|
|
1221
|
+
clearTimeout(timeoutId);
|
|
1222
|
+
try {
|
|
1223
|
+
updateTiming(config, timing, contentLengthCounter);
|
|
1224
|
+
if (!config.transfer) {
|
|
1225
|
+
config.transfer = { requestSize: 0, responseSize: 0, headerSize: 0, bodySize: 0 };
|
|
1226
|
+
}
|
|
1227
|
+
if (config.transfer.requestSize === undefined) {
|
|
1228
|
+
config.transfer.requestSize = 0;
|
|
1229
|
+
}
|
|
1230
|
+
if (config.transfer.requestSize === 0 && body) {
|
|
1231
|
+
if (typeof body === "string") {
|
|
1232
|
+
config.transfer.requestSize = Buffer.byteLength(body, "utf8");
|
|
1233
|
+
} else if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
1234
|
+
config.transfer.requestSize = body.length;
|
|
1235
|
+
} else if (body instanceof URLSearchParams || body instanceof RezoURLSearchParams) {
|
|
1236
|
+
config.transfer.requestSize = Buffer.byteLength(body.toString(), "utf8");
|
|
1237
|
+
} else if (body instanceof RezoFormData) {
|
|
1238
|
+
config.transfer.requestSize = await body.getLength() || 0;
|
|
1239
|
+
} else if (typeof body === "object") {
|
|
1240
|
+
config.transfer.requestSize = Buffer.byteLength(JSON.stringify(body), "utf8");
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
(sessionPool || Http2SessionPool.getInstance()).releaseSession(url);
|
|
1244
|
+
if (isRedirect) {
|
|
1245
|
+
_stats.statusOnNext = "redirect";
|
|
1246
|
+
const partialResponse = {
|
|
1247
|
+
data: "",
|
|
1248
|
+
status,
|
|
1249
|
+
statusText,
|
|
1250
|
+
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1251
|
+
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
1252
|
+
config,
|
|
945
1253
|
contentType: responseHeaders["content-type"],
|
|
946
|
-
contentLength:
|
|
947
|
-
cookies: config.responseCookies?.setCookiesString || [],
|
|
948
|
-
statusText: err.message,
|
|
949
|
-
url: url.href,
|
|
950
|
-
body: responseBody,
|
|
1254
|
+
contentLength: contentLengthCounter,
|
|
951
1255
|
finalUrl: url.href,
|
|
952
|
-
config,
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
_stats.statusOnNext = "error";
|
|
956
|
-
resolve(error);
|
|
1256
|
+
urls: buildUrlTree(config, url.href)
|
|
1257
|
+
};
|
|
1258
|
+
resolve(partialResponse);
|
|
957
1259
|
return;
|
|
958
1260
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1261
|
+
let responseBody = Buffer.concat(chunks);
|
|
1262
|
+
const contentEncoding = responseHeaders["content-encoding"];
|
|
1263
|
+
if (contentEncoding && contentLengthCounter > 0 && CompressionUtil.shouldDecompress(contentEncoding, config)) {
|
|
1264
|
+
try {
|
|
1265
|
+
const decompressed = await decompressBuffer(responseBody, contentEncoding);
|
|
1266
|
+
responseBody = decompressed;
|
|
1267
|
+
} catch (err) {
|
|
1268
|
+
const error = buildDecompressionError({
|
|
1269
|
+
statusCode: status,
|
|
1270
|
+
headers: sanitizeHttp2Headers(responseHeaders),
|
|
1271
|
+
contentType: responseHeaders["content-type"],
|
|
1272
|
+
contentLength: String(contentLengthCounter),
|
|
1273
|
+
cookies: config.responseCookies?.setCookiesString || [],
|
|
1274
|
+
statusText: err.message,
|
|
1275
|
+
url: url.href,
|
|
1276
|
+
body: responseBody,
|
|
1277
|
+
finalUrl: url.href,
|
|
1278
|
+
config,
|
|
1279
|
+
request: fetchOptions
|
|
1280
|
+
});
|
|
1281
|
+
_stats.statusOnNext = "error";
|
|
1282
|
+
resolve(error);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
972
1285
|
}
|
|
973
|
-
|
|
974
|
-
|
|
1286
|
+
let data;
|
|
1287
|
+
const contentType = responseHeaders["content-type"] || "";
|
|
1288
|
+
const responseType = config.responseType || fetchOptions.responseType || "auto";
|
|
1289
|
+
if (responseType === "buffer" || responseType === "arrayBuffer") {
|
|
1290
|
+
data = responseBody;
|
|
1291
|
+
} else if (responseType === "text") {
|
|
1292
|
+
data = responseBody.toString("utf-8");
|
|
1293
|
+
} else if (responseType === "json" || responseType === "auto" && contentType.includes("application/json")) {
|
|
975
1294
|
try {
|
|
976
1295
|
data = JSON.parse(responseBody.toString("utf-8"));
|
|
977
1296
|
} catch {
|
|
978
1297
|
data = responseBody.toString("utf-8");
|
|
979
1298
|
}
|
|
980
1299
|
} else {
|
|
981
|
-
|
|
1300
|
+
if (contentType.includes("application/json")) {
|
|
1301
|
+
try {
|
|
1302
|
+
data = JSON.parse(responseBody.toString("utf-8"));
|
|
1303
|
+
} catch {
|
|
1304
|
+
data = responseBody.toString("utf-8");
|
|
1305
|
+
}
|
|
1306
|
+
} else {
|
|
1307
|
+
data = responseBody.toString("utf-8");
|
|
1308
|
+
}
|
|
982
1309
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1310
|
+
config.status = status;
|
|
1311
|
+
config.statusText = statusText;
|
|
1312
|
+
_stats.statusOnNext = status >= 400 ? "error" : "success";
|
|
1313
|
+
const responseCookies = config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] };
|
|
1314
|
+
const mergedCookies = mergeRequestAndResponseCookies(config, responseCookies, url.href);
|
|
1315
|
+
const finalResponse = {
|
|
1316
|
+
data,
|
|
1317
|
+
status,
|
|
1318
|
+
statusText,
|
|
1319
|
+
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1320
|
+
cookies: mergedCookies,
|
|
1321
|
+
config,
|
|
1322
|
+
contentType,
|
|
1323
|
+
contentLength: contentLengthCounter,
|
|
1324
|
+
finalUrl: url.href,
|
|
1325
|
+
urls: buildUrlTree(config, url.href)
|
|
1326
|
+
};
|
|
1327
|
+
if (downloadResult && fs && config.fileName) {
|
|
1328
|
+
try {
|
|
1329
|
+
fs.writeFileSync(config.fileName, responseBody);
|
|
1330
|
+
const downloadFinishEvent = {
|
|
1331
|
+
status,
|
|
1332
|
+
statusText,
|
|
1333
|
+
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1334
|
+
contentType,
|
|
1335
|
+
contentLength: responseBody.length,
|
|
1336
|
+
finalUrl: url.href,
|
|
1337
|
+
cookies: mergedCookies,
|
|
1338
|
+
urls: buildUrlTree(config, url.href),
|
|
1339
|
+
fileName: config.fileName,
|
|
1340
|
+
fileSize: responseBody.length,
|
|
1341
|
+
timing: {
|
|
1342
|
+
...getTimingDurations(config),
|
|
1343
|
+
download: getTimingDurations(config).download || 0
|
|
1344
|
+
},
|
|
1345
|
+
averageSpeed: getTimingDurations(config).download ? responseBody.length / getTimingDurations(config).download * 1000 : 0,
|
|
1346
|
+
config: sanitizeConfig(config)
|
|
1347
|
+
};
|
|
1348
|
+
downloadResult.emit("finish", downloadFinishEvent);
|
|
1349
|
+
downloadResult.emit("done", downloadFinishEvent);
|
|
1350
|
+
downloadResult._markFinished();
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
const error = buildDownloadError({
|
|
1353
|
+
statusCode: status,
|
|
1354
|
+
headers: sanitizeHttp2Headers(responseHeaders),
|
|
1355
|
+
contentType,
|
|
1356
|
+
contentLength: String(contentLengthCounter),
|
|
1357
|
+
cookies: config.responseCookies?.setCookiesString || [],
|
|
1358
|
+
statusText: err.message,
|
|
1359
|
+
url: url.href,
|
|
1360
|
+
body: responseBody,
|
|
1361
|
+
finalUrl: url.href,
|
|
1362
|
+
config,
|
|
1363
|
+
request: fetchOptions
|
|
1364
|
+
});
|
|
1365
|
+
downloadResult.emit("error", error);
|
|
1366
|
+
resolve(error);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (streamResult) {
|
|
1371
|
+
const streamFinishEvent = {
|
|
1005
1372
|
status,
|
|
1006
1373
|
statusText,
|
|
1007
1374
|
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1008
1375
|
contentType,
|
|
1009
|
-
contentLength:
|
|
1376
|
+
contentLength: contentLengthCounter,
|
|
1377
|
+
finalUrl: url.href,
|
|
1378
|
+
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
1379
|
+
urls: buildUrlTree(config, url.href),
|
|
1380
|
+
timing: getTimingDurations(config),
|
|
1381
|
+
config: sanitizeConfig(config)
|
|
1382
|
+
};
|
|
1383
|
+
streamResult.emit("finish", streamFinishEvent);
|
|
1384
|
+
streamResult.emit("done", streamFinishEvent);
|
|
1385
|
+
streamResult.emit("end");
|
|
1386
|
+
streamResult._markFinished();
|
|
1387
|
+
}
|
|
1388
|
+
if (uploadResult) {
|
|
1389
|
+
const uploadFinishEvent = {
|
|
1390
|
+
response: {
|
|
1391
|
+
status,
|
|
1392
|
+
statusText,
|
|
1393
|
+
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1394
|
+
data,
|
|
1395
|
+
contentType,
|
|
1396
|
+
contentLength: contentLengthCounter
|
|
1397
|
+
},
|
|
1010
1398
|
finalUrl: url.href,
|
|
1011
|
-
cookies:
|
|
1399
|
+
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
1012
1400
|
urls: buildUrlTree(config, url.href),
|
|
1013
|
-
|
|
1014
|
-
fileSize: responseBody.length,
|
|
1401
|
+
uploadSize: config.transfer.requestSize || 0,
|
|
1015
1402
|
timing: {
|
|
1016
1403
|
...getTimingDurations(config),
|
|
1017
|
-
|
|
1404
|
+
upload: getTimingDurations(config).firstByte || 0,
|
|
1405
|
+
waiting: getTimingDurations(config).download > 0 && getTimingDurations(config).firstByte > 0 ? getTimingDurations(config).download - getTimingDurations(config).firstByte : 0
|
|
1018
1406
|
},
|
|
1019
|
-
|
|
1407
|
+
averageUploadSpeed: getTimingDurations(config).firstByte && config.transfer.requestSize ? config.transfer.requestSize / getTimingDurations(config).firstByte * 1000 : 0,
|
|
1408
|
+
averageDownloadSpeed: getTimingDurations(config).download ? contentLengthCounter / getTimingDurations(config).download * 1000 : 0,
|
|
1020
1409
|
config: sanitizeConfig(config)
|
|
1021
1410
|
};
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
} catch (err) {
|
|
1026
|
-
const error = buildDownloadError({
|
|
1027
|
-
statusCode: status,
|
|
1028
|
-
headers: sanitizeHttp2Headers(responseHeaders),
|
|
1029
|
-
contentType,
|
|
1030
|
-
contentLength: String(contentLengthCounter),
|
|
1031
|
-
cookies: config.responseCookies?.setCookiesString || [],
|
|
1032
|
-
statusText: err.message,
|
|
1033
|
-
url: url.href,
|
|
1034
|
-
body: responseBody,
|
|
1035
|
-
finalUrl: url.href,
|
|
1036
|
-
config,
|
|
1037
|
-
request: fetchOptions
|
|
1038
|
-
});
|
|
1039
|
-
downloadResult.emit("error", error);
|
|
1040
|
-
resolve(error);
|
|
1041
|
-
return;
|
|
1411
|
+
uploadResult.emit("finish", uploadFinishEvent);
|
|
1412
|
+
uploadResult.emit("done", uploadFinishEvent);
|
|
1413
|
+
uploadResult._markFinished();
|
|
1042
1414
|
}
|
|
1415
|
+
resolve(finalResponse);
|
|
1416
|
+
} catch (endError) {
|
|
1417
|
+
if (config.debug) {
|
|
1418
|
+
console.log(`[Rezo Debug] HTTP/2: Error in 'end' handler:`, endError.message);
|
|
1419
|
+
}
|
|
1420
|
+
(sessionPool || Http2SessionPool.getInstance()).releaseSession(url);
|
|
1421
|
+
const error = buildSmartError(config, fetchOptions, endError);
|
|
1422
|
+
_stats.statusOnNext = "error";
|
|
1423
|
+
resolve(error);
|
|
1043
1424
|
}
|
|
1044
|
-
if (streamResult) {
|
|
1045
|
-
const streamFinishEvent = {
|
|
1046
|
-
status,
|
|
1047
|
-
statusText,
|
|
1048
|
-
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1049
|
-
contentType,
|
|
1050
|
-
contentLength: contentLengthCounter,
|
|
1051
|
-
finalUrl: url.href,
|
|
1052
|
-
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
1053
|
-
urls: buildUrlTree(config, url.href),
|
|
1054
|
-
timing: getTimingDurations(config),
|
|
1055
|
-
config: sanitizeConfig(config)
|
|
1056
|
-
};
|
|
1057
|
-
streamResult.emit("finish", streamFinishEvent);
|
|
1058
|
-
streamResult.emit("done", streamFinishEvent);
|
|
1059
|
-
streamResult.emit("end");
|
|
1060
|
-
streamResult._markFinished();
|
|
1061
|
-
}
|
|
1062
|
-
if (uploadResult) {
|
|
1063
|
-
const uploadFinishEvent = {
|
|
1064
|
-
response: {
|
|
1065
|
-
status,
|
|
1066
|
-
statusText,
|
|
1067
|
-
headers: new RezoHeaders(sanitizeHttp2Headers(responseHeaders)),
|
|
1068
|
-
data,
|
|
1069
|
-
contentType,
|
|
1070
|
-
contentLength: contentLengthCounter
|
|
1071
|
-
},
|
|
1072
|
-
finalUrl: url.href,
|
|
1073
|
-
cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
|
|
1074
|
-
urls: buildUrlTree(config, url.href),
|
|
1075
|
-
uploadSize: config.transfer.requestSize || 0,
|
|
1076
|
-
timing: {
|
|
1077
|
-
...getTimingDurations(config),
|
|
1078
|
-
upload: getTimingDurations(config).firstByte || 0,
|
|
1079
|
-
waiting: getTimingDurations(config).download > 0 && getTimingDurations(config).firstByte > 0 ? getTimingDurations(config).download - getTimingDurations(config).firstByte : 0
|
|
1080
|
-
},
|
|
1081
|
-
averageUploadSpeed: getTimingDurations(config).firstByte && config.transfer.requestSize ? config.transfer.requestSize / getTimingDurations(config).firstByte * 1000 : 0,
|
|
1082
|
-
averageDownloadSpeed: getTimingDurations(config).download ? contentLengthCounter / getTimingDurations(config).download * 1000 : 0,
|
|
1083
|
-
config: sanitizeConfig(config)
|
|
1084
|
-
};
|
|
1085
|
-
uploadResult.emit("finish", uploadFinishEvent);
|
|
1086
|
-
uploadResult.emit("done", uploadFinishEvent);
|
|
1087
|
-
uploadResult._markFinished();
|
|
1088
|
-
}
|
|
1089
|
-
resolve(finalResponse);
|
|
1090
|
-
});
|
|
1091
|
-
req.on("error", (err) => {
|
|
1092
|
-
_stats.statusOnNext = "error";
|
|
1093
|
-
(sessionPool || Http2SessionPool.getInstance()).releaseSession(url);
|
|
1094
|
-
const error = buildSmartError(config, fetchOptions, err);
|
|
1095
|
-
if (eventEmitter) {
|
|
1096
|
-
eventEmitter.emit("error", error);
|
|
1097
|
-
}
|
|
1098
|
-
resolve(error);
|
|
1099
1425
|
});
|
|
1100
1426
|
if (body) {
|
|
1427
|
+
if (config.debug) {
|
|
1428
|
+
console.log(`[Rezo Debug] HTTP/2: Writing request body (type: ${body?.constructor?.name || typeof body})...`);
|
|
1429
|
+
}
|
|
1101
1430
|
if (body instanceof URLSearchParams || body instanceof RezoURLSearchParams) {
|
|
1102
1431
|
req.write(body.toString());
|
|
1103
1432
|
} else if (body instanceof FormData || body instanceof RezoFormData) {
|
|
@@ -1107,13 +1436,26 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
|
|
|
1107
1436
|
} else if (typeof body === "object" && !Buffer.isBuffer(body) && !(body instanceof Uint8Array) && !(body instanceof Readable)) {
|
|
1108
1437
|
req.write(JSON.stringify(body));
|
|
1109
1438
|
} else if (body instanceof Readable) {
|
|
1439
|
+
if (config.debug) {
|
|
1440
|
+
console.log(`[Rezo Debug] HTTP/2: Piping stream body...`);
|
|
1441
|
+
}
|
|
1110
1442
|
body.pipe(req);
|
|
1111
1443
|
return;
|
|
1112
1444
|
} else {
|
|
1113
1445
|
req.write(body);
|
|
1114
1446
|
}
|
|
1447
|
+
if (config.debug) {
|
|
1448
|
+
console.log(`[Rezo Debug] HTTP/2: Body written, calling req.end()...`);
|
|
1449
|
+
}
|
|
1450
|
+
} else {
|
|
1451
|
+
if (config.debug) {
|
|
1452
|
+
console.log(`[Rezo Debug] HTTP/2: No body, calling req.end()...`);
|
|
1453
|
+
}
|
|
1115
1454
|
}
|
|
1116
1455
|
req.end();
|
|
1456
|
+
if (config.debug) {
|
|
1457
|
+
console.log(`[Rezo Debug] HTTP/2: req.end() called, waiting for response...`);
|
|
1458
|
+
}
|
|
1117
1459
|
} catch (error) {
|
|
1118
1460
|
_stats.statusOnNext = "error";
|
|
1119
1461
|
const rezoError = buildSmartError(config, fetchOptions, error);
|