From b56c13bc43cdadf20abfde0f9de55a2fc64f537b Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Mon, 29 Apr 2019 16:34:47 -0700 Subject: [PATCH] feat: remove middleware and expose getCurrentLambdaInvoke method --- CONTRIBUTING.md | 2 +- README.md | 25 ++++++------ __tests__/middleware.js | 73 ----------------------------------- __tests__/unit.js | 8 +--- examples/basic-starter/app.js | 6 +-- middleware.js | 17 -------- package.json | 1 - src/index.js | 34 +++++++++------- src/middleware.js | 22 ----------- 9 files changed, 38 insertions(+), 150 deletions(-) delete mode 100644 __tests__/middleware.js delete mode 100644 middleware.js delete mode 100644 src/middleware.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0ffee79..fe19036c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ When filing an issue, please check [existing open](https://github.com/awslabs/aw Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *master* branch. +1. You are working against the latest source on the *develop* branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. diff --git a/README.md b/README.md index bb1ce7bf..d3e8898f 100644 --- a/README.md +++ b/README.md @@ -33,28 +33,29 @@ Want to get up and running quickly? [Check out our basic starter example](exampl - [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model)/[CloudFormation](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) template - Helper scripts to configure, deploy, and manage your application -### Getting the API Gateway event object +### Accessing the event and context objects -This package includes middleware to easily get the event object Lambda receives from API Gateway +This package exposes a function to easily get the `event` and `context` objects Lambda receives from the event source. ```js -const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') -app.use(awsServerlessExpressMiddleware.eventContext()) +const { getCurrentLambdaInvoke } = require('aws-serverless-express') app.get('/', (req, res) => { - res.json(req.lambda.event) + const currentLambdaInvoke = getCurrentLambdaInvoke() + + res.json(currentLambdaInvoke.event) }) ``` ## 4.0.0 Goals 1. Improved API - Simpler for end user to use and configure; extensible without breaking backwards compatibility or hurting API -1. Node.js 8+ only - can upgrade dependencies to latest (Jest); can use latest syntax in source and tests; can use server.listening; future-proof for Node.js 10 -1. Promise resolution mode by default? Requires benchmarking. Otherwise try callback with callbackWaitsForEventLoop=false (configurable by user); requires benchmarking. If context.succeed is still most performant, leave as default. -1. Additional event sources - currently only supports API Gateway Proxy; should also support Lambda@Edge (https://github.com/awslabs/aws-serverless-express/issues/152) and ALB; have had a customer request for DynamoDB; should make it easy to provide your own IO mapping function. -1. Multiple header values - can get rid of set-cookie hack -1. Configure logging - NONE, ERROR, INFO, DEBUG; also include option to respond to 500s with the stack trace instead of empty string currently -1. Improved documentation -1. Option to strip base path for custom domains (https://github.com/awslabs/aws-serverless-express/issues/86) +2. Node.js 8+ only - can upgrade dependencies to latest (Jest); can use latest syntax in source and tests; can use server.listening; future-proof for Node.js 10 +3. Promise resolution mode by default? Requires benchmarking. Otherwise try callback with callbackWaitsForEventLoop=false (configurable by user); requires benchmarking. If context.succeed is still most performant, leave as default. +4. Additional event sources - currently only supports API Gateway Proxy; should also support Lambda@Edge (https://github.com/awslabs/aws-serverless-express/issues/152) and ALB; have had a customer request for DynamoDB; should make it easy to provide your own IO mapping function. +5. Multiple header values - can get rid of set-cookie hack +6. Configure logging - NONE, ERROR, INFO, DEBUG; also include option to respond to 500s with the stack trace instead of empty string currently +7. Improved documentation +8. Option to strip base path for custom domains (https://github.com/awslabs/aws-serverless-express/issues/86) ### Is AWS serverless right for my app? diff --git a/__tests__/middleware.js b/__tests__/middleware.js deleted file mode 100644 index cc0f264e..00000000 --- a/__tests__/middleware.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict' -const awsServerlessExpressMiddleware = require('../middleware') -const eventContextMiddleware = awsServerlessExpressMiddleware.eventContext -const mockNext = () => true -const generateMockReq = () => { - return { - headers: { - 'x-lambda-event': encodeURIComponent(JSON.stringify({ - path: '/foo/bar', - multiValueQueryStringParameters: { - foo: '🖖', - bar: '~!@#$%^&*()_+`-=;\':",./<>?`' - } - })), - 'x-lambda-context': encodeURIComponent(JSON.stringify({foo: 'bar'})) - } - } -} -const mockRes = {} - -test('defaults', () => { - const req = generateMockReq() - const originalHeaders = Object.assign({}, req.headers) - - eventContextMiddleware()(req, mockRes, mockNext) - - 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', () => { - const req = generateMockReq() - const originalHeaders = Object.assign({}, req.headers) - - eventContextMiddleware({ reqPropKey: '_apiGateway' })(req, mockRes, mockNext) - - 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', () => { - const req = generateMockReq() - const originalHeaders = Object.assign({}, req.headers) - - eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - - 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-lambda-event', () => { - const req = generateMockReq() - delete req.headers['x-lambda-event'] - - eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - - expect(req.lambda).toBe(undefined) -}) - -test('Missing x-lambda-context', () => { - const req = generateMockReq() - delete req.headers['x-lambda-context'] - - eventContextMiddleware({ deleteHeaders: false })(req, mockRes, mockNext) - - expect(req.lambda).toBe(undefined) -}) diff --git a/__tests__/unit.js b/__tests__/unit.js index f43d0066..28f6db13 100644 --- a/__tests__/unit.js +++ b/__tests__/unit.js @@ -85,9 +85,7 @@ test('mapApiGatewayEventToHttpRequest: with headers', () => { path: '/foo', headers: { 'x-foo': 'foo', - 'Content-Length': Buffer.byteLength('Hello serverless!'), - 'x-lambda-event': encodeURIComponent(JSON.stringify(r.eventClone)), - 'x-lambda-context': encodeURIComponent(JSON.stringify(r.context)) + 'Content-Length': Buffer.byteLength('Hello serverless!') }, socketPath: '/tmp/server0.sock' }) @@ -100,9 +98,7 @@ test('mapApiGatewayEventToHttpRequest: without headers', () => { method: 'GET', path: '/foo', headers: { - 'Content-Length': Buffer.byteLength('Hello serverless!'), - 'x-lambda-event': encodeURIComponent(JSON.stringify(r.eventClone)), - 'x-lambda-context': encodeURIComponent(JSON.stringify(r.context)) + 'Content-Length': Buffer.byteLength('Hello serverless!') }, socketPath: '/tmp/server0.sock' }) diff --git a/examples/basic-starter/app.js b/examples/basic-starter/app.js index e500b1bd..cdc1e44b 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(process.env.NODE_ENV === 'test' ? '../../middleware' : 'aws-serverless-express/middleware') +const { getCurrentLambdaInvoke } = require(process.env.NODE_ENV === 'test' ? '../..' : 'aws-serverless-express') const app = express() const router = express.Router() @@ -21,14 +21,14 @@ if (process.env.NODE_ENV === 'test') { router.use(cors()) router.use(bodyParser.json()) router.use(bodyParser.urlencoded({ extended: true })) -router.use(awsServerlessExpressMiddleware.eventContext()) // NOTE: tests can't find the views directory without this app.set('views', path.join(__dirname, 'views')) router.get('/', (req, res) => { + const currentLambdaInvoke = getCurrentLambdaInvoke() res.render('index', { - apiUrl: req.lambda ? `https://${req.lambda.event.multiValueHeaders.Host}/${req.lambda.event.requestContext.stage}` : 'http://localhost:3000' + apiUrl: currentLambdaInvoke ? `https://${currentLambdaInvoke.event.multiValueHeaders.Host}/${currentLambdaInvoke.event.requestContext.stage}` : 'http://localhost:3000' }) }) diff --git a/middleware.js b/middleware.js deleted file mode 100644 index d0e73ecf..00000000 --- a/middleware.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2016-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -'use strict' - -module.exports = require('./src/middleware') diff --git a/package.json b/package.json index 6a3932b8..090eeca8 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "license": "Apache-2.0", "files": [ "index.js", - "middleware.js", "src/" ], "main": "index.js", diff --git a/src/index.js b/src/index.js index 2c65dc9c..855e27bc 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,17 @@ const http = require('http') const url = require('url') const isType = require('type-is') +const currentLambdaInvoke = {} + +function getCurrentLambdaInvoke () { + return currentLambdaInvoke +} + +function setCurrentLambdaInvoke ({ event, context }) { + currentLambdaInvoke.event = event + currentLambdaInvoke.context = context +} + function getPathWithQueryStringParams ({ event }) { return url.format({ pathname: event.path, @@ -28,10 +39,6 @@ function getEventBody ({ event }) { return Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') } -function clone ({ object }) { - return JSON.parse(JSON.stringify(object)) -} - function getContentType ({ contentTypeHeader }) { // only compare mime type; ignore encoding part return contentTypeHeader ? contentTypeHeader.split(';')[0] : '' @@ -43,13 +50,9 @@ function isContentTypeBinaryMimeType ({ contentType, binaryMimeTypes }) { function mapEventToHttpRequest ({ event, - eventWithoutBody = { ...clone({ object: event }), body: undefined }, - context, socketPath, headers = { - ...event.multiValueHeaders, - 'x-lambda-event': encodeURIComponent(JSON.stringify(eventWithoutBody)), - 'x-lambda-context': encodeURIComponent(JSON.stringify(context)) + ...event.multiValueHeaders } }) { return { @@ -64,8 +67,8 @@ function mapEventToHttpRequest ({ } } -function mapApiGatewayEventToHttpRequest ({ event, context, socketPath }) { - const httpRequest = mapEventToHttpRequest({ event, context, socketPath }) +function mapApiGatewayEventToHttpRequest ({ event, socketPath }) { + const httpRequest = mapEventToHttpRequest({ event, 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']) { @@ -76,14 +79,14 @@ function mapApiGatewayEventToHttpRequest ({ event, context, socketPath }) { return httpRequest } -function mapAlbEventToHttpRequest ({ event, context, socketPath }) { - const httpRequest = mapEventToHttpRequest({ event, context, socketPath }) +function mapAlbEventToHttpRequest ({ event, socketPath }) { + const httpRequest = mapEventToHttpRequest({ event, socketPath }) return httpRequest } function mapLambdaEdgeEventToHttpRequest ({ event, context, socketPath }) { - const httpRequest = mapEventToHttpRequest({ event, context, socketPath }) + const httpRequest = mapEventToHttpRequest({ event, socketPath }) return httpRequest } @@ -190,10 +193,10 @@ function forwardRequestToNodeServer ({ eventSource, eventFns = getEventFnsBasedOnEventSource({ eventSource }) }) { + setCurrentLambdaInvoke({ event, context }) try { const requestOptions = eventFns.request({ event, - context, socketPath: getSocketPath({ socketPathSuffix: server._socketPathSuffix }) }) const req = http.request(requestOptions, (response) => forwardResponse({ server, response, resolver, responseFn: eventFns.response })) @@ -378,6 +381,7 @@ function configure ({ } exports.configure = configure +exports.getCurrentLambdaInvoke = getCurrentLambdaInvoke /* istanbul ignore else */ if (process.env.NODE_ENV === 'test') { diff --git a/src/middleware.js b/src/middleware.js deleted file mode 100644 index 89c80803..00000000 --- a/src/middleware.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports.eventContext = ({ - reqPropKey = 'lambda', - deleteHeaders = true -} = {}) => function apiGatewayEventParser (req, res, next) { - 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-lambda-event'])), - context: JSON.parse(decodeURIComponent(req.headers['x-lambda-context'])) - } - - if (deleteHeaders) { - delete req.headers['x-lambda-event'] - delete req.headers['x-lambda-context'] - } - - next() -}