-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeError: Body is unusable when using Remix(experimental-netlify-edge) Actions #3003
Comments
As discussed on Discord, the reason It's still unclear to me why this is only happening with Netlify Edge functions, but works on other platforms that support edge functions. Maybe the ReadableStream implemented on Deno is different than V8s in terms of expected behaviour? The solution I had proposed was to do a request clone where the issue occurs, but as @mcansh mentioned on Discord, there used to be a |
I'm going to continue to look into this for the time being. |
I updated the initial message as I had misunderstood the flow of the error.
It's still not clear to me what the issue is as the same thing works with regular Netlify functions. In let matchesToLoad = matches || [];
if (appState.catch) {
matchesToLoad = getMatchesUpToDeepestBoundary(matchesToLoad.slice(0, -1), "CatchBoundary");
} else if (appState.error) {
matchesToLoad = getMatchesUpToDeepestBoundary(matchesToLoad.slice(0, -1), "ErrorBoundary");
}
console.log('matches to load')
console.dir(matchesToLoad)
let routeLoaderResults = await Promise.allSettled(matchesToLoad.map((match) => match.route.module.loader ? callRouteLoader({
loadContext,
match,
request
}) : Promise.resolve(void 0))); For Edge functions the matches to load are the following: matches to load
and when those Promises have settled, they result in our error in one of the entries
For regular Netlify functions, as mentioned there is no error. The matches to load are the following:
and when the promises have settled, no errors:
The flow in the code from what I can tell is the same whether it's Edge functions or Netlify functions, so I'm still not sure what's up. In actionResponse = await data.callRouteAction({
loadContext,
match: actionMatch,
request: request
}); it will call the action, i.e. result = await action({
request: stripDataParam(stripIndexParam(request)),
context: loadContext,
params: match.params
}); see https://github.com/remix-run/remix/blob/main/packages/remix-server-runtime/data.ts#L35-L39 For Netlify functions I'm going to chat with @ascorbic next week some more about this and will post more findings here. Here is the source code that I've been using to debug this, https://github.com/nickytonline/debug-remix-netlify-edge-from |
Alright, so I did some more digging @mcansh and it turns out it's throwing in deno because deno is spec compliant for Below is what happens, but I have a repository you can check out for yourself, https://github.com/nickytonline/remix-node-fetch-request-vs-deno-request-bug-repro I'm simulating what this bit of code does: function stripIndexParam(request) {
let url = new URL(request.url);
let indexValues = url.searchParams.getAll("index");
url.searchParams.delete("index");
let indexValuesToKeep = [];
for (let indexValue of indexValues) {
if (indexValue) {
indexValuesToKeep.push(indexValue);
}
}
for (let toKeep of indexValuesToKeep) {
url.searchParams.append("index", toKeep);
}
return new Request(url.href, request);
}
function stripDataParam(request) {
let url = new URL(request.url);
url.searchParams.delete("_data");
return new Request(url.href, request);
}
async function callRouteAction({
loadContext,
match,
request
}) {
...
let result;
try {
result = await action({
request: stripDataParam(stripIndexParam(request)),
context: loadContext,
params: match.params
});
} catch (error) {
if (!isResponse(error)) {
throw error;
}
...
} As soon as that request is used later, Clear skies in Node.js with node-fetch /** Run node index.js and you should see the following output:
body used: false
{ json: { message: 'Hello world!' } }
body used: false
{ json: { message: 'Hello world!' } }
*/
(async () => {
const { default: fetch, Request} = await import('node-fetch');
const firstRequest = new Request("https://post.deno.dev", {
method: "POST",
body: JSON.stringify({
message: "Hello world!",
}),
headers: {
"content-type": "application/json",
},
});
const secondRequest = new Request('https://post.deno.dev', firstRequest);
try {
console.log(`body used: ${firstRequest.bodyUsed}`);
const firstResponse = await fetch(firstRequest);
const firstJson = await firstResponse.json();
console.log(firstJson);
console.log(`body used: ${firstRequest.bodyUsed}`);
const secondResponse = await fetch(secondRequest);
const secondJson = await secondResponse.json(); // No boom. All good because secondResponse.bodyUsed is false.
console.log(secondJson);
} catch (error) {
console.error(error);
}
})(); Rough waters as expected in specs compliant land with deno and the native Request object. /** Run deno run --allow-all --unstable ./index.ts and you should see the following output:
body used: false
{ json: { message: "Hello world!" } }
body used: true
TypeError: Input request's body is unusable.
at new Request (deno:ext/fetch/23_request.js:325:17)
at deno:ext/fetch/26_fetch.js:422:29
at new Promise (<anonymous>)
at fetch (deno:ext/fetch/26_fetch.js:418:20)
at file:///Users/nicktaylor/dev/deno-request-demo/index.ts:18:34
*/
const firstRequest = new Request("https://post.deno.dev", {
method: "POST",
body: JSON.stringify({
message: "Hello world!",
}),
headers: {
"content-type": "application/json",
},
});
const secondRequest = new Request('https://post.deno.dev', firstRequest);
try {
console.log(`body used: ${firstRequest.bodyUsed}`)
const firstResponse = await fetch(firstRequest)
const firstJson = await firstResponse.json()
console.log(firstJson)
console.log(`body used: ${firstRequest.bodyUsed}`)
const secondResponse = await fetch(secondRequest)
const secondJson = await secondResponse.json() // 💥 boom!
console.log(secondJson)
} catch (error) {
console.error(error);
} |
Since we don't want to clone because of #1766, I'm curious how we should proceed. Happy to help with this still. |
hey @nickytonline, thanks so much for digging into this!! we're in the process of swapping node-fetch for edit: over there, i get the following body used: false
{ json: { message: 'Hello world!' } }
body used: false
TypeError [ERR_INVALID_STATE]: Invalid state: ReadableStream is locked and cloning the const secondRequest = new Request("https://post.deno.dev", firstRequest.clone());
//=> body used: false
//=> { json: { message: 'Hello world!' } }
//=> body used: false
//=> { json: { message: 'Hello world!' } } |
Great detective work 🕵🏻 ! As Deno and newer versions of Node support fetch natively, could we use the native version in those cases instead of the polyfill? |
interestingly enough without any cloning it works https://netlify-thinks-mcansh-is-great.netlify.app/ https://github.com/mcansh/gdshfd-sjfbdsh i'll verify again in the morning after the nightly goes out |
fixed by #3207 |
Woohoo! Nice work @jacob-ebey! 🔥 |
Writing this for others who will be coming here to for this error I had the same issue while using react-hook-form. The issue with me was that I was doing this.
these two lines used together throws this error So, to fix this, I just removed any one of them, and it's fixed. I don't know why it's like this, If anyone knows, I would love to know |
What version of Remix are you using?
0.0.0-experimental-fd9fa7f4
Steps to Reproduce
npx create-remix --template https://github.com/netlify/remix-edge-template
. Use the default answers when the Remix CLI asks you questions. Note: You can create a TypeScript project (default) or a JavaScript project. It doesn't matter, the result is the same in regards to the error./app/routes/index.js
with the following code:ntl dev
to start the application in development mode.To test out the action, use a tool like Postman or Thunder Client (if using VS Code).
Create a
POST
for the URL to http://localhost:3000/?index. Pass in some form dataThis is how it would look in Postman
ntl dev
, you'll see the following.There is also a discussion in Discord for more context if need be. See https://discord.com/channels/770287896669978684/778004294673760266/968514769223049326
Expected Behavior
No errors should occur when a Remix action is called, e.g. a POST to http://localhost:3000/?index when Netlify Edge functions are enabled.
Actual Behavior
An error occurs when a Remix action is called, e.g. a POST to http://localhost:3000/?index when Netlify Edge functions are enabled.
The text was updated successfully, but these errors were encountered: