Skip to content

Commit

Permalink
fix(aws-lambda,netlify): base64 encode binary responses (#1274)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <pooya@pi0.io>
  • Loading branch information
harlan-zw and pi0 committed Jul 21, 2023
1 parent 0bd8662 commit 7b8475c
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 11 deletions.
5 changes: 3 additions & 2 deletions src/runtime/entries/aws-lambda.ts
Expand Up @@ -10,6 +10,7 @@ import { withQuery } from "ufo";
import { nitroApp } from "../app";
import {
normalizeLambdaIncomingHeaders,
normalizeLambdaOutgoingBody,
normalizeLambdaOutgoingHeaders,
} from "../utils.lambda";

Expand Down Expand Up @@ -64,13 +65,13 @@ export async function handler(
cookies,
statusCode: r.status,
headers: normalizeLambdaOutgoingHeaders(r.headers, true),
body: r.body.toString(),
body: normalizeLambdaOutgoingBody(r.body, r.headers),
};
}

return {
statusCode: r.status,
headers: normalizeLambdaOutgoingHeaders(r.headers),
body: r.body.toString(),
body: normalizeLambdaOutgoingBody(r.body, r.headers),
};
}
3 changes: 2 additions & 1 deletion src/runtime/entries/netlify-lambda.ts
Expand Up @@ -9,6 +9,7 @@ import { splitCookiesString } from "h3";
import { nitroApp } from "../app";
import {
normalizeLambdaIncomingHeaders,
normalizeLambdaOutgoingBody,
normalizeLambdaOutgoingHeaders,
} from "../utils.lambda";
import { normalizeCookieHeader } from "../utils";
Expand Down Expand Up @@ -40,7 +41,7 @@ export async function lambda(
return {
statusCode: r.status,
headers: normalizeLambdaOutgoingHeaders(r.headers, true),
body: r.body.toString(),
body: normalizeLambdaOutgoingBody(r.body, r.headers),
multiValueHeaders: {
...(cookies.length > 0 ? { "set-cookie": cookies } : {}),
},
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/entries/stormkit.ts
@@ -1,6 +1,7 @@
import type { Handler } from "aws-lambda";
import "#internal/nitro/virtual/polyfill";
import { nitroApp } from "../app";
import { normalizeLambdaOutgoingBody } from "#internal/nitro/utils.lambda";

interface StormkitEvent {
url: string; // e.g. /my/path, /my/path?with=query
Expand Down Expand Up @@ -38,7 +39,7 @@ export const handler: Handler<StormkitEvent, StormkitResult> = async function (
return {
statusCode: r.status,
headers: normalizeOutgoingHeaders(r.headers),
body: r.body.toString(),
body: normalizeLambdaOutgoingBody(r.body, r.headers),
};
};

Expand Down
31 changes: 31 additions & 0 deletions src/runtime/utils.lambda.ts
Expand Up @@ -24,3 +24,34 @@ export function normalizeLambdaOutgoingHeaders(
entries.map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : v])
);
}

// AWS Lambda proxy integrations requires base64 encoded buffers
// binaryMediaTypes should be */*
// see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html
export function normalizeLambdaOutgoingBody(
body: BodyInit,
headers: HeadersObject
): string {
if (typeof body === "string") {
return body;
}
if (!body) {
return "";
}
if (Buffer.isBuffer(body)) {
const contentType = (headers["content-type"] as string) || "";
if (isTextType(contentType)) {
return body.toString("utf8");
}
return body.toString("base64");
}
throw new Error(`Unsupported body type: ${typeof body}`);
}

// -- Internal --

const TEXT_TYPE_RE = /^text\/|\/(json|xml)|utf-?8/;

function isTextType(contentType = "") {
return TEXT_TYPE_RE.test(contentType);
}
15 changes: 8 additions & 7 deletions test/fixture/routes/icon.png.ts
@@ -1,12 +1,13 @@
const LOGO_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAT9JREFUOE+t081KAlEUwPH/nRkn0pmgZWQgFLTpBYqQVkFUmgTRNnqKkFrZop7Blm4KQq02rVqYr1AILaKPZYIzGY46ExdRNMWP6u4ul/O759x7jggli0fgxQGd0ZYD4liEkh+VXwQ3r3Ik4DV3igBVQNXtnYomQB65rQjoALbndE7DAfZvbe5eq11KJmKSeqxwXnBaZ13ASThAyfHYTJd4sztTGQrYWxjjueQybSjsXFs4bcbQwO6NTSZqkn+vcZgvt9IdGthIW8xPqlxGTOK5MumnRs0jATIgNquTWPYTy1oUivXRAYkklvwsTmlEsxapNWPwL8hHlCU0l67CxbrJi+0yYyqkHgZ8409AQkFD4WprAsMniN9/9u+DXoBEVoI+zlYNDnJ9AE0Bn4Cveu9WHtcabV5r643/GKa/jfM3OT68lZwxK8oAAAAASUVORK5CYII=";

export default eventHandler((event) => {
const buff = base64ToArray(LOGO_BASE64);
event.res.end(buff);
export default defineEventHandler((event) => {
setHeader(event, "Content-Type", "image/png");
return Buffer.from(_base64ToArray(_getLogoBase64()));
});

function base64ToArray(base64: string) {
function _getLogoBase64() {
return "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAT9JREFUOE+t081KAlEUwPH/nRkn0pmgZWQgFLTpBYqQVkFUmgTRNnqKkFrZop7Blm4KQq02rVqYr1AILaKPZYIzGY46ExdRNMWP6u4ul/O759x7jggli0fgxQGd0ZYD4liEkh+VXwQ3r3Ik4DV3igBVQNXtnYomQB65rQjoALbndE7DAfZvbe5eq11KJmKSeqxwXnBaZ13ASThAyfHYTJd4sztTGQrYWxjjueQybSjsXFs4bcbQwO6NTSZqkn+vcZgvt9IdGthIW8xPqlxGTOK5MumnRs0jATIgNquTWPYTy1oUivXRAYkklvwsTmlEsxapNWPwL8hHlCU0l67CxbrJi+0yYyqkHgZ8409AQkFD4WprAsMniN9/9u+DXoBEVoI+zlYNDnJ9AE0Bn4Cveu9WHtcabV5r643/GKa/jfM3OT68lZwxK8oAAAAASUVORK5CYII=";
}

function _base64ToArray(base64: string) {
const str = atob(base64);
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
Expand Down
21 changes: 21 additions & 0 deletions test/tests.ts
Expand Up @@ -185,6 +185,27 @@ export function testNitro(
expect(obj.headers.location).toBe("https://nitro.unjs.io/");
});

// aws lambda requires buffer responses to be base 64
const LambdaPresets = ["netlify", "aws-lambda"];
it.runIf(LambdaPresets.includes(ctx.preset))(
"buffer image responses",
async () => {
const { data } = await callHandler({ url: "/icon.png" });
expect(typeof data).toBe("string");
const buffer = Buffer.from(data, "base64");
// check if buffer is a png
function isBufferPng(buffer: Buffer) {
return (
buffer[0] === 0x89 &&
buffer[1] === 0x50 &&
buffer[2] === 0x4e &&
buffer[3] === 0x47
);
}
expect(isBufferPng(buffer)).toBe(true);
}
);

it("render JSX", async () => {
const { data } = await callHandler({ url: "/jsx" });
expect(data).toMatch("<h1 >Hello JSX!</h1>");
Expand Down

0 comments on commit 7b8475c

Please sign in to comment.