kit icon indicating copy to clipboard operation
kit copied to clipboard

Error when doing external fetch on Cloudflare Workers

Open xforce opened this issue 1 year ago • 5 comments

Describe the bug

Since https://github.com/sveltejs/kit/pull/6565 doing external fetch in a load function will fail with Failed to get the 'mode' property on 'Request' on Cloudflare Workers.

Reproduction

The simplest reproduction is simply having a load function like this

export async function load({ fetch }) {
  await fetch("https://google.com");
}

A full project here: https://github.com/xforce/sveltekit-cloudflare-error This requires are cloudflare account with workers enabled. run: npm run build && npx wrangler publish.

Logs

"outcome": "ok",
  "scriptName": "sveltekit-cloudflare-worker-error",
  "exceptions": [],
  "logs": [
    {
      "message": [
        "Error: Failed to get the 'mode' property on 'Request': the property is not implemented.\n    at fetch (worker.js:1748:24)\n    at async fetcher (worker.js:1735:22)\n    at async load (worker.js:597:3)\n    at async load_data (worker.js:2011:16)\n    at async worker.js:2823:18"
      ],
      "level": "error",
      "timestamp": 1662933225460
    }
  ],


### System Info

```shell
System:
    OS: Linux 5.19 Arch Linux
    CPU: (32) x64 AMD Ryzen 9 5950X 16-Core Processor
    Memory: 31.19 GB / 62.72 GB
    Container: Yes
    Shell: 3.5.1 - /bin/fish
  Binaries:
    Node: 18.9.0 - /bin/node
    Yarn: 1.22.19 - /bin/yarn
    npm: 8.19.1 - /bin/npm
  Browsers:
    Brave Browser: 105.1.43.89
    Chromium: 105.0.5195.102
    Firefox: 104.0.2
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.72
    @sveltejs/adapter-cloudflare-workers: ^1.0.0-next.54 => 1.0.0-next.54
    @sveltejs/kit: next => 1.0.0-next.480
    svelte: ^3.44.0 => 3.50.1
    vite: ^3.1.0 => 3.1.0

Severity

blocking an upgrade

Additional Information

This appears to come from this line https://github.com/sveltejs/kit/blob/4922e26856681fdfbe3afb1a31be0d7c7000283f/packages/kit/src/runtime/server/page/fetch.js#L90

xforce avatar Sep 11 '22 22:09 xforce

It looks like cloudflare does not support everything on RequestInit, here's what they have https://developers.cloudflare.com/workers//runtime-apis/request#requestinit mode and credentials are not on it

dominikg avatar Sep 12 '22 15:09 dominikg

Just accessing credentials (or mode i guess) throws an error, so even req.credentials !== 'omit' isn't working. It's not just undefined, you cannot use it at all in cloudflare at runtime. Unfortunately it doesn't throw an error while using it in miniflare.

https://github.com/sveltejs/kit/pull/6565/files#diff-3f12c17057d74a7d75f121baea53823d2ebfefa033e5cb834c05f6edca1a847dR81

dominikg avatar Sep 12 '22 16:09 dominikg

only workaround i found so far is reverting to 1.0.0-next.469 which is the release before #6565 was introduced (next.470).

dominikg avatar Sep 12 '22 16:09 dominikg

Adding an handleFetch in src/hooks.server.js fixes this bug quite awfully. But hey, it works! ¯\_(ツ)_/¯

....
export async function handleFetch({ request, fetch }) {
	// FIXES: https://github.com/sveltejs/kit/issues/6739
	const rekuest = {
		get(target: Request, prop: string) {
			if (['credentials', 'mode'].includes(prop)) {
				return '¯¯\\_(ツ)_//¯¯';
			}
			return target[prop];
		}
	};
	return fetch(new Proxy(request, rekuest));
}

PS: wrangler dev --local (powered by miniflare) was unable to detect this bug due its slightly different implementation of the Runtime spec. A fix has been committed to miniflare after reporting this behaviour (kudos to @mrbbot). Hopefully next miniflare release can detect custom behaviour of cloudflare's Request/Response objects.

denizkenan avatar Sep 13 '22 00:09 denizkenan

@denizkenan This workaround doesn't seem to work on Cloudflare Workers, the runtime fetch doesn't appear to treat the proxy as a Request object and does a toString(). Which then leads to fetch failing with Fetch API cannot load: [object Object]

xforce avatar Sep 13 '22 18:09 xforce

This is particularly hideous if you want to access a +server.js helper. With SSR, you have two choices:

  • use Svelte's internal fetch: triggers this bug
  • use global fetch: CF Workers can't reach their own URLs (I guess to avoid infinite recursion), the request 404s.

I'm not aware of any workaround, except downgrading / patching SvelteKit :-(

EDIT: This sucks extra for me personally, because I already have code depending on handleFetch being called for internal URLs. I can't downgrade easily.

tv42 avatar Sep 28 '22 20:09 tv42

I made a quick workaround for this. Here's the tl;dr:

- request.credentials !== 'omit'
+ ('cf' in request ? 'same-origin' : request.credentials) !== 'omit'

See https://github.com/tripoutside/sveltekit-cloudflare-workaround-issue6739/commit/2dcb3e3cc0c2178850e617ff5595d8042d91a408

npm i 'https://gitpkg.now.sh/tripoutside/sveltekit-cloudflare-workaround-issue6739/packages/kit?2dcb3e3cc0c2178850e617ff5595d8042d91a408'

(Jump through hoops due to https://github.com/npm/npm/issues/2974 and github tarballs always starting from root.)

tv42 avatar Sep 28 '22 23:09 tv42

My workaround was similar to @denizkenan - add src/hooks.server.js:

import type { HandleFetch } from "@sveltejs/kit";

type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
type RequestCredentials = "include" | "omit" | "same-origin";

class RequestFix extends Request {
    get mode(): RequestMode {
        return "cors";
    }

    get credentials(): RequestCredentials {
        return "same-origin";
    }
}

export const handleFetch: HandleFetch = async ({ request, fetch }) => {
    // FIXME: Hack for https://github.com/sveltejs/kit/issues/6739
    const req = new RequestFix(request);
    return fetch(req);
}

This works when publishing via wrangler, but not when using Pages' GitHub integration to have Cloudflare perform the build. This is using a different adapter, which I suspect explains the difference.

Deebster avatar Oct 03 '22 08:10 Deebster

My workaround was similar to @denizkenan - add src/hooks.server.js:

I've tried this and it does indeed work when using wrangler (miniflare) to run it locally. However it still errors when deployed to Cloudflare Pages, even when using wrangler.

The-Noah avatar Oct 07 '22 22:10 The-Noah