Skip to content

Commit

Permalink
refactor(aws-lambda): replace conditional logics with polymorphism (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
exoego committed Apr 19, 2024
1 parent fa9ed80 commit c83d80f
Showing 1 changed file with 135 additions and 75 deletions.
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

0 comments on commit c83d80f

Please sign in to comment.