Skip to content
This repository has been archived by the owner on Aug 8, 2024. It is now read-only.

Commit

Permalink
Added SQS route handler (#16)
Browse files Browse the repository at this point in the history
added sqs route handler
  • Loading branch information
jpavek authored and connystrecker committed Nov 14, 2018
1 parent 752743c commit dd1cc5c
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.idea
.idea
*.iml
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A small library for [AWS Lambda](https://aws.amazon.com/lambda/details) providin
* Lambda Proxy Resource support for AWS API Gateway
* Enable CORS for requests
* No external dependencies
* Currently there are two `processors` (callers for Lambda) implemented: API Gateway ANY method (called proxyIntegration) and SNS.
* Currently there are two `processors` (callers for Lambda) implemented: API Gateway ANY method (called proxyIntegration), SNS and SQS.

## Installation
Install via npm
Expand Down Expand Up @@ -141,6 +141,38 @@ exports.handler = router.handler({
});
```
## SQS to Lambda Integrations
For handling calls in Lambdas initiated from AWS-SQS you can use the following code snippet:
```js
const router = require('aws-lambda-router');

exports.handler = router.handler({
sqs: {
routes: [
{
// match complete SQS ARN:
source: 'arn:aws:sqs:us-west-2:594035263019:aticle-import',
// Attention: the messages Array is JSON-stringified
action: (messages, context) => messages.forEach(message => console.log(JSON.parse(message)))
},
{
// a regex to match the source SQS ARN:
source: /.*notification/,
// Attention: the messages array is JSON-stringified
action: (messages, context) => service.doNotify(messages)
}
]
}
});
```
An SQS message always contains an array of records. In each SQS record there is the message in the body JSON key.
The `action` method gets all body elements from the router as an array.
If more than one route matches, only the **first** is used!
### Custom response
Per default a status code 200 will be returned. This behavior can be overridden.
Expand Down Expand Up @@ -174,6 +206,7 @@ See here: https://yarnpkg.com/en/docs/cli/link
## Release History
* 0.5.0 new feature: SQS route integration now available; bugfix: SNS integration now works woth Array of message instead of single message
* 0.4.0 now [the Context Object](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html) pass through
* 0.3.1 proxyIntegration: avoid error if response object is not set; add some debug logging
* 0.3.0 proxyIntegration: add PATCH method; allow for custom status codes from route (thanks to [@mintuz](https://github.com/mintuz))
Expand Down
17 changes: 14 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function handler(routeConfig: RouteConfig): any;
export interface ProxyIntegrationRoute {
path: string;
method: string;
action?: (request: any, context: any) => any;
action: (request: any, context: any) => any;
}

export interface ProxyIntegrationConfig {
Expand All @@ -18,16 +18,27 @@ export interface ProxyIntegrationConfig {
}

export interface SnsRoute {
subject: any;
action?: (sns: any, context: any) => any;
subject: RegExp;
action: (sns: any, context: any) => any;
}

export interface SnsConfig {
routes: SnsRoute[];
debug?: boolean;
}

export interface SqsRoute {
source: string | RegExp;
action: (messages: any[], context: any) => any;
}

export interface SqsConfig {
routes: SqsRoute[];
debug?: boolean;
}

export interface RouteConfig {
proxyIntegration?: ProxyIntegrationConfig;
sns?: SnsConfig;
sqs?: SqsConfig;
}
75 changes: 75 additions & 0 deletions lib/sqs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use strict";

function process(sqsConfig, event, context) {
// detect if it's an sqs-event at all:
if (sqsConfig.debug) {
console.log('sqs:Event', JSON.stringify(event));
console.log('sqs:context', context);
}

if (!Array.isArray(event.Records) || event.Records.length < 1 || event.Records[0].eventSource !== 'aws:sqs') {
console.log('Event does not look like SQS');
return null;
}

const records = event.Records;
const recordSourceArn = records[0].eventSourceARN;
for (let routeConfig of sqsConfig.routes) {
if (routeConfig.source instanceof RegExp) {
if (routeConfig.source.test(recordSourceArn)) {
const result = routeConfig.action(records.map(record => record.body) , context);
return result || {};
}
} else {
if (routeConfig.source === recordSourceArn) {
const result = routeConfig.action(records.map(record => record.body) , context);
return result || {};
}
}
}

if (sqsConfig.debug) {
console.log(`No source-match for ${recordSourceArn}`);
}

return null;
}

module.exports = process;

/*
const cfgExample = {
routes:[
{
source: /.*\/,
action: (record, context) => service.import(JSON.parse(record.body), context)
}
]
};
*/


/* this is an example for a standard SQS notification message:
{
"Records": [
{
"messageId": "c80e8021-a70a-42c7-a470-796e1186f753",
"receiptHandle": "AQEBJQ+/u6NsnT5t8Q/VbVxgdUl4TMKZ5FqhksRdIQvLBhwNvADoBxYSOVeCBXdnS9P+erlTtwEALHsnBXynkfPLH3BOUqmgzP25U8kl8eHzq6RAlzrSOfTO8ox9dcp6GLmW33YjO3zkq5VRYyQlJgLCiAZUpY2D4UQcE5D1Vm8RoKfbE+xtVaOctYeINjaQJ1u3mWx9T7tork3uAlOe1uyFjCWU5aPX/1OHhWCGi2EPPZj6vchNqDOJC/Y2k1gkivqCjz1CZl6FlZ7UVPOx3AMoszPuOYZ+Nuqpx2uCE2MHTtMHD8PVjlsWirt56oUr6JPp9aRGo6bitPIOmi4dX0FmuMKD6u/JnuZCp+AXtJVTmSHS8IXt/twsKU7A+fiMK01NtD5msNgVPoe9JbFtlGwvTQ==",
"body": "{\"foo\":\"bar\"}",
"attributes": {
"ApproximateReceiveCount": "3",
"SentTimestamp": "1529104986221",
"SenderId": "594035263019",
"ApproximateFirstReceiveTimestamp": "1529104986230"
},
"messageAttributes": {},
"md5OfBody": "9bb58f26192e4ba00f01e2e7b136bbd8",
"eventSource": "aws:sqs",
"eventSourceARN": "arn:aws:sqs:eu-central-1:594035263019:article-import",
"awsRegion": "eu-central-1"
}
]
}
*/
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aws-lambda-router",
"version": "0.4.1",
"version": "0.5.0",
"description": "AWS lambda router",
"main": "index.js",
"types": "index.d.ts",
Expand Down
13 changes: 13 additions & 0 deletions test/sns.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,18 @@ describe('sns.processor', () => {
expect(sns(snsCfg, event)).toBe(2);
});

it('should not fail on missing subject', () => {
const snsCfg = {routes: [{action: () => 1}]};
sns(snsCfg, {Records: [{Sns: {Subject: 'Subject'}}]});
});

it('should fail on missing action', () => {
const snsCfg = {routes: [{subject: /.*/}]};
try {
sns(snsCfg, {Records: [{Sns: {Subject: 'Subject'}}]});
fail();
} catch (e) {
}
});

});
79 changes: 79 additions & 0 deletions test/sqs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";

describe('sqs.processor', () => {

const sqs = require('../lib/sqs');

it('context should be passed through', () => {
const actionSpy = jasmine.createSpy('action');

const context = {bla: "blup"};
const sqsCfg = {routes: [{source: /.*/, action: actionSpy}]};
const event = {Records: [{eventSource: 'aws:sqs', body: 'B'}]};

sqs(sqsCfg, event, context);

expect(actionSpy).toHaveBeenCalledWith([event.Records[0].body], context);
});

it('should ignore event if it is no SQS event', () => {
const sqsCfg = {routes: [{source: /.*/, action: () => 1}]};
expect(sqs(sqsCfg, {})).toBe(null);
expect(sqs(sqsCfg, {Records: 1})).toBe(null);
expect(sqs(sqsCfg, {Records: []})).toBe(null);
});

it('should match null source for ".*"', () => {
const sqsCfg = {routes: [{source: /.*/, action: () => 1}]};
expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: null}]})).toBe(1);
});

it('should match empty subject for ".*"', () => {
const sqsCfg = {routes: [{subject: /.*/, action: () => 1}]};
expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', body: 'B'}]})).toBe(1);
});

it('should match source for "/porter/"', () => {
const sqsCfg = {routes: [{source: /porter/, action: () => 1}]};
expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]})).toBe(1);
});

it('should call action with sqs-message', () => {
const sqsCfg = {routes: [{source: /porter/, action: (events) => events}]};
const event = {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer', body: 'B'}]};

expect(sqs(sqsCfg, event)).toEqual([event.Records[0].body]);
});

it('should call first action with matching subject', () => {
const sqsCfg = {
routes: [
{source: /^123$/, action: () => 1},
{source: /123/, action: () => 2},
{source: /1234/, action: () => 3}
]
};
const event = {Records: [{eventSource: 'aws:sqs', eventSourceARN: '1234', body: 'B'}]};
expect(sqs(sqsCfg, event)).toBe(2);
});

it('should match complete source', () => {
const sqsCfg = {routes: [{source: 'aws:123:importer', action: () => 1}]};
expect(sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'aws:123:importer'}]})).toBe(1);
});

it('should not throw error on missing source', () => {
const sqsCfg = {routes: [{action: () => 1}]};
sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]});
});

it('should fail on missing action', () => {
const sqsCfg = {routes: [{source: /.*/}]};
try {
sqs(sqsCfg, {Records: [{eventSource: 'aws:sqs', eventSourceARN: 'importer'}]});
fail();
} catch (e) {
}
});

});
11 changes: 0 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1241,13 +1241,6 @@ jasmine-core@~3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.2.1.tgz#8e4ff5b861603ee83343f2b49eee6a0ffe9650ce"

[email protected]:
version "2.3.2"
resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz#898818ffc234eb8b3f635d693de4586f95548d43"
dependencies:
mkdirp "^0.5.1"
xmldom "^0.1.22"

jasmine-terminal-reporter@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/jasmine-terminal-reporter/-/jasmine-terminal-reporter-1.0.3.tgz#896f1ec8fdf4bf6aecdd41c503eda7347f61526b"
Expand Down Expand Up @@ -2478,10 +2471,6 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"

xmldom@^0.1.22:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"

xtend@~4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
Expand Down

0 comments on commit dd1cc5c

Please sign in to comment.