mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-08 12:57:46 +00:00
[cli][dev] handle no response from edge functions (#8095)
When an edge function has no response during `vc dev`, we were seeing an unhelpful error message: > The event listener did not respond. Now, we'll see a much more specific error message: > Unhandled rejection: Edge Function "api/edge-no-response.ts" did not return a response. > Error! Failed to complete request to /api/edge-no-response: Error: socket hang up
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
export const config = {
|
||||||
|
runtime: 'experimental-edge',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function edge(request, event) {
|
||||||
|
// nothing returned
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function serverless(request, response) {
|
||||||
|
return response.send('hello from a serverless function');
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const config = {
|
||||||
|
runtime: 'experimental-edge',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function edge(request, event) {
|
||||||
|
// no response
|
||||||
|
}
|
||||||
@@ -56,6 +56,31 @@ test(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('[vercel dev] throws an error when an edge function has no response', async () => {
|
||||||
|
const dir = fixture('edge-function-error');
|
||||||
|
const { dev, port, readyResolver } = await testFixture(dir);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await readyResolver;
|
||||||
|
|
||||||
|
let res = await fetch(`http://localhost:${port}/api/edge-no-response`);
|
||||||
|
validateResponseHeaders(res);
|
||||||
|
|
||||||
|
const { stdout, stderr } = await dev.kill('SIGTERM');
|
||||||
|
|
||||||
|
expect(await res.status).toBe(500);
|
||||||
|
expect(await res.text()).toMatch('FUNCTION_INVOCATION_FAILED');
|
||||||
|
expect(stdout).toMatch(
|
||||||
|
/Unhandled rejection: Edge Function "api\/edge-no-response.js" did not return a response./g
|
||||||
|
);
|
||||||
|
expect(stderr).toMatch(
|
||||||
|
/Failed to complete request to \/api\/edge-no-response: Error: socket hang up/g
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await dev.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('[vercel dev] should support edge functions returning intentional 500 responses', async () => {
|
test('[vercel dev] should support edge functions returning intentional 500 responses', async () => {
|
||||||
const dir = fixture('edge-function');
|
const dir = fixture('edge-function');
|
||||||
const { dev, port, readyResolver } = await testFixture(dir);
|
const { dev, port, readyResolver } = await testFixture(dir);
|
||||||
|
|||||||
@@ -442,6 +442,17 @@ test(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'[vercel dev] Middleware that has no response',
|
||||||
|
testFixtureStdio('middleware-no-response', async (testPath: any) => {
|
||||||
|
await testPath(
|
||||||
|
500,
|
||||||
|
'/api/hello',
|
||||||
|
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'[vercel dev] Middleware that does basic rewrite',
|
'[vercel dev] Middleware that does basic rewrite',
|
||||||
testFixtureStdio('middleware-rewrite', async (testPath: any) => {
|
testFixtureStdio('middleware-rewrite', async (testPath: any) => {
|
||||||
|
|||||||
@@ -148,21 +148,26 @@ async function serializeRequest(message: IncomingMessage) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compileUserCode(entrypoint: string) {
|
async function compileUserCode(
|
||||||
|
entrypointPath: string,
|
||||||
|
entrypointLabel: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const result = await esbuild.build({
|
const result = await esbuild.build({
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
target: 'node14',
|
target: 'node14',
|
||||||
sourcemap: 'inline',
|
sourcemap: 'inline',
|
||||||
bundle: true,
|
bundle: true,
|
||||||
entryPoints: [entrypoint],
|
entryPoints: [entrypointPath],
|
||||||
write: false, // operate in memory
|
write: false, // operate in memory
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
});
|
});
|
||||||
|
|
||||||
const compiledFile = result.outputFiles?.[0];
|
const compiledFile = result.outputFiles?.[0];
|
||||||
if (!compiledFile) {
|
if (!compiledFile) {
|
||||||
throw new Error(`Compilation of ${entrypoint} produced no output files.`);
|
throw new Error(
|
||||||
|
`Compilation of ${entrypointLabel} produced no output files.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCode = new TextDecoder().decode(compiledFile.contents);
|
const userCode = new TextDecoder().decode(compiledFile.contents);
|
||||||
@@ -198,6 +203,10 @@ async function compileUserCode(entrypoint: string) {
|
|||||||
|
|
||||||
let response = await edgeHandler(event.request, event);
|
let response = await edgeHandler(event.request, event);
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Edge Function "${entrypointLabel}" did not return a response.');
|
||||||
|
}
|
||||||
|
|
||||||
return event.respondWith(response);
|
return event.respondWith(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// we can't easily show a meaningful stack trace
|
// we can't easily show a meaningful stack trace
|
||||||
@@ -252,9 +261,10 @@ async function createEdgeRuntime(userCode: string | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createEdgeEventHandler(
|
async function createEdgeEventHandler(
|
||||||
entrypoint: string
|
entrypointPath: string,
|
||||||
|
entrypointLabel: string
|
||||||
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
|
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
|
||||||
const userCode = await compileUserCode(entrypoint);
|
const userCode = await compileUserCode(entrypointPath, entrypointLabel);
|
||||||
const server = await createEdgeRuntime(userCode);
|
const server = await createEdgeRuntime(userCode);
|
||||||
|
|
||||||
return async function (request: IncomingMessage) {
|
return async function (request: IncomingMessage) {
|
||||||
@@ -317,17 +327,17 @@ async function createEventHandler(
|
|||||||
config: Config,
|
config: Config,
|
||||||
options: { shouldAddHelpers: boolean }
|
options: { shouldAddHelpers: boolean }
|
||||||
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
|
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
|
||||||
const entryPointPath = join(process.cwd(), entrypoint!);
|
const entrypointPath = join(process.cwd(), entrypoint!);
|
||||||
const runtime = parseRuntime(entrypoint, entryPointPath);
|
const runtime = parseRuntime(entrypoint, entrypointPath);
|
||||||
|
|
||||||
// `middleware.js`/`middleware.ts` file is always run as
|
// `middleware.js`/`middleware.ts` file is always run as
|
||||||
// an Edge Function, otherwise needs to be opted-in via
|
// an Edge Function, otherwise needs to be opted-in via
|
||||||
// `export const config = { runtime: 'experimental-edge' }`
|
// `export const config = { runtime: 'experimental-edge' }`
|
||||||
if (config.middleware === true || runtime === 'experimental-edge') {
|
if (config.middleware === true || runtime === 'experimental-edge') {
|
||||||
return createEdgeEventHandler(entryPointPath);
|
return createEdgeEventHandler(entrypointPath, entrypoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createServerlessEventHandler(entryPointPath, options);
|
return createServerlessEventHandler(entrypointPath, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
let handleEvent: (request: IncomingMessage) => Promise<VercelProxyResponse>;
|
let handleEvent: (request: IncomingMessage) => Promise<VercelProxyResponse>;
|
||||||
|
|||||||
Reference in New Issue
Block a user