11import { Readable } from 'node:stream' ;
22import type streamWeb from 'node:stream/web' ;
33import type {
4+ ALBEvent ,
5+ ALBResult ,
46 APIGatewayProxyEvent ,
57 APIGatewayProxyEventV2 ,
68 APIGatewayProxyResult ,
@@ -16,9 +18,10 @@ import type {
1618 V1Headers ,
1719 WebResponseToProxyResultOptions ,
1820} from '../types/rest.js' ;
19- import { HttpStatusCodes } from './constants.js' ;
21+ import { HttpStatusCodes , HttpStatusText , HttpVerbs } from './constants.js' ;
2022import { InvalidHttpMethodError } from './errors.js' ;
2123import {
24+ isALBEvent ,
2225 isAPIGatewayProxyEventV2 ,
2326 isBinaryResult ,
2427 isExtendedAPIGatewayProxyResult ,
@@ -47,11 +50,11 @@ const createBody = (body: string | null, isBase64Encoded: boolean) => {
4750 * Populates headers from single and multi-value header entries.
4851 *
4952 * @param headers - The Headers object to populate
50- * @param event - The API Gateway proxy event
53+ * @param event - The API Gateway proxy event or ALB event
5154 */
5255const populateV1Headers = (
5356 headers : Headers ,
54- event : APIGatewayProxyEvent
57+ event : APIGatewayProxyEvent | ALBEvent
5558) : void => {
5659 for ( const [ name , value ] of Object . entries ( event . headers ?? { } ) ) {
5760 if ( value !== undefined ) headers . set ( name , value ) ;
@@ -71,9 +74,12 @@ const populateV1Headers = (
7174 * Populates URL search parameters from single and multi-value query string parameters.
7275 *
7376 * @param url - The URL object to populate
74- * @param event - The API Gateway proxy event
77+ * @param event - The API Gateway proxy event or ALB event
7578 */
76- const populateV1QueryParams = ( url : URL , event : APIGatewayProxyEvent ) : void => {
79+ const populateV1QueryParams = (
80+ url : URL ,
81+ event : APIGatewayProxyEvent | ALBEvent
82+ ) : void => {
7783 for ( const [ name , value ] of Object . entries (
7884 event . queryStringParameters ?? { }
7985 ) ) {
@@ -154,14 +160,45 @@ const proxyEventV2ToWebRequest = (event: APIGatewayProxyEventV2): Request => {
154160} ;
155161
156162/**
157- * Converts an API Gateway proxy event (V1 or V2) to a Web API Request object.
163+ * Converts an ALB event to a Web API Request object.
164+ *
165+ * @param event - The ALB event
166+ * @returns A Web API Request object
167+ */
168+ const albEventToWebRequest = ( event : ALBEvent ) : Request => {
169+ const { httpMethod, path } = event ;
170+
171+ const headers = new Headers ( ) ;
172+ populateV1Headers ( headers , event ) ;
173+
174+ const hostname = headers . get ( 'Host' ) ?? 'localhost' ;
175+ const protocol = headers . get ( 'X-Forwarded-Proto' ) ?? 'https' ;
176+
177+ const url = new URL ( path , `${ protocol } ://${ hostname } /` ) ;
178+ populateV1QueryParams ( url , event ) ;
179+
180+ // ALB events represent GET and PATCH request bodies as empty strings
181+ const body =
182+ httpMethod === HttpVerbs . GET || httpMethod === HttpVerbs . PATCH
183+ ? null
184+ : createBody ( event . body ?? null , event . isBase64Encoded ) ;
185+
186+ return new Request ( url . toString ( ) , {
187+ method : httpMethod ,
188+ headers,
189+ body : body ,
190+ } ) ;
191+ } ;
192+
193+ /**
194+ * Converts an API Gateway proxy event (V1 or V2) or ALB event to a Web API Request object.
158195 * Automatically detects the event version and calls the appropriate converter.
159196 *
160- * @param event - The API Gateway proxy event (V1 or V2)
197+ * @param event - The API Gateway proxy event (V1 or V2) or ALB event
161198 * @returns A Web API Request object
162199 */
163200const proxyEventToWebRequest = (
164- event : APIGatewayProxyEvent | APIGatewayProxyEventV2
201+ event : APIGatewayProxyEvent | APIGatewayProxyEventV2 | ALBEvent
165202) : Request => {
166203 if ( isAPIGatewayProxyEventV2 ( event ) ) {
167204 const method = event . requestContext . http . method . toUpperCase ( ) ;
@@ -170,10 +207,13 @@ const proxyEventToWebRequest = (
170207 }
171208 return proxyEventV2ToWebRequest ( event ) ;
172209 }
173- const method = event . requestContext . httpMethod . toUpperCase ( ) ;
210+ const method = event . httpMethod . toUpperCase ( ) ;
174211 if ( ! isHttpMethod ( method ) ) {
175212 throw new InvalidHttpMethodError ( method ) ;
176213 }
214+ if ( isALBEvent ( event ) ) {
215+ return albEventToWebRequest ( event ) ;
216+ }
177217 return proxyEventV1ToWebRequest ( event ) ;
178218} ;
179219
@@ -319,6 +359,42 @@ const webResponseToProxyResultV2 = async (
319359 return result ;
320360} ;
321361
362+ /**
363+ * Converts a Web API Response object to an ALB result.
364+ *
365+ * @param response - The Web API Response object
366+ * @param isBase64Encoded - Whether the response body should be base64 encoded (e.g., for binary or compressed content)
367+ * @returns An ALB result
368+ */
369+ const webResponseToALBResult = async (
370+ response : Response ,
371+ isBase64Encoded ?: boolean
372+ ) : Promise < ALBResult > => {
373+ const { headers, multiValueHeaders } = webHeadersToApiGatewayV1Headers (
374+ response . headers
375+ ) ;
376+
377+ const body = isBase64Encoded
378+ ? await responseBodyToBase64 ( response )
379+ : await response . text ( ) ;
380+
381+ const statusText = response . statusText || HttpStatusText [ response . status ] ;
382+
383+ const result : ALBResult = {
384+ statusCode : response . status ,
385+ statusDescription : `${ response . status } ${ statusText } ` ,
386+ headers,
387+ body,
388+ isBase64Encoded,
389+ } ;
390+
391+ if ( Object . keys ( multiValueHeaders ) . length > 0 ) {
392+ result . multiValueHeaders = multiValueHeaders ;
393+ }
394+
395+ return result ;
396+ } ;
397+
322398const webResponseToProxyResult = < T extends ResponseType > (
323399 response : Response ,
324400 responseType : T ,
@@ -330,6 +406,11 @@ const webResponseToProxyResult = <T extends ResponseType>(
330406 ResponseTypeMap [ T ]
331407 > ;
332408 }
409+ if ( responseType === 'ALB' ) {
410+ return webResponseToALBResult ( response , isBase64Encoded ) as Promise <
411+ ResponseTypeMap [ T ]
412+ > ;
413+ }
333414 return webResponseToProxyResultV2 ( response , isBase64Encoded ) as Promise <
334415 ResponseTypeMap [ T ]
335416 > ;
0 commit comments