@api-client/core 0.18.12 → 0.18.13
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/build/src/browser.d.ts +0 -3
- package/build/src/browser.d.ts.map +1 -1
- package/build/src/browser.js +0 -3
- package/build/src/browser.js.map +1 -1
- package/build/src/index.d.ts +2 -5
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -5
- package/build/src/index.js.map +1 -1
- package/build/src/lib/logging/DefaultLogger.d.ts +14 -0
- package/build/src/lib/logging/DefaultLogger.d.ts.map +1 -1
- package/build/src/lib/logging/DefaultLogger.js +27 -0
- package/build/src/lib/logging/DefaultLogger.js.map +1 -1
- package/build/src/lib/logging/index.d.ts +4 -0
- package/build/src/lib/logging/index.d.ts.map +1 -0
- package/build/src/lib/logging/index.js +10 -0
- package/build/src/lib/logging/index.js.map +1 -0
- package/build/src/models/ClientCertificate.d.ts +1 -1
- package/build/src/models/ClientCertificate.js.map +1 -1
- package/build/src/models/RequestConfig.d.ts +1 -1
- package/build/src/models/RequestConfig.js.map +1 -1
- package/build/src/models/SerializableError.d.ts +1 -1
- package/build/src/models/SerializableError.d.ts.map +1 -1
- package/build/src/models/SerializableError.js.map +1 -1
- package/build/src/proxy/RequestProxy.d.ts.map +1 -1
- package/build/src/proxy/RequestProxy.js +2 -2
- package/build/src/proxy/RequestProxy.js.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.d.ts +218 -139
- package/build/src/runtime/http-engine/CoreEngine.d.ts.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.js +716 -870
- package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
- package/build/src/runtime/http-engine/PayloadSupport.d.ts.map +1 -1
- package/build/src/runtime/http-engine/PayloadSupport.js +2 -1
- package/build/src/runtime/http-engine/PayloadSupport.js.map +1 -1
- package/build/src/runtime/http-engine/auth/AuthManager.d.ts +73 -0
- package/build/src/runtime/http-engine/auth/AuthManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/auth/AuthManager.js +186 -0
- package/build/src/runtime/http-engine/auth/AuthManager.js.map +1 -0
- package/build/src/runtime/http-engine/auth/index.d.ts +2 -0
- package/build/src/runtime/http-engine/auth/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/auth/index.js +2 -0
- package/build/src/runtime/http-engine/auth/index.js.map +1 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts +11 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.js +76 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.js.map +1 -0
- package/build/src/runtime/http-engine/certificates/index.d.ts +2 -0
- package/build/src/runtime/http-engine/certificates/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/certificates/index.js +2 -0
- package/build/src/runtime/http-engine/certificates/index.js.map +1 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.d.ts +25 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.js +89 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.js.map +1 -0
- package/build/src/runtime/http-engine/compression/index.d.ts +2 -0
- package/build/src/runtime/http-engine/compression/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/compression/index.js +2 -0
- package/build/src/runtime/http-engine/compression/index.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts +57 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.js +78 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.js.map +1 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts +70 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.js +184 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.js.map +1 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.d.ts +22 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.js +105 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts +60 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js +138 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts +14 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.js +47 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts +13 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.js +175 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/index.d.ts +7 -0
- package/build/src/runtime/http-engine/connections/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/index.js +7 -0
- package/build/src/runtime/http-engine/connections/index.js.map +1 -0
- package/build/src/runtime/http-engine/constants.d.ts +69 -0
- package/build/src/runtime/http-engine/constants.d.ts.map +1 -0
- package/build/src/runtime/http-engine/constants.js +90 -0
- package/build/src/runtime/http-engine/constants.js.map +1 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts +5 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.js +20 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/cookies/index.d.ts +2 -0
- package/build/src/runtime/http-engine/cookies/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/cookies/index.js +2 -0
- package/build/src/runtime/http-engine/cookies/index.js.map +1 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts +156 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts.map +1 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.js +227 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.js.map +1 -0
- package/build/src/runtime/http-engine/errors/index.d.ts +2 -0
- package/build/src/runtime/http-engine/errors/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/errors/index.js +2 -0
- package/build/src/runtime/http-engine/errors/index.js.map +1 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.d.ts +66 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.js +161 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.js.map +1 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.d.ts +27 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.js +51 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/message/index.d.ts +3 -0
- package/build/src/runtime/http-engine/message/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/index.js +3 -0
- package/build/src/runtime/http-engine/message/index.js.map +1 -0
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts +2 -8
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts.map +1 -1
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.js +11 -5
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.js.map +1 -1
- package/build/src/runtime/http-engine/ntlm/NtlmMessage.js +6 -6
- package/build/src/runtime/http-engine/ntlm/NtlmMessage.js.map +1 -1
- package/build/src/runtime/http-engine/parsers/BodyParser.d.ts +39 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.js +145 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts +29 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.js +88 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts +91 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.js +236 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.d.ts +20 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.js +51 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/index.d.ts +5 -0
- package/build/src/runtime/http-engine/parsers/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/index.js +5 -0
- package/build/src/runtime/http-engine/parsers/index.js.map +1 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts +22 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.js +25 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/response/index.d.ts +2 -0
- package/build/src/runtime/http-engine/response/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/response/index.js +2 -0
- package/build/src/runtime/http-engine/response/index.js.map +1 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts +7 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js +40 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/statistics/index.d.ts +2 -0
- package/build/src/runtime/http-engine/statistics/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/statistics/index.js +2 -0
- package/build/src/runtime/http-engine/statistics/index.js.map +1 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.d.ts +24 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.js +50 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/url/index.d.ts +2 -0
- package/build/src/runtime/http-engine/url/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/url/index.js +2 -0
- package/build/src/runtime/http-engine/url/index.js.map +1 -0
- package/build/src/runtime/http-runner/HttpRequestRunner.d.ts +3 -3
- package/build/src/runtime/http-runner/HttpRequestRunner.d.ts.map +1 -1
- package/build/src/runtime/http-runner/HttpRequestRunner.js.map +1 -1
- package/build/src/runtime/node/InteropInterfaces.d.ts +3 -3
- package/build/src/runtime/node/InteropInterfaces.d.ts.map +1 -1
- package/build/src/runtime/node/InteropInterfaces.js.map +1 -1
- package/build/src/runtime/node/ProjectRequestRunner.d.ts +2 -2
- package/build/src/runtime/node/ProjectRequestRunner.d.ts.map +1 -1
- package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRunner.d.ts.map +1 -1
- package/build/src/runtime/node/ProjectRunner.js +2 -2
- package/build/src/runtime/node/ProjectRunner.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +6 -6
- package/package.json +2 -2
- package/src/lib/logging/DefaultLogger.ts +32 -0
- package/src/models/ClientCertificate.ts +1 -1
- package/src/models/RequestConfig.ts +1 -1
- package/src/models/SerializableError.ts +1 -1
- package/src/proxy/RequestProxy.ts +2 -2
- package/src/runtime/http-engine/CoreEngine.ts +858 -893
- package/src/runtime/http-engine/PayloadSupport.ts +2 -1
- package/src/runtime/http-engine/auth/AuthManager.ts +242 -0
- package/src/runtime/http-engine/certificates/CertificateManager.ts +74 -0
- package/src/runtime/http-engine/compression/CompressionManager.ts +99 -0
- package/src/runtime/http-engine/connections/ConnectionManager.ts +123 -0
- package/src/runtime/http-engine/connections/DigestAuthHandler.ts +238 -0
- package/src/runtime/http-engine/connections/DirectConnection.ts +134 -0
- package/src/runtime/http-engine/connections/ProxyAuthHandler.ts +179 -0
- package/src/runtime/http-engine/connections/ProxyConnection.ts +55 -0
- package/src/runtime/http-engine/connections/TunnelConnection.ts +192 -0
- package/src/runtime/http-engine/constants.ts +103 -0
- package/src/runtime/http-engine/cookies/CookieProcessor.ts +25 -0
- package/src/runtime/http-engine/errors/HttpEngineErrors.ts +319 -0
- package/src/runtime/http-engine/message/MessageBuilder.ts +201 -0
- package/src/runtime/http-engine/message/MessageProcessor.ts +73 -0
- package/src/runtime/http-engine/ntlm/NtlmAuth.ts +16 -13
- package/src/runtime/http-engine/ntlm/NtlmMessage.ts +6 -6
- package/src/runtime/http-engine/parsers/BodyParser.ts +171 -0
- package/src/runtime/http-engine/parsers/HeadersParser.ts +103 -0
- package/src/runtime/http-engine/parsers/HttpResponseParser.ts +280 -0
- package/src/runtime/http-engine/parsers/StatusParser.ts +69 -0
- package/src/runtime/http-engine/response/ResponseProcessor.ts +46 -0
- package/src/runtime/http-engine/statistics/StatisticsProcessor.ts +52 -0
- package/src/runtime/http-engine/url/UrlProcessor.ts +55 -0
- package/src/runtime/http-runner/HttpRequestRunner.ts +3 -3
- package/src/runtime/node/InteropInterfaces.ts +3 -3
- package/src/runtime/node/ProjectRequestRunner.ts +2 -2
- package/src/runtime/node/ProjectRunner.ts +2 -2
- package/tests/servers/ProxyServer.ts +32 -19
- package/tests/servers/express-routes/ApiEndpoint.ts +24 -0
- package/tests/servers/express-routes/BasicAuthRoute.ts +36 -0
- package/tests/servers/express-routes/BearerAuthRoute.ts +35 -0
- package/tests/servers/express-routes/NTLMRoute.ts +2 -3
- package/tests/servers/express-routes/PostApi.ts +15 -2
- package/tests/servers/express-routes/RedirectsApi.ts +12 -1
- package/tests/servers/express-routes/ResponsesApi.ts +1 -1
- package/tests/servers/express-routes/StreamApi.ts +19 -0
- package/tests/servers/oauth2mock/ServerMock.js +1 -1
- package/tests/unit/runtime/http-engine/HttpResponseParser.spec.ts +337 -0
- package/tests/unit/runtime/http-engine/abort.spec.ts +4 -5
- package/tests/unit/runtime/http-engine/auth.spec.ts +7 -58
- package/tests/unit/runtime/http-engine/certificates/CertificateManager.spec.ts +482 -0
- package/tests/unit/runtime/http-engine/certificates.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/compression/CompressionManager.spec.ts +498 -0
- package/tests/unit/runtime/http-engine/compression.spec.ts +3 -72
- package/tests/unit/runtime/http-engine/connections/ConnectionManager.spec.ts +379 -0
- package/tests/unit/runtime/http-engine/connections/DigestAuthHandler.spec.ts +164 -0
- package/tests/unit/runtime/http-engine/core_engine.spec.ts +561 -0
- package/tests/unit/runtime/http-engine/engine_statuses.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/events.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/headers.spec.ts +2 -88
- package/tests/unit/runtime/http-engine/hosts.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/http-get.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/http-post.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/logger.spec.ts +0 -8
- package/tests/unit/runtime/http-engine/message.spec.ts +2 -194
- package/tests/unit/runtime/http-engine/params.spec.ts +4 -4
- package/tests/unit/runtime/http-engine/proxy.spec.ts +15 -14
- package/tests/unit/runtime/http-engine/redirects.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/responses.spec.ts +170 -277
- package/tests/unit/runtime/http-engine/timeout.spec.ts +3 -3
- package/tests/unit/runtime/http-engine/timings.spec.ts +2 -2
- package/tests/unit/runtime/proxy/HttpProjectProxy.spec.ts +25 -28
- package/tests/unit/runtime/runners/project_runner.spec.ts +2 -2
- package/tests/unit/runtime/runners/request_runner.spec.ts +2 -2
- package/build/src/runtime/http-engine/HttpEngine.d.ts +0 -311
- package/build/src/runtime/http-engine/HttpEngine.d.ts.map +0 -1
- package/build/src/runtime/http-engine/HttpEngine.js +0 -802
- package/build/src/runtime/http-engine/HttpEngine.js.map +0 -1
- package/src/runtime/http-engine/HttpEngine.ts +0 -952
- package/tests/unit/runtime/http-engine/connecting.spec.ts +0 -140
|
@@ -1,45 +1,268 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import https from 'https';
|
|
5
|
-
import { HttpEngine } from './HttpEngine.js';
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { HttpRequest } from '../../models/HttpRequest.js';
|
|
3
|
+
import { SentRequest } from '../../models/SentRequest.js';
|
|
6
4
|
import { Response } from '../../models/Response.js';
|
|
5
|
+
import { ErrorResponse } from '../../models/ErrorResponse.js';
|
|
6
|
+
import { RequestsSize } from '../../models/RequestsSize.js';
|
|
7
|
+
import { HttpResponse } from '../../models/HttpResponse.js';
|
|
8
|
+
import { ResponseRedirect } from '../../models/ResponseRedirect.js';
|
|
9
|
+
import { RequestLog } from '../../models/RequestLog.js';
|
|
7
10
|
import { SerializableError } from '../../models/SerializableError.js';
|
|
11
|
+
import { ResponseAuthorization } from '../../models/ResponseAuthorization.js';
|
|
8
12
|
import { Headers } from '../../lib/headers/Headers.js';
|
|
13
|
+
import { getPort } from './RequestUtils.js';
|
|
14
|
+
import { HttpResponseParser, RequestState } from './parsers/index.js';
|
|
15
|
+
import { ConnectionManager } from './connections/index.js';
|
|
16
|
+
import { AuthManager } from './auth/index.js';
|
|
17
|
+
import { MessageBuilder } from './message/index.js';
|
|
18
|
+
import { HttpEngineErrorFactory } from './errors/index.js';
|
|
19
|
+
import { isMethodWithoutBody, HEADER_PROXY_AUTHORIZATION } from './constants.js';
|
|
20
|
+
import { decompress } from './compression/index.js';
|
|
21
|
+
import { checkServerIdentity } from './certificates/index.js';
|
|
22
|
+
import { readUrl, getHostHeader, getRedirectLocation, isRedirectLoop } from './url/index.js';
|
|
23
|
+
import { processRedirectCookies } from './cookies/index.js';
|
|
24
|
+
import { computeStats } from './statistics/index.js';
|
|
25
|
+
import { processResponse } from './response/index.js';
|
|
26
|
+
import { getCodeMessage } from './HttpErrorCodes.js';
|
|
27
|
+
import { createLogger } from '../../lib/logging/index.js';
|
|
9
28
|
import * as PayloadSupport from './PayloadSupport.js';
|
|
10
|
-
import { addContentLength
|
|
11
|
-
import { NtlmAuth } from './ntlm/NtlmAuth.js';
|
|
29
|
+
import { addContentLength } from './RequestUtils.js';
|
|
12
30
|
import { PayloadSerializer } from '../../lib/transformers/PayloadSerializer.js';
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
export var RequestState;
|
|
16
|
-
(function (RequestState) {
|
|
17
|
-
RequestState[RequestState["Status"] = 0] = "Status";
|
|
18
|
-
RequestState[RequestState["Headers"] = 1] = "Headers";
|
|
19
|
-
RequestState[RequestState["Body"] = 2] = "Body";
|
|
20
|
-
RequestState[RequestState["Done"] = 3] = "Done";
|
|
21
|
-
})(RequestState || (RequestState = {}));
|
|
31
|
+
import { ProxyAuthenticationError } from './errors/HttpEngineErrors.js';
|
|
32
|
+
export const mainPromiseSymbol = Symbol('mainPromise');
|
|
22
33
|
/**
|
|
23
|
-
* API Client's HTTP engine.
|
|
34
|
+
* API Client's HTTP engine with refactored parsing and connection handling.
|
|
24
35
|
* An HTTP 1.1 engine working directly on the socket. It communicates with the remote machine and
|
|
25
36
|
* collects stats about the request and response.
|
|
26
37
|
*/
|
|
27
|
-
export class CoreEngine extends
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
export class CoreEngine extends EventEmitter {
|
|
39
|
+
request;
|
|
40
|
+
opts;
|
|
41
|
+
logger;
|
|
42
|
+
/**
|
|
43
|
+
* The current sent request
|
|
44
|
+
*/
|
|
45
|
+
sentRequest;
|
|
46
|
+
redirects = [];
|
|
47
|
+
/**
|
|
48
|
+
* When true the request has been aborted.
|
|
49
|
+
*/
|
|
50
|
+
aborted = false;
|
|
51
|
+
/**
|
|
52
|
+
* Parsed value of the request URL.
|
|
53
|
+
*/
|
|
54
|
+
uri;
|
|
55
|
+
socket;
|
|
56
|
+
/**
|
|
57
|
+
* Host header can be different than registered URL because of
|
|
58
|
+
* `hosts` rules.
|
|
59
|
+
* If a rule changes host value of the URL the original URL host value
|
|
60
|
+
* is used when generating the request and not overwritten one.
|
|
61
|
+
* This way virtual hosts can be tested using hosts.
|
|
62
|
+
*/
|
|
63
|
+
hostHeader;
|
|
64
|
+
hostTestReg = /^\s*host\s*:/im;
|
|
65
|
+
/**
|
|
66
|
+
* Set when the request is redirected.
|
|
67
|
+
*/
|
|
68
|
+
redirecting = false;
|
|
69
|
+
/**
|
|
70
|
+
* The response headers.
|
|
71
|
+
* The object may be empty when the response is not set.
|
|
72
|
+
*/
|
|
73
|
+
currentHeaders = new Headers();
|
|
74
|
+
/**
|
|
75
|
+
* The response object build during the execution.
|
|
76
|
+
*/
|
|
77
|
+
currentResponse;
|
|
78
|
+
/**
|
|
79
|
+
* Keeps the raw body in a temporary buffer while processing the response.
|
|
80
|
+
*/
|
|
81
|
+
_rawBody;
|
|
82
|
+
stats = {};
|
|
83
|
+
auth;
|
|
84
|
+
mainResolver;
|
|
85
|
+
mainRejecter;
|
|
86
|
+
[mainPromiseSymbol];
|
|
87
|
+
_signal;
|
|
88
|
+
// CoreEngine specific properties
|
|
89
|
+
parser;
|
|
90
|
+
connectionManager;
|
|
91
|
+
authManager;
|
|
92
|
+
messageBuilder;
|
|
32
93
|
hasProxy;
|
|
33
94
|
isProxyTunnel;
|
|
34
95
|
isProxySsl;
|
|
96
|
+
/**
|
|
97
|
+
* @return True if following redirects is allowed.
|
|
98
|
+
*/
|
|
99
|
+
get followRedirects() {
|
|
100
|
+
const { opts } = this;
|
|
101
|
+
if (typeof opts.followRedirects === 'boolean') {
|
|
102
|
+
return opts.followRedirects;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The request timeout.
|
|
108
|
+
*/
|
|
109
|
+
get timeout() {
|
|
110
|
+
const { opts } = this;
|
|
111
|
+
if (typeof opts.timeout === 'number') {
|
|
112
|
+
return opts.timeout;
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The abort signal to set on this request.
|
|
118
|
+
* Aborts the request when the signal fires.
|
|
119
|
+
*/
|
|
120
|
+
get signal() {
|
|
121
|
+
return this._signal;
|
|
122
|
+
}
|
|
123
|
+
set signal(value) {
|
|
124
|
+
const old = this._signal;
|
|
125
|
+
if (old === value) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this._signal = value;
|
|
129
|
+
if (old) {
|
|
130
|
+
old.removeEventListener('abort', this._abortHandler);
|
|
131
|
+
}
|
|
132
|
+
if (value) {
|
|
133
|
+
value.addEventListener('abort', this._abortHandler);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
35
136
|
constructor(request, opts = {}) {
|
|
36
|
-
super(
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
137
|
+
super();
|
|
138
|
+
this.request = new HttpRequest({ ...request });
|
|
139
|
+
this.opts = opts;
|
|
140
|
+
this.logger = this.setupLogger(opts);
|
|
141
|
+
this.sentRequest = new SentRequest({ ...request, startTime: Date.now() });
|
|
142
|
+
this.uri = this.readUrl(request.url);
|
|
143
|
+
this.hostHeader = this.getHostHeader(request.url);
|
|
144
|
+
// CoreEngine specific initialization
|
|
40
145
|
this.hasProxy = !!this.opts.proxy;
|
|
41
146
|
this.isProxyTunnel = this.hasProxy && this.request.url.startsWith('https:');
|
|
42
147
|
this.isProxySsl = !!this.opts.proxy && this.opts.proxy.startsWith('https:');
|
|
148
|
+
// Initialize the HTTP response parser
|
|
149
|
+
this.parser = new HttpResponseParser(this.logger, this.createParserCallbacks());
|
|
150
|
+
// Initialize the connection manager
|
|
151
|
+
this.connectionManager = new ConnectionManager(this.logger, this.stats);
|
|
152
|
+
// Initialize the auth manager
|
|
153
|
+
const proxyCredentials = {
|
|
154
|
+
proxyUsername: this.opts.proxyUsername,
|
|
155
|
+
proxyPassword: this.opts.proxyPassword,
|
|
156
|
+
proxyAuthorization: this.opts.proxyAuthorization,
|
|
157
|
+
};
|
|
158
|
+
this.authManager = new AuthManager({
|
|
159
|
+
logger: this.logger,
|
|
160
|
+
proxyCredentials,
|
|
161
|
+
});
|
|
162
|
+
// Initialize the message builder
|
|
163
|
+
this.messageBuilder = new MessageBuilder({
|
|
164
|
+
logger: this.logger,
|
|
165
|
+
request: this.request,
|
|
166
|
+
hasProxy: this.hasProxy,
|
|
167
|
+
isProxyTunnel: this.isProxyTunnel,
|
|
168
|
+
hostHeader: this.hostHeader,
|
|
169
|
+
});
|
|
170
|
+
this._abortHandler = this._abortHandler.bind(this);
|
|
171
|
+
if (opts.signal) {
|
|
172
|
+
this.signal = opts.signal;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Creates a logger object to log debug output.
|
|
177
|
+
*/
|
|
178
|
+
setupLogger(opts = {}) {
|
|
179
|
+
if (opts.logger) {
|
|
180
|
+
return opts.logger;
|
|
181
|
+
}
|
|
182
|
+
return createLogger();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Updates the `uri` property from current request URL
|
|
186
|
+
*/
|
|
187
|
+
readUrl(value) {
|
|
188
|
+
return readUrl(value, { hosts: this.opts.hosts });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get host header for the request
|
|
192
|
+
*/
|
|
193
|
+
getHostHeader(url) {
|
|
194
|
+
return getHostHeader(url, { hosts: this.opts.hosts });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Aborts current request.
|
|
198
|
+
* It emits `error` event
|
|
199
|
+
*/
|
|
200
|
+
abort() {
|
|
201
|
+
this.logger.debug('Aborting the request...');
|
|
202
|
+
this.aborted = true;
|
|
203
|
+
if (!this.socket) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.socket.removeAllListeners();
|
|
207
|
+
if (this.socket.destroyed) {
|
|
208
|
+
this.socket = undefined;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.socket.pause();
|
|
212
|
+
this.socket.destroy();
|
|
213
|
+
this.socket = undefined;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Handler for the `abort` event on the `AbortSignal`.
|
|
217
|
+
*/
|
|
218
|
+
_abortHandler() {
|
|
219
|
+
const e = new SerializableError('Request aborted', 3);
|
|
220
|
+
this._errorRequest(e);
|
|
221
|
+
this.abort();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Create parser callbacks to handle parsed data
|
|
225
|
+
*/
|
|
226
|
+
createParserCallbacks() {
|
|
227
|
+
return {
|
|
228
|
+
onStatusParsed: (status, statusText) => {
|
|
229
|
+
this.logger.debug('onStatusParsed called', status, statusText);
|
|
230
|
+
const response = Response.fromValues(status, statusText);
|
|
231
|
+
response.loadingTime = 0;
|
|
232
|
+
this.currentResponse = response;
|
|
233
|
+
},
|
|
234
|
+
onHeadersParsed: (headers) => {
|
|
235
|
+
this.logger.debug('onHeadersParsed called');
|
|
236
|
+
this.currentHeaders = headers;
|
|
237
|
+
if (this.currentResponse) {
|
|
238
|
+
this.currentResponse.headers = headers.toString();
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
onBodyComplete: (body) => {
|
|
242
|
+
this.logger.debug('onBodyComplete called');
|
|
243
|
+
this._rawBody = body;
|
|
244
|
+
this._reportResponse().catch((error) => {
|
|
245
|
+
this.logger.error('Error in _reportResponse:', error);
|
|
246
|
+
this._errorRequest({
|
|
247
|
+
message: `Response processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
onError: (error) => {
|
|
252
|
+
this.logger.debug('onError called', error);
|
|
253
|
+
this._errorRequest({
|
|
254
|
+
message: error.message || 'Unknown error occurred',
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
onAbort: () => {
|
|
258
|
+
this.logger.debug('onAbort called');
|
|
259
|
+
this.abort();
|
|
260
|
+
},
|
|
261
|
+
emit: (event, detail) => {
|
|
262
|
+
this.logger.debug('emit called', event, detail);
|
|
263
|
+
this.emit(event, detail);
|
|
264
|
+
},
|
|
265
|
+
};
|
|
43
266
|
}
|
|
44
267
|
/**
|
|
45
268
|
* Sends the request
|
|
@@ -51,19 +274,27 @@ export class CoreEngine extends HttpEngine {
|
|
|
51
274
|
}
|
|
52
275
|
async sendRequest() {
|
|
53
276
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
else {
|
|
58
|
-
await this.connect();
|
|
59
|
-
}
|
|
60
|
-
if (!this.socket || this.aborted) {
|
|
277
|
+
// Use the connection manager to establish the connection
|
|
278
|
+
const socket = await this.establishConnection();
|
|
279
|
+
if (!socket || this.aborted) {
|
|
61
280
|
return;
|
|
62
281
|
}
|
|
63
282
|
const message = await this.prepareMessage();
|
|
64
283
|
await this.writeMessage(message);
|
|
65
284
|
}
|
|
66
285
|
catch (cause) {
|
|
286
|
+
if (cause instanceof ProxyAuthenticationError) {
|
|
287
|
+
this.currentHeaders = new Headers(cause.response.headers);
|
|
288
|
+
const currentResponse = Response.fromValues(cause.response.statusCode || 401, cause.response.statusMessage, this.currentHeaders.toString());
|
|
289
|
+
currentResponse.loadingTime = 0;
|
|
290
|
+
this.currentResponse = currentResponse;
|
|
291
|
+
if (cause.head.length) {
|
|
292
|
+
this._rawBody = cause.head;
|
|
293
|
+
currentResponse.payload = PayloadSerializer.stringifyBuffer(cause.head);
|
|
294
|
+
}
|
|
295
|
+
this._publishResponse();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
67
298
|
const e = cause;
|
|
68
299
|
const err = new SerializableError(e.message, { cause: e });
|
|
69
300
|
if (e.code || e.code === 0) {
|
|
@@ -74,46 +305,371 @@ export class CoreEngine extends HttpEngine {
|
|
|
74
305
|
message: err.message,
|
|
75
306
|
code: err.code,
|
|
76
307
|
});
|
|
77
|
-
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Establish connection using the connection manager
|
|
312
|
+
*/
|
|
313
|
+
async establishConnection() {
|
|
314
|
+
const port = getPort(this.uri.port, this.uri.protocol);
|
|
315
|
+
const host = this.uri.hostname;
|
|
316
|
+
const protocol = this.uri.protocol;
|
|
317
|
+
const socket = await this.connectionManager.createConnection(host, port, protocol, this.opts);
|
|
318
|
+
const { timeout } = this;
|
|
319
|
+
if (timeout > 0) {
|
|
320
|
+
socket.setTimeout(timeout);
|
|
321
|
+
}
|
|
322
|
+
this.socket = socket;
|
|
323
|
+
this._addSocketListeners(socket);
|
|
324
|
+
socket.resume();
|
|
325
|
+
return socket;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Decompresses received body if `content-encoding` header is set.
|
|
329
|
+
*/
|
|
330
|
+
async decompress(body) {
|
|
331
|
+
return decompress(body, {
|
|
332
|
+
aborted: this.aborted,
|
|
333
|
+
headers: this.currentHeaders,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Prepares headers list to be send to the remote machine.
|
|
338
|
+
* If `defaultHeaders` option is set then it adds `user-agent` and `accept`
|
|
339
|
+
* headers.
|
|
340
|
+
*/
|
|
341
|
+
prepareHeaders(headers, withPayload = false) {
|
|
342
|
+
if (this.opts.defaultHeaders) {
|
|
343
|
+
if (!headers.has('user-agent')) {
|
|
344
|
+
if (this.opts.defaultUserAgent) {
|
|
345
|
+
headers.set('user-agent', this.opts.defaultUserAgent);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
headers.set('user-agent', 'api client');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (!headers.has('accept')) {
|
|
352
|
+
if (this.opts.defaultAccept) {
|
|
353
|
+
headers.set('accept', this.opts.defaultAccept);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
headers.set('accept', '*/*');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (withPayload) {
|
|
361
|
+
if (!headers.has('content-type') && this.request.payloadOptions) {
|
|
362
|
+
if (this.request.payloadOptions.mime) {
|
|
363
|
+
headers.set('content-type', this.request.payloadOptions.mime);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
78
366
|
}
|
|
79
367
|
}
|
|
80
368
|
/**
|
|
81
369
|
* Cleans the state after finished.
|
|
82
370
|
*/
|
|
83
371
|
_cleanUp() {
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
};
|
|
372
|
+
this.redirects = [];
|
|
373
|
+
this.currentHeaders = new Headers();
|
|
374
|
+
this.currentResponse = undefined;
|
|
375
|
+
this._rawBody = undefined;
|
|
376
|
+
this.redirecting = false;
|
|
377
|
+
this.stats = {};
|
|
378
|
+
this.mainRejecter = undefined;
|
|
379
|
+
this.mainResolver = undefined;
|
|
380
|
+
this._clearSocketEventListeners();
|
|
381
|
+
this.parser.reset();
|
|
90
382
|
}
|
|
91
383
|
/**
|
|
92
384
|
* Cleans up the state for redirect.
|
|
93
385
|
*/
|
|
94
386
|
_cleanUpRedirect() {
|
|
95
|
-
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
99
|
-
|
|
387
|
+
this.currentHeaders = new Headers();
|
|
388
|
+
this.currentResponse = undefined;
|
|
389
|
+
this._rawBody = undefined;
|
|
390
|
+
this.stats = {};
|
|
391
|
+
this._clearSocketEventListeners();
|
|
392
|
+
this.parser.reset();
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Reports response when redirected.
|
|
396
|
+
*/
|
|
397
|
+
_reportRedirect(status) {
|
|
398
|
+
const { request, currentHeaders } = this;
|
|
399
|
+
if (!currentHeaders) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const rerUrl = currentHeaders.get('location');
|
|
403
|
+
// https://github.com/jarrodek/socket-fetch/issues/13
|
|
404
|
+
const redirectOptions = this.getRedirectOptions(status, request.method, rerUrl);
|
|
405
|
+
if (!redirectOptions.redirect) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
this.redirecting = true;
|
|
409
|
+
setTimeout(() => this._redirectRequest(redirectOptions));
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Get redirect options based on status and method
|
|
414
|
+
*/
|
|
415
|
+
getRedirectOptions(status, method, location) {
|
|
416
|
+
if (!location) {
|
|
417
|
+
return { redirect: false };
|
|
418
|
+
}
|
|
419
|
+
// 301, 302, 303, 307, 308 are redirect status codes
|
|
420
|
+
if (status >= 300 && status < 400) {
|
|
421
|
+
const forceGet = status === 301 || status === 302 || status === 303;
|
|
422
|
+
return { redirect: true, location, forceGet };
|
|
423
|
+
}
|
|
424
|
+
return { redirect: false };
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Creates a response and adds it to the redirects list and redirects
|
|
428
|
+
* the request to the new location.
|
|
429
|
+
*/
|
|
430
|
+
async _redirectRequest(options) {
|
|
431
|
+
if (this.followRedirects === false) {
|
|
432
|
+
this._publishResponse();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const location = options.location && this.getRedirectLocation(options.location, this.request.url);
|
|
436
|
+
if (!location) {
|
|
437
|
+
this._errorRequest({ code: 302 });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// check if this is infinite loop
|
|
441
|
+
if (this.isRedirectLoop(location, this.redirects)) {
|
|
442
|
+
this._errorRequest({ code: 310 });
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const detail = {
|
|
446
|
+
location,
|
|
447
|
+
returnValue: true,
|
|
100
448
|
};
|
|
449
|
+
this.emit('beforeredirect', detail);
|
|
450
|
+
if (!detail.returnValue) {
|
|
451
|
+
this._publishResponse();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const responseCookies = this.currentHeaders.get('set-cookie');
|
|
456
|
+
const response = await this._createRedirectResponse(location);
|
|
457
|
+
this.redirects.push(response);
|
|
458
|
+
this._cleanUpRedirect();
|
|
459
|
+
if (responseCookies) {
|
|
460
|
+
this._processRedirectCookies(responseCookies, location);
|
|
461
|
+
}
|
|
462
|
+
this.redirecting = false;
|
|
463
|
+
this.request.url = location;
|
|
464
|
+
this.sentRequest.url = location;
|
|
465
|
+
if (options.forceGet) {
|
|
466
|
+
this.request.method = 'GET';
|
|
467
|
+
}
|
|
468
|
+
this.uri = this.readUrl(location);
|
|
469
|
+
this.hostHeader = this.getHostHeader(location);
|
|
470
|
+
// No idea why but without setTimeout the program loses it's
|
|
471
|
+
// scope after calling the function.
|
|
472
|
+
setTimeout(() => this.send());
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
const error = e;
|
|
476
|
+
this._errorRequest({
|
|
477
|
+
message: (error && error.message) || 'Unknown error occurred',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get redirect location
|
|
483
|
+
*/
|
|
484
|
+
getRedirectLocation(location, currentUrl) {
|
|
485
|
+
return getRedirectLocation(location, currentUrl);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if redirect is a loop
|
|
489
|
+
*/
|
|
490
|
+
isRedirectLoop(location, redirects) {
|
|
491
|
+
return isRedirectLoop(location, redirects);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* @param location The redirect location.
|
|
495
|
+
* @return Redirect response object
|
|
496
|
+
*/
|
|
497
|
+
async _createRedirectResponse(location) {
|
|
498
|
+
const { currentResponse = new Response() } = this;
|
|
499
|
+
const response = HttpResponse.fromValues(currentResponse.status, currentResponse.statusText, currentResponse.headers);
|
|
500
|
+
if (currentResponse.payload) {
|
|
501
|
+
response.payload = currentResponse.payload;
|
|
502
|
+
}
|
|
503
|
+
const body = await this.decompress(this._rawBody);
|
|
504
|
+
if (body) {
|
|
505
|
+
await response.writePayload(body);
|
|
506
|
+
currentResponse.payload = response.payload;
|
|
507
|
+
}
|
|
508
|
+
const redirect = ResponseRedirect.fromValues(location, response.toJSON(), this.stats.startTime, this.stats.responseTime);
|
|
509
|
+
redirect.timings = this._computeStats(this.stats);
|
|
510
|
+
return redirect;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Creates a response object
|
|
514
|
+
*/
|
|
515
|
+
async _createResponse() {
|
|
516
|
+
if (this.aborted) {
|
|
517
|
+
throw new Error(`Request aborted.`);
|
|
518
|
+
}
|
|
519
|
+
const { currentResponse } = this;
|
|
520
|
+
if (!currentResponse) {
|
|
521
|
+
throw new Error(`Tried to create a response but no response data is set.`);
|
|
522
|
+
}
|
|
523
|
+
const { status } = currentResponse;
|
|
524
|
+
if (status === undefined) {
|
|
525
|
+
throw new Error(`The response status is empty.
|
|
526
|
+
It means that the successful connection wasn't made.
|
|
527
|
+
Check your request parameters.`);
|
|
528
|
+
}
|
|
529
|
+
const body = await this.decompress(this._rawBody);
|
|
530
|
+
const response = Response.fromValues(status, currentResponse.statusText, currentResponse.headers);
|
|
531
|
+
response.timings = this._computeStats(this.stats);
|
|
532
|
+
response.loadingTime = response.timings.total();
|
|
533
|
+
if (body) {
|
|
534
|
+
await response.writePayload(body);
|
|
535
|
+
currentResponse.payload = response.payload;
|
|
536
|
+
}
|
|
537
|
+
if (status === 401) {
|
|
538
|
+
this.logger.silly('Setting auth on the response...');
|
|
539
|
+
response.auth = this._getAuth();
|
|
540
|
+
}
|
|
541
|
+
return response;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Finishes the response with error message.
|
|
545
|
+
*/
|
|
546
|
+
_errorRequest(opts) {
|
|
547
|
+
const { currentResponse } = this;
|
|
548
|
+
this.aborted = true;
|
|
549
|
+
let message;
|
|
550
|
+
if (opts.code && !opts.message) {
|
|
551
|
+
message = getCodeMessage(opts.code);
|
|
552
|
+
}
|
|
553
|
+
else if (opts.message) {
|
|
554
|
+
message = opts.message;
|
|
555
|
+
}
|
|
556
|
+
message = message || 'Unknown error occurred';
|
|
557
|
+
const error = new SerializableError(message, opts.code);
|
|
558
|
+
const log = RequestLog.fromRequest(this.sentRequest.toJSON());
|
|
559
|
+
const response = ErrorResponse.fromError(error);
|
|
560
|
+
log.response = response;
|
|
561
|
+
if (currentResponse && currentResponse.status) {
|
|
562
|
+
response.status = currentResponse.status;
|
|
563
|
+
response.statusText = currentResponse.statusText;
|
|
564
|
+
response.headers = currentResponse.headers;
|
|
565
|
+
response.payload = currentResponse.payload;
|
|
566
|
+
}
|
|
567
|
+
this.finalizeRequest(log);
|
|
568
|
+
this._cleanUp();
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Generates authorization info object from response.
|
|
572
|
+
*/
|
|
573
|
+
_getAuth() {
|
|
574
|
+
this.logger.debug('Getting auth from response headers');
|
|
575
|
+
let auth = this.currentHeaders.get('www-authenticate');
|
|
576
|
+
const result = new ResponseAuthorization();
|
|
577
|
+
if (auth) {
|
|
578
|
+
this.logger.silly('Auth header found', auth);
|
|
579
|
+
auth = auth.toLowerCase();
|
|
580
|
+
if (auth.indexOf('ntlm') !== -1) {
|
|
581
|
+
this.logger.silly('Detected NTLM authorization');
|
|
582
|
+
result.method = 'ntlm';
|
|
583
|
+
}
|
|
584
|
+
else if (auth.indexOf('basic') !== -1) {
|
|
585
|
+
result.method = 'basic';
|
|
586
|
+
this.logger.silly('Detected basic authorization');
|
|
587
|
+
}
|
|
588
|
+
else if (auth.indexOf('digest') !== -1) {
|
|
589
|
+
result.method = 'digest';
|
|
590
|
+
this.logger.silly('Detected digest authorization');
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Creates HAR 1.2 timings object from stats.
|
|
597
|
+
*/
|
|
598
|
+
_computeStats(stats) {
|
|
599
|
+
return computeStats(stats);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Handles cookie exchange when redirecting the request.
|
|
603
|
+
*/
|
|
604
|
+
_processRedirectCookies(responseCookies, location) {
|
|
605
|
+
this.request.headers = processRedirectCookies(responseCookies, location, this.request.url, this.request.toHeadersString());
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Checks certificate identity using TLS api.
|
|
609
|
+
*/
|
|
610
|
+
_checkServerIdentity(host, cert) {
|
|
611
|
+
return checkServerIdentity(host, cert);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Clears event listeners of the socket object,
|
|
615
|
+
*/
|
|
616
|
+
_clearSocketEventListeners() {
|
|
617
|
+
if (!this.socket) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.socket.removeAllListeners('error');
|
|
621
|
+
this.socket.removeAllListeners('timeout');
|
|
622
|
+
this.socket.removeAllListeners('end');
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Called with the `send()` function to initialize the main promise returned by the send function.
|
|
626
|
+
* The send function returns a promise that is resolved when the request finish.
|
|
627
|
+
*/
|
|
628
|
+
wrapExecution() {
|
|
629
|
+
let promise;
|
|
630
|
+
if (this[mainPromiseSymbol]) {
|
|
631
|
+
promise = this[mainPromiseSymbol];
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
promise = new Promise((resolve, reject) => {
|
|
635
|
+
this.mainResolver = resolve;
|
|
636
|
+
this.mainRejecter = reject;
|
|
637
|
+
});
|
|
638
|
+
this[mainPromiseSymbol] = promise;
|
|
639
|
+
}
|
|
640
|
+
return promise;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Called by the request finalizer or error finalized to report the response.
|
|
644
|
+
*/
|
|
645
|
+
finalizeRequest(log) {
|
|
646
|
+
const { mainRejecter, mainResolver } = this;
|
|
647
|
+
if (!mainRejecter || !mainResolver) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (log instanceof SerializableError) {
|
|
651
|
+
mainRejecter(log);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
mainResolver(log.toJSON());
|
|
655
|
+
}
|
|
656
|
+
this.mainRejecter = undefined;
|
|
657
|
+
this.mainResolver = undefined;
|
|
658
|
+
this[mainPromiseSymbol] = undefined;
|
|
101
659
|
}
|
|
102
660
|
/**
|
|
103
661
|
* Prepares an HTTP message from API Client's request object.
|
|
104
|
-
*
|
|
105
|
-
* @returns Resolved promise to an `ArrayBuffer`.
|
|
106
662
|
*/
|
|
107
663
|
async prepareMessage() {
|
|
108
664
|
let payload = this.request.payload;
|
|
109
|
-
if (
|
|
665
|
+
if (isMethodWithoutBody(this.request.method)) {
|
|
110
666
|
payload = undefined;
|
|
111
667
|
}
|
|
112
668
|
const headers = new Headers(this.request.toHeadersString());
|
|
113
669
|
this.prepareHeaders(headers, !!payload);
|
|
114
670
|
const auth = this.hasProxy && !this.isProxyTunnel ? this._proxyAuthHeader() : undefined;
|
|
115
671
|
if (auth) {
|
|
116
|
-
headers.set(
|
|
672
|
+
headers.set(HEADER_PROXY_AUTHORIZATION, auth);
|
|
117
673
|
}
|
|
118
674
|
const buffer = PayloadSupport.payloadToBuffer(headers, payload);
|
|
119
675
|
if (buffer) {
|
|
@@ -121,7 +677,9 @@ export class CoreEngine extends HttpEngine {
|
|
|
121
677
|
}
|
|
122
678
|
this._handleAuthorization(headers);
|
|
123
679
|
this.sentRequest.headers = headers.toString();
|
|
124
|
-
|
|
680
|
+
// Update message builder with current host header
|
|
681
|
+
this.messageBuilder.updateHostHeader(this.hostHeader || '');
|
|
682
|
+
const message = this.messageBuilder.buildMessage(headers, buffer);
|
|
125
683
|
if (this.auth) {
|
|
126
684
|
// This restores altered by authorization original headers
|
|
127
685
|
// so it can be safe to use when redirecting
|
|
@@ -134,15 +692,13 @@ export class CoreEngine extends HttpEngine {
|
|
|
134
692
|
}
|
|
135
693
|
/**
|
|
136
694
|
* Sends a data to a socket.
|
|
137
|
-
*
|
|
138
|
-
* @param buffer HTTP message to send
|
|
139
695
|
*/
|
|
140
696
|
writeMessage(buffer) {
|
|
141
697
|
this.logger.debug(`Writing the message to the socket...`);
|
|
142
698
|
let msg = buffer.toString();
|
|
143
699
|
const limit = this.opts.sentMessageLimit;
|
|
144
700
|
if (limit && limit > 0 && msg.length >= limit) {
|
|
145
|
-
msg = msg.
|
|
701
|
+
msg = msg.substring(0, limit);
|
|
146
702
|
msg += ' ...';
|
|
147
703
|
}
|
|
148
704
|
this.sentRequest.httpMessage = msg;
|
|
@@ -164,241 +720,26 @@ export class CoreEngine extends HttpEngine {
|
|
|
164
720
|
});
|
|
165
721
|
});
|
|
166
722
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Connects to a server and sends the message.
|
|
169
|
-
*
|
|
170
|
-
* @returns Promise resolved when socket is connected.
|
|
171
|
-
*/
|
|
172
|
-
async connect() {
|
|
173
|
-
const port = getPort(this.uri.port, this.uri.protocol);
|
|
174
|
-
const host = this.uri.hostname;
|
|
175
|
-
let socket;
|
|
176
|
-
if (port === 443 || this.uri.protocol === 'https:') {
|
|
177
|
-
socket = await this._connectTls(port, host);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
socket = await this._connect(port, host);
|
|
181
|
-
}
|
|
182
|
-
const { timeout } = this;
|
|
183
|
-
if (timeout > 0) {
|
|
184
|
-
socket.setTimeout(timeout);
|
|
185
|
-
}
|
|
186
|
-
this.socket = socket;
|
|
187
|
-
this._addSocketListeners(socket);
|
|
188
|
-
socket.resume();
|
|
189
|
-
return socket;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Connects to a server and writes a message using insecure connection.
|
|
193
|
-
*
|
|
194
|
-
* @param port A port number to connect to.
|
|
195
|
-
* @param host A host name to connect to
|
|
196
|
-
* @returns A promise resolved when the message was sent to a server
|
|
197
|
-
*/
|
|
198
|
-
_connect(port, host) {
|
|
199
|
-
this.logger.debug('Opening an HTTP connection...');
|
|
200
|
-
return new Promise((resolve, reject) => {
|
|
201
|
-
this.stats.connectionTime = Date.now();
|
|
202
|
-
const isIp = net.isIP(host);
|
|
203
|
-
if (isIp) {
|
|
204
|
-
this.stats.lookupTime = Date.now();
|
|
205
|
-
}
|
|
206
|
-
const client = net.createConnection(port, host, () => {
|
|
207
|
-
this.logger.debug('HTTP connection established.');
|
|
208
|
-
this.stats.connectedTime = Date.now();
|
|
209
|
-
resolve(client);
|
|
210
|
-
});
|
|
211
|
-
client.pause();
|
|
212
|
-
if (!isIp) {
|
|
213
|
-
client.once('lookup', () => {
|
|
214
|
-
this.stats.lookupTime = Date.now();
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
client.once('error', (err) => reject(err));
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Connects to a server and writes a message using secured connection.
|
|
222
|
-
*
|
|
223
|
-
* @param port A port number to connect to.
|
|
224
|
-
* @param host A host name to connect to
|
|
225
|
-
* @returns A promise resolved when the message was sent to a server
|
|
226
|
-
*/
|
|
227
|
-
_connectTls(port, host) {
|
|
228
|
-
this.logger.debug('Opening an SSL connection...');
|
|
229
|
-
const { opts } = this;
|
|
230
|
-
const options = {};
|
|
231
|
-
const isIp = net.isIP(host);
|
|
232
|
-
if (!isIp) {
|
|
233
|
-
options.servername = host;
|
|
234
|
-
}
|
|
235
|
-
if (opts.validateCertificates) {
|
|
236
|
-
options.checkServerIdentity = this._checkServerIdentity.bind(this);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
options.rejectUnauthorized = false;
|
|
240
|
-
options.checkServerIdentity = () => {
|
|
241
|
-
return undefined;
|
|
242
|
-
};
|
|
243
|
-
// target.requestOCSP = false;
|
|
244
|
-
}
|
|
245
|
-
const certs = this.opts.certificates;
|
|
246
|
-
if (Array.isArray(certs)) {
|
|
247
|
-
certs.forEach((cert) => this._addClientCertificate(cert, options));
|
|
248
|
-
}
|
|
249
|
-
return new Promise((resolve, reject) => {
|
|
250
|
-
const time = Date.now();
|
|
251
|
-
this.stats.connectionTime = time;
|
|
252
|
-
if (isIp) {
|
|
253
|
-
this.stats.lookupTime = time;
|
|
254
|
-
}
|
|
255
|
-
const client = tls.connect(port, host, options, () => {
|
|
256
|
-
this.logger.debug('SSL connection established.');
|
|
257
|
-
const connectTime = Date.now();
|
|
258
|
-
this.stats.connectedTime = connectTime;
|
|
259
|
-
this.stats.secureStartTime = connectTime;
|
|
260
|
-
resolve(client);
|
|
261
|
-
});
|
|
262
|
-
client.pause();
|
|
263
|
-
client.once('error', (e) => reject(e));
|
|
264
|
-
if (!isIp) {
|
|
265
|
-
client.once('lookup', () => {
|
|
266
|
-
this.stats.lookupTime = Date.now();
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
client.once('secureConnect', () => {
|
|
270
|
-
this.stats.secureConnectedTime = Date.now();
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Prepares a full HTTP message body
|
|
276
|
-
*
|
|
277
|
-
* @param httpHeaders The list ogf headers to append.
|
|
278
|
-
* @param buffer The buffer with the HTTP message
|
|
279
|
-
* @returns The Buffer of the HTTP message
|
|
280
|
-
*/
|
|
281
|
-
_prepareMessage(httpHeaders, buffer) {
|
|
282
|
-
this.logger.debug('Preparing an HTTP message...');
|
|
283
|
-
const headers = [];
|
|
284
|
-
// const search = this.uri.search;
|
|
285
|
-
// let path = this.uri.pathname;
|
|
286
|
-
// if (search) {
|
|
287
|
-
// path += search;
|
|
288
|
-
// }
|
|
289
|
-
// headers.push(`${this.arcRequest.method} ${path} HTTP/1.1`);
|
|
290
|
-
const status = this._createHttpStatus();
|
|
291
|
-
this.logger.debug(`Created message status: ${status}`);
|
|
292
|
-
headers.push(status);
|
|
293
|
-
if (this._hostRequired()) {
|
|
294
|
-
this.logger.debug(`Adding the "host" header: ${this.hostHeader}`);
|
|
295
|
-
headers.push(`Host: ${this.hostHeader}`);
|
|
296
|
-
}
|
|
297
|
-
let str = headers.join('\r\n');
|
|
298
|
-
const addHeaders = httpHeaders.toString();
|
|
299
|
-
if (addHeaders) {
|
|
300
|
-
this.logger.debug(`Adding headers to the request...`);
|
|
301
|
-
str += '\r\n';
|
|
302
|
-
str += PayloadSupport.normalizeString(addHeaders);
|
|
303
|
-
}
|
|
304
|
-
const startBuffer = Buffer.from(str, 'utf8');
|
|
305
|
-
const endBuffer = Buffer.from(new Uint8Array([13, 10, 13, 10]));
|
|
306
|
-
let body;
|
|
307
|
-
let sum = startBuffer.length + endBuffer.length;
|
|
308
|
-
if (buffer) {
|
|
309
|
-
sum += buffer.length;
|
|
310
|
-
body = Buffer.concat([startBuffer, endBuffer, buffer], sum);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
body = Buffer.concat([startBuffer, endBuffer], sum);
|
|
314
|
-
}
|
|
315
|
-
this.logger.debug(`The message is ready.`);
|
|
316
|
-
return body;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Creates an HTTP status line for the message.
|
|
320
|
-
* For proxy connections it, depending whether target is SSL or not, sets the path
|
|
321
|
-
* as the full URL or just the authority.
|
|
322
|
-
* @returns The generates status message.
|
|
323
|
-
*/
|
|
324
|
-
_createHttpStatus() {
|
|
325
|
-
const { request, uri, hasProxy, isProxyTunnel } = this;
|
|
326
|
-
const parts = [request.method];
|
|
327
|
-
if (hasProxy && !isProxyTunnel) {
|
|
328
|
-
// if (isProxyTunnel) {
|
|
329
|
-
// // when a tunnel then the target is over SSL so the default port is 443.
|
|
330
|
-
// parts.push(`${uri.hostname}:${uri.port || 443}`);
|
|
331
|
-
// } else {
|
|
332
|
-
// parts.push(arcRequest.url);
|
|
333
|
-
// }
|
|
334
|
-
parts.push(request.url);
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
let path = uri.pathname;
|
|
338
|
-
if (uri.search) {
|
|
339
|
-
path += uri.search;
|
|
340
|
-
}
|
|
341
|
-
parts.push(path);
|
|
342
|
-
}
|
|
343
|
-
parts.push('HTTP/1.1');
|
|
344
|
-
return parts.join(' ');
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Tests if current connection is required to add `host` header.
|
|
348
|
-
* It returns `false` only if `host` header has been already provided.
|
|
349
|
-
*
|
|
350
|
-
* @returns True when the `host` header should be added to the headers list.
|
|
351
|
-
*/
|
|
352
|
-
_hostRequired() {
|
|
353
|
-
const headers = this.request.headers;
|
|
354
|
-
if (typeof headers !== 'string') {
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
return !this._hostTestReg.test(headers);
|
|
358
|
-
}
|
|
359
723
|
/**
|
|
360
724
|
* Alters authorization header depending on the `auth` object
|
|
361
|
-
* @param headers A headers object where to append headers when needed
|
|
362
725
|
*/
|
|
363
726
|
_handleAuthorization(headers) {
|
|
364
727
|
const { authorization } = this.opts;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (ntlm) {
|
|
371
|
-
this._authorizeNtlm(ntlm.config, headers);
|
|
728
|
+
this.authManager.handleRequestAuthorization(headers, authorization, this.uri.host);
|
|
729
|
+
// Sync the auth state from AuthManager to parent class for compatibility
|
|
730
|
+
const authState = this.authManager.getAuthState();
|
|
731
|
+
if (authState) {
|
|
732
|
+
this.auth = authState;
|
|
372
733
|
}
|
|
373
734
|
}
|
|
374
735
|
/**
|
|
375
|
-
*
|
|
376
|
-
* @param authData Credentials to use
|
|
377
|
-
* @param headers A headers object where to append headers if needed
|
|
736
|
+
* Generate proxy authorization header
|
|
378
737
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const auth = new NtlmAuth(init);
|
|
382
|
-
if (!this.auth) {
|
|
383
|
-
this.auth = {
|
|
384
|
-
method: 'ntlm',
|
|
385
|
-
state: 0,
|
|
386
|
-
headers: headers.toString(),
|
|
387
|
-
};
|
|
388
|
-
const msg = auth.createMessage1(this.uri.host);
|
|
389
|
-
headers.set('Authorization', `NTLM ${msg.toBase64()}`);
|
|
390
|
-
headers.set('Connection', 'keep-alive');
|
|
391
|
-
}
|
|
392
|
-
else if (this.auth && this.auth.state === 1) {
|
|
393
|
-
const msg = auth.createMessage3(this.auth.challengeHeader, this.uri.host);
|
|
394
|
-
this.auth.state = 2;
|
|
395
|
-
headers.set('Authorization', `NTLM ${msg.toBase64()}`);
|
|
396
|
-
}
|
|
738
|
+
_proxyAuthHeader() {
|
|
739
|
+
return this.authManager.generateProxyAuthHeader();
|
|
397
740
|
}
|
|
398
741
|
/**
|
|
399
742
|
* Add event listeners to existing socket.
|
|
400
|
-
* @param socket An instance of the socket.
|
|
401
|
-
* @return The same socket. Used for chaining.
|
|
402
743
|
*/
|
|
403
744
|
_addSocketListeners(socket) {
|
|
404
745
|
let received = false;
|
|
@@ -412,9 +753,12 @@ export class CoreEngine extends HttpEngine {
|
|
|
412
753
|
}
|
|
413
754
|
data = Buffer.from(data);
|
|
414
755
|
try {
|
|
415
|
-
|
|
756
|
+
// Use the new parser instead of the old parsing methods
|
|
757
|
+
this.parser.processData(data);
|
|
758
|
+
this.logger.debug('Parser state:', this.parser.getState());
|
|
416
759
|
}
|
|
417
760
|
catch (e) {
|
|
761
|
+
this.logger.error('Error in _addSocketListeners:', e);
|
|
418
762
|
const err = e;
|
|
419
763
|
this._errorRequest({
|
|
420
764
|
message: err.message || 'Unknown error occurred',
|
|
@@ -423,50 +767,44 @@ export class CoreEngine extends HttpEngine {
|
|
|
423
767
|
}
|
|
424
768
|
});
|
|
425
769
|
socket.once('timeout', () => {
|
|
426
|
-
this.
|
|
427
|
-
this.
|
|
770
|
+
this.logger.debug(`Socket timeout occurred after ${this.timeout}ms.`);
|
|
771
|
+
this.parser.abort();
|
|
772
|
+
this._errorRequest(HttpEngineErrorFactory.connectionTimeout(this.timeout));
|
|
428
773
|
socket.destroy();
|
|
429
774
|
});
|
|
430
775
|
socket.on('end', () => {
|
|
776
|
+
const endTime = Date.now();
|
|
431
777
|
this.logger.debug(`Server connection ended.`);
|
|
432
778
|
socket.removeAllListeners('timeout');
|
|
433
779
|
socket.removeAllListeners('error');
|
|
434
|
-
|
|
780
|
+
socket.removeAllListeners('data');
|
|
435
781
|
this.stats.lastReceivedTime = endTime;
|
|
436
782
|
this.sentRequest.endTime = endTime;
|
|
437
|
-
|
|
783
|
+
// console.log(`Socket ended at ${endTime} in state`, this.parser.getState())
|
|
784
|
+
if (this.parser.getState() !== RequestState.Done) {
|
|
438
785
|
if (!this.currentResponse) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const e =
|
|
786
|
+
const message = `Connection closed without receiving any data.`;
|
|
787
|
+
this.logger.error(message);
|
|
788
|
+
const e = HttpEngineErrorFactory.connectionError(message, 100);
|
|
442
789
|
this._errorRequest(e);
|
|
443
790
|
}
|
|
444
791
|
else {
|
|
445
|
-
// There is an issue with the response. Size mismatch? Anyway,
|
|
446
|
-
// it tries to create a response from current data.
|
|
447
792
|
this.emit('loadend');
|
|
448
793
|
this._publishResponse();
|
|
449
794
|
}
|
|
450
795
|
}
|
|
451
796
|
});
|
|
452
797
|
socket.once('error', (err) => {
|
|
798
|
+
this.logger.error(`Socket error occurred: ${err.message}`);
|
|
453
799
|
socket.removeAllListeners('timeout');
|
|
454
800
|
this._errorRequest(err);
|
|
455
801
|
});
|
|
456
802
|
return socket;
|
|
457
803
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Processes response message chunk
|
|
460
|
-
* @param buffer Message buffer
|
|
461
|
-
*/
|
|
462
|
-
_processResponse(buffer) {
|
|
463
|
-
this._processSocketMessage(buffer);
|
|
464
|
-
this._reportResponse();
|
|
465
|
-
}
|
|
466
804
|
/**
|
|
467
805
|
* Reports response after processing it.
|
|
468
806
|
*/
|
|
469
|
-
_reportResponse() {
|
|
807
|
+
async _reportResponse() {
|
|
470
808
|
this._clearSocketEventListeners();
|
|
471
809
|
const { aborted, currentResponse } = this;
|
|
472
810
|
if (aborted || !currentResponse) {
|
|
@@ -476,607 +814,115 @@ export class CoreEngine extends HttpEngine {
|
|
|
476
814
|
const endTime = Date.now();
|
|
477
815
|
this.stats.lastReceivedTime = endTime;
|
|
478
816
|
this.sentRequest.endTime = endTime;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
817
|
+
const result = processResponse({
|
|
818
|
+
status,
|
|
819
|
+
currentResponse,
|
|
820
|
+
currentHeaders: this.currentHeaders,
|
|
821
|
+
followRedirects: this.followRedirects,
|
|
822
|
+
auth: this.auth,
|
|
823
|
+
aborted: this.aborted,
|
|
824
|
+
});
|
|
825
|
+
switch (result.action) {
|
|
826
|
+
case 'redirect':
|
|
827
|
+
if (this._reportRedirect(status)) {
|
|
828
|
+
this.closeClient();
|
|
489
829
|
return;
|
|
490
|
-
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
case 'auth':
|
|
833
|
+
if (result.data?.method === 'ntlm') {
|
|
834
|
+
await this.handleNtlmResponse();
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
case 'publish':
|
|
839
|
+
this.closeClient();
|
|
840
|
+
this.emit('loadend');
|
|
841
|
+
this._publishResponse();
|
|
842
|
+
break;
|
|
843
|
+
case 'abort':
|
|
844
|
+
// Already aborted, do nothing
|
|
845
|
+
break;
|
|
491
846
|
}
|
|
492
|
-
this.closeClient();
|
|
493
|
-
this.emit('loadend');
|
|
494
|
-
this._publishResponse();
|
|
495
847
|
}
|
|
496
848
|
/**
|
|
497
849
|
* Generate response object and publish it to the listeners.
|
|
498
850
|
*/
|
|
499
|
-
_publishResponse() {
|
|
500
|
-
this.
|
|
501
|
-
|
|
502
|
-
if (this.responseInfo.body) {
|
|
503
|
-
this._rawBody = this.responseInfo.body;
|
|
504
|
-
}
|
|
505
|
-
else if (this.responseInfo.chunk) {
|
|
506
|
-
this._rawBody = this.responseInfo.chunk;
|
|
507
|
-
}
|
|
851
|
+
async _publishResponse() {
|
|
852
|
+
if (this.aborted) {
|
|
853
|
+
return;
|
|
508
854
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this.responseInfo.chunk);
|
|
855
|
+
try {
|
|
856
|
+
const response = await this._createResponse();
|
|
857
|
+
const result = RequestLog.fromRequestResponse(this.sentRequest.toJSON(), response.toJSON());
|
|
858
|
+
if (this.redirects.length) {
|
|
859
|
+
result.redirects = this.redirects;
|
|
860
|
+
}
|
|
861
|
+
result.size = new RequestsSize();
|
|
862
|
+
if (this.sentRequest.httpMessage) {
|
|
863
|
+
result.size.request = Buffer.from(this.sentRequest.httpMessage).length;
|
|
864
|
+
}
|
|
865
|
+
if (response.payload) {
|
|
866
|
+
if (typeof response.payload === 'string') {
|
|
867
|
+
result.size.response = response.payload.length;
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
result.size.response = response.payload.data.length;
|
|
871
|
+
}
|
|
527
872
|
}
|
|
873
|
+
this.finalizeRequest(result);
|
|
528
874
|
}
|
|
529
|
-
|
|
875
|
+
catch (e) {
|
|
876
|
+
const error = e;
|
|
877
|
+
this.logger.error('Error in _publishResponse:', error);
|
|
878
|
+
this._errorRequest({
|
|
879
|
+
message: (error && error.message) || 'Unknown error occurred',
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
this.abort();
|
|
883
|
+
this._cleanUp();
|
|
530
884
|
}
|
|
531
885
|
/**
|
|
532
886
|
* closes the connection, if any
|
|
533
887
|
*/
|
|
534
888
|
closeClient() {
|
|
535
889
|
if (this.socket && !this.socket.destroyed) {
|
|
890
|
+
this.logger.debug('Closing the socket connection...');
|
|
536
891
|
this.socket.destroy();
|
|
537
892
|
}
|
|
538
893
|
}
|
|
539
894
|
/**
|
|
540
895
|
* Handles the response with NTLM authorization
|
|
541
896
|
*/
|
|
542
|
-
handleNtlmResponse() {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
897
|
+
async handleNtlmResponse() {
|
|
898
|
+
this.logger.debug('Handling NTLM response...');
|
|
899
|
+
this.logger.debug('Current headers:', this.currentHeaders?.toString());
|
|
900
|
+
this.logger.debug('Auth state:', this.authManager.getAuthState());
|
|
901
|
+
const shouldRetry = this.authManager.handleNtlmResponse(this.currentHeaders);
|
|
902
|
+
this.logger.debug('Should retry NTLM:', shouldRetry);
|
|
903
|
+
if (shouldRetry) {
|
|
904
|
+
// Update the parent auth state to match AuthManager state
|
|
905
|
+
this.auth = this.authManager.getAuthState();
|
|
906
|
+
this._cleanUpRedirect();
|
|
907
|
+
try {
|
|
908
|
+
const message = await this.prepareMessage();
|
|
909
|
+
await this.writeMessage(message);
|
|
910
|
+
// Don't return here - let the socket listeners handle the response
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
this.logger.error('Failed to retry NTLM authentication:', error);
|
|
914
|
+
this._errorRequest({
|
|
915
|
+
message: `NTLM retry failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
916
|
+
});
|
|
554
917
|
}
|
|
918
|
+
return;
|
|
555
919
|
}
|
|
920
|
+
this.logger.debug('NTLM authentication completed');
|
|
921
|
+
// Clear both auth states
|
|
922
|
+
this.authManager.clearAuthState();
|
|
556
923
|
delete this.auth;
|
|
557
924
|
this.emit('loadend');
|
|
558
925
|
this._publishResponse();
|
|
559
926
|
}
|
|
560
|
-
/**
|
|
561
|
-
* Process received message.
|
|
562
|
-
*
|
|
563
|
-
* @param data Received message.
|
|
564
|
-
*/
|
|
565
|
-
_processSocketMessage(data) {
|
|
566
|
-
if (this.aborted) {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
if (this.state === RequestState.Done) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
let remaining = data;
|
|
573
|
-
if (this.state === RequestState.Status) {
|
|
574
|
-
remaining = this._processStatus(remaining);
|
|
575
|
-
if (!remaining) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (this.state === RequestState.Headers) {
|
|
580
|
-
remaining = this._processHeaders(remaining);
|
|
581
|
-
if (!remaining) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (this.state === RequestState.Body) {
|
|
586
|
-
this._processBody(remaining);
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Read status line from the response.
|
|
592
|
-
* This function will set `status` and `statusText` fields
|
|
593
|
-
* and then will set `state` to HEADERS.
|
|
594
|
-
*
|
|
595
|
-
* @param data The received data
|
|
596
|
-
*/
|
|
597
|
-
_processStatus(data) {
|
|
598
|
-
if (this.aborted) {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
const response = Response.fromValues(0);
|
|
602
|
-
response.loadingTime = 0;
|
|
603
|
-
this.currentResponse = response;
|
|
604
|
-
if (!data) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
this.logger.info('Processing status');
|
|
608
|
-
const index = data.indexOf(nlBuffer);
|
|
609
|
-
let statusLine = data.slice(0, index).toString();
|
|
610
|
-
data = data.slice(index + 2);
|
|
611
|
-
statusLine = statusLine.replace(/HTTP\/\d(\.\d)?\s/, '');
|
|
612
|
-
const delimiterPos = statusLine.indexOf(' ');
|
|
613
|
-
let status;
|
|
614
|
-
let msg = '';
|
|
615
|
-
if (delimiterPos === -1) {
|
|
616
|
-
status = statusLine;
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
status = statusLine.substr(0, delimiterPos);
|
|
620
|
-
msg = statusLine.substr(delimiterPos + 1);
|
|
621
|
-
}
|
|
622
|
-
let typedStatus = Number(status);
|
|
623
|
-
if (Number.isNaN(typedStatus)) {
|
|
624
|
-
typedStatus = 0;
|
|
625
|
-
}
|
|
626
|
-
if (msg && msg.indexOf('\n') !== -1) {
|
|
627
|
-
msg = msg.split('\n')[0];
|
|
628
|
-
}
|
|
629
|
-
const cr = this.currentResponse;
|
|
630
|
-
cr.status = typedStatus;
|
|
631
|
-
cr.statusText = msg;
|
|
632
|
-
this.logger.info('Received status', typedStatus, msg);
|
|
633
|
-
this.state = RequestState.Headers;
|
|
634
|
-
return data;
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Read headers from the received data.
|
|
638
|
-
*
|
|
639
|
-
* @param data Received data
|
|
640
|
-
* @returns Remaining data in the buffer.
|
|
641
|
-
*/
|
|
642
|
-
_processHeaders(data) {
|
|
643
|
-
if (this.aborted) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (!data) {
|
|
647
|
-
this._parseHeaders();
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
this.logger.info('Processing headers');
|
|
651
|
-
// Looking for end of headers section
|
|
652
|
-
let index = data.indexOf(nlNlBuffer);
|
|
653
|
-
let padding = 4;
|
|
654
|
-
if (index === -1) {
|
|
655
|
-
// It can also be 2x ASCII 10
|
|
656
|
-
const _index = data.indexOf(Buffer.from([10, 10]));
|
|
657
|
-
if (_index !== -1) {
|
|
658
|
-
index = _index;
|
|
659
|
-
padding = 2;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
// https://github.com/jarrodek/socket-fetch/issues/3
|
|
663
|
-
const enterIndex = data.indexOf(nlBuffer);
|
|
664
|
-
if (index === -1 && enterIndex !== 0) {
|
|
665
|
-
// end in next chunk
|
|
666
|
-
if (!this.rawHeaders) {
|
|
667
|
-
this.rawHeaders = data;
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
const sum = this.rawHeaders.length + data.length;
|
|
671
|
-
this.rawHeaders = Buffer.concat([this.rawHeaders, data], sum);
|
|
672
|
-
}
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
if (enterIndex !== 0) {
|
|
676
|
-
const headersArray = data.slice(0, index);
|
|
677
|
-
if (!this.rawHeaders) {
|
|
678
|
-
this.rawHeaders = headersArray;
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
const sum = this.rawHeaders.length + headersArray.length;
|
|
682
|
-
this.rawHeaders = Buffer.concat([this.rawHeaders, headersArray], sum);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
this._parseHeaders(this.rawHeaders);
|
|
686
|
-
delete this.rawHeaders;
|
|
687
|
-
this.state = RequestState.Body;
|
|
688
|
-
const start = index === -1 ? 0 : index;
|
|
689
|
-
const move = enterIndex === 0 ? 2 : padding;
|
|
690
|
-
data = data.slice(start + move);
|
|
691
|
-
return this._postHeaders(data);
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Check the response headers and end the request if necessary.
|
|
695
|
-
* @param data Current response data buffer
|
|
696
|
-
*/
|
|
697
|
-
_postHeaders(data) {
|
|
698
|
-
if (this.request.method === 'HEAD') {
|
|
699
|
-
this._reportResponse();
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
if (data.length === 0) {
|
|
703
|
-
if (this.currentResponse?.status === 204) {
|
|
704
|
-
this._reportResponse();
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
if (this.currentHeaders.has('Content-Length')) {
|
|
708
|
-
// If the server do not close the connection and clearly indicate that
|
|
709
|
-
// there are no further data to receive the app can close the connection
|
|
710
|
-
// and prepare the response.
|
|
711
|
-
const length = Number(this.currentHeaders.get('Content-Length'));
|
|
712
|
-
if (!Number.isNaN(length) && length === 0) {
|
|
713
|
-
this._reportResponse();
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
// See: https://github.com/advanced-rest-client/arc-electron/issues/106
|
|
718
|
-
// The client should wait until the connection is closed instead of assuming it should end the request.
|
|
719
|
-
// else if (!this.currentHeaders.has('Transfer-Encoding') || !this.currentHeaders.get('Transfer-Encoding')) {
|
|
720
|
-
// // Fix for https://github.com/jarrodek/socket-fetch/issues/6
|
|
721
|
-
// // There is no body in the response.
|
|
722
|
-
// // this._reportResponse();
|
|
723
|
-
// return;
|
|
724
|
-
// }
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
return data;
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* This function assumes that all headers has been read and it's
|
|
731
|
-
* just before changing the status to BODY.
|
|
732
|
-
*/
|
|
733
|
-
_parseHeaders(buffer) {
|
|
734
|
-
let raw = '';
|
|
735
|
-
if (buffer) {
|
|
736
|
-
raw = buffer.toString();
|
|
737
|
-
}
|
|
738
|
-
;
|
|
739
|
-
this.currentResponse.headers = raw;
|
|
740
|
-
this.logger.info('Received headers list', raw);
|
|
741
|
-
const headers = new Headers(raw);
|
|
742
|
-
this.currentHeaders = headers;
|
|
743
|
-
if (headers.has('Content-Length')) {
|
|
744
|
-
this.responseInfo.contentLength = Number(headers.get('Content-Length'));
|
|
745
|
-
}
|
|
746
|
-
if (headers.has('Transfer-Encoding')) {
|
|
747
|
-
const tr = headers.get('Transfer-Encoding');
|
|
748
|
-
if (tr === 'chunked') {
|
|
749
|
-
this.responseInfo.chunked = true;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
const rawHeaders = headers.toString();
|
|
753
|
-
const detail = {
|
|
754
|
-
returnValue: true,
|
|
755
|
-
value: rawHeaders,
|
|
756
|
-
};
|
|
757
|
-
this.emit('headersreceived', detail);
|
|
758
|
-
if (!detail.returnValue) {
|
|
759
|
-
this.abort();
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* @param data A data to process
|
|
764
|
-
*/
|
|
765
|
-
_processBody(data) {
|
|
766
|
-
if (this.aborted || !data) {
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
if (this.responseInfo.chunked) {
|
|
770
|
-
this._processBodyChunked(data);
|
|
771
|
-
}
|
|
772
|
-
else {
|
|
773
|
-
this._processBodyContentLength(data);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
_processBodyContentLength(data) {
|
|
777
|
-
if (typeof this.responseInfo.contentLength === 'undefined') {
|
|
778
|
-
this._errorRequest(new Error(`The content-length header of the response is missing.`));
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
if (!this.responseInfo.body) {
|
|
782
|
-
this.responseInfo.body = data;
|
|
783
|
-
if (data.length >= this.responseInfo.contentLength) {
|
|
784
|
-
this._reportResponse();
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
const sum = this.responseInfo.body.length + data.length;
|
|
790
|
-
this.responseInfo.body = Buffer.concat([this.responseInfo.body, data], sum);
|
|
791
|
-
if (this.responseInfo.body.length >= this.responseInfo.contentLength) {
|
|
792
|
-
this._reportResponse();
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* @param data A latest data to process
|
|
798
|
-
*/
|
|
799
|
-
_processBodyChunked(data) {
|
|
800
|
-
if (!data) {
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
if (this.responseInfo.chunk) {
|
|
804
|
-
data = Buffer.concat([this.responseInfo.chunk, data], this.responseInfo.chunk.length + data.length);
|
|
805
|
-
this.responseInfo.chunk = undefined;
|
|
806
|
-
}
|
|
807
|
-
while (true) {
|
|
808
|
-
if (this.responseInfo.chunkSize === 0 && data.indexOf(nlNlBuffer) === 0) {
|
|
809
|
-
this._reportResponse();
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
if (!this.responseInfo.chunkSize) {
|
|
813
|
-
data = this.readChunkSize(data);
|
|
814
|
-
if (!data) {
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
if (!this.responseInfo.chunkSize && this.responseInfo.chunkSize !== 0) {
|
|
818
|
-
// It may happen that node's buffer cuts the data
|
|
819
|
-
// just before the chunk size.
|
|
820
|
-
// It should proceed it in next portion of the data.
|
|
821
|
-
this.responseInfo.chunk = data;
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
if (!this.responseInfo.chunkSize) {
|
|
825
|
-
this._reportResponse();
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
const size = Math.min(this.responseInfo.chunkSize, data.length);
|
|
830
|
-
const sliced = data.slice(0, size);
|
|
831
|
-
if (!this._rawBody) {
|
|
832
|
-
this._rawBody = sliced;
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
const sum = size + this._rawBody.length;
|
|
836
|
-
this._rawBody = Buffer.concat([this._rawBody, sliced], sum);
|
|
837
|
-
}
|
|
838
|
-
this.responseInfo.chunkSize -= size;
|
|
839
|
-
if (data.length === 0) {
|
|
840
|
-
// this.logger.warn('Next chunk will start with CRLF!');
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
data = data.slice(size + 2); // + CR
|
|
844
|
-
if (data.length === 0) {
|
|
845
|
-
// this.logger.info('No more data here. Waiting for new chunk');
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* If response's Transfer-Encoding is 'chunked' read until next CR.
|
|
852
|
-
* Everything before it is a chunk size.
|
|
853
|
-
*/
|
|
854
|
-
readChunkSize(array) {
|
|
855
|
-
if (this.aborted) {
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
let index = array.indexOf(nlBuffer);
|
|
859
|
-
if (index === -1) {
|
|
860
|
-
// not found in this portion of data.
|
|
861
|
-
return array;
|
|
862
|
-
}
|
|
863
|
-
if (index === 0) {
|
|
864
|
-
// Node's buffer cuts CRLF after the end of chunk data, without last CLCR,
|
|
865
|
-
// here's to fix it.
|
|
866
|
-
// It can be either new line from the last chunk or end of
|
|
867
|
-
// the message where
|
|
868
|
-
// the rest of the array is [13, 10, 48, 13, 10, 13, 10]
|
|
869
|
-
if (array.indexOf(nlNlBuffer) === 0) {
|
|
870
|
-
this.responseInfo.chunkSize = 0;
|
|
871
|
-
return Buffer.alloc(0);
|
|
872
|
-
}
|
|
873
|
-
array = array.slice(index + 2);
|
|
874
|
-
index = array.indexOf(nlBuffer);
|
|
875
|
-
}
|
|
876
|
-
// this.logger.info('Size index: ', index);
|
|
877
|
-
const chunkSize = parseInt(array.slice(0, index).toString(), 16);
|
|
878
|
-
if (Number.isNaN(chunkSize)) {
|
|
879
|
-
this.responseInfo.chunkSize = undefined;
|
|
880
|
-
return array.slice(index + 2);
|
|
881
|
-
}
|
|
882
|
-
this.responseInfo.chunkSize = chunkSize;
|
|
883
|
-
return array.slice(index + 2);
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
|
-
* Connects to a server through a proxy. Depending on the proxy type the returned socket
|
|
887
|
-
* is a socket created after creating a tunnel (SSL) or the proxy socket.
|
|
888
|
-
*
|
|
889
|
-
* @returns Promise resolved when socket is connected.
|
|
890
|
-
*/
|
|
891
|
-
async connectProxy() {
|
|
892
|
-
let socket;
|
|
893
|
-
if (this.isProxyTunnel) {
|
|
894
|
-
socket = await this.connectTunnel(this.isProxySsl);
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
socket = await this.proxyHttp(this.isProxySsl);
|
|
898
|
-
}
|
|
899
|
-
if (!socket) {
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
const { timeout } = this;
|
|
903
|
-
if (timeout > 0) {
|
|
904
|
-
socket.setTimeout(timeout);
|
|
905
|
-
}
|
|
906
|
-
this.socket = socket;
|
|
907
|
-
this._addSocketListeners(socket);
|
|
908
|
-
socket.resume();
|
|
909
|
-
return socket;
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Creates a tunnel to a Proxy for SSL connections.
|
|
913
|
-
* The returned socket is the one created after the tunnel is established.
|
|
914
|
-
* @param proxyIsSsl Whether the proxy is an SSL connection.
|
|
915
|
-
* @returns Promise resolved when socket is connected.
|
|
916
|
-
*/
|
|
917
|
-
async connectTunnel(proxyIsSsl = false) {
|
|
918
|
-
this.logger.debug(`Creating a tunnel through the proxy...`);
|
|
919
|
-
const { proxy } = this.opts;
|
|
920
|
-
const { url } = this.request;
|
|
921
|
-
if (!proxy) {
|
|
922
|
-
throw new Error(`No proxy configuration found.`);
|
|
923
|
-
}
|
|
924
|
-
let proxyUrl = proxy;
|
|
925
|
-
if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
|
|
926
|
-
proxyUrl = `https://${proxyUrl}`;
|
|
927
|
-
}
|
|
928
|
-
else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
|
|
929
|
-
proxyUrl = `http://${proxyUrl}`;
|
|
930
|
-
}
|
|
931
|
-
const proxyUri = new URL(proxyUrl);
|
|
932
|
-
const targetUrl = new URL(url);
|
|
933
|
-
const proxyPort = proxyUri.port || (proxyIsSsl ? 443 : 80);
|
|
934
|
-
const targetPort = targetUrl.port || 443; // target is always SSL so 443.
|
|
935
|
-
const authority = `${targetUrl.hostname}:${targetPort}`;
|
|
936
|
-
const connectOptions = {
|
|
937
|
-
host: proxyUri.hostname,
|
|
938
|
-
port: proxyPort,
|
|
939
|
-
method: 'CONNECT',
|
|
940
|
-
path: authority,
|
|
941
|
-
headers: {
|
|
942
|
-
host: authority,
|
|
943
|
-
},
|
|
944
|
-
};
|
|
945
|
-
if (proxyIsSsl) {
|
|
946
|
-
connectOptions.rejectUnauthorized = false;
|
|
947
|
-
// @ts-expect-error This is correct!
|
|
948
|
-
connectOptions.requestOCSP = false;
|
|
949
|
-
}
|
|
950
|
-
const auth = this._proxyAuthHeader();
|
|
951
|
-
if (auth) {
|
|
952
|
-
this.logger.debug(`Adding proxy authorization.`);
|
|
953
|
-
connectOptions.headers['proxy-authorization'] = auth;
|
|
954
|
-
}
|
|
955
|
-
const lib = proxyIsSsl ? https : http;
|
|
956
|
-
return new Promise((resolve, reject) => {
|
|
957
|
-
this.stats.connectionTime = Date.now();
|
|
958
|
-
const connectRequest = lib.request(connectOptions);
|
|
959
|
-
connectRequest.once('socket', (socket) => {
|
|
960
|
-
socket.on('lookup', () => {
|
|
961
|
-
this.stats.lookupTime = Date.now();
|
|
962
|
-
});
|
|
963
|
-
});
|
|
964
|
-
connectRequest.on('error', (err) => {
|
|
965
|
-
this.logger.error('Error establishing proxy tunnel:', err);
|
|
966
|
-
reject(new SerializableError(`Failed to establish proxy tunnel: ${err.message}`, err.code ? Number(err.code) : 111));
|
|
967
|
-
});
|
|
968
|
-
connectRequest.on('connect', async (res, socket, head) => {
|
|
969
|
-
const time = Date.now();
|
|
970
|
-
this.stats.connectedTime = time;
|
|
971
|
-
this.stats.secureStartTime = time;
|
|
972
|
-
if (typeof this.stats.lookupTime === 'undefined') {
|
|
973
|
-
this.stats.lookupTime = time;
|
|
974
|
-
}
|
|
975
|
-
if (res.statusCode === 401) {
|
|
976
|
-
this.currentHeaders = new Headers(res.headers);
|
|
977
|
-
const currentResponse = Response.fromValues(res.statusCode, res.statusMessage, this.currentHeaders.toString());
|
|
978
|
-
currentResponse.loadingTime = 0;
|
|
979
|
-
this.currentResponse = currentResponse;
|
|
980
|
-
if (head.length) {
|
|
981
|
-
this._rawBody = head;
|
|
982
|
-
currentResponse.payload = PayloadSerializer.stringifyBuffer(head);
|
|
983
|
-
}
|
|
984
|
-
connectRequest.destroy();
|
|
985
|
-
resolve(undefined);
|
|
986
|
-
setTimeout(() => {
|
|
987
|
-
// const e = new NetError('The proxy requires authentication.', 127);
|
|
988
|
-
this._publishResponse();
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
else if (res.statusCode === 407) {
|
|
992
|
-
await this.handleProxyAuthentication(res, socket);
|
|
993
|
-
}
|
|
994
|
-
else if (res.statusCode !== 200) {
|
|
995
|
-
this.logger.debug(`The proxy tunnel ended with ${res.statusCode} status code. Erroring request.`);
|
|
996
|
-
connectRequest.destroy();
|
|
997
|
-
const errorMessage = res.statusMessage || `Proxy tunnel establishment failed with status code ${res.statusCode}`;
|
|
998
|
-
const e = new SerializableError(errorMessage, res.statusCode);
|
|
999
|
-
reject(e);
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
this.logger.debug(`Established a proxy tunnel.`);
|
|
1003
|
-
this.logger.debug(`Upgrading connection to SSL...`);
|
|
1004
|
-
const tlsSocket = tls.connect({ socket, rejectUnauthorized: false }, () => {
|
|
1005
|
-
this.logger.debug(`Connection upgraded to SSL.`);
|
|
1006
|
-
resolve(tlsSocket);
|
|
1007
|
-
});
|
|
1008
|
-
tlsSocket.once('secureConnect', () => {
|
|
1009
|
-
this.stats.secureConnectedTime = Date.now();
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
connectRequest.end();
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
async handleProxyAuthentication(res, socket) {
|
|
1017
|
-
if (res.statusCode === 407) {
|
|
1018
|
-
// Proxy Authentication Required
|
|
1019
|
-
this.currentHeaders = new Headers(res.headers);
|
|
1020
|
-
const authHeader = this.currentHeaders.get('proxy-authenticate');
|
|
1021
|
-
if (authHeader && authHeader.startsWith('Digest')) {
|
|
1022
|
-
// Parse the Digest challenge and update _proxyAuthHeader to generate the correct response.
|
|
1023
|
-
this._errorRequest(new SerializableError(`Unsupported proxy authentication scheme: ${authHeader}`, 127));
|
|
1024
|
-
socket.destroy();
|
|
1025
|
-
// this._cleanUpRedirect(); // Might need adjustments
|
|
1026
|
-
// const message = await this.prepareMessage();
|
|
1027
|
-
// this.writeMessage(message);
|
|
1028
|
-
}
|
|
1029
|
-
else if (authHeader && authHeader.startsWith('Basic')) {
|
|
1030
|
-
// Basic auth is usually handled directly in _proxyAuthHeader, so this might be redundant.
|
|
1031
|
-
this._cleanUpRedirect(); // Might need adjustments
|
|
1032
|
-
const message = await this.prepareMessage();
|
|
1033
|
-
this.writeMessage(message);
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
// Unsupported authentication scheme
|
|
1037
|
-
this._errorRequest(new SerializableError(`Unsupported proxy authentication scheme: ${authHeader}`, 127));
|
|
1038
|
-
socket.destroy();
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Creates connection to a proxy for an HTTP (non-SSL) transport.
|
|
1044
|
-
* This is the same as calling _connect or _connectTls but the target is the proxy and not the
|
|
1045
|
-
* target URL. The message sent to the proxy server is different than the one sent
|
|
1046
|
-
* to the target.
|
|
1047
|
-
* @param proxyIsSsl
|
|
1048
|
-
* @returns Promise resolved when socket is connected.
|
|
1049
|
-
*/
|
|
1050
|
-
async proxyHttp(proxyIsSsl = false) {
|
|
1051
|
-
this.logger.debug('Proxying an HTTP request...');
|
|
1052
|
-
const { proxy } = this.opts;
|
|
1053
|
-
if (!proxy) {
|
|
1054
|
-
throw new Error(`No proxy configuration found.`);
|
|
1055
|
-
}
|
|
1056
|
-
let proxyUrl = proxy;
|
|
1057
|
-
if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
|
|
1058
|
-
proxyUrl = `https://${proxyUrl}`;
|
|
1059
|
-
}
|
|
1060
|
-
else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
|
|
1061
|
-
proxyUrl = `http://${proxyUrl}`;
|
|
1062
|
-
}
|
|
1063
|
-
const proxyUri = new URL(proxyUrl);
|
|
1064
|
-
const port = Number(proxyUri.port || 443);
|
|
1065
|
-
const host = proxyUri.hostname;
|
|
1066
|
-
let socket;
|
|
1067
|
-
try {
|
|
1068
|
-
if (proxyIsSsl) {
|
|
1069
|
-
socket = await this._connectTls(port, host);
|
|
1070
|
-
}
|
|
1071
|
-
else {
|
|
1072
|
-
socket = await this._connect(port, host);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
catch (error) {
|
|
1076
|
-
const err = error;
|
|
1077
|
-
throw new SerializableError(`Failed to connect to proxy: ${err.message}`, err.code ? Number(err.code) : 112);
|
|
1078
|
-
}
|
|
1079
|
-
return socket;
|
|
1080
|
-
}
|
|
1081
927
|
}
|
|
1082
928
|
//# sourceMappingURL=CoreEngine.js.map
|