Open ido323 opened 1 year ago
Hi, @ido323. Thanks for bringing this up.
I agree with you, we shouldn't modify the outgoing requests. This means we should find a better way to implement the internal logic dependent on the X-Request-Id
request header.
The intention here is that in some environments, the request client can be implemented using multiple nested APIs. For example, when you construct an XMLHttpRequest
instance in JSDOM, you are constructing what is, effectively, a polyfill for that browser API. Underneath it, the polyfill uses the native http
module of Node.js to actually perform requests.
This library ships with both XHR and ClientRequest (http) interceptors. Moreover, the higher consumer, like MSW, is likely to use both of those interceptors at the same time. This means that whenever an XHR request happens in Node.js, it will trigger the XHR interceptor first and then the ClientRequest interceptor second (based on the invocation order of XHR -> http.ClientReuqest; this is not the library's specific behavior). When that happens, we will see the same request attempted to be handled twice, which is a mistake given that both interceptors will try to resolve the XHR request against a single request
event listener.
To prevent this, and halt the double resolution of XHR requests in Node.js, we've added an internal X-Request-Id
request header on the XHR requests. This way when the request is unhandled, it will bubble to the ClientRequest interceptor and will be skipped altogether based on the presence of that request header:
By design, the outgoing request should never have this internal header set since the XHR request journey will always be:
new XMLHttpRequest()
(XMLHttpRequestInterceptor)
new http.ClientRequest()
(ClientRequestInterceptor)X-Request-Id
request headerDo you have a reproduction repository for me to see when this issue occurs for you, @ido323?
@kettanaito Hello. I faced exactly the same problem.
I realized that X-Request-Id
is added to requests and not removed. Because of this x-request-id
in the header Access-Control-Request-Headers
. If the server does not support this header, then the request fails due to a CORS violation.
The problem is that the header X-Request-Id
is only removed in the ClientRequestInterceptor
but not in the XMLHttpRequestInterceptor
or FetchInterceptor
.
You can reproduce problem in this repo.
Run the following commands and open address http://localhost:5173
in browser
npm i
npm run dev
node server.js
As a result you can see that request to http://localhost:3000/get-user
failed with CORS error.
Access to XMLHttpRequest at 'http://localhost:3000/get-user' from origin 'http://localhost:5173' has been blocked by CORS policy: Request header field x-request-id is not allowed by Access-Control-Allow-Headers in preflight response.
Thanks for the input, @avivasyuta.
The problem is that the header X-Request-Id is only removed in the ClientRequestInterceptor but not in the XMLHttpRequestInterceptor or FetchInterceptor.
Yes, this is intentional. In the older versions of Node.js, various request clients could be implemented via the http
module. But what's more important, both fetch
(from third parties like node-fetch
) and XMLHttpRequest
(again, from third parties like jsdom
) were also implemented via the http
module. As we move forward and deprecate support for Node.js < 18, perhaps it's time we reconsider the necessity of the X-Request-Id
header.
I'm still unsure we can remove it completely due to the XMLHttpRequest
-> http.ClientRequest
relationship. Since the Interceptors only hooks at specific request points, and doesn't replace the entire request client (which is, again, intentional to retain the integrity of your code), then XHR implementations through http
will always trigger one interceptor through another. We need to design a different deduplication algorithm.
One of the things I considered was using Symbols on request instances. If an XHR request happens, the underlying http.ClientRequest
will also happen in the same process. This also implies that they will share global state as well as (I assume) request instance reference. Perhaps we can set some internal Symbol on the XMLHttpRequest
instance that would let the underlying ClientRequestInterceptor
know that it should dedupe the request (or the other way around).
@kettanaito Unfortunately, I have no idea how you can pass an identifier between xmlhttprequest and http instances, given that these are completely unrelated entities.
It seems to me that it makes sense to use the header entirely. In modern solutions, it is not necessary.
@avivasyuta, actually, they are not. XMLHttpRequest
is often implemented by http.ClientRequest
, which is very fortunate since we may try to find a way to let one know about the other.
@kettanaito However http.ClientRequest
is not present in the browser. As far as I understand, this deduplication mechanism should not work at all on the client. Alternatively, you can set the header X-Request-Id
only if the runtime is nodejs. What do you think?
We can easily check in XMLHttpRequestInterceptor
if we are in a browser. If we are, there's nothing to de-duplicate, so no need for the header, so we can skip setting it. Do you think that won't work?
We can easily check in
XMLHttpRequestInterceptor
if we are in a browser. If we are, there's nothing to de-duplicate, so no need for the header, so we can skip setting it. Do you think that won't work?
Yes. this is exactly what I mean. It should work.
I think we can simply make such logic. https://github.com/mswjs/interceptors/pull/397
@kettanaito Hello. It would be great if you can review my PR and make a release with bug fix.
AsyncLocalStorage in Node.js works great for this but it's not a universal algorithm. Interceptors are meant to be environment-agnostic. The deduping of events must work the same in Node.js and in the browser.
Since the request that hits multiple interceptors will happen in the same context by design, perhaps we can try using symbols on the request somehow to let the underlying interceptor know it should ignore it?
Affecting request headers, which is a user-defined input, is not nice.
Hi!
Thanks for your work. I appreciate the recent addition of the X-Request-Id header for each request. However, it seems to be causing a header CORS issue when the server doesn't allow it. Is there a way to make it optional or find an alternative solution without modifying the server? Your help would be greatly appreciated.
Link to the code:
https://github.com/mswjs/interceptors/blob/94f0369f315a264d5aca0c1b463f30734f41b8dd/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts#L173