AWS SNS 正在绕过 API 网关并直接调用 Lambda 函数

AWS SNS is bypassing API Gateway and calling Lamba functions directly

我偶然发现了一种相当奇怪的行为,据我所知,这是 Amazon SNS 的未记录行为。我正在寻找修复它的解决方案或设置。

摘要

我有一个 SNS 主题,带有指向 Amazon API 网关 REST 端点的 HTTPS 订阅,由 Node.js Lambda 函数支持以执行请求。

现在,如果我在主题上使用 SNS 和 Publish,整个 API 网关 映射模板 得到 ignored/short-circuited。 Lambda 函数 ends-up 仅接收 原始 SNS JSON object.

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

API 网关端点

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

两种方法都有一个通用的映射模板,它获取所有路径参数、所有headers参数、所有查询参数和整个请求body并翻译将其合并为一个大型请求 body JSON object。这是模板的样子:

{
    "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('$')
}

这个结果 object 然后作为 lambda 函数运行的 event 被馈送到 Lambda 函数。这是一个简单的 API 网关 "Test":

的结果
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"}
}

从 Web 浏览器(或 curl 调用)调用时,此端点和调用的 Lambda 函数可以完美运行。 AWS Cloud Watch日志显示一切正常,收到的Lambda事件与上面相同,因此调用了Mapping翻译。

问题

现在,如果我在主题上使用 SNS 和 Publish(顶部列出的 API 网关端点上的 HTTPS 订阅),整个API 网关 映射模板 获取 ignored/short-circuited.

Lambda 函数 ends-up 接收 ONLY 原始 SNS JSON object 和 none 我写的自定义映射. Lambda 函数没有收到有关调用代理、请求的 url、headers 的任何信息... 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"
        }
    }
}

可以看出,这个JSON object完全不一样

思想的食物

  1. 有些人可能想知道:"Why I go to the trouble of making the API Gateway when I could forward SNS events directly to the Lambda functions?"。原因很简单,我需要在 SNS 消息中附加其他信息,在本例中为 phone 发送消息的号码。使用 API 网关,我可以创建尽可能多的 phone 号码订阅,而无需复制任何代码。

  2. 其他人可能想知道:"Why not using the SMS subscription built into SNS instead of making my own?"。一方面,我在加拿大,Amazon SMS 订阅在加拿大不再有效。其次,我可能希望使用亚马逊的另一种短信服务。

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

  4. 事实上,当我在我控制的另一个域上构建另一个 REST 端点时,我确实收到了带有 SNS JSON body 的 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 发送带有 header 'Content-type: text/plain' (SNS Send Message Over HTTP) 的请求,它与您的映射模板的内容类型不匹配,因此将忽略它​​。要开始工作,您可以更改当前映射中的内容类型或添加另一个匹配 'text/plain'.

的内容类型

有关更多详细信息,您还可以在 AWS 论坛中查看此处:Default Content-Type for Mapping Template

最佳,

于尔根