@depup/mswjs__interceptors 0.41.3-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ClientRequest/package.json +11 -0
- package/LICENSE.md +9 -0
- package/README.md +31 -0
- package/RemoteHttpInterceptor/package.json +11 -0
- package/WebSocket/package.json +12 -0
- package/XMLHttpRequest/package.json +12 -0
- package/changes.json +10 -0
- package/fetch/package.json +12 -0
- package/lib/browser/Interceptor-Deczogc8.d.cts +65 -0
- package/lib/browser/Interceptor-gqKgs-aF.d.mts +65 -0
- package/lib/browser/XMLHttpRequest-BACqefB-.cjs +761 -0
- package/lib/browser/XMLHttpRequest-BACqefB-.cjs.map +1 -0
- package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs +756 -0
- package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs.map +1 -0
- package/lib/browser/bufferUtils-BiiO6HZv.mjs +20 -0
- package/lib/browser/bufferUtils-BiiO6HZv.mjs.map +1 -0
- package/lib/browser/bufferUtils-Uc0eRItL.cjs +38 -0
- package/lib/browser/bufferUtils-Uc0eRItL.cjs.map +1 -0
- package/lib/browser/createRequestId-Cs4oXfa1.cjs +205 -0
- package/lib/browser/createRequestId-Cs4oXfa1.cjs.map +1 -0
- package/lib/browser/createRequestId-DQcIlohW.mjs +170 -0
- package/lib/browser/createRequestId-DQcIlohW.mjs.map +1 -0
- package/lib/browser/fetch-DdKEdDOR.mjs +248 -0
- package/lib/browser/fetch-DdKEdDOR.mjs.map +1 -0
- package/lib/browser/fetch-U3v3Y4ap.cjs +253 -0
- package/lib/browser/fetch-U3v3Y4ap.cjs.map +1 -0
- package/lib/browser/getRawRequest-BTaNLFr0.mjs +218 -0
- package/lib/browser/getRawRequest-BTaNLFr0.mjs.map +1 -0
- package/lib/browser/getRawRequest-zx8rUJL2.cjs +259 -0
- package/lib/browser/getRawRequest-zx8rUJL2.cjs.map +1 -0
- package/lib/browser/glossary-BdLS4k1H.d.cts +70 -0
- package/lib/browser/glossary-DYwOrogs.d.mts +70 -0
- package/lib/browser/handleRequest-CvX2G-Lz.cjs +189 -0
- package/lib/browser/handleRequest-CvX2G-Lz.cjs.map +1 -0
- package/lib/browser/handleRequest-D7kpTI5U.mjs +178 -0
- package/lib/browser/handleRequest-D7kpTI5U.mjs.map +1 -0
- package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs +45 -0
- package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs.map +1 -0
- package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs +33 -0
- package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs.map +1 -0
- package/lib/browser/index.cjs +70 -0
- package/lib/browser/index.cjs.map +1 -0
- package/lib/browser/index.d.cts +96 -0
- package/lib/browser/index.d.mts +96 -0
- package/lib/browser/index.mjs +56 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/browser/interceptors/WebSocket/index.cjs +622 -0
- package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -0
- package/lib/browser/interceptors/WebSocket/index.d.cts +277 -0
- package/lib/browser/interceptors/WebSocket/index.d.mts +277 -0
- package/lib/browser/interceptors/WebSocket/index.mjs +615 -0
- package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -0
- package/lib/browser/interceptors/XMLHttpRequest/index.cjs +7 -0
- package/lib/browser/interceptors/XMLHttpRequest/index.d.cts +15 -0
- package/lib/browser/interceptors/XMLHttpRequest/index.d.mts +15 -0
- package/lib/browser/interceptors/XMLHttpRequest/index.mjs +7 -0
- package/lib/browser/interceptors/fetch/index.cjs +6 -0
- package/lib/browser/interceptors/fetch/index.d.cts +13 -0
- package/lib/browser/interceptors/fetch/index.d.mts +13 -0
- package/lib/browser/interceptors/fetch/index.mjs +6 -0
- package/lib/browser/presets/browser.cjs +17 -0
- package/lib/browser/presets/browser.cjs.map +1 -0
- package/lib/browser/presets/browser.d.cts +12 -0
- package/lib/browser/presets/browser.d.mts +14 -0
- package/lib/browser/presets/browser.mjs +17 -0
- package/lib/browser/presets/browser.mjs.map +1 -0
- package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs +31 -0
- package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs.map +1 -0
- package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs +25 -0
- package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs.map +1 -0
- package/lib/node/BatchInterceptor-3LnAnLTx.cjs +49 -0
- package/lib/node/BatchInterceptor-3LnAnLTx.cjs.map +1 -0
- package/lib/node/BatchInterceptor-D7mXzHcQ.d.mts +26 -0
- package/lib/node/BatchInterceptor-DFaBPilf.mjs +44 -0
- package/lib/node/BatchInterceptor-DFaBPilf.mjs.map +1 -0
- package/lib/node/BatchInterceptor-D_YqR8qU.d.cts +26 -0
- package/lib/node/ClientRequest-2rDe54Ui.cjs +1043 -0
- package/lib/node/ClientRequest-2rDe54Ui.cjs.map +1 -0
- package/lib/node/ClientRequest-Ca8Qykuv.mjs +1034 -0
- package/lib/node/ClientRequest-Ca8Qykuv.mjs.map +1 -0
- package/lib/node/Interceptor-DEazpLJd.d.mts +133 -0
- package/lib/node/Interceptor-DJ2akVWI.d.cts +133 -0
- package/lib/node/RemoteHttpInterceptor.cjs +154 -0
- package/lib/node/RemoteHttpInterceptor.cjs.map +1 -0
- package/lib/node/RemoteHttpInterceptor.d.cts +39 -0
- package/lib/node/RemoteHttpInterceptor.d.mts +39 -0
- package/lib/node/RemoteHttpInterceptor.mjs +152 -0
- package/lib/node/RemoteHttpInterceptor.mjs.map +1 -0
- package/lib/node/XMLHttpRequest-B7kJdYYI.cjs +763 -0
- package/lib/node/XMLHttpRequest-B7kJdYYI.cjs.map +1 -0
- package/lib/node/XMLHttpRequest-C8dIZpds.mjs +757 -0
- package/lib/node/XMLHttpRequest-C8dIZpds.mjs.map +1 -0
- package/lib/node/bufferUtils-DiCTqG-7.cjs +38 -0
- package/lib/node/bufferUtils-DiCTqG-7.cjs.map +1 -0
- package/lib/node/bufferUtils-_8XfKIfX.mjs +20 -0
- package/lib/node/bufferUtils-_8XfKIfX.mjs.map +1 -0
- package/lib/node/chunk-CbDLau6x.cjs +34 -0
- package/lib/node/fetch-BmXpK10r.cjs +272 -0
- package/lib/node/fetch-BmXpK10r.cjs.map +1 -0
- package/lib/node/fetch-G1DVwDKG.mjs +265 -0
- package/lib/node/fetch-G1DVwDKG.mjs.map +1 -0
- package/lib/node/fetchUtils-BaY5iWXw.cjs +419 -0
- package/lib/node/fetchUtils-BaY5iWXw.cjs.map +1 -0
- package/lib/node/fetchUtils-CoU35g3M.mjs +359 -0
- package/lib/node/fetchUtils-CoU35g3M.mjs.map +1 -0
- package/lib/node/getRawRequest-BavnMWh_.cjs +36 -0
- package/lib/node/getRawRequest-BavnMWh_.cjs.map +1 -0
- package/lib/node/getRawRequest-DnwmXyOW.mjs +24 -0
- package/lib/node/getRawRequest-DnwmXyOW.mjs.map +1 -0
- package/lib/node/glossary-BLKRyLBd.cjs +12 -0
- package/lib/node/glossary-BLKRyLBd.cjs.map +1 -0
- package/lib/node/glossary-glQBRnVD.mjs +6 -0
- package/lib/node/glossary-glQBRnVD.mjs.map +1 -0
- package/lib/node/handleRequest-Bb7Y-XLw.cjs +220 -0
- package/lib/node/handleRequest-Bb7Y-XLw.cjs.map +1 -0
- package/lib/node/handleRequest-Y97UwBbF.mjs +190 -0
- package/lib/node/handleRequest-Y97UwBbF.mjs.map +1 -0
- package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs +26 -0
- package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs.map +1 -0
- package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs +20 -0
- package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs.map +1 -0
- package/lib/node/index-BMbJ8FXL.d.cts +113 -0
- package/lib/node/index-C0YAQ36w.d.mts +113 -0
- package/lib/node/index.cjs +54 -0
- package/lib/node/index.cjs.map +1 -0
- package/lib/node/index.d.cts +75 -0
- package/lib/node/index.d.mts +75 -0
- package/lib/node/index.mjs +40 -0
- package/lib/node/index.mjs.map +1 -0
- package/lib/node/interceptors/ClientRequest/index.cjs +6 -0
- package/lib/node/interceptors/ClientRequest/index.d.cts +2 -0
- package/lib/node/interceptors/ClientRequest/index.d.mts +3 -0
- package/lib/node/interceptors/ClientRequest/index.mjs +6 -0
- package/lib/node/interceptors/XMLHttpRequest/index.cjs +6 -0
- package/lib/node/interceptors/XMLHttpRequest/index.d.cts +14 -0
- package/lib/node/interceptors/XMLHttpRequest/index.d.mts +14 -0
- package/lib/node/interceptors/XMLHttpRequest/index.mjs +6 -0
- package/lib/node/interceptors/fetch/index.cjs +5 -0
- package/lib/node/interceptors/fetch/index.d.cts +12 -0
- package/lib/node/interceptors/fetch/index.d.mts +12 -0
- package/lib/node/interceptors/fetch/index.mjs +5 -0
- package/lib/node/node-DwCc6iuP.mjs +27 -0
- package/lib/node/node-DwCc6iuP.mjs.map +1 -0
- package/lib/node/node-dKdAf3tC.cjs +39 -0
- package/lib/node/node-dKdAf3tC.cjs.map +1 -0
- package/lib/node/presets/node.cjs +22 -0
- package/lib/node/presets/node.cjs.map +1 -0
- package/lib/node/presets/node.d.cts +13 -0
- package/lib/node/presets/node.d.mts +15 -0
- package/lib/node/presets/node.mjs +22 -0
- package/lib/node/presets/node.mjs.map +1 -0
- package/lib/node/utils/node/index.cjs +4 -0
- package/lib/node/utils/node/index.d.cts +16 -0
- package/lib/node/utils/node/index.d.mts +16 -0
- package/lib/node/utils/node/index.mjs +3 -0
- package/package.json +204 -0
- package/presets/browser/package.json +5 -0
- package/presets/node/package.json +11 -0
- package/src/BatchInterceptor.test.ts +255 -0
- package/src/BatchInterceptor.ts +95 -0
- package/src/Interceptor.test.ts +205 -0
- package/src/Interceptor.ts +249 -0
- package/src/InterceptorError.ts +7 -0
- package/src/RemoteHttpInterceptor.ts +251 -0
- package/src/RequestController.test.ts +104 -0
- package/src/RequestController.ts +109 -0
- package/src/createRequestId.test.ts +7 -0
- package/src/createRequestId.ts +9 -0
- package/src/getRawRequest.ts +21 -0
- package/src/glossary.ts +37 -0
- package/src/index.ts +15 -0
- package/src/interceptors/ClientRequest/MockHttpSocket.ts +724 -0
- package/src/interceptors/ClientRequest/agents.ts +110 -0
- package/src/interceptors/ClientRequest/index.test.ts +75 -0
- package/src/interceptors/ClientRequest/index.ts +193 -0
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +54 -0
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +45 -0
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +427 -0
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +268 -0
- package/src/interceptors/ClientRequest/utils/parserUtils.ts +48 -0
- package/src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts +258 -0
- package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +262 -0
- package/src/interceptors/Socket/MockSocket.test.ts +264 -0
- package/src/interceptors/Socket/MockSocket.ts +58 -0
- package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +26 -0
- package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +52 -0
- package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +33 -0
- package/src/interceptors/WebSocket/WebSocketClassTransport.ts +116 -0
- package/src/interceptors/WebSocket/WebSocketClientConnection.ts +152 -0
- package/src/interceptors/WebSocket/WebSocketOverride.ts +252 -0
- package/src/interceptors/WebSocket/WebSocketServerConnection.ts +420 -0
- package/src/interceptors/WebSocket/WebSocketTransport.ts +39 -0
- package/src/interceptors/WebSocket/index.ts +191 -0
- package/src/interceptors/WebSocket/utils/bindEvent.test.ts +27 -0
- package/src/interceptors/WebSocket/utils/bindEvent.ts +21 -0
- package/src/interceptors/WebSocket/utils/events.test.ts +101 -0
- package/src/interceptors/WebSocket/utils/events.ts +94 -0
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +746 -0
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +121 -0
- package/src/interceptors/XMLHttpRequest/index.ts +61 -0
- package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +51 -0
- package/src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts +17 -0
- package/src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts +12 -0
- package/src/interceptors/XMLHttpRequest/utils/concateArrayBuffer.test.ts +12 -0
- package/src/interceptors/XMLHttpRequest/utils/createEvent.test.ts +26 -0
- package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +41 -0
- package/src/interceptors/XMLHttpRequest/utils/createResponse.ts +49 -0
- package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.test.ts +164 -0
- package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts +16 -0
- package/src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts +14 -0
- package/src/interceptors/fetch/index.ts +214 -0
- package/src/interceptors/fetch/utils/brotli-decompress.browser.ts +14 -0
- package/src/interceptors/fetch/utils/brotli-decompress.ts +31 -0
- package/src/interceptors/fetch/utils/createNetworkError.ts +5 -0
- package/src/interceptors/fetch/utils/decompression.ts +85 -0
- package/src/interceptors/fetch/utils/followRedirect.ts +114 -0
- package/src/presets/browser.ts +11 -0
- package/src/presets/node.ts +13 -0
- package/src/utils/bufferUtils.test.ts +21 -0
- package/src/utils/bufferUtils.ts +22 -0
- package/src/utils/canParseUrl.ts +13 -0
- package/src/utils/cloneObject.test.ts +94 -0
- package/src/utils/cloneObject.ts +36 -0
- package/src/utils/createProxy.test.ts +164 -0
- package/src/utils/createProxy.ts +104 -0
- package/src/utils/emitAsync.ts +25 -0
- package/src/utils/fetchUtils.ts +119 -0
- package/src/utils/findPropertySource.test.ts +27 -0
- package/src/utils/findPropertySource.ts +20 -0
- package/src/utils/getCleanUrl.test.ts +32 -0
- package/src/utils/getCleanUrl.ts +6 -0
- package/src/utils/getUrlByRequestOptions.test.ts +163 -0
- package/src/utils/getUrlByRequestOptions.ts +152 -0
- package/src/utils/getValueBySymbol.test.ts +14 -0
- package/src/utils/getValueBySymbol.ts +19 -0
- package/src/utils/handleRequest.ts +205 -0
- package/src/utils/hasConfigurableGlobal.test.ts +83 -0
- package/src/utils/hasConfigurableGlobal.ts +34 -0
- package/src/utils/isNodeLikeError.ts +13 -0
- package/src/utils/isObject.test.ts +21 -0
- package/src/utils/isObject.ts +8 -0
- package/src/utils/isPropertyAccessible.ts +19 -0
- package/src/utils/nextTick.ts +11 -0
- package/src/utils/node/index.ts +39 -0
- package/src/utils/parseJson.test.ts +10 -0
- package/src/utils/parseJson.ts +12 -0
- package/src/utils/resolveWebSocketUrl.ts +45 -0
- package/src/utils/responseUtils.ts +59 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
import net from 'node:net'
|
|
2
|
+
import {
|
|
3
|
+
type HeadersCallback,
|
|
4
|
+
HTTPParser,
|
|
5
|
+
type RequestHeadersCompleteCallback,
|
|
6
|
+
type ResponseHeadersCompleteCallback,
|
|
7
|
+
} from '_http_common'
|
|
8
|
+
import { STATUS_CODES, IncomingMessage, ServerResponse } from 'node:http'
|
|
9
|
+
import { Readable } from 'node:stream'
|
|
10
|
+
import { invariant } from 'outvariant'
|
|
11
|
+
import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
|
|
12
|
+
import { MockSocket } from '../Socket/MockSocket'
|
|
13
|
+
import type { NormalizedSocketWriteArgs } from '../Socket/utils/normalizeSocketWriteArgs'
|
|
14
|
+
import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
|
|
15
|
+
import { baseUrlFromConnectionOptions } from '../Socket/utils/baseUrlFromConnectionOptions'
|
|
16
|
+
import { createRequestId } from '../../createRequestId'
|
|
17
|
+
import { getRawFetchHeaders } from './utils/recordRawHeaders'
|
|
18
|
+
import { FetchResponse } from '../../utils/fetchUtils'
|
|
19
|
+
import { setRawRequest } from '../../getRawRequest'
|
|
20
|
+
import { setRawRequestBodyStream } from '../../utils/node'
|
|
21
|
+
import { freeParser } from './utils/parserUtils'
|
|
22
|
+
|
|
23
|
+
type HttpConnectionOptions = any
|
|
24
|
+
|
|
25
|
+
export type MockHttpSocketRequestCallback = (args: {
|
|
26
|
+
requestId: string
|
|
27
|
+
request: Request
|
|
28
|
+
socket: MockHttpSocket
|
|
29
|
+
}) => void
|
|
30
|
+
|
|
31
|
+
export type MockHttpSocketResponseCallback = (args: {
|
|
32
|
+
requestId: string
|
|
33
|
+
request: Request
|
|
34
|
+
response: Response
|
|
35
|
+
isMockedResponse: boolean
|
|
36
|
+
socket: MockHttpSocket
|
|
37
|
+
}) => Promise<void>
|
|
38
|
+
|
|
39
|
+
interface MockHttpSocketOptions {
|
|
40
|
+
connectionOptions: HttpConnectionOptions
|
|
41
|
+
createConnection: () => net.Socket
|
|
42
|
+
onRequest: MockHttpSocketRequestCallback
|
|
43
|
+
onResponse: MockHttpSocketResponseCallback
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const kRequestId = Symbol('kRequestId')
|
|
47
|
+
|
|
48
|
+
export class MockHttpSocket extends MockSocket {
|
|
49
|
+
private connectionOptions: HttpConnectionOptions
|
|
50
|
+
private createConnection: () => net.Socket
|
|
51
|
+
private baseUrl: URL
|
|
52
|
+
|
|
53
|
+
private onRequest: MockHttpSocketRequestCallback
|
|
54
|
+
private onResponse: MockHttpSocketResponseCallback
|
|
55
|
+
private responseListenersPromise?: Promise<void>
|
|
56
|
+
|
|
57
|
+
private requestRawHeadersBuffer: Array<string> = []
|
|
58
|
+
private responseRawHeadersBuffer: Array<string> = []
|
|
59
|
+
private writeBuffer: Array<NormalizedSocketWriteArgs> = []
|
|
60
|
+
private request?: Request
|
|
61
|
+
private requestParser: HTTPParser<0>
|
|
62
|
+
private requestStream?: Readable
|
|
63
|
+
private shouldKeepAlive?: boolean
|
|
64
|
+
|
|
65
|
+
private socketState: 'unknown' | 'mock' | 'passthrough' = 'unknown'
|
|
66
|
+
private responseParser: HTTPParser<1>
|
|
67
|
+
private responseStream?: Readable
|
|
68
|
+
private originalSocket?: net.Socket
|
|
69
|
+
|
|
70
|
+
constructor(options: MockHttpSocketOptions) {
|
|
71
|
+
super({
|
|
72
|
+
write: (chunk, encoding, callback) => {
|
|
73
|
+
// Buffer the writes so they can be flushed in case of the original connection
|
|
74
|
+
// and when reading the request body in the interceptor. If the connection has
|
|
75
|
+
// been established, no need to buffer the chunks anymore, they will be forwarded.
|
|
76
|
+
if (this.socketState !== 'passthrough') {
|
|
77
|
+
this.writeBuffer.push([chunk, encoding, callback])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (chunk) {
|
|
81
|
+
/**
|
|
82
|
+
* Forward any writes to the mock socket to the underlying original socket.
|
|
83
|
+
* This ensures functional duplex connections, like WebSocket.
|
|
84
|
+
* @see https://github.com/mswjs/interceptors/issues/682
|
|
85
|
+
*/
|
|
86
|
+
if (this.socketState === 'passthrough') {
|
|
87
|
+
this.originalSocket?.write(chunk, encoding, callback)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.requestParser.execute(
|
|
91
|
+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
read: (chunk) => {
|
|
96
|
+
if (chunk !== null) {
|
|
97
|
+
/**
|
|
98
|
+
* @todo We need to free the parser if the connection has been
|
|
99
|
+
* upgraded to a non-HTTP protocol. It won't be able to parse data
|
|
100
|
+
* from that point onward anyway. No need to keep it in memory.
|
|
101
|
+
*/
|
|
102
|
+
this.responseParser.execute(
|
|
103
|
+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
this.connectionOptions = options.connectionOptions
|
|
110
|
+
this.createConnection = options.createConnection
|
|
111
|
+
this.onRequest = options.onRequest
|
|
112
|
+
this.onResponse = options.onResponse
|
|
113
|
+
|
|
114
|
+
this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions)
|
|
115
|
+
|
|
116
|
+
// Request parser.
|
|
117
|
+
this.requestParser = new HTTPParser()
|
|
118
|
+
this.requestParser.initialize(HTTPParser.REQUEST, {})
|
|
119
|
+
this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this)
|
|
120
|
+
this.requestParser[HTTPParser.kOnHeadersComplete] =
|
|
121
|
+
this.onRequestStart.bind(this)
|
|
122
|
+
this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this)
|
|
123
|
+
this.requestParser[HTTPParser.kOnMessageComplete] =
|
|
124
|
+
this.onRequestEnd.bind(this)
|
|
125
|
+
|
|
126
|
+
// Response parser.
|
|
127
|
+
this.responseParser = new HTTPParser()
|
|
128
|
+
this.responseParser.initialize(HTTPParser.RESPONSE, {})
|
|
129
|
+
this.responseParser[HTTPParser.kOnHeaders] =
|
|
130
|
+
this.onResponseHeaders.bind(this)
|
|
131
|
+
this.responseParser[HTTPParser.kOnHeadersComplete] =
|
|
132
|
+
this.onResponseStart.bind(this)
|
|
133
|
+
this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this)
|
|
134
|
+
this.responseParser[HTTPParser.kOnMessageComplete] =
|
|
135
|
+
this.onResponseEnd.bind(this)
|
|
136
|
+
|
|
137
|
+
// Once the socket is finished, nothing can write to it
|
|
138
|
+
// anymore. It has also flushed any buffered chunks.
|
|
139
|
+
this.once('finish', () => freeParser(this.requestParser, this))
|
|
140
|
+
|
|
141
|
+
if (this.baseUrl.protocol === 'https:') {
|
|
142
|
+
Reflect.set(this, 'encrypted', true)
|
|
143
|
+
// The server certificate is not the same as a CA
|
|
144
|
+
// passed to the TLS socket connection options.
|
|
145
|
+
Reflect.set(this, 'authorized', false)
|
|
146
|
+
Reflect.set(this, 'getProtocol', () => 'TLSv1.3')
|
|
147
|
+
Reflect.set(this, 'getSession', () => undefined)
|
|
148
|
+
Reflect.set(this, 'isSessionReused', () => false)
|
|
149
|
+
Reflect.set(this, 'getCipher', () => ({
|
|
150
|
+
name: 'AES256-SHA',
|
|
151
|
+
standardName: 'TLS_RSA_WITH_AES_256_CBC_SHA',
|
|
152
|
+
version: 'TLSv1.3',
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public emit(event: string | symbol, ...args: any[]): boolean {
|
|
158
|
+
const emitEvent = super.emit.bind(this, event as any, ...args)
|
|
159
|
+
|
|
160
|
+
if (this.responseListenersPromise) {
|
|
161
|
+
this.responseListenersPromise.finally(emitEvent)
|
|
162
|
+
return this.listenerCount(event) > 0
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return emitEvent()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public destroy(error?: Error | undefined): this {
|
|
169
|
+
// Destroy the response parser when the socket gets destroyed.
|
|
170
|
+
// Normally, we should listen to the "close" event but it
|
|
171
|
+
// can be suppressed by using the "emitClose: false" option.
|
|
172
|
+
freeParser(this.responseParser, this)
|
|
173
|
+
|
|
174
|
+
if (error) {
|
|
175
|
+
this.emit('error', error)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return super.destroy(error)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Establish this Socket connection as-is and pipe
|
|
183
|
+
* its data/events through this Socket.
|
|
184
|
+
*/
|
|
185
|
+
public passthrough(): void {
|
|
186
|
+
this.socketState = 'passthrough'
|
|
187
|
+
|
|
188
|
+
if (this.destroyed) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const socket = this.createConnection()
|
|
193
|
+
this.originalSocket = socket
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @note Inherit the original socket's connection handle.
|
|
197
|
+
* Without this, each push to the mock socket results in a
|
|
198
|
+
* new "connection" listener being added (i.e. buffering pushes).
|
|
199
|
+
* @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734
|
|
200
|
+
*/
|
|
201
|
+
if ('_handle' in socket) {
|
|
202
|
+
Object.defineProperty(this, '_handle', {
|
|
203
|
+
value: socket._handle,
|
|
204
|
+
enumerable: true,
|
|
205
|
+
writable: true,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// The client-facing socket can be destroyed in two ways:
|
|
210
|
+
// 1. The developer destroys the socket.
|
|
211
|
+
// 2. The passthrough socket "close" is forwarded to the socket.
|
|
212
|
+
this.once('close', () => {
|
|
213
|
+
socket.removeAllListeners()
|
|
214
|
+
|
|
215
|
+
// If the closure didn't originate from the passthrough socket, destroy it.
|
|
216
|
+
if (!socket.destroyed) {
|
|
217
|
+
socket.destroy()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.originalSocket = undefined
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
this.address = socket.address.bind(socket)
|
|
224
|
+
|
|
225
|
+
// Flush the buffered "socket.write()" calls onto
|
|
226
|
+
// the original socket instance (i.e. write request body).
|
|
227
|
+
// Exhaust the "requestBuffer" in case this Socket
|
|
228
|
+
// gets reused for different requests.
|
|
229
|
+
let writeArgs: NormalizedSocketWriteArgs | undefined
|
|
230
|
+
let headersWritten = false
|
|
231
|
+
|
|
232
|
+
while ((writeArgs = this.writeBuffer.shift())) {
|
|
233
|
+
if (writeArgs !== undefined) {
|
|
234
|
+
if (!headersWritten) {
|
|
235
|
+
const [chunk, encoding, callback] = writeArgs
|
|
236
|
+
const chunkString = chunk.toString()
|
|
237
|
+
const chunkBeforeRequestHeaders = chunkString.slice(
|
|
238
|
+
0,
|
|
239
|
+
chunkString.indexOf('\r\n') + 2
|
|
240
|
+
)
|
|
241
|
+
const chunkAfterRequestHeaders = chunkString.slice(
|
|
242
|
+
chunk.indexOf('\r\n\r\n')
|
|
243
|
+
)
|
|
244
|
+
const rawRequestHeaders = getRawFetchHeaders(this.request!.headers)
|
|
245
|
+
const requestHeadersString = rawRequestHeaders
|
|
246
|
+
// Skip the internal request ID deduplication header.
|
|
247
|
+
.filter(([name]) => {
|
|
248
|
+
return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME
|
|
249
|
+
})
|
|
250
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
251
|
+
.join('\r\n')
|
|
252
|
+
|
|
253
|
+
// Modify the HTTP request message headers
|
|
254
|
+
// to reflect any changes to the request headers
|
|
255
|
+
// from the "request" event listener.
|
|
256
|
+
const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`
|
|
257
|
+
socket.write(headersChunk, encoding, callback)
|
|
258
|
+
headersWritten = true
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
socket.write(...writeArgs)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Forward TLS Socket properties onto this Socket instance
|
|
267
|
+
// in the case of a TLS/SSL connection.
|
|
268
|
+
if (Reflect.get(socket, 'encrypted')) {
|
|
269
|
+
const tlsProperties = [
|
|
270
|
+
'encrypted',
|
|
271
|
+
'authorized',
|
|
272
|
+
'getProtocol',
|
|
273
|
+
'getSession',
|
|
274
|
+
'isSessionReused',
|
|
275
|
+
'getCipher',
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
tlsProperties.forEach((propertyName) => {
|
|
279
|
+
Object.defineProperty(this, propertyName, {
|
|
280
|
+
enumerable: true,
|
|
281
|
+
get: () => {
|
|
282
|
+
const value = Reflect.get(socket, propertyName)
|
|
283
|
+
return typeof value === 'function' ? value.bind(socket) : value
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
socket
|
|
290
|
+
.on('lookup', (...args) => this.emit('lookup', ...args))
|
|
291
|
+
.on('connect', () => {
|
|
292
|
+
this.connecting = socket.connecting
|
|
293
|
+
this.emit('connect')
|
|
294
|
+
})
|
|
295
|
+
.on('secureConnect', () => this.emit('secureConnect'))
|
|
296
|
+
.on('secure', () => this.emit('secure'))
|
|
297
|
+
.on('session', (session) => this.emit('session', session))
|
|
298
|
+
.on('ready', () => this.emit('ready'))
|
|
299
|
+
.on('drain', () => this.emit('drain'))
|
|
300
|
+
.on('data', (chunk) => {
|
|
301
|
+
// Push the original response to this socket
|
|
302
|
+
// so it triggers the HTTP response parser. This unifies
|
|
303
|
+
// the handling pipeline for original and mocked response.
|
|
304
|
+
this.push(chunk)
|
|
305
|
+
})
|
|
306
|
+
.on('error', (error) => {
|
|
307
|
+
Reflect.set(this, '_hadError', Reflect.get(socket, '_hadError'))
|
|
308
|
+
this.emit('error', error)
|
|
309
|
+
})
|
|
310
|
+
.on('resume', () => this.emit('resume'))
|
|
311
|
+
.on('timeout', () => this.emit('timeout'))
|
|
312
|
+
.on('prefinish', () => this.emit('prefinish'))
|
|
313
|
+
.on('finish', () => this.emit('finish'))
|
|
314
|
+
.on('close', (hadError) => this.emit('close', hadError))
|
|
315
|
+
.on('end', () => this.emit('end'))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Convert the given Fetch API `Response` instance to an
|
|
320
|
+
* HTTP message and push it to the socket.
|
|
321
|
+
*/
|
|
322
|
+
public async respondWith(response: Response): Promise<void> {
|
|
323
|
+
// Ignore the mocked response if the socket has been destroyed
|
|
324
|
+
// (e.g. aborted or timed out),
|
|
325
|
+
if (this.destroyed) {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Prevent recursive calls.
|
|
330
|
+
invariant(
|
|
331
|
+
this.socketState !== 'mock',
|
|
332
|
+
'[MockHttpSocket] Failed to respond to the "%s %s" request with "%s %s": the request has already been handled',
|
|
333
|
+
this.request?.method,
|
|
334
|
+
this.request?.url,
|
|
335
|
+
response.status,
|
|
336
|
+
response.statusText
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
// Handle "type: error" responses.
|
|
340
|
+
if (isPropertyAccessible(response, 'type') && response.type === 'error') {
|
|
341
|
+
this.errorWith(new TypeError('Network error'))
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// First, emit all the connection events
|
|
346
|
+
// to emulate a successful connection.
|
|
347
|
+
this.mockConnect()
|
|
348
|
+
this.socketState = 'mock'
|
|
349
|
+
|
|
350
|
+
// Flush the write buffer to trigger write callbacks
|
|
351
|
+
// if it hasn't been flushed already (e.g. someone started reading request stream).
|
|
352
|
+
this.flushWriteBuffer()
|
|
353
|
+
|
|
354
|
+
// Create a `ServerResponse` instance to delegate HTTP message parsing,
|
|
355
|
+
// Transfer-Encoding, and other things to Node.js internals.
|
|
356
|
+
const serverResponse = new ServerResponse(new IncomingMessage(this))
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Assign a mock socket instance to the server response to
|
|
360
|
+
* spy on the response chunk writes. Push the transformed response chunks
|
|
361
|
+
* to this `MockHttpSocket` instance to trigger the "data" event.
|
|
362
|
+
* @note Providing the same `MockSocket` instance when creating `ServerResponse`
|
|
363
|
+
* does not have the same effect.
|
|
364
|
+
* @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32
|
|
365
|
+
*/
|
|
366
|
+
serverResponse.assignSocket(
|
|
367
|
+
new MockSocket({
|
|
368
|
+
write: (chunk, encoding, callback) => {
|
|
369
|
+
this.push(chunk, encoding)
|
|
370
|
+
callback?.()
|
|
371
|
+
},
|
|
372
|
+
read() {},
|
|
373
|
+
})
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @note Remove the `Connection` and `Date` response headers
|
|
378
|
+
* injected by `ServerResponse` by default. Those are required
|
|
379
|
+
* from the server but the interceptor is NOT technically a server.
|
|
380
|
+
* It's confusing to add response headers that the developer didn't
|
|
381
|
+
* specify themselves. They can always add these if they wish.
|
|
382
|
+
* @see https://www.rfc-editor.org/rfc/rfc9110#field.date
|
|
383
|
+
* @see https://www.rfc-editor.org/rfc/rfc9110#field.connection
|
|
384
|
+
*/
|
|
385
|
+
serverResponse.removeHeader('connection')
|
|
386
|
+
serverResponse.removeHeader('date')
|
|
387
|
+
|
|
388
|
+
const rawResponseHeaders = getRawFetchHeaders(response.headers)
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @note Call `.writeHead` in order to set the raw response headers
|
|
392
|
+
* in the same case as they were provided by the developer. Using
|
|
393
|
+
* `.setHeader()`/`.appendHeader()` normalizes header names.
|
|
394
|
+
*/
|
|
395
|
+
serverResponse.writeHead(
|
|
396
|
+
response.status,
|
|
397
|
+
response.statusText || STATUS_CODES[response.status],
|
|
398
|
+
rawResponseHeaders
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
// If the developer destroy the socket, gracefully destroy the response.
|
|
402
|
+
this.once('error', () => {
|
|
403
|
+
serverResponse.destroy()
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
if (response.body) {
|
|
407
|
+
try {
|
|
408
|
+
const reader = response.body.getReader()
|
|
409
|
+
|
|
410
|
+
while (true) {
|
|
411
|
+
const { done, value } = await reader.read()
|
|
412
|
+
|
|
413
|
+
if (done) {
|
|
414
|
+
serverResponse.end()
|
|
415
|
+
break
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
serverResponse.write(value)
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error instanceof Error) {
|
|
422
|
+
serverResponse.destroy()
|
|
423
|
+
/**
|
|
424
|
+
* @note Destroy the request socket gracefully.
|
|
425
|
+
* Response stream errors do NOT produce request errors.
|
|
426
|
+
*/
|
|
427
|
+
this.destroy()
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
serverResponse.destroy()
|
|
432
|
+
throw error
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
serverResponse.end()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Close the socket if the connection wasn't marked as keep-alive.
|
|
439
|
+
if (!this.shouldKeepAlive) {
|
|
440
|
+
this.emit('readable')
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @todo @fixme This is likely a hack.
|
|
444
|
+
* Since we push null to the socket, it never propagates to the
|
|
445
|
+
* parser, and the parser never calls "onResponseEnd" to close
|
|
446
|
+
* the response stream. We are closing the stream here manually
|
|
447
|
+
* but that shouldn't be the case.
|
|
448
|
+
*/
|
|
449
|
+
this.responseStream?.push(null)
|
|
450
|
+
this.push(null)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Close this socket connection with the given error.
|
|
456
|
+
*/
|
|
457
|
+
public errorWith(error?: Error): void {
|
|
458
|
+
this.destroy(error)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private mockConnect(): void {
|
|
462
|
+
// Calling this method immediately puts the socket
|
|
463
|
+
// into the connected state.
|
|
464
|
+
this.connecting = false
|
|
465
|
+
|
|
466
|
+
const isIPv6 =
|
|
467
|
+
net.isIPv6(this.connectionOptions.hostname) ||
|
|
468
|
+
this.connectionOptions.family === 6
|
|
469
|
+
const addressInfo = {
|
|
470
|
+
address: isIPv6 ? '::1' : '127.0.0.1',
|
|
471
|
+
family: isIPv6 ? 'IPv6' : 'IPv4',
|
|
472
|
+
port: this.connectionOptions.port,
|
|
473
|
+
}
|
|
474
|
+
// Return fake address information for the socket.
|
|
475
|
+
this.address = () => addressInfo
|
|
476
|
+
this.emit(
|
|
477
|
+
'lookup',
|
|
478
|
+
null,
|
|
479
|
+
addressInfo.address,
|
|
480
|
+
addressInfo.family === 'IPv6' ? 6 : 4,
|
|
481
|
+
this.connectionOptions.host
|
|
482
|
+
)
|
|
483
|
+
this.emit('connect')
|
|
484
|
+
this.emit('ready')
|
|
485
|
+
|
|
486
|
+
if (this.baseUrl.protocol === 'https:') {
|
|
487
|
+
this.emit('secure')
|
|
488
|
+
this.emit('secureConnect')
|
|
489
|
+
|
|
490
|
+
// A single TLS connection is represented by two "session" events.
|
|
491
|
+
this.emit(
|
|
492
|
+
'session',
|
|
493
|
+
this.connectionOptions.session ||
|
|
494
|
+
Buffer.from('mock-session-renegotiate')
|
|
495
|
+
)
|
|
496
|
+
this.emit('session', Buffer.from('mock-session-resume'))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private flushWriteBuffer(): void {
|
|
501
|
+
for (const writeCall of this.writeBuffer) {
|
|
502
|
+
if (typeof writeCall[2] === 'function') {
|
|
503
|
+
writeCall[2]()
|
|
504
|
+
/**
|
|
505
|
+
* @note Remove the callback from the write call
|
|
506
|
+
* so it doesn't get called twice on passthrough
|
|
507
|
+
* if `request.end()` was called within `request.write()`.
|
|
508
|
+
* @see https://github.com/mswjs/interceptors/issues/684
|
|
509
|
+
*/
|
|
510
|
+
writeCall[2] = undefined
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* This callback might be called when the request is "slow":
|
|
517
|
+
* - Request headers were fragmented across multiple TCP packages;
|
|
518
|
+
* - Request headers were too large to be processed in a single run
|
|
519
|
+
* (e.g. more than 30 request headers).
|
|
520
|
+
* @note This is called before request start.
|
|
521
|
+
*/
|
|
522
|
+
private onRequestHeaders: HeadersCallback = (rawHeaders) => {
|
|
523
|
+
this.requestRawHeadersBuffer.push(...rawHeaders)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private onRequestStart: RequestHeadersCompleteCallback = (
|
|
527
|
+
versionMajor,
|
|
528
|
+
versionMinor,
|
|
529
|
+
rawHeaders,
|
|
530
|
+
_,
|
|
531
|
+
path,
|
|
532
|
+
__,
|
|
533
|
+
___,
|
|
534
|
+
____,
|
|
535
|
+
shouldKeepAlive
|
|
536
|
+
) => {
|
|
537
|
+
this.shouldKeepAlive = shouldKeepAlive
|
|
538
|
+
|
|
539
|
+
const url = new URL(path || '', this.baseUrl)
|
|
540
|
+
const method = this.connectionOptions.method?.toUpperCase() || 'GET'
|
|
541
|
+
const headers = FetchResponse.parseRawHeaders([
|
|
542
|
+
...this.requestRawHeadersBuffer,
|
|
543
|
+
...(rawHeaders || []),
|
|
544
|
+
])
|
|
545
|
+
this.requestRawHeadersBuffer.length = 0
|
|
546
|
+
|
|
547
|
+
const canHaveBody = method !== 'GET' && method !== 'HEAD'
|
|
548
|
+
|
|
549
|
+
// Translate the basic authorization in the URL to the request header.
|
|
550
|
+
// Constructing a Request instance with a URL containing auth is no-op.
|
|
551
|
+
if (url.username || url.password) {
|
|
552
|
+
if (!headers.has('authorization')) {
|
|
553
|
+
headers.set('authorization', `Basic ${url.username}:${url.password}`)
|
|
554
|
+
}
|
|
555
|
+
url.username = ''
|
|
556
|
+
url.password = ''
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Create a new stream for each request.
|
|
560
|
+
// If this Socket is reused for multiple requests,
|
|
561
|
+
// this ensures that each request gets its own stream.
|
|
562
|
+
// One Socket instance can only handle one request at a time.
|
|
563
|
+
this.requestStream = new Readable({
|
|
564
|
+
/**
|
|
565
|
+
* @note Provide the `read()` method so a `Readable` could be
|
|
566
|
+
* used as the actual request body (the stream calls "read()").
|
|
567
|
+
* We control the queue in the onRequestBody/End functions.
|
|
568
|
+
*/
|
|
569
|
+
read: () => {
|
|
570
|
+
// If the user attempts to read the request body,
|
|
571
|
+
// flush the write buffer to trigger the callbacks.
|
|
572
|
+
// This way, if the request stream ends in the write callback,
|
|
573
|
+
// it will indeed end correctly.
|
|
574
|
+
this.flushWriteBuffer()
|
|
575
|
+
},
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
const requestId = createRequestId()
|
|
579
|
+
this.request = new Request(url, {
|
|
580
|
+
method,
|
|
581
|
+
headers,
|
|
582
|
+
credentials: 'same-origin',
|
|
583
|
+
// @ts-expect-error Undocumented Fetch property.
|
|
584
|
+
duplex: canHaveBody ? 'half' : undefined,
|
|
585
|
+
body: canHaveBody ? (Readable.toWeb(this.requestStream!) as any) : null,
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
Reflect.set(this.request, kRequestId, requestId)
|
|
589
|
+
|
|
590
|
+
// Set the raw `http.ClientRequest` instance on the request instance.
|
|
591
|
+
// This is useful for cases like getting the raw headers of the request.
|
|
592
|
+
setRawRequest(this.request, Reflect.get(this, '_httpMessage'))
|
|
593
|
+
|
|
594
|
+
// Create a copy of the request body stream and store it on the request.
|
|
595
|
+
// This is only needed for the consumers who wish to read the request body stream
|
|
596
|
+
// of requests that cannot have a body per Fetch API specification (i.e. GET, HEAD).
|
|
597
|
+
setRawRequestBodyStream(this.request, this.requestStream)
|
|
598
|
+
|
|
599
|
+
// Skip handling the request that's already being handled
|
|
600
|
+
// by another (parent) interceptor. For example, XMLHttpRequest
|
|
601
|
+
// is often implemented via ClientRequest in Node.js (e.g. JSDOM).
|
|
602
|
+
// In that case, XHR interceptor will bubble down to the ClientRequest
|
|
603
|
+
// interceptor. No need to try to handle that request again.
|
|
604
|
+
/**
|
|
605
|
+
* @fixme Stop relying on the "X-Request-Id" request header
|
|
606
|
+
* to figure out if one interceptor has been invoked within another.
|
|
607
|
+
* @see https://github.com/mswjs/interceptors/issues/378
|
|
608
|
+
*/
|
|
609
|
+
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
|
|
610
|
+
this.passthrough()
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
this.onRequest({
|
|
615
|
+
requestId,
|
|
616
|
+
request: this.request,
|
|
617
|
+
socket: this,
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private onRequestBody(chunk: Buffer): void {
|
|
622
|
+
invariant(
|
|
623
|
+
this.requestStream,
|
|
624
|
+
'Failed to write to a request stream: stream does not exist'
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
this.requestStream.push(chunk)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private onRequestEnd(): void {
|
|
631
|
+
// Request end can be called for requests without body.
|
|
632
|
+
if (this.requestStream) {
|
|
633
|
+
this.requestStream.push(null)
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* This callback might be called when the response is "slow":
|
|
639
|
+
* - Response headers were fragmented across multiple TCP packages;
|
|
640
|
+
* - Response headers were too large to be processed in a single run
|
|
641
|
+
* (e.g. more than 30 response headers).
|
|
642
|
+
* @note This is called before response start.
|
|
643
|
+
*/
|
|
644
|
+
private onResponseHeaders: HeadersCallback = (rawHeaders) => {
|
|
645
|
+
this.responseRawHeadersBuffer.push(...rawHeaders)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private onResponseStart: ResponseHeadersCompleteCallback = (
|
|
649
|
+
versionMajor,
|
|
650
|
+
versionMinor,
|
|
651
|
+
rawHeaders,
|
|
652
|
+
method,
|
|
653
|
+
url,
|
|
654
|
+
status,
|
|
655
|
+
statusText
|
|
656
|
+
) => {
|
|
657
|
+
const headers = FetchResponse.parseRawHeaders([
|
|
658
|
+
...this.responseRawHeadersBuffer,
|
|
659
|
+
...(rawHeaders || []),
|
|
660
|
+
])
|
|
661
|
+
this.responseRawHeadersBuffer.length = 0
|
|
662
|
+
|
|
663
|
+
const response = new FetchResponse(
|
|
664
|
+
/**
|
|
665
|
+
* @note The Fetch API response instance exposed to the consumer
|
|
666
|
+
* is created over the response stream of the HTTP parser. It is NOT
|
|
667
|
+
* related to the Socket instance. This way, you can read response body
|
|
668
|
+
* in response listener while the Socket instance delays the emission
|
|
669
|
+
* of "end" and other events until those response listeners are finished.
|
|
670
|
+
*/
|
|
671
|
+
FetchResponse.isResponseWithBody(status)
|
|
672
|
+
? (Readable.toWeb(
|
|
673
|
+
(this.responseStream = new Readable({ read() {} }))
|
|
674
|
+
) as any)
|
|
675
|
+
: null,
|
|
676
|
+
{
|
|
677
|
+
url,
|
|
678
|
+
status,
|
|
679
|
+
statusText,
|
|
680
|
+
headers,
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
invariant(
|
|
685
|
+
this.request,
|
|
686
|
+
'Failed to handle a response: request does not exist'
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
FetchResponse.setUrl(this.request.url, response)
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* @fixme Stop relying on the "X-Request-Id" request header
|
|
693
|
+
* to figure out if one interceptor has been invoked within another.
|
|
694
|
+
* @see https://github.com/mswjs/interceptors/issues/378
|
|
695
|
+
*/
|
|
696
|
+
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
this.responseListenersPromise = this.onResponse({
|
|
701
|
+
response,
|
|
702
|
+
isMockedResponse: this.socketState === 'mock',
|
|
703
|
+
requestId: Reflect.get(this.request, kRequestId),
|
|
704
|
+
request: this.request,
|
|
705
|
+
socket: this,
|
|
706
|
+
})
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private onResponseBody(chunk: Buffer) {
|
|
710
|
+
invariant(
|
|
711
|
+
this.responseStream,
|
|
712
|
+
'Failed to write to a response stream: stream does not exist'
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
this.responseStream.push(chunk)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private onResponseEnd(): void {
|
|
719
|
+
// Response end can be called for responses without body.
|
|
720
|
+
if (this.responseStream) {
|
|
721
|
+
this.responseStream.push(null)
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|