Skip to content

Commit

Permalink
fix: allow proxy middleware to reuse body from memo (#679)
Browse files Browse the repository at this point in the history
Resolves #642
  • Loading branch information
bodograumann authored Oct 24, 2024
1 parent b773105 commit d33e9d5
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 14 deletions.
16 changes: 16 additions & 0 deletions body.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,19 @@ Deno.test({
assert(timingSafeEqual(actual, expected));
},
});

Deno.test({
name: "body - empty",
async fn() {
const body = new Body(nativeToServer(
new Request(
"http://localhost/index.html",
{
method: "GET",
},
),
));
assert(!body.has);
assertEquals(await body.init(), null);
},
});
8 changes: 8 additions & 0 deletions body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ export class Body {
return this.#request?.bodyUsed ?? !!this.#used;
}

/** Return the body to be reused as BodyInit. */
async init(): Promise<BodyInit | null> {
if (!this.has) {
return null;
}
return await this.#memo ?? this.stream;
}

/** Reads a body to the end and resolves with the value as an
* {@linkcode ArrayBuffer} */
async arrayBuffer(): Promise<ArrayBuffer> {
Expand Down
25 changes: 25 additions & 0 deletions middleware/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,28 @@ Deno.test({
await mw(ctx, next);
},
});

Deno.test({
name: "proxy - consumed body",
async fn() {
async function fetch(request: Request): Promise<Response> {
const body = await request.text();
assertEquals(body, "hello world");
return new Response(body);
}

const mw = proxy("https://oakserver.github.io/", { fetch });
const stream = ReadableStream.from([
new TextEncoder().encode("hello world"),
]);
const ctx = createMockContext({
method: "POST",
path: "/oak/FAQ",
body: stream,
});
const next = createMockNext();

assertEquals(await ctx.request.body.text(), "hello world");
await mw(ctx, next);
},
});
15 changes: 1 addition & 14 deletions middleware/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async function createRequest<
}
url.search = ctx.request.url.search;

const body = getBodyInit(ctx);
const body = await ctx.request.body?.init() ?? null;
const headers = new Headers(ctx.request.headers);
if (optHeaders) {
if (typeof optHeaders === "function") {
Expand Down Expand Up @@ -195,19 +195,6 @@ async function createRequest<
return request;
}

function getBodyInit<
R extends string,
P extends RouteParams<R>,
S extends State,
>(
ctx: Context<S> | RouterContext<R, P, S>,
): BodyInit | null {
if (!ctx.request.hasBody) {
return null;
}
return ctx.request.body.stream;
}

function iterableHeaders(
headers: HeadersInit,
): IterableIterator<[string, string]> {
Expand Down
5 changes: 5 additions & 0 deletions testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type ErrorStatus,
SecureCookieMap,
} from "./deps.ts";
import { Body } from "./body.ts";
import type { RouteParams, RouterContext } from "./router.ts";
import type { Request } from "./request.ts";
import { Response } from "./response.ts";
Expand Down Expand Up @@ -67,6 +68,7 @@ export interface MockContextOptions<
path?: string;
state?: S;
headers?: [string, string][];
body?: ReadableStream;
}

/** Allows external parties to modify the context state. */
Expand All @@ -90,6 +92,7 @@ export function createMockContext<
state,
app = createMockApp(state),
headers: requestHeaders,
body = undefined,
}: MockContextOptions<R> = {},
): RouterContext<R, P, S> {
function createMockRequest(): Request {
Expand Down Expand Up @@ -120,6 +123,8 @@ export function createMockContext<
search: undefined,
searchParams: new URLSearchParams(),
url: new URL(path, "http://localhost/"),
hasBody: !!body,
body: body ? new Body({ headers, getBody: () => body }) : undefined,
} as any;
}

Expand Down

0 comments on commit d33e9d5

Please sign in to comment.