From 2db86f28f37658198d19654bbc8852843b65a9e7 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Mon, 29 Apr 2019 09:11:22 -0700 Subject: [PATCH] feat: lay groundwork for different event sources rename middleware from x-apigateway to x-lambda --- README.md | 2 +- __tests__/middleware.js | 40 +++++++++--------- __tests__/unit.js | 8 ++-- examples/basic-starter/app.js | 4 +- src/index.js | 77 ++++++++++++++++++++++++++--------- src/middleware.js | 14 +++---- 6 files changed, 92 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 03bfb42a..bb1ce7bf 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This package includes middleware to easily get the event object Lambda receives const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') app.use(awsServerlessExpressMiddleware.eventContext()) app.get('/', (req, res) => { - res.json(req.apiGateway.event) + res.json(req.lambda.event) }) ``` diff --git a/__tests__/middleware.js b/__tests__/middleware.js index 44e3d7ae..09a6acef 100644 --- a/__tests__/middleware.js +++ b/__tests__/middleware.js @@ -5,14 +5,14 @@ const mockNext = () => true const generateMockReq = () => { return { headers: { - 'x-apigateway-event': encodeURIComponent(JSON.stringify({ + 'x-lambda-event': encodeURIComponent(JSON.stringify({ path: '/foo/bar', queryStringParameters: { foo: '🖖', bar: '~!@#$%^&*()_+`-=;\':",./<>?`' } })), - 'x-apigateway-context': encodeURIComponent(JSON.stringify({foo: 'bar'})) + 'x-lambda-context': encodeURIComponent(JSON.stringify({foo: 'bar'})) } } } @@ -24,10 +24,10 @@ test('defaults', () => { eventContextMiddleware()(req, mockRes, mockNext) - expect(req.apiGateway.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-event']))) - expect(req.apiGateway.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-context']))) - expect(req.headers['x-apigateway-event']).toBe(undefined) - expect(req.headers['x-apigateway-context']).toBe(undefined) + expect(req.lambda.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-event']))) + expect(req.lambda.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-context']))) + expect(req.headers['x-lambda-event']).toBe(undefined) + expect(req.headers['x-lambda-context']).toBe(undefined) }) test('options.reqPropKey', () => { @@ -36,10 +36,10 @@ test('options.reqPropKey', () => { eventContextMiddleware({ reqPropKey: '_apiGateway' })(req, mockRes, mockNext) - expect(req._apiGateway.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-event']))) - expect(req._apiGateway.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-context']))) - expect(req.headers['x-apigateway-event']).toBe(undefined) - expect(req.headers['x-apigateway-context']).toBe(undefined) + expect(req._apiGateway.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-event']))) + expect(req._apiGateway.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-context']))) + expect(req.headers['x-lambda-event']).toBe(undefined) + expect(req.headers['x-lambda-context']).toBe(undefined) }) test('options.deleteHeaders = false', () => { @@ -48,26 +48,26 @@ test('options.deleteHeaders = false', () => { eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - expect(req.apiGateway.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-event']))) - expect(req.apiGateway.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-apigateway-context']))) - expect(req.headers['x-apigateway-event']).toEqual(originalHeaders['x-apigateway-event']) - expect(req.headers['x-apigateway-context']).toEqual(originalHeaders['x-apigateway-context']) + expect(req.lambda.event).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-event']))) + expect(req.lambda.context).toEqual(JSON.parse(decodeURIComponent(originalHeaders['x-lambda-context']))) + expect(req.headers['x-lambda-event']).toEqual(originalHeaders['x-lambda-event']) + expect(req.headers['x-lambda-context']).toEqual(originalHeaders['x-lambda-context']) }) -test('Missing x-apigateway-event', () => { +test('Missing x-lambda-event', () => { const req = generateMockReq() - delete req.headers['x-apigateway-event'] + delete req.headers['x-lambda-event'] eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - expect(req.apiGateway).toBe(undefined) + expect(req.lambda).toBe(undefined) }) -test('Missing x-apigateway-context', () => { +test('Missing x-lambda-context', () => { const req = generateMockReq() - delete req.headers['x-apigateway-context'] + delete req.headers['x-lambda-context'] eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - expect(req.apiGateway).toBe(undefined) + expect(req.lambda).toBe(undefined) }) diff --git a/__tests__/unit.js b/__tests__/unit.js index 6bdc0473..740d67ed 100644 --- a/__tests__/unit.js +++ b/__tests__/unit.js @@ -72,8 +72,8 @@ test('mapApiGatewayEventToHttpRequest: with headers', () => { headers: { 'x-foo': 'foo', 'Content-Length': Buffer.byteLength('Hello serverless!'), - 'x-apigateway-event': encodeURIComponent(JSON.stringify(r.eventClone)), - 'x-apigateway-context': encodeURIComponent(JSON.stringify(r.context)) + 'x-lambda-event': encodeURIComponent(JSON.stringify(r.eventClone)), + 'x-lambda-context': encodeURIComponent(JSON.stringify(r.context)) }, socketPath: '/tmp/server0.sock' }) @@ -87,8 +87,8 @@ test('mapApiGatewayEventToHttpRequest: without headers', () => { path: '/foo', headers: { 'Content-Length': Buffer.byteLength('Hello serverless!'), - 'x-apigateway-event': encodeURIComponent(JSON.stringify(r.eventClone)), - 'x-apigateway-context': encodeURIComponent(JSON.stringify(r.context)) + 'x-lambda-event': encodeURIComponent(JSON.stringify(r.eventClone)), + 'x-lambda-context': encodeURIComponent(JSON.stringify(r.context)) }, socketPath: '/tmp/server0.sock' }) diff --git a/examples/basic-starter/app.js b/examples/basic-starter/app.js index f002329b..0e0d7414 100644 --- a/examples/basic-starter/app.js +++ b/examples/basic-starter/app.js @@ -4,7 +4,7 @@ const express = require('express') const bodyParser = require('body-parser') const cors = require('cors') const compression = require('compression') -const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') +const awsServerlessExpressMiddleware = require(process.env.NODE_ENV === 'test' ? '../../middleware' : 'aws-serverless-express/middleware') const app = express() const router = express.Router() @@ -28,7 +28,7 @@ app.set('views', path.join(__dirname, 'views')) router.get('/', (req, res) => { res.render('index', { - apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000' + apiUrl: req.lambda ? `https://${req.lambda.event.headers.Host}/${req.lambda.event.requestContext.stage}` : 'http://localhost:3000' }) }) diff --git a/src/index.js b/src/index.js index e01bd0b7..7e67e0c6 100644 --- a/src/index.js +++ b/src/index.js @@ -42,20 +42,17 @@ function isContentTypeBinaryMimeType ({ contentType, binaryMimeTypes }) { return binaryMimeTypes.length > 0 && !!isType.is(contentType, binaryMimeTypes) } -function mapApiGatewayEventToHttpRequest ({ event, context, socketPath }) { - const headers = Object.assign({}, event.headers) - - // NOTE: API Gateway is not setting Content-Length header on requests even when they have a body - if (event.body && !headers['Content-Length']) { - const body = getEventBody({ event }) - headers['Content-Length'] = Buffer.byteLength(body) - } - +function mapEventToHttpRequest ({ + event, + context, + socketPath, + headers = Object.assign({}, event.headers) +}) { const clonedEventWithoutBody = clone({ object: event }) delete clonedEventWithoutBody.body - headers['x-apigateway-event'] = encodeURIComponent(JSON.stringify(clonedEventWithoutBody)) - headers['x-apigateway-context'] = encodeURIComponent(JSON.stringify(context)) + headers['x-lambda-event'] = encodeURIComponent(JSON.stringify(clonedEventWithoutBody)) + headers['x-lambda-context'] = encodeURIComponent(JSON.stringify(context)) return { method: event.httpMethod, @@ -69,6 +66,30 @@ function mapApiGatewayEventToHttpRequest ({ event, context, socketPath }) { } } +function mapApiGatewayEventToHttpRequest ({ event, context, socketPath }) { + const httpRequest = mapEventToHttpRequest({ event, context, socketPath }) + + // NOTE: API Gateway is not setting Content-Length header on requests even when they have a body + if (event.body && !httpRequest.headers['Content-Length']) { + const body = getEventBody({ event }) + httpRequest.headers['Content-Length'] = Buffer.byteLength(body) + } + + return httpRequest +} + +function mapAlbEventToHttpRequest ({ event, context, socketPath }) { + const httpRequest = mapEventToHttpRequest({ event, context, socketPath }) + + // NOTE: API Gateway is not setting Content-Length header on requests even when they have a body + if (event.body && !httpRequest.headers['Content-Length']) { + const body = getEventBody({ event }) + httpRequest.headers['Content-Length'] = Buffer.byteLength(body) + } + + return httpRequest +} + function forwardResponseToApiGateway ({ server, response, resolver }) { let buf = [] @@ -150,14 +171,25 @@ function forwardLibraryErrorResponseToApiGateway ({ error, resolver }) { }) } +function getEventMapperBasedOnEventSource ({ eventSource }) { + switch (eventSource) { + case 'ALB': + return mapAlbEventToHttpRequest + default: + return mapApiGatewayEventToHttpRequest + } +} + function forwardRequestToNodeServer ({ server, event, context, - resolver + resolver, + eventSource, + eventMapper = getEventMapperBasedOnEventSource({ eventSource }) }) { try { - const requestOptions = mapApiGatewayEventToHttpRequest({ + const requestOptions = eventMapper({ event, context, socketPath: getSocketPath({ socketPathSuffix: server._socketPathSuffix }) @@ -220,8 +252,10 @@ function createServer ({ } function getEventSourceBasedOnEvent ({ - eventSource + event }) { + if (event && event.requestContext && event.requestContext.elb) return 'ALB' + return 'API_GATEWAY' } @@ -252,7 +286,8 @@ function proxy ({ server, event, context, - resolver + resolver, + eventSource }) } else { startServer({ server }) @@ -260,7 +295,8 @@ function proxy ({ server, event, context, - resolver + resolver, + eventSource })) } }) @@ -287,7 +323,8 @@ function makeResolver ({ function configure ({ app: configureApp, binaryMimeTypes: configureBinaryMimeTypes = [], - resolutionMode: configureResolutionMode = 'CONTEXT_SUCCEED' + resolutionMode: configureResolutionMode = 'CONTEXT_SUCCEED', + eventSource } = {}) { function _createServer ({ app = configureApp, @@ -306,14 +343,16 @@ function configure ({ resolutionMode = configureResolutionMode, event, context, - callback + callback, + eventSource } = {}) { return proxy({ server, event, context, resolutionMode, - callback + callback, + eventSource }) } diff --git a/src/middleware.js b/src/middleware.js index d9ba9e7f..89c80803 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,21 +1,21 @@ module.exports.eventContext = ({ - reqPropKey = 'apiGateway', + reqPropKey = 'lambda', deleteHeaders = true } = {}) => function apiGatewayEventParser (req, res, next) { - if (!req.headers['x-apigateway-event'] || !req.headers['x-apigateway-context']) { - console.error('Missing x-apigateway-event or x-apigateway-context header(s)') + if (!req.headers['x-lambda-event'] || !req.headers['x-lambda-context']) { + console.error('Missing x-lambda-event or x-lambda-context header(s)') next() return } req[reqPropKey] = { - event: JSON.parse(decodeURIComponent(req.headers['x-apigateway-event'])), - context: JSON.parse(decodeURIComponent(req.headers['x-apigateway-context'])) + event: JSON.parse(decodeURIComponent(req.headers['x-lambda-event'])), + context: JSON.parse(decodeURIComponent(req.headers['x-lambda-context'])) } if (deleteHeaders) { - delete req.headers['x-apigateway-event'] - delete req.headers['x-apigateway-context'] + delete req.headers['x-lambda-event'] + delete req.headers['x-lambda-context'] } next()