Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(adapter/aws): Optimize multiple call of same conditions with polymorphism #2521

Merged
merged 1 commit into from
Apr 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
210 changes: 135 additions & 75 deletions src/adapter/aws-lambda/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ export const streamHandle = <
) => {
return awslambda.streamifyResponse(
async (event: LambdaEvent, responseStream: NodeJS.WritableStream, context: LambdaContext) => {
const processor = getProcessor(event)
try {
const req = createRequest(event)
const req = processor.createRequest(event)
const requestContext = getRequestContext(event)

const res = await app.fetch(req, {
Expand Down Expand Up @@ -156,7 +157,9 @@ export const handle = <E extends Env = Env, S extends Schema = {}, BasePath exte
event: LambdaEvent,
lambdaContext?: LambdaContext
): Promise<APIGatewayProxyResult> => {
const req = createRequest(event)
const processor = getProcessor(event)

const req = processor.createRequest(event)
const requestContext = getRequestContext(event)

const res = await app.fetch(req, {
Expand All @@ -165,105 +168,162 @@ export const handle = <E extends Env = Env, S extends Schema = {}, BasePath exte
lambdaContext,
})

return createResult(event, res)
return processor.createResult(event, res)
}
}

const createResult = async (event: LambdaEvent, res: Response): Promise<APIGatewayProxyResult> => {
const contentType = res.headers.get('content-type')
let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false
abstract class EventProcessor<E extends LambdaEvent> {
protected abstract getPath(event: E): string

if (!isBase64Encoded) {
const contentEncoding = res.headers.get('content-encoding')
isBase64Encoded = isContentEncodingBinary(contentEncoding)
}
protected abstract getMethod(event: E): string

const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text()
protected abstract getQueryString(event: E): string

const result: APIGatewayProxyResult = {
body: body,
headers: {},
statusCode: res.status,
isBase64Encoded,
}
protected abstract getCookies(event: E, headers: Headers): void

setCookies(event, res, result)
res.headers.forEach((value, key) => {
result.headers[key] = value
})
protected abstract setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void

return result
}
createRequest(event: E): Request {
const queryString = this.getQueryString(event)
const domainName =
event.requestContext && 'domainName' in event.requestContext
? event.requestContext.domainName
: event.headers?.['host'] ?? event.multiValueHeaders?.['host']?.[0]
const path = this.getPath(event)
const urlPath = `https://${domainName}${path}`
const url = queryString ? `${urlPath}?${queryString}` : urlPath

const createRequest = (event: LambdaEvent) => {
const queryString = extractQueryString(event)
const domainName =
event.requestContext && 'domainName' in event.requestContext
? event.requestContext.domainName
: event.headers?.['host'] ?? event.multiValueHeaders?.['host']?.[0]
const path = isProxyEventV2(event) ? event.rawPath : event.path
const urlPath = `https://${domainName}${path}`
const url = queryString ? `${urlPath}?${queryString}` : urlPath

const headers = new Headers()
getCookies(event, headers)
if (event.headers) {
for (const [k, v] of Object.entries(event.headers)) {
if (v) {
headers.set(k, v)
const headers = new Headers()
this.getCookies(event, headers)
if (event.headers) {
for (const [k, v] of Object.entries(event.headers)) {
if (v) {
headers.set(k, v)
}
}
}
if (event.multiValueHeaders) {
for (const [k, values] of Object.entries(event.multiValueHeaders)) {
if (values) {
values.forEach((v) => headers.append(k, v))
}
}
}

const method = this.getMethod(event)
const requestInit: RequestInit = {
headers,
method,
}

if (event.body) {
requestInit.body = event.isBase64Encoded ? Buffer.from(event.body, 'base64') : event.body
}

return new Request(url, requestInit)
}
if (event.multiValueHeaders) {
for (const [k, values] of Object.entries(event.multiValueHeaders)) {
if (values) {
values.forEach((v) => headers.append(k, v))

async createResult(event: E, res: Response): Promise<APIGatewayProxyResult> {
const contentType = res.headers.get('content-type')
let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false

if (!isBase64Encoded) {
const contentEncoding = res.headers.get('content-encoding')
isBase64Encoded = isContentEncodingBinary(contentEncoding)
}

const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text()

const result: APIGatewayProxyResult = {
body: body,
headers: {},
statusCode: res.status,
isBase64Encoded,
}

this.setCookies(event, res, result)
res.headers.forEach((value, key) => {
result.headers[key] = value
})

return result
}

setCookies = (event: LambdaEvent, res: Response, result: APIGatewayProxyResult) => {
if (res.headers.has('set-cookie')) {
const cookies = res.headers.get('set-cookie')?.split(', ')
if (Array.isArray(cookies)) {
this.setCookiesToResult(result, cookies)
res.headers.delete('set-cookie')
}
}
}
}

const method = isProxyEventV2(event) ? event.requestContext.http.method : event.httpMethod
const requestInit: RequestInit = {
headers,
method,
const v2Processor = new (class EventV2Processor extends EventProcessor<APIGatewayProxyEventV2> {
protected getPath(event: APIGatewayProxyEventV2): string {
return event.rawPath
}

if (event.body) {
requestInit.body = event.isBase64Encoded ? Buffer.from(event.body, 'base64') : event.body
protected getMethod(event: APIGatewayProxyEventV2): string {
return event.requestContext.http.method
}

return new Request(url, requestInit)
}
protected getQueryString(event: APIGatewayProxyEventV2): string {
return event.rawQueryString
}

const extractQueryString = (event: LambdaEvent) => {
return isProxyEventV2(event)
? event.rawQueryString
: Object.entries(event.queryStringParameters || {})
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join('&')
}
protected getCookies(event: APIGatewayProxyEventV2, headers: Headers): void {
if (Array.isArray(event.cookies)) {
headers.set('Cookie', event.cookies.join('; '))
}
}

const getCookies = (event: LambdaEvent, headers: Headers) => {
if (isProxyEventV2(event) && Array.isArray(event.cookies)) {
headers.set('Cookie', event.cookies.join('; '))
protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
result.cookies = cookies
}
}
})()

const setCookies = (event: LambdaEvent, res: Response, result: APIGatewayProxyResult) => {
if (res.headers.has('set-cookie')) {
const cookies = res.headers.get('set-cookie')?.split(', ')
if (Array.isArray(cookies)) {
if (isProxyEventV2(event)) {
result.cookies = cookies
} else {
result.multiValueHeaders = {
'set-cookie': cookies,
}
}
res.headers.delete('set-cookie')
const v1Processor = new (class EventV1Processor extends EventProcessor<
Exclude<LambdaEvent, APIGatewayProxyEventV2>
> {
protected getPath(event: Exclude<LambdaEvent, APIGatewayProxyEventV2>): string {
return event.path
}

protected getMethod(event: Exclude<LambdaEvent, APIGatewayProxyEventV2>): string {
return event.httpMethod
}

protected getQueryString(event: Exclude<LambdaEvent, APIGatewayProxyEventV2>): string {
return Object.entries(event.queryStringParameters || {})
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join('&')
}

protected getCookies(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
event: Exclude<LambdaEvent, APIGatewayProxyEventV2>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
headers: Headers
): void {
// nop
}

protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
result.multiValueHeaders = {
'set-cookie': cookies,
}
}
})()

const getProcessor = (event: LambdaEvent): EventProcessor<LambdaEvent> => {
if (isProxyEventV2(event)) {
return v2Processor
} else {
return v1Processor
}
}

const isProxyEventV2 = (event: LambdaEvent): event is APIGatewayProxyEventV2 => {
Expand Down