Skip to content

Commit 35c6ffa

Browse files
committed
feat: add sns support
1 parent ddb11e9 commit 35c6ffa

23 files changed

+1747
-13
lines changed

README.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Please pull request if you are intersted in it.
2121
- Kinesis Streams
2222
- SQS
2323
- S3
24+
- SNS
2425

2526
## How to use
2627

@@ -77,7 +78,7 @@ resources:
7778
Sample request after deploying.
7879
7980
```bash
80-
curl -X POST https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/kinesis -d '{"message": "some data"}' -H 'Content-Type:application/json'
81+
curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/kinesis -d '{"message": "some data"}' -H 'Content-Type:application/json'
8182
```
8283
8384
### SQS
@@ -102,7 +103,7 @@ resources:
102103
Sample request after deploying.
103104

104105
```bash
105-
curl -X POST https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/sqs -d '{"message": "testtest"}' -H 'Content-Type:application/json'
106+
curl https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/sqs -d '{"message": "testtest"}' -H 'Content-Type:application/json'
106107
```
107108

108109
#### Customizing request parameters
@@ -172,7 +173,32 @@ resources:
172173
Sample request after deploying.
173174

174175
```bash
175-
curl -X POST https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/s3 -d '{"message": "testtest"}' -H 'Content-Type:application/json'
176+
curl https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/s3 -d '{"message": "testtest"}' -H 'Content-Type:application/json'
177+
```
178+
179+
### SNS
180+
181+
Sample syntax for SNS proxy in `serverless.yml`.
182+
183+
```yaml
184+
custom:
185+
apiGatewayServiceProxies:
186+
- sns:
187+
path: /sns
188+
method: post
189+
topicName: { 'Fn::GetAtt': ['SNSTopic', 'TopicName'] }
190+
cors: true
191+
192+
resources:
193+
Resources:
194+
SNSTopic:
195+
Type: AWS::SNS::Topic
196+
```
197+
198+
Sample request after deploying.
199+
200+
```bash
201+
curl https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/sns -d '{"message": "testtest"}' -H 'Content-Type:application/json'
176202
```
177203

178204
## Common API Gateway features
@@ -282,6 +308,8 @@ Source: [AWS::ApiGateway::Method docs](https://docs.aws.amazon.com/AWSCloudForma
282308

283309
### Customizing request body mapping templates
284310

311+
#### Kinesis
312+
285313
If you'd like to add content types or customize the default templates, you can do so by including your custom [API Gateway request mapping template](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) in `serverless.yml` like so:
286314

287315
```yml
@@ -307,4 +335,31 @@ custom:
307335
Fn::GetAtt: [MyStream, Arn]
308336
```
309337

338+
> It is important that the mapping template will return a valid `application/json` string
339+
310340
Source: [How to connect SNS to Kinesis for cross-account delivery via API Gateway](https://theburningmonk.com/2019/07/how-to-connect-sns-to-kinesis-for-cross-account-delivery-via-api-gateway/)
341+
342+
#### SNS
343+
344+
Similar to the [Kinesis](#kinesis-1) support, you can customize the default request mapping templates in `serverless.yml` like so:
345+
346+
```yml
347+
custom:
348+
apiGatewayServiceProxies:
349+
- kinesis:
350+
path: /sns
351+
method: post
352+
topicName: { 'Fn::GetAtt': ['SNSTopic', 'TopicName'] }
353+
request:
354+
template:
355+
application/json:
356+
'Fn::Join':
357+
- ''
358+
- - "Action=Publish&Message=$util.urlEncode('This is a fixed message')&TopicArn=$util.urlEncode('"
359+
- { Ref: MyTopic }
360+
- "')"
361+
```
362+
363+
> It is important that the mapping template will return a valid `application/x-www-form-urlencoded` string
364+
365+
Source: [Connect AWS API Gateway directly to SNS using a service integration](https://www.alexdebrie.com/posts/aws-api-gateway-service-proxy/)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
service: sns-proxy
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs10.x
6+
stage: ${opt:stage, 'dev'}
7+
8+
plugins:
9+
localPath: './../../../../../../'
10+
modules:
11+
- serverless-apigateway-service-proxy
12+
13+
custom:
14+
apiGatewayServiceProxies:
15+
- sns:
16+
path: /sns
17+
method: post
18+
topicName: { 'Fn::GetAtt': ['SNSTopic', 'TopicName'] }
19+
cors: true
20+
request:
21+
template:
22+
application/json:
23+
'Fn::Join':
24+
- ''
25+
- - "Action=Publish&Message=$util.urlEncode('This is a fixed message')&TopicArn=$util.urlEncode('"
26+
- { Ref: SNSTopic }
27+
- "')"
28+
29+
resources:
30+
Resources:
31+
SNSTopic:
32+
Type: AWS::SNS::Topic
33+
Properties:
34+
Subscription:
35+
- Endpoint: { 'Fn::GetAtt': ['SqsQueue', 'Arn'] }
36+
Protocol: sqs
37+
38+
SqsQueue:
39+
Type: AWS::SQS::Queue
40+
41+
SqsQueuePolicy:
42+
Type: AWS::SQS::QueuePolicy
43+
Properties:
44+
PolicyDocument:
45+
Version: '2012-10-17'
46+
Id: SqsQueuePolicy
47+
Statement:
48+
- Sid: Allow-SendMessage-To-Queue-From-SNS-Topic
49+
Effect: Allow
50+
Principal: "*"
51+
Action:
52+
- sqs:SendMessage
53+
Resource: "*"
54+
Condition:
55+
ArnEquals:
56+
aws:SourceArn:
57+
Ref: SNSTopic
58+
59+
Queues:
60+
- Ref: SqsQueue
61+
62+
Outputs:
63+
SqsQueueUrl:
64+
Value: { Ref: SqsQueue }
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict'
2+
3+
const { default: awsTesting } = require('aws-testing-library/lib/chai')
4+
const chai = require('chai')
5+
chai.use(awsTesting)
6+
const { expect } = chai
7+
8+
const fetch = require('node-fetch')
9+
const { deployWithRandomStage, removeService } = require('../../../utils')
10+
11+
describe('Single SNS Proxy Integration Test', () => {
12+
let endpoint
13+
let stage
14+
let region
15+
let queueUrl
16+
const config = '__tests__/integration/sns/custom-mapping-template/service/serverless.yml'
17+
18+
beforeAll(async () => {
19+
;({
20+
stage,
21+
region,
22+
endpoint,
23+
outputs: { SqsQueueUrl: queueUrl }
24+
} = await deployWithRandomStage(config))
25+
})
26+
27+
afterAll(() => {
28+
removeService(stage, config)
29+
})
30+
31+
it('should get correct response from sqs proxy endpoint', async () => {
32+
const testEndpoint = `${endpoint}/sns`
33+
34+
const response = await fetch(testEndpoint, {
35+
method: 'POST',
36+
headers: { 'Content-Type': 'application/json' }
37+
})
38+
39+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
40+
expect(response.status).to.be.equal(200)
41+
const json = await response.json()
42+
43+
expect(json).to.have.own.property('PublishResponse')
44+
45+
expect(json.PublishResponse).to.have.own.property('PublishResult')
46+
expect(json.PublishResponse).to.have.own.property('ResponseMetadata')
47+
48+
expect(json.PublishResponse.PublishResult).to.have.own.property('MessageId')
49+
expect(json.PublishResponse.PublishResult).to.have.own.property('SequenceNumber')
50+
51+
expect(json.PublishResponse.ResponseMetadata).to.have.own.property('RequestId')
52+
53+
const expectedFixedMessage = 'This is a fixed message'
54+
await expect({
55+
region,
56+
queueUrl,
57+
timeout: 10000,
58+
pollEvery: 2500
59+
}).to.have.message((message) => message.Message === expectedFixedMessage)
60+
})
61+
})
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
service: multiple-sns-proxy
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs10.x
6+
7+
plugins:
8+
localPath: './../../../../../../'
9+
modules:
10+
- serverless-apigateway-service-proxy
11+
12+
custom:
13+
apiGatewayServiceProxies:
14+
- sns:
15+
path: /sns1
16+
method: post
17+
topicName: { 'Fn::GetAtt': ['SNSTopic1', 'TopicName'] }
18+
cors: true
19+
20+
- sns:
21+
path: /sns2
22+
method: post
23+
topicName: { 'Fn::GetAtt': ['SNSTopic2', 'TopicName'] }
24+
cors: true
25+
26+
- sns:
27+
path: /sns3
28+
method: post
29+
topicName: { 'Fn::GetAtt': ['SNSTopic3', 'TopicName'] }
30+
cors: true
31+
32+
resources:
33+
Resources:
34+
SNSTopic1:
35+
Type: AWS::SNS::Topic
36+
Properties:
37+
Subscription:
38+
- Endpoint: { 'Fn::GetAtt': ['SqsQueue', 'Arn'] }
39+
Protocol: sqs
40+
41+
SNSTopic2:
42+
Type: AWS::SNS::Topic
43+
Properties:
44+
Subscription:
45+
- Endpoint: { 'Fn::GetAtt': ['SqsQueue', 'Arn'] }
46+
Protocol: sqs
47+
48+
SNSTopic3:
49+
Type: AWS::SNS::Topic
50+
Properties:
51+
Subscription:
52+
- Endpoint: { 'Fn::GetAtt': ['SqsQueue', 'Arn'] }
53+
Protocol: sqs
54+
55+
SqsQueue:
56+
Type: AWS::SQS::Queue
57+
58+
SqsQueuePolicy:
59+
Type: AWS::SQS::QueuePolicy
60+
Properties:
61+
PolicyDocument:
62+
Version: '2012-10-17'
63+
Id: SqsQueuePolicy
64+
Statement:
65+
- Sid: Allow-SendMessage-To-Queue-From-SNS-Topic1
66+
Effect: Allow
67+
Principal: "*"
68+
Action:
69+
- sqs:SendMessage
70+
Resource: "*"
71+
Condition:
72+
ArnEquals:
73+
aws:SourceArn:
74+
Ref: SNSTopic1
75+
76+
- Sid: Allow-SendMessage-To-Queue-From-SNS-Topic2
77+
Effect: Allow
78+
Principal: "*"
79+
Action:
80+
- sqs:SendMessage
81+
Resource: "*"
82+
Condition:
83+
ArnEquals:
84+
aws:SourceArn:
85+
Ref: SNSTopic2
86+
87+
- Sid: Allow-SendMessage-To-Queue-From-SNS-Topic3
88+
Effect: Allow
89+
Principal: "*"
90+
Action:
91+
- sqs:SendMessage
92+
Resource: "*"
93+
Condition:
94+
ArnEquals:
95+
aws:SourceArn:
96+
Ref: SNSTopic3
97+
98+
Queues:
99+
- Ref: SqsQueue
100+
101+
Outputs:
102+
SqsQueueUrl:
103+
Value: { Ref: SqsQueue }
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict'
2+
3+
const { default: awsTesting } = require('aws-testing-library/lib/chai')
4+
const chai = require('chai')
5+
chai.use(awsTesting)
6+
const { expect } = chai
7+
8+
const fetch = require('node-fetch')
9+
const { deployWithRandomStage, removeService } = require('../../../utils')
10+
11+
describe('Multiple SQS Proxy Integrations Test', () => {
12+
let endpoint
13+
let stage
14+
let region
15+
let queueUrl
16+
const config = '__tests__/integration/sns/multiple-integrations/service/serverless.yml'
17+
18+
beforeAll(async () => {
19+
;({
20+
stage,
21+
region,
22+
endpoint,
23+
outputs: { SqsQueueUrl: queueUrl }
24+
} = await deployWithRandomStage(config))
25+
})
26+
27+
afterAll(() => {
28+
removeService(stage, config)
29+
})
30+
31+
it('should get correct response from multiple sqs proxy endpoints', async () => {
32+
const topics = ['sns1', 'sns2', 'sns3']
33+
34+
for (const topic of topics) {
35+
const testEndpoint = `${endpoint}/${topic}`
36+
37+
const body = JSON.stringify({ message: `message for ${topic}` })
38+
const response = await fetch(testEndpoint, {
39+
method: 'POST',
40+
headers: { 'Content-Type': 'application/json' },
41+
body
42+
})
43+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
44+
expect(response.status).to.be.equal(200)
45+
const json = await response.json()
46+
47+
expect(json).to.have.own.property('PublishResponse')
48+
49+
expect(json.PublishResponse).to.have.own.property('PublishResult')
50+
expect(json.PublishResponse).to.have.own.property('ResponseMetadata')
51+
52+
expect(json.PublishResponse.PublishResult).to.have.own.property('MessageId')
53+
expect(json.PublishResponse.PublishResult).to.have.own.property('SequenceNumber')
54+
55+
expect(json.PublishResponse.ResponseMetadata).to.have.own.property('RequestId')
56+
57+
await expect({
58+
region,
59+
queueUrl,
60+
timeout: 10000,
61+
pollEvery: 2500
62+
}).to.have.message((message) => message.Message === body)
63+
}
64+
})
65+
})

0 commit comments

Comments
 (0)