From e83bd4dd61295c1d19d3bf6ba537ad5ced48cd65 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 11 Jan 2019 10:28:11 +1000 Subject: [PATCH 1/3] include support to invoke via the AWS SDK --- README.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++--------- index.js | 30 +++++++++++++++++++++- proxy.js | 18 +++++++++++--- 3 files changed, 106 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0b58f64..50e81ee 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ # serverless-offline-direct-lambda A Serverless Offline plugin that exposes lambdas with no API Gateway event via HTTP, to allow offline direct lambda-to-lambda interactions. - + +## Setup Note - this requires the plugin 'serverless-offline'. To include in your project, add the following to the plugins section in serverless.yml: ``` - serverless-offline-direct-lambda +``` + +You may also want to change the port that the plugin runs on - you can do this by specifying the following custom config in your serverless yml file: + +```yml +custom: + serverless-offline: + port: 4000 ``` + +## Running & Calling To run: @@ -21,7 +32,7 @@ The plugin will create api-gateway proxies for all lambdas with *no* triggering You will see output like this: -``` +```bash $ sls offline start Serverless: Running Serverless Offline with direct lambda support Serverless: Starting Offline: dev/us-east-1. @@ -30,15 +41,56 @@ Serverless: Routes for myLambda: Serverless: (none) Serverless: Routes for my-project-dev-myLambda_proxy: -Serverless: POST /proxy/my-project-dev-myLambda +Serverless: POST /proxy/my-project-dev-myLambda +Serverless: POST /2015-03-31/functions/my-project-dev-myLambda/invocations ``` - + +### Calling via HTTP Post: + The body of the POST should match the JSON data that would ordinarily be passed in a lambda-to-lambda call. i.e. + +```bash +curl -X POST \ + http://localhost:4000/proxy/my-project-dev-myLambda \ + -H 'Cache-Control: no-cache' \ + -H 'Content-Type: application/json' \ + -d '{ + "some-key": "some-value", + "other-key": false +}' +``` + +### Invoking the function via the AWS SDK: + +You may also invoke the function by using the AWS SDK on your client side... +This can be done by specifying a custom "endpoint" in your Lambda configuration like so: + +```javascript + +var AWS = require('aws-sdk'); +AWS.config.region = 'us-east-1'; + +let lambda = new AWS.Lambda({ + region: 'us-east-1', + endpoint: 'http://localhost:4000' +}) + +var lambda_args = { + "some-key": "some-value", + "other-key": false +} + +var params = { + FunctionName: 'my-project-dev-myLambda', // the lambda function we are going to invoke + Payload: JSON.stringify(lambda_args) +}; + +lambda.invoke(params, function(err, data) { + if (err) { + console.error(err); + } else { + console.dir(data); + } +}) + ``` -{ - "Payload":... -} -``` - -On the client side, abstract the decision to use a direct AWS.lambda.invoke() call or an http call to the proxy using: -https://github.com/civicteam/lambda-wrapper diff --git a/index.js b/index.js index f8683f1..b60328d 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,7 @@ const addProxies = (functionsObject, location) => { // filter out functions with event config, // leaving just those intended for direct lambda-to-lambda invocation const functionObject = functionsObject[fn]; - if (!functionObject.events || functionObject.events.length == 0) { + if (!functionObject.events || functionObject.events.length === 0) { const pf = functionProxy(functionObject, location); functionsObject[pf.name] = pf; } @@ -44,6 +44,7 @@ const functionProxy = (functionBeingProxied, location) => ({ handler: `${packagePath}/proxy.handler`, environment: functionBeingProxied.environment, events: [ + // This is the original `/post/FUNCTION-NAME` from the plugin... { http: { method: 'POST', @@ -64,6 +65,33 @@ const functionProxy = (functionBeingProxied, location) => ({ headers: {} } } + }, + + // Additional support to call the function from the AWS SDK directly... + { + http: { + method: 'POST', + // This is the path to the Lambda API.. + path: `2015-03-31/functions/${functionBeingProxied.name}/invocations`, + integration: 'lambda', + request: { + template: { + // NB: AWS SDK for NodeJS specifies as 'binary/octet-stream' not 'application/json' + 'binary/octet-stream': JSON.stringify( + { + location, + body: "$input.body", + targetHandler : functionBeingProxied.handler, + } + ) + } + }, + response: { + headers: { + "Content-Type": "application/json" + } + } + } } ], package: { diff --git a/proxy.js b/proxy.js index 39c508c..fbdf05d 100644 --- a/proxy.js +++ b/proxy.js @@ -2,12 +2,20 @@ const serializeError = require('serialize-error'); const path = require('path'); function handler(event, context, callback) { - // extract the path to the handler (relative to the project root) - // and the function to call on the handler + const [targetHandlerFile, targetHandlerFunction] = event.targetHandler.split("."); - const target = require(path.resolve(__dirname, '../..', event.location, targetHandlerFile)); + const target = require(path.resolve(__dirname, '../..', targetHandlerFile)); + + // target[targetHandlerFunction](event.body, context) + // .then((data) => { + // let response = data; + // response.body = JSON.stringify(data.body); + // callback(null, response); + // }) + // .catch((error) => { + // callback(null, serializeError(error)); + // }); - // call the target function target[targetHandlerFunction](event.body, context, (error, response) => { if (error) { callback(null, { @@ -22,6 +30,8 @@ function handler(event, context, callback) { }) } }); + + } module.exports.handler = handler; From b91c36e95081e9992f9ce63afdc189111f5a389d Mon Sep 17 00:00:00 2001 From: David Date: Mon, 14 Jan 2019 12:40:39 +1000 Subject: [PATCH 2/3] update to include correct callbacks from 200/500 from serverless function when developing locally --- README.md | 80 +++++++++++++++++++++++++++---------------------------- index.js | 4 +-- proxy.js | 28 +++++++------------ 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 50e81ee..75babb5 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# serverless-offline-direct-lambda -A Serverless Offline plugin that exposes lambdas with no API Gateway event via HTTP, to allow offline direct lambda-to-lambda interactions. - -## Setup -Note - this requires the plugin 'serverless-offline'. - -To include in your project, add the following to the plugins section in serverless.yml: - -``` -- serverless-offline-direct-lambda +# serverless-offline-direct-lambda +A Serverless Offline plugin that exposes lambdas with no API Gateway event via HTTP, to allow offline direct lambda-to-lambda interactions. + +## Setup +Note - this requires the plugin 'serverless-offline'. + +To include in your project, add the following to the plugins section in serverless.yml: + +``` +- serverless-offline-direct-lambda ``` You may also want to change the port that the plugin runs on - you can do this by specifying the following custom config in your serverless yml file: @@ -16,40 +16,40 @@ You may also want to change the port that the plugin runs on - you can do this b custom: serverless-offline: port: 4000 -``` +``` ## Running & Calling - -To run: - -``` -servlerless offline start -``` - -(calling the command 'start' is necessary to trigger the plugin, simply running 'serverless online' does not trigger the start hooks). - -The plugin will create api-gateway proxies for all lambdas with *no* triggering events. - -You will see output like this: - -```bash -$ sls offline start -Serverless: Running Serverless Offline with direct lambda support -Serverless: Starting Offline: dev/us-east-1. - -Serverless: Routes for myLambda: -Serverless: (none) - -Serverless: Routes for my-project-dev-myLambda_proxy: + +To run: + +``` +servlerless offline start +``` + +(calling the command 'start' is necessary to trigger the plugin, simply running 'serverless online' does not trigger the start hooks). + +The plugin will create api-gateway proxies for all lambdas with *no* triggering events. + +You will see output like this: + +```bash +$ sls offline start +Serverless: Running Serverless Offline with direct lambda support +Serverless: Starting Offline: dev/us-east-1. + +Serverless: Routes for myLambda: +Serverless: (none) + +Serverless: Routes for my-project-dev-myLambda_proxy: Serverless: POST /proxy/my-project-dev-myLambda -Serverless: POST /2015-03-31/functions/my-project-dev-myLambda/invocations -``` +Serverless: POST /2015-03-31/functions/my-project-dev-myLambda/invocations +``` -### Calling via HTTP Post: +### Calling via HTTP Post: -The body of the POST should match the JSON data that would ordinarily be passed in a lambda-to-lambda call. i.e. +The body of the POST should match the JSON data that would ordinarily be passed in a lambda-to-lambda call. i.e. -```bash +```bash curl -X POST \ http://localhost:4000/proxy/my-project-dev-myLambda \ -H 'Cache-Control: no-cache' \ @@ -57,7 +57,7 @@ curl -X POST \ -d '{ "some-key": "some-value", "other-key": false -}' +}' ``` ### Invoking the function via the AWS SDK: @@ -93,4 +93,4 @@ lambda.invoke(params, function(err, data) { } }) -``` +``` diff --git a/index.js b/index.js index b60328d..8784497 100644 --- a/index.js +++ b/index.js @@ -87,9 +87,7 @@ const functionProxy = (functionBeingProxied, location) => ({ } }, response: { - headers: { - "Content-Type": "application/json" - } + headers: {} } } } diff --git a/proxy.js b/proxy.js index fbdf05d..6c6846b 100644 --- a/proxy.js +++ b/proxy.js @@ -6,28 +6,18 @@ function handler(event, context, callback) { const [targetHandlerFile, targetHandlerFunction] = event.targetHandler.split("."); const target = require(path.resolve(__dirname, '../..', targetHandlerFile)); - // target[targetHandlerFunction](event.body, context) - // .then((data) => { - // let response = data; - // response.body = JSON.stringify(data.body); - // callback(null, response); - // }) - // .catch((error) => { - // callback(null, serializeError(error)); - // }); - target[targetHandlerFunction](event.body, context, (error, response) => { + if (error) { - callback(null, { - StatusCode: 500, - FunctionError: 'Handled', - Payload: serializeError(error) - }) + // Return Serverless error to AWS sdk + callback(null, { + StatusCode: 500, + FunctionError: 'Handled', + Payload: serializeError(error) + }) } else { - callback(null, { - StatusCode: 200, - Payload: JSON.stringify(response) - }) + // Return lambda function response to AWS SDK & pass through args from serverless. + callback(null, response) } }); From b5553962e1a9fa8221134af3b9ad27ec31db186a Mon Sep 17 00:00:00 2001 From: David Date: Fri, 18 Jan 2019 16:11:49 +1000 Subject: [PATCH 3/3] add support for both binary content header and application/json --- README.md | 8 ++++- index.js | 93 +++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 75babb5..d64b42a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,10 @@ The plugin will create api-gateway proxies for all lambdas with *no* triggering You will see output like this: ```bash -$ sls offline start +export AWS_SDK_USED=node + +sls offline start + Serverless: Running Serverless Offline with direct lambda support Serverless: Starting Offline: dev/us-east-1. @@ -65,6 +68,8 @@ curl -X POST \ You may also invoke the function by using the AWS SDK on your client side... This can be done by specifying a custom "endpoint" in your Lambda configuration like so: +**Note:** the AWS SDK for NodeJS actually sends a different content type header on it's request to the Lambda API then all the other AWS SDK's (Python, Rails etc).. You will need to `export AWS_SDK_USED=node` before running the `serverless offline` if you wish to use this with the NodeJS AWS SDK. + ```javascript var AWS = require('aws-sdk'); @@ -94,3 +99,4 @@ lambda.invoke(params, function(err, data) { }) ``` + diff --git a/index.js b/index.js index 8784497..0dd87b2 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,72 @@ const packagePath = 'node_modules/serverless-offline-direct-lambda'; const handlerPath = `proxy.js`; +var AWS_SDK_USED = process.env.AWS_SDK_USED || 'rails'; +function AWS_SDK_METHOD(functionBeingProxied, location) { + + if(AWS_SDK_USED == 'node') { + + // Additional support to call the function from the AWS SDK (NodeJS) directly... + var AWS_SDK_NODE_METHOD = { + http: { + method: 'POST', + // This is the path to the Lambda API.. + path: `2015-03-31/functions/${functionBeingProxied.name}/invocations`, + integration: 'lambda', + request: { + template: { + // NB: AWS SDK for NodeJS specifies as 'binary/octet-stream' not 'application/json' + 'binary/octet-stream': JSON.stringify( + { + location, + body: "$input.body", + targetHandler : functionBeingProxied.handler, + } + ) + } + }, + response: { + headers: { + "Content-Type": "application/json" + } + } + } + }; + return AWS_SDK_NODE_METHOD; + + } else { + + // Additional support to call the function from the All other SDK's (Don't ask why AWS did it like this ......) + var AWS_SDK_RAILS_METHOD = { + http: { + method: 'POST', + // This is the path to the Lambda API.. + path: `2015-03-31/functions/${functionBeingProxied.name}/invocations`, + integration: 'lambda', + request: { + template: { + // NB: AWS SDK for NodeJS specifies as 'binary/octet-stream' not 'application/json' + 'application/json': JSON.stringify( + { + location, + body: "$input.json('$')", + targetHandler : functionBeingProxied.handler, + } + ) + } + }, + response: { + headers: { + "Content-Type": "application/json" + } + } + } + }; + return AWS_SDK_RAILS_METHOD; + } + +}; + class ServerlessPlugin { constructor(serverless, options) { this.serverless = serverless; @@ -66,31 +132,10 @@ const functionProxy = (functionBeingProxied, location) => ({ } } }, + + // See methods above for further details + AWS_SDK_METHOD(functionBeingProxied, location) - // Additional support to call the function from the AWS SDK directly... - { - http: { - method: 'POST', - // This is the path to the Lambda API.. - path: `2015-03-31/functions/${functionBeingProxied.name}/invocations`, - integration: 'lambda', - request: { - template: { - // NB: AWS SDK for NodeJS specifies as 'binary/octet-stream' not 'application/json' - 'binary/octet-stream': JSON.stringify( - { - location, - body: "$input.body", - targetHandler : functionBeingProxied.handler, - } - ) - } - }, - response: { - headers: {} - } - } - } ], package: { include: [handlerPath],