Skip to content
24 changes: 16 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,48 @@
'use strict';

const packagePath = 'node_modules/serverless-offline-direct-lambda';
const handlerPath = `proxy.js`;
const handlerPath = 'proxy.js';

class ServerlessPlugin {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;

this.hooks = {
"before:offline:start:init": this.startHandler.bind(this),
'before:offline:start': this.startHandler.bind(this),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work in that it allows me to just run sls offline instead of sls offline start, but sls offline start no longer creates the proxies. Is there any way to support both?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I just tested with both hooks there and it seems like just one of them will trigger when sls offline is invoked with or without start.

};
}

startHandler() {
let location = '';
try {
location = this.serverless.service.custom['serverless-offline'].location;
this.serverless.service.custom['serverless-offline'].location = '';
} catch (_) { }

this.serverless.cli.log('Running Serverless Offline with direct lambda support');

addProxies(this.serverless.service.functions);
addProxies(this.serverless.service.functions, location);
}
}

const addProxies = functionsObject => {
const addProxies = (functionsObject, location) => {
Object.keys(functionsObject).forEach(fn => {

// 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) {
const pf = functionProxy(functionObject);
if (!functionObject.events || !functionObject.events.some((event) => event === 'http')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be faulty - I just tried it locally and it created proxies for all functions, not just those with no http events (tested with serverless 1.27.3).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it is indeed faulty. event is an object with http as key. I'll update the pull request with a fix.

const pf = functionProxy(functionObject, location);
functionsObject[pf.name] = pf;
}
});
};

const functionProxy = functionBeingProxied => ({
const functionProxy = (functionBeingProxied, location) => ({
name: `${functionBeingProxied.name}_proxy`,
handler: `${packagePath}/proxy.handler`,
environment: functionBeingProxied.environment,
events: [
{
http: {
Expand All @@ -46,8 +53,9 @@ const functionProxy = functionBeingProxied => ({
template: {
'application/json': JSON.stringify(
{
location,
body: "$input.json('$')",
targetHandler : functionBeingProxied.handler,
body: "$input.json('$')"
}
)
}
Expand Down
45 changes: 29 additions & 16 deletions proxy.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
const serializeError = require('serialize-error');
const path = require('path');

async function handler(event, context) {
const { ClientContext, FunctionName, InvocationType, LogType, Payload } = event.body;

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('../../' + targetHandlerFile);
const [targetHandlerFile, targetHandlerFunction] = event.targetHandler.split('.');
const target = require(path.resolve(__dirname, '../..', event.location, targetHandlerFile));

const targetEvent = JSON.parse(Payload);
const targetContext = {
...context,
clientContext: JSON.parse(Buffer.from(ClientContext, 'base64')),
};

// call the target function
target[targetHandlerFunction](event.body, context, (error, response) => {
if (error) {
callback(null, {
StatusCode: 500,
FunctionError: 'Handled',
Payload: serializeError(error)
})
} else {
callback(null, {
StatusCode: 200,
Payload: JSON.stringify(response)
})
const funcResult = new Promise((resolve, reject) => {
const result = target[targetHandlerFunction](targetEvent, targetContext, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});

if (result && typeof result.then === 'function' && typeof result.catch === 'function') {
result.then(resolve).catch(reject);
}
});

try {
return { StatusCode: 200, Payload: JSON.stringify(await funcResult) };
} catch (error) {
return { StatusCode: 500, FunctionError: 'Handled', Payload: serializeError(error) };
}
}

module.exports.handler = handler;