import * as http from "http" ; import { IncomingMessage } from "http" ; import { ServerResponse } from "http" ; import { Server } from "http" ; import { AddressInfo } from "net" ; import { LoggingHelper } from "../core/logging-helper" ; import { ModuleManager } from "./module-manager" ; const chalk = require ( "chalk" ) ; let Logger = "BST-LAMBDA" ; /**
 * The LambdaServer wraps a Node.js Lambda routine in a simple HTTP service.
 *
 * It makes it easy to test Lambdas locally, and in conjunction with our other tools.
 *
 * To use it, simply provide the filename of the Lambda function along with the port the HTTP server should listen on.
 */ export class LambdaServer { private moduleManager : ModuleManager ; private requests : Array < IncomingMessage > = [ ] ; private server : Server = null ; param file param port param verbose param functionName public constructor ( private file ? , private port ? , private verbose ? , private functionName ? ) { } param callback public start ( callback ? ) { let self = this ; this . moduleManager = new ModuleManager ( process . cwd ( ) ) ; this . moduleManager . start ( ) ; this . server = http . createServer ( ) ; this . server . listen ( this . port ) ; this . server . on ( "request" , function ( request , response ) { self . requests . push ( request ) ; let requestBody = Buffer . from ( "" ) ; request . on ( "data" , function ( chunk ) { requestBody = Buffer . concat ( [ requestBody , chunk ] ) ; } ) ; request . on ( "end" , function ( ) { const isPing = request . method === "GET" && request . url && request . url . indexOf ( "/localPing" ) !== - 1 ; if ( isPing ) { return response . end ( "ALIVE" ) ; } self . invoke ( request , requestBody , response ) ; } ) ; } ) ; this . server . on ( "listening" , function ( ) { LoggingHelper . debug ( Logger , "LambdaServer started on port: " + ( self . server . address ( ) as AddressInfo ) . port . toString ( ) ) ; if ( callback !== undefined && callback !== null ) { callback ( ) ; } } ) ; } param onStop public stop ( onStop ? ) { this . moduleManager . stop ( ) ; let request = null ; for ( request of this . requests ) { try { request . socket . end ( ) ; } catch ( e ) { } } this . server . close ( function ( ) { if ( onStop !== undefined && onStop !== null ) { onStop ( ) ; } } ) ; } private invoke ( request , body , response ) { let path ; let handlerFunction ; const context = new LambdaContext ( request , body , response , this . verbose ) ; const onlyUrl = request . url . split ( "?" ) [ 0 ] ; if ( this . file ) { path = this . file ; } else { if ( onlyUrl !== "/" ) { if ( /(.*\..*\.)|(.*node_modules)/ . test ( onlyUrl ) ) { context . fail ( Error ( `LambdaServer input url should not contain more than '.' or node_modules.  found: ${ onlyUrl }` ) ) ; return ; } const splitUrl = onlyUrl . split ( "." ) ; path = splitUrl [ 0 ] ; handlerFunction = splitUrl [ 1 ] ; } else { context . fail ( Error ( "You should provide the lambda file or pass it in the url" ) ) ; return ; } } LoggingHelper . debug ( Logger , "Invoking Lambda: " + path ) ; const lambda = this . moduleManager . module ( path ) ; try { const bodyToString = body . toString ( ) ; const bodyJSON = JSON . parse ( bodyToString === "" ? null : bodyToString ) ; if ( this . verbose ) { console . log ( "Request:" ) ; console . log ( JSON . stringify ( bodyJSON , null , 2 ) ) ; } handlerFunction = handlerFunction ? handlerFunction : this . functionName ? this . functionName : "handler" ; Promise . resolve ( lambda [ handlerFunction ] ( bodyJSON , context , function ( error , result ) { context . done ( error , result ) ; } ) ) . then ( ( result ) => { if ( result ) { context . done ( null , result ) ; } } , ( error ) => context . done ( error , null ) ) ; } catch ( e ) { console . error ( chalk . red ( e . toString ( ) ) ) ; context . fail ( e ) ; } } } class LambdaContext { public awsRequestId = "N/A" ; public callbackWaitsForEmptyEventLoop = true ; public functionName = "BST.LambdaServer" ; public functionVersion = "N/A" ; public memoryLimitInMB = - 1 ; public invokedFunctionArn = "N/A" ; public logGroupName = "N/A" ; public logStreamName : string = null ; public identity : any = null ; public clientContext : any = null ; public constructor ( public request , public body , public response , public verbose ) { } public fail ( error ) { this . done ( error , null ) ; } public succeed ( body ) { this . done ( null , body ) ; } public getRemainingTimeMillis ( ) { return - 1 ; } public done ( error , body ) { let statusCode = 200 ; let contentType = "application/json" ; let bodyString = null ; if ( error === null ) { bodyString = JSON . stringify ( body ) ; if ( this . verbose ) { console . log ( "Response:" ) ; console . log ( JSON . stringify ( body , null , 2 ) ) ; } } else { statusCode = 500 ; contentType = "text/plain" ; bodyString = "Unhandled Exception from Lambda: " + error . toString ( ) ; } this . response . writeHead ( statusCode , { "Content-Type" : contentType } ) ; this . response . end ( Buffer . from ( bodyString ) ) ; } }