Fetch如何检测请求是否来自浏览器,使用相对路径。

huangapple go评论79阅读模式
英文:

How does fetch detect if a request using a relative path is coming from a browser?

问题

以下是你要翻译的内容:

"I am making a request from a react app using a relative path "/api/test". (an NGINX controller routes traffic to either the api or the react client, so they have the same base url)

I found in the msw documentation that relative paths are supported.

// Given your application runs on "http://localhost:8080",
// this request handler URL gets resolved to "http://localhost:8080/invoices"
rest.get('/invoices', invoicesResolver)

Note that relative URLs are resolved against the current location (location.origin).

If I console.log(window.location.origin), it prints http://localhost:3000. I think that the origin is being set because of import '@testing-library/jest-dom'.

However, if I make a fetch call during testing (msw is mocking api calls), I get a TypeError: Invalid URL: /api/test.

fetch('/api/test')
    .then((response) => response.json())
    .then((data) => console.log(data))
Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
TypeError: Failed to parse URL from /api/test
❯ new Request node:internal/deps/undici/undici:9474:19
❯ FetchInterceptor.<anonymous> node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:42:23
❯ step node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:59:23
❯ Object.next node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:40:53
❯ node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:34:71
❯ __awaiter node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:30:12
❯ globalThis.fetch node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:41:42
❯ App src/App.tsx:9:3
  7|
  8| function App() {
  9|   fetch('/api/test')
   |   ^
  10|     .then((response) => response.json())
  11|     .then((data) => console.log(data))
❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:16305:18

This error originated in "src/App.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
Caused by: TypeError: Invalid URL: /api/test
❯ new URLImpl node_modules/whatwg-url/lib/URL-impl.js:21:13
❯ Object exports.setup node_modules/whatwg-url/lib/URL.js:54:12
❯ new URL node_modules/whatwg-url/lib/URL.js:115:22
❯ new Request node:internal/deps/undici/undici:9472:25
❯ FetchInterceptor.<anonymous> node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:42:23
❯ step node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:59:23
❯ Object.next node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:40:53
❯ node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:34:71
❯ __awaiter node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:30:12

请注意,我已经省略掉了代码部分,只翻译了文本内容。

英文:

I am making a request from a react app using a relative path "/api/test". (an NGINX controller routes traffic to either the api or the react client, so they have the same base url)

I found in the msw documentation that relative paths are supported.

// Given your application runs on "http://localhost:8080",
// this request handler URL gets resolved to "http://localhost:8080/invoices"
rest.get('/invoices', invoicesResolver)
Note that relative URL are resolved against the current location (location.origin).

If I console.log(window.location.origin), it prints http://localhost:3000. I think that the origin is being set because of import '@testing-library/jest-dom'.

However, if I make a fetch call during testing (msw is mocking api calls), I get a TypeError: Invalid URL: /api/test.

fetch('/api/test')
    .then((response) => response.json())
    .then((data) => console.log(data))
Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
TypeError: Failed to parse URL from /api/test
 ❯ new Request node:internal/deps/undici/undici:9474:19
 ❯ FetchInterceptor.<anonymous> node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:42:23
 ❯ step node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:59:23
 ❯ Object.next node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:40:53
 ❯ node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:34:71
 ❯ __awaiter node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:30:12
 ❯ globalThis.fetch node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:41:42
 ❯ App src/App.tsx:9:3
      7|
      8| function App() {
      9|   fetch('/api/test')
       |   ^
     10|     .then((response) => response.json())
     11|     .then((data) => console.log(data))
 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:16305:18

This error originated in "src/App.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
Caused by: TypeError: Invalid URL: /api/test
 ❯ new URLImpl node_modules/whatwg-url/lib/URL-impl.js:21:13
 ❯ Object.exports.setup node_modules/whatwg-url/lib/URL.js:54:12
 ❯ new URL node_modules/whatwg-url/lib/URL.js:115:22
 ❯ new Request node:internal/deps/undici/undici:9472:25
 ❯ FetchInterceptor.<anonymous> node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:42:23
 ❯ step node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:59:23
 ❯ Object.next node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:40:53
 ❯ node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:34:71
 ❯ __awaiter node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:30:12

When I run the code in the browser, there are no issues. Given that document.baseURI and window.location.origin are set, how does fetch "know" it is not running in a browser? Is it possible to avoid this error by setting another field in window?

答案1

得分: 2

The global fetch function in one runtime might not implement the same relative URL resolution behavior as another — especially given implicit global state (such as globalThis.window), and the fact that some environments mock the function or replace it with a custom proxy.

In order to implement deterministic resolution, you can provide a fully-qualified address at the time of invocation — this will guarantee reliable behavior across environments. Node.js includes the same URL class that browsers do, and you can use it to construct a fully-qualified address.

In the question details you state that window.location.origin is set to a valid URL origin string, so you can use it to resolve URLs. Below is an example that will work in Node.js and in a browser — you can run it in Node.js, or you can copy + paste the code into your dev tools JS console on this page and run it to see the same output. (I'm not posting it as a code snippet because snippets on this site run at a different origin.)

./example.mjs:

/// <reference types="node" />

// According to your question, this is set elsewhere in your environment:
if (!globalThis.window) {
  globalThis.window = {
    location: {
      origin: "https://stackoverflow.com",
    },
  };
}

function resolveUrl(url) {
  return new URL(url, window.location.origin);
}

// A path relative to the current origin:
const url1 = resolveUrl(
  "/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse",
);

// Address at a separate origin:
const url2 = resolveUrl("https://developer.mozilla.org/en-US/docs/Web/API/URL");

console.log("URL 1:", url1.href); // https://stackoverflow.com/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse
console.log("URL 2:", url2.href); // https://developer.mozilla.org/en-US/docs/Web/API/URL

const response = await fetch(url1);

console.log({
  contentType: response.headers.get("Content-Type"), // text/html; charset=utf-8
  docType: (await response.text()).slice(0, 15), // <!DOCTYPE html>
});

In Node.js:

% node --version      
v18.15.0

% node example.mjs
URL 1: https://stackoverflow.com/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse
URL 2: https://developer.mozilla.org/en-US/docs/Web/API/URL
{ contentType: 'text/html; charset=utf-8', docType: '<!DOCTYPE html>' }
英文:

Rather than try to grok environment and include special behavior...

The global fetch function in one runtime might not implement the same relative URL resolution behavior as another — especially given implicit global state (such as globalThis.window), and the fact that some environments mock the function or replace it with a custom proxy.

In order to implement deterministic resolution, you can provide a fully-qualified address at the time of invocation — this will guarantee reliable behavior across environments. Node.js includes the same URL class that browsers do, and you can use it to construct a fully-qualified address.

In the question details you state that window.location.origin is set to a valid URL origin string, so you can use it to resolve URLs. Below is an example that will work in Node.js and in a browser — you can run it in Node.js, or you can copy + paste the code into your dev tools JS console on this page and run it to see the same output. (I'm not posting it as a code snippet because snippets on this site run at a different origin.)

./example.mjs:

/// <reference types="node" />

// According to your question, this is set elsewhere in your environment:
if (!globalThis.window) {
  globalThis.window = {
    location: {
      origin: "https://stackoverflow.com",
    },
  };
}

function resolveUrl(url) {
  return new URL(url, window.location.origin);
}

// A path relative to the current origin:
const url1 = resolveUrl(
  "/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse",
);

// Address at a separate origin:
const url2 = resolveUrl("https://developer.mozilla.org/en-US/docs/Web/API/URL");

console.log("URL 1:", url1.href); // https://stackoverflow.com/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse
console.log("URL 2:", url2.href); // https://developer.mozilla.org/en-US/docs/Web/API/URL

const response = await fetch(url1);

console.log({
  contentType: response.headers.get("Content-Type"), // text/html; charset=utf-8
  docType: (await response.text()).slice(0, 15), // <!DOCTYPE html>
});

In Node.js:

% node --version      
v18.15.0

% node example.mjs
URL 1: https://stackoverflow.com/questions/75794309/how-does-fetch-detect-if-a-request-using-a-relative-path-is-coming-from-a-browse
URL 2: https://developer.mozilla.org/en-US/docs/Web/API/URL
{ contentType: 'text/html; charset=utf-8', docType: '<!DOCTYPE html>' }

huangapple
  • 本文由 发表于 2023年3月21日 03:03:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75794309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定