AWS SNS绕过API网关并直接调用Lamba函数

Phi*_*sse 3 amazon-web-services amazon-sns aws-lambda aws-api-gateway

我偶然发现了一个相当奇怪的,据我所知,亚马逊SNS的无证行为.我正在寻找解决方案或设置来解决它.

摘要

我有一个SNS主题,其HTTPS订阅指向一个Amazon API Gateway REST端点,支持Node.js Lambda函数以执行请求.

现在,如果我在主题上使用SNS和发布,整个API网关映射模板将被忽略/短路.Lambda函数最终接收原始SNS JSON对象.

但是,如果我使用Web浏览器(或curl)访问端点,则会调用API网关映射转换,并将正确的JSON数据传递给Lambda函数.

API网关端点

API网关(以下称为TheApi)是使用sms"路径参数"下的资源创建的{phone}.因此,您可以https://TheApi/sms/111-222-3333使用a POSTGET方法进行查询.

这两种方法都有一个通用的映射模板,它可以获取所有路径参数,所有头参数,所有查询参数和整个请求体,并将其转换为一个LARGE请求体JSON对象.这是模板的样子:

{
    "resource-path" : "$context.resourcePath",
    "http-method" : "$context.httpMethod",
    "headers": {
    #foreach($param in $input.params().header.keySet())
      "$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
    #end
    },
    "query": {
    #foreach($param in $input.params().querystring.keySet())
      "$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
    #end
    },
    "paths": {
    #foreach($param in $input.params().path.keySet())
      "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end

    #end
    },
   "body" : $input.json('$')
}
Run Code Online (Sandbox Code Playgroud)

然后将得到的对象作为eventlambda函数运行的Lambda函数提供给Lambda函数.这是一个简单的API网关"测试"的结果:

Tue Feb 09 00:54:13 UTC 2016 : Endpoint request body after transformations:
{
    "resource-path" : "/sms/{phone}",
    "http-method" : "POST",
    "headers": {
        },
    "query": {
        },
    "paths": {
          "phone": "111-222-3333" 
        },
   "body" : {"foo":"bar","Alice":"Bob"}
}
Run Code Online (Sandbox Code Playgroud)

从Web浏览器(或卷曲调用)调用时,此端点和被调用的Lambda函数可以完美地工作.AWS Cloud Watch日志显示在阳光下一切都很好,收到的Lambda事件与上面相同,因此调用映射转换.

问题

现在,如果我在主题(在顶部列出的API网关端点上具有HTTPS订阅的主题)上使用SNS和发布,则整个API网关映射模板将被忽略/短路.

Lambda函数最终接收原始SNS JSON对象,而不是我写的那个自定义映射.Lambda函数不会收到有关呼叫代理,请求的URL,标题的任何信息.... nada !! 这里是Lambda事件的样子,如CloudWatch所示:

{
    "Type": "Notification",
    "MessageId": "d38077e1-406a-5122-8a57-38cecfc635fd",
    "TopicArn": "arn:aws:sns:us-east-1:...:...",
    "Subject": "Ceci est un test",
    "Message": "Ceci est un message de test.",
    "Timestamp": "2016-02-06T06:06:36.649Z",
    "SignatureVersion": "1",
    "Signature": "...",
    "SigningCertURL": "...",
    "MessageAttributes": {
        "AWS.SNS.MOBILE.MPNS.Type": {
            "Type": "String",
            "Value": "token"
        },
        "AWS.SNS.MOBILE.MPNS.NotificationClass": {
            "Type": "String",
            "Value": "realtime"
        },
        "AWS.SNS.MOBILE.WNS.Type": {
            "Type": "String",
            "Value": "wns/badge"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

可以看出,这个JSON对象完全不同.

思想的食物

  1. 有些人可能会想:"为什么我在将SNS事件直接转发给Lambda函数时会遇到制作API网关的麻烦?".原因很简单,我需要附加SNS消息的附加信息,在这种情况下是发送消息的电话号码.使用API​​网关我可以为多个电话号码创建尽可能多的订阅,而无需复制任何代码.

  2. 其他人可能会想:"为什么不使用内置于SNS中的SMS订阅而不是自己制作?".首先,我在加拿大和亚马逊短信订阅不再适用于加拿大.其次,我可能希望使用另一个亚马逊的短信服务.

  3. 事实证明,SNS主题可以直接调用Lambda函数.在这种情况下,SNS JSON对象完全相同.因此,就好像AWS正在检测HTTPS端点域,解析基础Lambda函数并将调用直接路由到Lambda函数而不通过API网关服务.

  4. 事实上,当我在另一个我控制的域上构建另一个REST端点时,我确实收到了带有SNS JSON主体的POST请求,我可以将其转发到API网关端点,并且它可以很好地转换.

像这样:

{
    "resource-path": "/sms/{phone}",
    "http-method": "POST",
    "headers": {
        "Accept": "*/*",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "US",
        "Content-Type": "application/json",
        "Via": "1.1 c903e93e57c533ecd52152e4407a295e.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "Fy_dCf5yJbW1GOZWJMVJqhbz1qt6sLfNO0N33FqAtf56X1tB4py8Ig==",
        "X-Forwarded-For": "69.65.27.156, 54.182.212.5",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "query": {},
    "paths": {
        "phone": "14184901585"
    },
    "body": {
        "Type": "Notification",
        "MessageId": "d38077e1-406a-5122-8a57-38cecfc635fd",
        "TopicArn": "arn:aws:sns:us-east-1:...:...",
        "Subject": "Ceci est un test",
        "Message": "Ceci est un message de test.",
        "Timestamp": "2016-02-06T06:06:36.649Z",
        "SignatureVersion": "1",
        "Signature": "...",
        "SigningCertURL": "...",
        "UnsubscribeURL": "...",
        "MessageAttributes": {
            "AWS.SNS.MOBILE.MPNS.Type": {
                "Type": "String",
                "Value": "token"
            },
            "AWS.SNS.MOBILE.MPNS.NotificationClass": {
                "Type": "String",
                "Value": "realtime"
            },
            "AWS.SNS.MOBILE.WNS.Type": {
                "Type": "String",
                "Value": "wns/badge"
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

寻求帮助

当我可以SNS -> API Gateway -> Lambda使用正确的映射翻译工作时,是否有任何隐藏设置?

Jur*_*gen 5

根据请求的内容类型应用映射模板.如果请求中没有指定内容类型,则默认为"application/json".

根据您的描述,我假设您的映射模板是使用内容类型'application/json'设置的.只要客户端未在其请求中指定不同的内容类型(例如浏览器的情况),这就可以正常工作.

由于SNS使用标题'Content-type:text/plain'(SNS通过HTTP发送消息)发送请求,因此它与您的映射模板的内容类型不匹配,因此将忽略它​​.要开始工作,您可以更改当前映射中的内容类型,也可以添加另一个匹配"text/plain"的内容类型.

有关更多详细信息,您还可以在AWS论坛中查看:映射模板的默认内容类型

最好,

尤尔根