-
-
Notifications
You must be signed in to change notification settings - Fork 813
Expand file tree
/
Copy path_utils.ts
More file actions
146 lines (125 loc) · 4.25 KB
/
_utils.ts
File metadata and controls
146 lines (125 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import type { APIGatewayProxyEvent, APIGatewayProxyEventV2 } from "aws-lambda";
import type { ServerRequest } from "srvx";
import { stringifyQuery } from "ufo";
// Incoming (AWS => Web)
export function awsRequest(
event: APIGatewayProxyEvent | APIGatewayProxyEventV2,
context: unknown
): ServerRequest {
const method = awsEventMethod(event);
const url = awsEventURL(event);
const headers = awsEventHeaders(event);
const body = awsEventBody(event);
const req = new Request(url, { method, headers, body }) as ServerRequest;
// srvx compatibility
req.runtime ??= { name: "aws-lambda" };
// @ts-expect-error (add to srvx types)
req.runtime.aws ??= { event, context } as any;
return new Request(url, { method, headers, body });
}
function awsEventMethod(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): string {
return (
(event as APIGatewayProxyEvent).httpMethod ||
(event as APIGatewayProxyEventV2).requestContext?.http?.method ||
"GET"
);
}
function awsEventURL(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): URL {
const hostname =
event.headers.host || event.headers.Host || event.requestContext?.domainName || ".";
const path = (event as APIGatewayProxyEvent).path || (event as APIGatewayProxyEventV2).rawPath;
const query = awsEventQuery(event);
const protocol =
(event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"]) === "http"
? "http"
: "https";
return new URL(`${path}${query ? `?${query}` : ""}`, `${protocol}://${hostname}`);
}
function awsEventQuery(event: APIGatewayProxyEvent | APIGatewayProxyEventV2) {
if (typeof (event as APIGatewayProxyEventV2).rawQueryString === "string") {
return (event as APIGatewayProxyEventV2).rawQueryString;
}
const queryObj = {
...event.queryStringParameters,
...(event as APIGatewayProxyEvent).multiValueQueryStringParameters,
};
return stringifyQuery(queryObj);
}
function awsEventHeaders(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): Headers {
const headers = new Headers();
for (const [key, value] of Object.entries(event.headers)) {
if (value) {
headers.set(key, value);
}
}
if ("cookies" in event && event.cookies) {
for (const cookie of event.cookies) {
headers.append("cookie", cookie);
}
}
return headers;
}
function awsEventBody(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): BodyInit | undefined {
if (!event.body) {
return undefined;
}
if (event.isBase64Encoded) {
return Buffer.from(event.body || "", "base64");
}
return event.body;
}
// Outgoing (Web => AWS)
export function awsResponseHeaders(response: Response) {
const headers: Record<string, string> = Object.create(null);
for (const [key, value] of response.headers) {
if (value) {
headers[key] = Array.isArray(value) ? value.join(",") : String(value);
}
}
const cookies = response.headers.getSetCookie();
return cookies.length > 0
? {
headers,
cookies, // ApiGateway v2
multiValueHeaders: { "set-cookie": cookies }, // ApiGateway v1
}
: { headers };
}
// 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 async function awsResponseBody(
response: Response
): Promise<{ body: string; isBase64Encoded?: boolean }> {
if (!response.body) {
return { body: "" };
}
const buffer = await toBuffer(response.body as any);
const contentType = response.headers.get("content-type") || "";
return isTextType(contentType)
? { body: buffer.toString("utf8") }
: { body: buffer.toString("base64"), isBase64Encoded: true };
}
function isTextType(contentType = "") {
return /^text\/|\/(javascript|json|xml)|utf-?8/i.test(contentType);
}
function toBuffer(data: ReadableStream): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
data
.pipeTo(
new WritableStream({
write(chunk) {
chunks.push(chunk);
},
close() {
resolve(Buffer.concat(chunks));
},
abort(reason) {
reject(reason);
},
})
)
.catch(reject);
});
}