Skip to content

Commit

Permalink
Add a passThroughOnException() handler to Pages Functions (#2111)
Browse files Browse the repository at this point in the history
  • Loading branch information
GregBrimble authored Nov 2, 2022
1 parent 0d2d6bf commit ab52f77
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 4 deletions.
19 changes: 19 additions & 0 deletions .changeset/dirty-gifts-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"wrangler": patch
---

feat: Add a `passThroughOnException()` handler in Pages Functions

This `passThroughOnException()` handler is not as good as the built-in for Workers. We're just adding it now as a stop-gap until we can do the behind-the-scenes plumbing required to make the built-in function work properly.

We wrap your Pages Functions code in a `try/catch` and on failure, if you call `passThroughOnException()` we defer to the static assets of your project.

For example:

```ts
export const onRequest = ({ passThroughOnException }) => {
passThroughOnException();

x; // Would ordinarily throw an error, but instead, static assets are served.
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const onRequest = ({ passThroughOnException, next }) => {
passThroughOnException();

return next();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const onRequest = ({ passThroughOnException }) => {
x;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const onRequest = ({ passThroughOnException }) => {
x;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const onRequest = ({ passThroughOnException }) => {
passThroughOnException();

x;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const onRequest = async ({ request, passThroughOnException, next }) => {
passThroughOnException();

try {
return await next();
} catch (e) {
if (new URL(request.url).searchParams.has("catch")) {
return new Response(`Manually caught error: ${e}`);
}

throw e;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const onRequest = ({ passThroughOnException }) => {
x;
};
47 changes: 47 additions & 0 deletions fixtures/pages-functions-app/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,51 @@ describe("Pages Functions", () => {
expect(response.headers.get("A-Header")).toEqual("New-Value");
});
});

describe("passThroughOnException", () => {
it("works on a single handler", async () => {
const response = await waitUntilReady(
"http://localhost:8789/passThroughOnExceptionOpen"
);

expect(response.status).toEqual(200);
expect(await response.text()).toContain("Hello, world!");
});

it("defaults closed", async () => {
const response = await waitUntilReady(
"http://localhost:8789/passThroughOnExceptionClosed"
);

expect(response.status).toEqual(500);
expect(await response.text()).not.toContain("Hello, world!");
});

it("works for nested handlers", async () => {
const response = await waitUntilReady(
"http://localhost:8789/passThroughOnException/nested"
);

expect(response.status).toEqual(200);
expect(await response.text()).toContain("Hello, world!");
});

it("allows errors to still be manually caught in middleware", async () => {
let response = await waitUntilReady(
"http://localhost:8789/passThroughOnExceptionWithCapture/nested"
);

expect(response.status).toEqual(200);
expect(await response.text()).toContain("Hello, world!");

response = await waitUntilReady(
"http://localhost:8789/passThroughOnExceptionWithCapture/nested?catch"
);

expect(response.status).toEqual(200);
expect(await response.text()).toMatchInlineSnapshot(
`"Manually caught error: ReferenceError: x is not defined"`
);
});
});
});
4 changes: 4 additions & 0 deletions packages/wrangler/templates/pages-template-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type EventContext<Env, P extends string, Data> = {
request: Request;
functionPath: string;
waitUntil: (promise: Promise<unknown>) => void;
passThroughOnException: () => void;
next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
env: Env & { ASSETS: { fetch: typeof fetch } };
params: Params<P>;
Expand All @@ -29,6 +30,7 @@ type EventPluginContext<Env, P extends string, Data, PluginArgs> = {
request: Request;
functionPath: string;
waitUntil: (promise: Promise<unknown>) => void;
passThroughOnException: () => void;
next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
env: Env & { ASSETS: { fetch: typeof fetch } };
params: Params<P>;
Expand Down Expand Up @@ -146,6 +148,8 @@ export default function (pluginArgs) {
pluginArgs,
env,
waitUntil: workerContext.waitUntil.bind(workerContext),
passThroughOnException:
workerContext.passThroughOnException.bind(workerContext),
};

const response = await handler(context);
Expand Down
25 changes: 21 additions & 4 deletions packages/wrangler/templates/pages-template-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type EventContext<Env, P extends string, Data> = {
request: Request;
functionPath: string;
waitUntil: (promise: Promise<unknown>) => void;
passThroughOnException: () => void;
next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
env: Env & { ASSETS: { fetch: typeof fetch } };
params: Params<P>;
Expand Down Expand Up @@ -53,6 +54,7 @@ type FetchEnv = {

type WorkerContext = {
waitUntil: (promise: Promise<unknown>) => void;
passThroughOnException: () => void;
};

function* executeRequest(request: Request) {
Expand Down Expand Up @@ -111,9 +113,16 @@ function* executeRequest(request: Request) {
}

export default {
async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
async fetch(
originalRequest: Request,
env: FetchEnv,
workerContext: WorkerContext
) {
let request = originalRequest;
const handlerIterator = executeRequest(request);
const data = {}; // arbitrary data the user can set between functions
let isFailOpen = false;

const next = async (input?: RequestInfo, init?: RequestInit) => {
if (input !== undefined) {
let url = input;
Expand All @@ -135,6 +144,9 @@ export default {
data,
env,
waitUntil: workerContext.waitUntil.bind(workerContext),
passThroughOnException: () => {
isFailOpen = true;
},
};

const response = await handler(context);
Expand All @@ -156,9 +168,14 @@ export default {
};

try {
return next();
} catch (err) {
return new Response("Internal Error", { status: 500 });
return await next();
} catch (error) {
if (isFailOpen) {
const response = await env[__FALLBACK_SERVICE__].fetch(request);
return cloneResponse(response);
}

throw error;
}
},
};
Expand Down

0 comments on commit ab52f77

Please sign in to comment.