Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(remix): Resolve Remix Request API compatibility issues. (#6215)
Ref: #6139 and #6139 (comment) This PR is not a complete solution for all issues mentioned in #6139, but it aims to solve the parsing issue, which should fix auto-instrumented usage. Remix uses its own implementation of Fetch API [1] on both back-end and front-end, which has different structures storing `headers` and `url`. That has caused `RequestData` integration to not work properly on Remix projects. I first attempted adding support to the core parser, and `PolymorphicRequest`. But it seemed very complex (if possible), because we have to type Proxy objects and Symbols, which our current TypeScript version doesn't fully support. So, I vendored / modified a couple of unexported utilities from `@remix-run/web-fetch` [2], to convert request objects to a compatible structure that we can consume in `RequestData` integration. [1] https://github.com/remix-run/web-std-io/tree/main/packages/fetch [2] https://www.npmjs.com/package/@remix-run/web-fetch
- Loading branch information
1 parent
fae0682
commit 0ee35f0
Showing
5 changed files
with
169 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Based on Remix's implementation of Fetch API | ||
// https://github.com/remix-run/web-std-io/tree/main/packages/fetch | ||
|
||
import { RemixRequest } from './types'; | ||
|
||
/* | ||
* Symbol extractor utility to be able to access internal fields of Remix requests. | ||
*/ | ||
const getInternalSymbols = ( | ||
request: Record<string, unknown>, | ||
): { | ||
bodyInternalsSymbol: string; | ||
requestInternalsSymbol: string; | ||
} => { | ||
const symbols = Object.getOwnPropertySymbols(request); | ||
return { | ||
bodyInternalsSymbol: symbols.find(symbol => symbol.toString().includes('Body internals')) as any, | ||
requestInternalsSymbol: symbols.find(symbol => symbol.toString().includes('Request internals')) as any, | ||
}; | ||
}; | ||
|
||
/** | ||
* Vendored from: | ||
* https://github.com/remix-run/web-std-io/blob/f715b354c8c5b8edc550c5442dec5712705e25e7/packages/fetch/src/utils/get-search.js#L5 | ||
*/ | ||
export const getSearch = (parsedURL: URL): string => { | ||
if (parsedURL.search) { | ||
return parsedURL.search; | ||
} | ||
|
||
const lastOffset = parsedURL.href.length - 1; | ||
const hash = parsedURL.hash || (parsedURL.href[lastOffset] === '#' ? '#' : ''); | ||
return parsedURL.href[lastOffset - hash.length] === '?' ? '?' : ''; | ||
}; | ||
|
||
/** | ||
* Convert a Request to Node.js http request options. | ||
* The options object to be passed to http.request | ||
* Vendored / modified from: | ||
* https://github.com/remix-run/web-std-io/blob/f715b354c8c5b8edc550c5442dec5712705e25e7/packages/fetch/src/request.js#L259 | ||
*/ | ||
export const normalizeRemixRequest = (request: RemixRequest): Record<string, any> => { | ||
const { requestInternalsSymbol, bodyInternalsSymbol } = getInternalSymbols(request); | ||
|
||
if (!requestInternalsSymbol) { | ||
throw new Error('Could not find request internals symbol'); | ||
} | ||
|
||
const { parsedURL } = request[requestInternalsSymbol]; | ||
const headers = new Headers(request[requestInternalsSymbol].headers); | ||
|
||
// Fetch step 1.3 | ||
if (!headers.has('Accept')) { | ||
headers.set('Accept', '*/*'); | ||
} | ||
|
||
// HTTP-network-or-cache fetch steps 2.4-2.7 | ||
let contentLengthValue = null; | ||
if (request.body === null && /^(post|put)$/i.test(request.method)) { | ||
contentLengthValue = '0'; | ||
} | ||
|
||
if (request.body !== null) { | ||
const totalBytes = request[bodyInternalsSymbol].size; | ||
// Set Content-Length if totalBytes is a number (that is not NaN) | ||
if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) { | ||
contentLengthValue = String(totalBytes); | ||
} | ||
} | ||
|
||
if (contentLengthValue) { | ||
headers.set('Content-Length', contentLengthValue); | ||
} | ||
|
||
// HTTP-network-or-cache fetch step 2.11 | ||
if (!headers.has('User-Agent')) { | ||
headers.set('User-Agent', 'node-fetch'); | ||
} | ||
|
||
// HTTP-network-or-cache fetch step 2.15 | ||
if (request.compress && !headers.has('Accept-Encoding')) { | ||
headers.set('Accept-Encoding', 'gzip,deflate,br'); | ||
} | ||
|
||
let { agent } = request; | ||
|
||
if (typeof agent === 'function') { | ||
agent = agent(parsedURL); | ||
} | ||
|
||
if (!headers.has('Connection') && !agent) { | ||
headers.set('Connection', 'close'); | ||
} | ||
|
||
// HTTP-network fetch step 4.2 | ||
// chunked encoding is handled by Node.js | ||
const search = getSearch(parsedURL); | ||
|
||
// Manually spread the URL object instead of spread syntax | ||
const requestOptions = { | ||
path: parsedURL.pathname + search, | ||
pathname: parsedURL.pathname, | ||
hostname: parsedURL.hostname, | ||
protocol: parsedURL.protocol, | ||
port: parsedURL.port, | ||
hash: parsedURL.hash, | ||
search: parsedURL.search, | ||
// @ts-ignore - it does not has a query | ||
query: parsedURL.query, | ||
href: parsedURL.href, | ||
method: request.method, | ||
// @ts-ignore - not sure what this supposed to do | ||
headers: headers[Symbol.for('nodejs.util.inspect.custom')](), | ||
insecureHTTPParser: request.insecureHTTPParser, | ||
agent, | ||
|
||
// [SENTRY] For compatibility with Sentry SDK RequestData parser, adding `originalUrl` property. | ||
originalUrl: parsedURL.href, | ||
}; | ||
|
||
return requestOptions; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters