@mswjs/interceptors 0.42.5 → 0.42.6

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.
@@ -70,6 +70,20 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
70
70
  let abortPendingRequest: (() => void) | undefined
71
71
  let pendingRequestController: RequestController | undefined
72
72
 
73
+ // Protocol detection runs inside a client write. Let the write finish,
74
+ // other data observers run, and the remaining "connection" listeners
75
+ // get their chance to claim before flushing it to the real socket.
76
+ const passthroughNonHttp = () => {
77
+ setImmediate(() => {
78
+ if (
79
+ socketController.readyState === SocketController.PENDING &&
80
+ !socket.destroyed
81
+ ) {
82
+ socketController.passthrough()
83
+ }
84
+ })
85
+ }
86
+
73
87
  // A malformed request loses the boundary for subsequent requests.
74
88
  // Preserve the original bytes for the client and server to handle.
75
89
  const stopParsingRequests = (error: Error) => {
@@ -81,7 +95,7 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
81
95
  ) {
82
96
  void pendingRequestController.passthrough()
83
97
  } else {
84
- socketController.decline()
98
+ passthroughNonHttp()
85
99
  }
86
100
 
87
101
  requestParser?.free(error)
@@ -112,394 +126,387 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
112
126
  realSocketDestroy(error, callback)
113
127
  }
114
128
 
115
- const addRequestDataListener = () => {
116
- const executeRequestParser = (
117
- parser: HttpRequestParser,
118
- chunk: Buffer
119
- ) => {
120
- // llhttp pauses permanently at an upgrade boundary. Release the
121
- // parser after execute returns, outside its native callbacks.
122
- if (parser.execute(chunk) !== null) {
123
- socket.removeListener('data', onRequestData)
124
- parser.free()
125
- requestParser = undefined
126
- }
129
+ const executeRequestParser = (
130
+ parser: HttpRequestParser,
131
+ chunk: Buffer
132
+ ) => {
133
+ // llhttp pauses permanently at an upgrade boundary. Release the
134
+ // parser after execute returns, outside its native callbacks.
135
+ if (parser.execute(chunk) !== null) {
136
+ socket.removeListener('data', onRequestData)
137
+ parser.free()
138
+ requestParser = undefined
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @note Inspect the first sent packet to determine the protocol,
144
+ * including when entering a mocked "CONNECT" tunnel.
145
+ */
146
+ const onRequestData = (chunk: Buffer) => {
147
+ if (isHttpConnection === false) {
148
+ passthroughNonHttp()
149
+ return
127
150
  }
128
151
 
129
152
  /**
130
- * @note Inspect the first sent packet to determine the protocol,
131
- * including when entering a mocked "CONNECT" tunnel.
153
+ * @note A mocked "CONNECT" request has established a tunnel.
154
+ * The data that follows belongs to a new exchange addressed to
155
+ * the tunnel target. The previous parser was freed at the upgrade
156
+ * boundary, so detect the tunneled protocol anew.
132
157
  */
133
- const onRequestData = (chunk: Buffer) => {
134
- if (isHttpConnection === false) {
135
- socketController.decline()
136
- return
137
- }
158
+ if (tunnelUrl && !requestParser) {
159
+ isHttpConnection = undefined
138
160
 
139
161
  /**
140
- * @note A mocked "CONNECT" request has established a tunnel.
141
- * The data that follows belongs to a new exchange addressed to
142
- * the tunnel target. The previous parser was freed at the upgrade
143
- * boundary, so detect the tunneled protocol anew.
162
+ * @note Retarget the connection to the tunnel authority.
163
+ * The exchanges that follow belong to the tunnel target,
164
+ * so an unclaimed exchange (HTTP or not) must pass through
165
+ * to that target — not to the proxy, which never actually
166
+ * established this tunnel — like a real established tunnel
167
+ * relays its traffic.
144
168
  */
145
- if (tunnelUrl && !requestParser) {
146
- isHttpConnection = undefined
147
-
148
- /**
149
- * @note Retarget the connection to the tunnel authority.
150
- * The exchanges that follow belong to the tunnel target,
151
- * so an unclaimed exchange (HTTP or not) must pass through
152
- * to that target — not to the proxy, which never actually
153
- * established this tunnel — like a real established tunnel
154
- * relays its traffic.
155
- */
156
- socketController.reset({
157
- host: tunnelUrl.hostname,
158
- port: Number(tunnelUrl.port) || 80,
159
- path: null,
160
- })
161
- }
162
-
163
- if (requestParser) {
164
- executeRequestParser(requestParser, toBuffer(chunk))
165
- return
166
- }
167
-
168
- const httpMessage = chunk.toString()
169
- const httpMethod = httpMessage.split(' ')[0] || ''
169
+ socketController.reset({
170
+ host: tunnelUrl.hostname,
171
+ port: Number(tunnelUrl.port) || 80,
172
+ path: null,
173
+ })
174
+ }
170
175
 
171
- // Decline non-HTTP connections so the socket controller can
172
- // pass them through once every subscriber has declined.
173
- if (!METHODS.includes(httpMethod.toUpperCase())) {
174
- isHttpConnection = false
175
- socketController.decline()
176
- return
177
- }
176
+ if (requestParser) {
177
+ executeRequestParser(requestParser, toBuffer(chunk))
178
+ return
179
+ }
178
180
 
179
- isHttpConnection = true
181
+ const httpMessage = chunk.toString()
182
+ const httpMethod = httpMessage.split(' ')[0] || ''
180
183
 
181
- const baseUrl =
182
- tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket)
184
+ // Stop parsing connections that do not carry HTTP traffic.
185
+ if (!METHODS.includes(httpMethod.toUpperCase())) {
186
+ isHttpConnection = false
187
+ passthroughNonHttp()
188
+ return
189
+ }
183
190
 
184
- httpLogger.verbose('handling http message %o', {
185
- httpMessage,
186
- httpMethod,
187
- baseUrl,
188
- })
191
+ isHttpConnection = true
192
+
193
+ const baseUrl =
194
+ tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket)
195
+
196
+ httpLogger.verbose('handling http message %o', {
197
+ httpMessage,
198
+ httpMethod,
199
+ baseUrl,
200
+ })
201
+
202
+ // Get the request initiator from the async context, falling
203
+ // back to the context captured at the connection time, then
204
+ // to the underlying socket.
205
+ const requestContextValue =
206
+ requestContext.getStore() ?? connectionRequestContext
207
+ const initiator = requestContextValue?.initiator || socket
208
+
209
+ requestParser = new HttpRequestParser({
210
+ onError: stopParsingRequests,
211
+ connectionOptions: {
212
+ method: httpMethod,
213
+ url: baseUrl,
214
+ },
215
+ /**
216
+ * @note The message boundary ends the current exchange.
217
+ * Schedule the controller reset so the next write on this
218
+ * (kept-alive) socket opens a new exchange and buffers for
219
+ * its own verdict instead of following the settled one
220
+ * (e.g. leaking a mocked request to the server of a
221
+ * previously passed-through exchange).
222
+ */
223
+ onMessageComplete: () => {
224
+ socketController.scheduleReset()
225
+ },
226
+ onRequest: async (parsedRequest, requestAbortController) => {
227
+ const request =
228
+ requestContextValue?.transformRequest?.(parsedRequest) ??
229
+ parsedRequest
189
230
 
190
- // Get the request initiator from the async context, falling
191
- // back to the context captured at the connection time, then
192
- // to the underlying socket.
193
- const requestContextValue =
194
- requestContext.getStore() ?? connectionRequestContext
195
- const initiator = requestContextValue?.initiator || socket
196
-
197
- requestParser = new HttpRequestParser({
198
- onError: stopParsingRequests,
199
- connectionOptions: {
200
- method: httpMethod,
201
- url: baseUrl,
202
- },
203
231
  /**
204
- * @note The message boundary ends the current exchange.
205
- * Schedule the controller reset so the next write on this
206
- * (kept-alive) socket opens a new exchange and buffers for
207
- * its own verdict instead of following the settled one
208
- * (e.g. leaking a mocked request to the server of a
209
- * previously passed-through exchange).
232
+ * @note A subsequent request arriving on a kept-alive socket
233
+ * that has already been handled (passed through or mocked).
234
+ * Clients like Undici reuse sockets without emitting the
235
+ * "free" event, so reset the controller here, at the HTTP
236
+ * message boundary, to handle the new request from the
237
+ * pending state again.
210
238
  */
211
- onMessageComplete: () => {
212
- socketController.scheduleReset()
213
- },
214
- onRequest: async (parsedRequest, requestAbortController) => {
215
- const request =
216
- requestContextValue?.transformRequest?.(parsedRequest) ??
217
- parsedRequest
218
-
219
- /**
220
- * @note A subsequent request arriving on a kept-alive socket
221
- * that has already been handled (passed through or mocked).
222
- * Clients like Undici reuse sockets without emitting the
223
- * "free" event, so reset the controller here, at the HTTP
224
- * message boundary, to handle the new request from the
225
- * pending state again.
226
- */
227
- if (
228
- socketController['readyState'] !== SocketController.PENDING
229
- ) {
230
- socketController.reset()
231
- }
239
+ if (socketController['readyState'] !== SocketController.PENDING) {
240
+ socketController.reset()
241
+ }
232
242
 
233
- const requestId = createRequestId()
234
- const requestLogger = requestContextValue?.logger ?? httpLogger
235
-
236
- httpLogger.verbose('received a parsed HTTP request %o', {
237
- method: request.method,
238
- url: request.url,
239
- })
240
-
241
- const requestController = new RequestController(
242
- request,
243
- {
244
- respondWith: async (rawResponse) => {
245
- httpLogger.verbose('respondWith() %o', {
246
- status: rawResponse.status,
247
- statusText: rawResponse.statusText,
248
- hasBody: rawResponse.body != null,
249
- })
243
+ const requestId = createRequestId()
244
+ const requestLogger = requestContextValue?.logger ?? httpLogger
250
245
 
251
- /**
252
- * @note The client may destroy the socket (e.g. on request
253
- * abort) moments before a response arrives. A destroyed
254
- * socket cannot be claimed and has no one reading it.
255
- */
256
- if (socket.destroyed) {
257
- return
258
- }
259
-
260
- socketController.claim()
246
+ httpLogger.verbose('received a parsed HTTP request %o', {
247
+ method: request.method,
248
+ url: request.url,
249
+ })
261
250
 
262
- const originalResponse = FetchResponse.from(rawResponse, {
263
- url: request.url,
251
+ const requestController = new RequestController(
252
+ request,
253
+ {
254
+ respondWith: async (rawResponse) => {
255
+ httpLogger.verbose('respondWith() %o', {
256
+ status: rawResponse.status,
257
+ statusText: rawResponse.statusText,
258
+ hasBody: rawResponse.body != null,
259
+ })
260
+
261
+ /**
262
+ * @note The client may destroy the socket (e.g. on request
263
+ * abort) moments before a response arrives. A destroyed
264
+ * socket cannot be claimed and has no one reading it.
265
+ */
266
+ if (socket.destroyed) {
267
+ return
268
+ }
269
+
270
+ socketController.claim()
271
+
272
+ const originalResponse = FetchResponse.from(rawResponse, {
273
+ url: request.url,
274
+ })
275
+ const [response, responseClone] =
276
+ !isResponseError(originalResponse) &&
277
+ this.emitter.listenerCount('response') > 0
278
+ ? cloneResponse(originalResponse)
279
+ : [originalResponse, null]
280
+
281
+ /**
282
+ * @note A successful mocked response to a "CONNECT"
283
+ * request establishes a tunnel to the requested authority
284
+ * (e.g. "127.0.0.1:80"). The exchange that follows on this
285
+ * socket is addressed to that authority, not to the proxy.
286
+ */
287
+ if (request.method === 'CONNECT' && response.ok) {
288
+ tunnelUrl = new URL(`http://${request.url}`)
289
+ socket.on('data', onRequestData)
290
+ }
291
+
292
+ const respond = () => {
293
+ return this.respondWith({
294
+ socket: socketController[kRawSocket],
295
+ request: context.request,
296
+ response,
264
297
  })
265
- const [response, responseClone] =
266
- !isResponseError(originalResponse) &&
267
- this.emitter.listenerCount('response') > 0
268
- ? cloneResponse(originalResponse)
269
- : [originalResponse, null]
270
-
271
- /**
272
- * @note A successful mocked response to a "CONNECT"
273
- * request establishes a tunnel to the requested authority
274
- * (e.g. "127.0.0.1:80"). The exchange that follows on this
275
- * socket is addressed to that authority, not to the proxy.
276
- */
277
- if (request.method === 'CONNECT' && response.ok) {
278
- tunnelUrl = new URL(`http://${request.url}`)
279
- addRequestDataListener()
280
- }
298
+ }
281
299
 
282
- const respond = () => {
283
- return this.respondWith({
284
- socket: socketController[kRawSocket],
300
+ if (responseClone) {
301
+ await this.emitter.emitAsPromise(
302
+ new HttpResponseEvent({
303
+ initiator,
304
+ requestId,
285
305
  request: context.request,
286
- response,
306
+ response: responseClone,
307
+ responseType: 'mock',
287
308
  })
288
- }
289
-
290
- if (responseClone) {
291
- await this.emitter.emitAsPromise(
292
- new HttpResponseEvent({
293
- initiator,
294
- requestId,
295
- request: context.request,
296
- response: responseClone,
297
- responseType: 'mock',
298
- })
299
- )
300
- }
301
-
302
- if (socket.connecting) {
303
- // Send a mocked response once the socket connects, just like the real server would.
304
- // This preserves the correct order of events (e.g. connect, then data).
305
- socket.once('connect', respond)
306
- } else {
307
- /**
308
- * @note Reused sockets stay connected between requests and will not
309
- * emit "connect" anymore. If that's the case, respond immediately.
310
- */
311
- await respond()
312
- }
313
- },
314
- errorWith: (reason) => {
315
- if (reason instanceof Error) {
316
- socket.destroy(reason)
317
- }
318
- },
319
- passthrough: () => {
320
- const realSocket = socketController.passthrough(
321
- isHttpConnection === false
322
- ? undefined
323
- : this.#modifyHttpHeaders(context.request)
324
309
  )
310
+ }
325
311
 
326
- if (isHttpConnection === false) {
327
- return
328
- }
312
+ if (socket.connecting) {
313
+ // Send a mocked response once the socket connects, just like the real server would.
314
+ // This preserves the correct order of events (e.g. connect, then data).
315
+ socket.once('connect', respond)
316
+ } else {
317
+ /**
318
+ * @note Reused sockets stay connected between requests and will not
319
+ * emit "connect" anymore. If that's the case, respond immediately.
320
+ */
321
+ await respond()
322
+ }
323
+ },
324
+ errorWith: (reason) => {
325
+ if (reason instanceof Error) {
326
+ socket.destroy(reason)
327
+ }
328
+ },
329
+ passthrough: () => {
330
+ const realSocket = socketController.passthrough(
331
+ isHttpConnection === false
332
+ ? undefined
333
+ : this.#modifyHttpHeaders(context.request)
334
+ )
335
+
336
+ if (isHttpConnection === false) {
337
+ return
338
+ }
339
+
340
+ if (this.emitter.listenerCount('response') > 0) {
341
+ httpLogger.verbose(
342
+ 'found "response" listener, corking socket reads'
343
+ )
329
344
 
330
- if (this.emitter.listenerCount('response') > 0) {
331
- httpLogger.verbose(
332
- 'found "response" listener, corking socket reads'
333
- )
334
-
335
- /**
336
- * Suspend the delivery of the original response to the client
337
- * until the "response" event listeners settle. This guarantees
338
- * that the request promise (e.g. `await fetch()`) does not
339
- * resolve before the listeners are done. The real socket keeps
340
- * emitting data for the response parser meanwhile.
341
- */
342
- socketController.corkReads()
343
-
344
- let responseParserDisposed = false
345
- let responseComplete = false
346
- let hasFinalResponse = false
347
- const responseParser = new HttpResponseParser({
348
- onError: (error) => {
349
- disposeResponseParser(error)
350
- socketController.uncorkReads()
351
- },
352
- onMessageComplete: (status) => {
353
- responseComplete = status >= 200 || status === 101
354
- },
355
- onResponse: async (response) => {
356
- hasFinalResponse =
357
- response.status >= 200 || response.status === 101
345
+ /**
346
+ * Suspend the delivery of the original response to the client
347
+ * until the "response" event listeners settle. This guarantees
348
+ * that the request promise (e.g. `await fetch()`) does not
349
+ * resolve before the listeners are done. The real socket keeps
350
+ * emitting data for the response parser meanwhile.
351
+ */
352
+ socketController.corkReads()
353
+
354
+ let responseParserDisposed = false
355
+ let responseComplete = false
356
+ let hasFinalResponse = false
357
+ const responseParser = new HttpResponseParser({
358
+ onError: (error) => {
359
+ disposeResponseParser(error)
360
+ socketController.uncorkReads()
361
+ },
362
+ onMessageComplete: (status) => {
363
+ responseComplete = status >= 200 || status === 101
364
+ },
365
+ onResponse: async (response) => {
366
+ hasFinalResponse =
367
+ response.status >= 200 || response.status === 101
368
+ httpLogger.verbose(
369
+ 'HTTP response parser parsed: %d %s',
370
+ response.status,
371
+ response.statusText
372
+ )
373
+
374
+ if (isResponseError(response)) {
358
375
  httpLogger.verbose(
359
- 'HTTP response parser parsed: %d %s',
360
- response.status,
361
- response.statusText
376
+ 'response is an error response, uncorking socket reads...'
362
377
  )
363
378
 
364
- if (isResponseError(response)) {
365
- httpLogger.verbose(
366
- 'response is an error response, uncorking socket reads...'
367
- )
379
+ socketController.uncorkReads()
380
+ return
381
+ }
368
382
 
369
- socketController.uncorkReads()
370
- return
371
- }
383
+ FetchResponse.setUrl(request.url, response)
384
+
385
+ try {
386
+ httpLogger.verbose('emitting "response" event')
387
+ await this.emitter.emitAsPromise(
388
+ new HttpResponseEvent({
389
+ initiator,
390
+ requestId,
391
+ request: context.request,
392
+ response,
393
+ responseType: 'original',
394
+ })
395
+ )
396
+ } finally {
397
+ httpLogger.verbose('uncorking socket reads')
398
+ socketController.uncorkReads()
372
399
 
373
- FetchResponse.setUrl(request.url, response)
374
-
375
- try {
376
- httpLogger.verbose('emitting "response" event')
377
- await this.emitter.emitAsPromise(
378
- new HttpResponseEvent({
379
- initiator,
380
- requestId,
381
- request: context.request,
382
- response,
383
- responseType: 'original',
384
- })
385
- )
386
- } finally {
387
- httpLogger.verbose('uncorking socket reads')
388
- socketController.uncorkReads()
389
-
390
- /**
391
- * @note Informational responses other than
392
- * "101 Switching Protocols" are followed by a final
393
- * response on the same connection. Keep gating that
394
- * final response on the "response" event listeners.
395
- */
396
- if (
397
- !responseParserDisposed &&
398
- response.status < 200 &&
399
- response.status !== 101
400
- ) {
401
- socketController.corkReads()
402
- }
400
+ /**
401
+ * @note Informational responses other than
402
+ * "101 Switching Protocols" are followed by a final
403
+ * response on the same connection. Keep gating that
404
+ * final response on the "response" event listeners.
405
+ */
406
+ if (
407
+ !responseParserDisposed &&
408
+ response.status < 200 &&
409
+ response.status !== 101
410
+ ) {
411
+ socketController.corkReads()
403
412
  }
404
- },
405
- })
406
-
407
- const onResponseData = (chunk: Buffer) => {
408
- responseParser.execute(chunk)
409
-
410
- // Free only after llhttp returns from its callbacks.
411
- if (responseComplete) {
412
- disposeResponseParser()
413
413
  }
414
- }
414
+ },
415
+ })
415
416
 
416
- const onResponseEnd = () => {
417
- disposeResponseParser()
417
+ const onResponseData = (chunk: Buffer) => {
418
+ responseParser.execute(chunk)
418
419
 
419
- // Without a final response, release EOF now. A half-open
420
- // socket cannot close until the client consumes it.
421
- if (!hasFinalResponse) {
422
- socketController.uncorkReads()
423
- }
420
+ // Free only after llhttp returns from its callbacks.
421
+ if (responseComplete) {
422
+ disposeResponseParser()
424
423
  }
424
+ }
425
+
426
+ const onResponseEnd = () => {
427
+ disposeResponseParser()
425
428
 
426
- const disposeResponseParser = (error?: Error) => {
427
- responseParserDisposed = true
428
- realSocket.removeListener('data', onResponseData)
429
- realSocket.removeListener('end', onResponseEnd)
430
- realSocket.removeListener('close', onResponseEnd)
431
- responseParser.free(error)
429
+ // Without a final response, release EOF now. A half-open
430
+ // socket cannot close until the client consumes it.
431
+ if (!hasFinalResponse) {
432
+ socketController.uncorkReads()
432
433
  }
434
+ }
433
435
 
434
- realSocket
435
- .on('data', onResponseData)
436
- .once('end', onResponseEnd)
437
- .once('close', onResponseEnd)
436
+ const disposeResponseParser = (error?: Error) => {
437
+ responseParserDisposed = true
438
+ realSocket.removeListener('data', onResponseData)
439
+ realSocket.removeListener('end', onResponseEnd)
440
+ realSocket.removeListener('close', onResponseEnd)
441
+ responseParser.free(error)
438
442
  }
439
- },
443
+
444
+ realSocket
445
+ .on('data', onResponseData)
446
+ .once('end', onResponseEnd)
447
+ .once('close', onResponseEnd)
448
+ }
440
449
  },
441
- {
442
- logger: requestLogger,
443
- requestId,
444
- }
445
- )
446
-
447
- invariant(
448
- socketController['readyState'] === SocketController.PENDING,
449
- 'CANNOT HANDLE ALREADY HANDLED REQUEST',
450
- request.method,
451
- request.url,
452
- socketController['readyState']
453
- )
454
-
455
- /**
456
- * @note Create a request resolution context.
457
- * This is so modifications to the "request" in upstream interceptors
458
- * are correctly picked up by the underlying HTTP interceptor.
459
- */
460
- const context: HandleRequestOptions = {
461
- initiator,
462
- requestId,
463
- request,
464
- controller: requestController,
465
- emitter: this.emitter,
450
+ },
451
+ {
466
452
  logger: requestLogger,
453
+ requestId,
467
454
  }
455
+ )
468
456
 
469
- /**
470
- * @note The client destroying the socket while the request
471
- * is still pending means the request was aborted (e.g. via
472
- * `AbortController`). Abort the parsed request so its
473
- * handling settles and late interactions with the request
474
- * controller become controlled errors.
475
- */
476
- abortPendingRequest = () => {
477
- if (
478
- requestController.readyState === RequestController.PENDING
479
- ) {
480
- requestAbortController.abort()
481
- }
482
- }
457
+ invariant(
458
+ socketController['readyState'] === SocketController.PENDING,
459
+ 'CANNOT HANDLE ALREADY HANDLED REQUEST',
460
+ request.method,
461
+ request.url,
462
+ socketController['readyState']
463
+ )
483
464
 
484
- pendingRequestController = requestController
465
+ /**
466
+ * @note Create a request resolution context.
467
+ * This is so modifications to the "request" in upstream interceptors
468
+ * are correctly picked up by the underlying HTTP interceptor.
469
+ */
470
+ const context: HandleRequestOptions = {
471
+ initiator,
472
+ requestId,
473
+ request,
474
+ controller: requestController,
475
+ emitter: this.emitter,
476
+ logger: requestLogger,
477
+ }
485
478
 
486
- try {
487
- await handleRequest(context)
488
- } finally {
489
- pendingRequestController = undefined
490
- abortPendingRequest = undefined
479
+ /**
480
+ * @note The client destroying the socket while the request
481
+ * is still pending means the request was aborted (e.g. via
482
+ * `AbortController`). Abort the parsed request so its
483
+ * handling settles and late interactions with the request
484
+ * controller become controlled errors.
485
+ */
486
+ abortPendingRequest = () => {
487
+ if (
488
+ requestController.readyState === RequestController.PENDING
489
+ ) {
490
+ requestAbortController.abort()
491
491
  }
492
- },
493
- })
492
+ }
494
493
 
495
- // Forward the first frame to the parser.
496
- executeRequestParser(requestParser, toBuffer(chunk))
497
- }
494
+ pendingRequestController = requestController
495
+
496
+ try {
497
+ await handleRequest(context)
498
+ } finally {
499
+ pendingRequestController = undefined
500
+ abortPendingRequest = undefined
501
+ }
502
+ },
503
+ })
498
504
 
499
- socket.on('data', onRequestData)
505
+ // Forward the first frame to the parser.
506
+ executeRequestParser(requestParser, toBuffer(chunk))
500
507
  }
501
508
 
502
- addRequestDataListener()
509
+ socket.on('data', onRequestData)
503
510
  socket.on('close', () => requestParser?.free())
504
511
  },
505
512
  {