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



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

总结

我有一个 SNS 主题,其中包含指向 Amazon API 网关 REST 终端节点的 HTTPS 订阅,该终端节点由用于执行请求的 Node.js Lambda 函数提供支持。

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

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

API 网关终端节点

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

这两种方法都有一个通用映射模板,该模板获取所有路径参数、所有标头参数、所有查询参数和整个请求正文,并将其转换为一个 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('$')
}

然后将此生成的对象作为 lambda 函数操作的event提供给 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"}
}

此终端节点和调用的 Lambda 函数在从 Web 浏览器调用(或 curl 调用(时完美运行。AWS 云观察日志显示,阳光下一切都很好,收到的 Lambda 事件与上述相同,因此调用了映射转换。

问题所在

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

Lambda 函数最终接收原始 SNS JSON 对象,而没有接收我编写的自定义映射。Lambda 函数不会接收有关调用代理、请求的 url、标头的任何信息。哒哒!!以下是 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"
        }
    }
}

可以看出,这个 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"
            }
        }
    }
}

呼救

是否有

任何隐藏设置,我可以使此SNS -> API Gateway -> Lambda与正确的映射翻译一起使用?

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

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

由于 SNS 使用标头"内容类型:文本/纯文本"(SNS 通过 HTTP 发送消息(发送请求,因此它与映射模板的内容类型不匹配,因此将忽略它。要开始工作,您可以更改当前映射中的内容类型,也可以添加另一个与"文本/纯文本"匹配的内容类型。

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

最好

尤尔根

相关内容

  • 没有找到相关文章

最新更新