Skip to content

Commit

Permalink
Fix multipart/form-data handling in the Edge runtime (vercel#49177)
Browse files Browse the repository at this point in the history
Adds proper `multipart/form-data` handling in the Edge runtime and fixes a bug (previously we are always using `serverActionsManifest.node`). For the form data, we just use `await webRequest.request.formData()` for now and will handle streaming later.
  • Loading branch information
shuding authored May 3, 2023
1 parent e7a2332 commit 0aac86e
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 20 deletions.
11 changes: 8 additions & 3 deletions packages/next/src/server/app-render/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
OutgoingHttpHeaders,
ServerResponse,
} from 'http'
import type { WebNextRequest } from '../base-http/web'

import {
ACTION,
Expand Down Expand Up @@ -189,7 +190,9 @@ export async function handleAction({
{
get: (_, id: string) => {
return {
id: serverActionsManifest.node[id].workers[workerName],
id: serverActionsManifest[
process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node'
][id].workers[workerName],
name: id,
chunks: [],
}
Expand All @@ -209,13 +212,15 @@ export async function handleAction({
// Use react-server-dom-webpack/server.edge
const { decodeReply } = ComponentMod

const webRequest = req as unknown as Request
const webRequest = req as unknown as WebNextRequest
if (!webRequest.body) {
throw new Error('invariant: Missing request body.')
}

if (isMultipartAction) {
throw new Error('invariant: Multipart form data is not supported.')
// TODO-APP: Add streaming support
const formData = await webRequest.request.formData()
bound = await decodeReply(formData, serverModuleMap)
} else {
let actionData = ''

Expand Down
8 changes: 1 addition & 7 deletions packages/next/src/server/web-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,13 +372,7 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {

if (curRenderToHTML) {
return await curRenderToHTML(
{
url: req.url,
method: req.method,
cookies: req.cookies,
headers: req.headers,
body: req.body,
} as any,
req as any,
res as any,
pathname,
query,
Expand Down
23 changes: 14 additions & 9 deletions test/e2e/app-dir/actions/app-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ createNextDescribe(

it('should support hoc auth wrappers', async () => {
const browser = await next.browser('/header')
await await browser.eval(`document.cookie = 'auth=0'`)
await browser.eval(`document.cookie = 'auth=0'`)

await browser.elementByCss('#authed').click()

Expand All @@ -130,7 +130,7 @@ createNextDescribe(
isNextDev ? 'Error: Unauthorized request' : GENERIC_RSC_ERROR
)

await await browser.eval(`document.cookie = 'auth=1'`)
await browser.eval(`document.cookie = 'auth=1'`)

await browser.elementByCss('#authed').click()

Expand Down Expand Up @@ -209,17 +209,22 @@ createNextDescribe(

it('should return error response for hoc auth wrappers in edge runtime', async () => {
const browser = await next.browser('/header/edge')
await await browser.eval(`document.cookie = 'auth=0'`)
await await browser.eval(`document.cookie = 'edge-auth=0'`)

await browser.elementByCss('#authed').click()

await check(
async () => {
const text = await browser.elementByCss('h1').text()
console.log('text', text)
return text && text.length > 0 ? text : 'failed'
},
isNextDev ? /Multipart form data is not supported/ : GENERIC_RSC_ERROR
() => browser.elementByCss('h1').text(),
isNextDev ? 'Error: Unauthorized request' : GENERIC_RSC_ERROR
)

await browser.eval(`document.cookie = 'edge-auth=1'`)

await browser.elementByCss('#authed').click()

await check(() => {
return browser.elementByCss('h1').text()
}, 'Prefix: HELLO, WORLD')
})
})

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/actions/app/header/edge/page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCookie, getHeader, setCookie } from '../actions'
import UI from '../ui'
import { validator } from '../validator'
import { validator } from './validator'

export default function Page() {
const prefix = 'Prefix:'
Expand Down
12 changes: 12 additions & 0 deletions test/e2e/app-dir/actions/app/header/edge/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cookies } from 'next/headers'

export function validator(action) {
return async function (arg) {
'use server'
const auth = cookies().get('edge-auth')
if (auth?.value !== '1') {
throw new Error('Unauthorized request')
}
return action(arg)
}
}

0 comments on commit 0aac86e

Please sign in to comment.