Skip to content

Commit 265af2e

Browse files
authored
fix: add getRawRequest utility (#719)
1 parent b814cfe commit 265af2e

File tree

6 files changed

+164
-4
lines changed

6 files changed

+164
-4
lines changed

src/getRawRequest.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const kRawRequest = Symbol('kRawRequest')
2+
3+
/**
4+
* Returns a raw request instance associated with this request.
5+
*
6+
* @example
7+
* interceptor.on('request', ({ request }) => {
8+
* const rawRequest = getRawRequest(request)
9+
*
10+
* if (rawRequest instanceof http.ClientRequest) {
11+
* console.log(rawRequest.rawHeaders)
12+
* }
13+
* })
14+
*/
15+
export function getRawRequest(request: Request): unknown | undefined {
16+
return Reflect.get(request, kRawRequest)
17+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { createRequestId } from './createRequestId'
77
export { getCleanUrl } from './utils/getCleanUrl'
88
export { encodeBuffer, decodeBuffer } from './utils/bufferUtils'
99
export { FetchResponse } from './utils/fetchUtils'
10+
export { getRawRequest } from './getRawRequest'

src/interceptors/ClientRequest/MockHttpSocket.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { createServerErrorResponse } from '../../utils/responseUtils'
1616
import { createRequestId } from '../../createRequestId'
1717
import { getRawFetchHeaders } from './utils/recordRawHeaders'
1818
import { FetchResponse } from '../../utils/fetchUtils'
19+
import { kRawRequest } from '../../getRawRequest'
1920

2021
type HttpConnectionOptions = any
2122

@@ -514,6 +515,7 @@ export class MockHttpSocket extends MockSocket {
514515
})
515516

516517
Reflect.set(this.request, kRequestId, requestId)
518+
Reflect.set(this.request, kRawRequest, Reflect.get(this, '_httpMessage'))
517519

518520
// Skip handling the request that's already being handled
519521
// by another (parent) interceptor. For example, XMLHttpRequest

src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { createResponse } from './utils/createResponse'
1515
import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
1616
import { createRequestId } from '../../createRequestId'
1717
import { getBodyByteLength } from './utils/getBodyByteLength'
18+
import { kRawRequest } from '../../getRawRequest'
1819

1920
const kIsRequestHandled = Symbol('kIsRequestHandled')
2021
const IS_NODE = isNodeProcess()
@@ -698,6 +699,7 @@ export class XMLHttpRequestController {
698699
},
699700
})
700701
define(fetchRequest, 'headers', proxyHeaders)
702+
Reflect.set(fetchRequest, kRawRequest, this.request)
701703

702704
this.logger.info('converted request to a Fetch API Request!', fetchRequest)
703705

src/interceptors/fetch/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { followFetchRedirect } from './utils/followRedirect'
1212
import { decompressResponse } from './utils/decompression'
1313
import { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'
1414
import { FetchResponse } from '../../utils/fetchUtils'
15+
import { kRawRequest } from '../../getRawRequest'
1516

1617
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
1718
static symbol = Symbol('fetch')
@@ -45,13 +46,18 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
4546
typeof input === 'string' &&
4647
typeof location !== 'undefined' &&
4748
!canParseUrl(input)
48-
? new URL(
49-
input,
50-
location.href
51-
)
49+
? new URL(input, location.href)
5250
: input
5351

5452
const request = new Request(resolvedInput, init)
53+
54+
/**
55+
* @note Set the raw request only if a Request instance was provided to fetch.
56+
*/
57+
if (input instanceof Request) {
58+
Reflect.set(request, kRawRequest, input)
59+
}
60+
5561
const responsePromise = new DeferredPromise<Response>()
5662
const controller = new RequestController(request)
5763

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// @vitest-environment jsdom
2+
import { it, expect, afterAll, afterEach, beforeAll } from 'vitest'
3+
import http from 'node:http'
4+
import { DeferredPromise } from '@open-draft/deferred-promise'
5+
import { BatchInterceptor, getRawRequest } from '../../src'
6+
import { ClientRequestInterceptor } from '../../src/interceptors/ClientRequest'
7+
import { XMLHttpRequestInterceptor } from '../../src/interceptors/XMLHttpRequest'
8+
import { FetchInterceptor } from '../../src/interceptors/fetch'
9+
import { createXMLHttpRequest } from '../helpers'
10+
11+
const interceptor = new BatchInterceptor({
12+
name: 'batch-interceptor',
13+
interceptors: [
14+
new ClientRequestInterceptor(),
15+
new XMLHttpRequestInterceptor(),
16+
new FetchInterceptor(),
17+
],
18+
})
19+
20+
beforeAll(() => {
21+
interceptor.apply()
22+
})
23+
24+
afterEach(() => {
25+
interceptor.removeAllListeners()
26+
})
27+
28+
afterAll(() => {
29+
interceptor.dispose()
30+
})
31+
32+
it('returns a reference to a raw http.ClientRequest instance', async () => {
33+
const rawRequestPromise = new DeferredPromise<http.ClientRequest>()
34+
35+
interceptor.on('request', ({ request, controller }) => {
36+
const rawRequest = getRawRequest(request)
37+
if (rawRequest instanceof http.ClientRequest) {
38+
rawRequestPromise.resolve(rawRequest)
39+
} else {
40+
console.error(rawRequest)
41+
rawRequestPromise.reject(
42+
new Error('Expected rawRequest to be an instance of http.ClientRequest')
43+
)
44+
}
45+
46+
controller.respondWith(new Response())
47+
})
48+
49+
http
50+
.request('http://localhost', {
51+
headers: {
52+
'X-CustoM-HeadeR-NamE': 'value',
53+
},
54+
})
55+
.end()
56+
57+
const rawRequest = await rawRequestPromise
58+
expect(rawRequest).toBeInstanceOf(http.ClientRequest)
59+
expect(rawRequest.getRawHeaderNames()).toContain('X-CustoM-HeadeR-NamE')
60+
})
61+
62+
it('returns a reference to a raw XMLHttpRequest instance', async () => {
63+
const rawRequestPromise = new DeferredPromise<XMLHttpRequest>()
64+
interceptor.on('request', ({ request, controller }) => {
65+
const rawRequest = getRawRequest(request)
66+
67+
if (rawRequest instanceof XMLHttpRequest) {
68+
rawRequestPromise.resolve(rawRequest)
69+
} else {
70+
console.error(rawRequest)
71+
rawRequestPromise.reject(
72+
new Error('Expected rawRequest to be an instance of XMLHttpRequest')
73+
)
74+
}
75+
76+
controller.respondWith(new Response('hello world'))
77+
})
78+
79+
await createXMLHttpRequest((request) => {
80+
request.open('GET', 'http://localhost:3000')
81+
request.withCredentials = true
82+
request.send()
83+
})
84+
85+
const rawRequest = await rawRequestPromise
86+
expect(rawRequest).toBeInstanceOf(XMLHttpRequest)
87+
expect(rawRequest.withCredentials).toBe(true)
88+
expect(rawRequest.responseText).toBe('hello world')
89+
})
90+
91+
it('returns a reference to a raw Request instance (fetch)', async () => {
92+
const rawRequestPromise = new DeferredPromise<Request>()
93+
interceptor.on('request', ({ request, controller }) => {
94+
const rawRequest = getRawRequest(request)
95+
96+
if (rawRequest instanceof Request) {
97+
rawRequestPromise.resolve(rawRequest)
98+
} else {
99+
console.error(rawRequest)
100+
rawRequestPromise.reject(
101+
new Error('Expected rawRequest to be an instance of XMLHttpRequest')
102+
)
103+
}
104+
105+
controller.respondWith(new Response('hello world'))
106+
})
107+
108+
const request = new Request('http://localhost:3000')
109+
await fetch(request)
110+
111+
const rawRequest = await rawRequestPromise
112+
expect(rawRequest).toEqual(request)
113+
})
114+
115+
it('returns undefined for a non-Request input (fetch)', async () => {
116+
const rawRequestPromise = new DeferredPromise<undefined>()
117+
interceptor.on('request', ({ request, controller }) => {
118+
const rawRequest = getRawRequest(request)
119+
120+
if (typeof rawRequest === 'undefined') {
121+
rawRequestPromise.resolve(rawRequest)
122+
} else {
123+
console.error(rawRequest)
124+
rawRequestPromise.reject(new Error('Expected rawRequest to be undefined'))
125+
}
126+
127+
controller.respondWith(new Response('hello world'))
128+
})
129+
130+
await fetch('http://localhost:3000')
131+
await expect(rawRequestPromise).resolves.toBeUndefined()
132+
})

0 commit comments

Comments
 (0)