如何使用Express / Node.js访问Amazon SNS发布主体

21

我正在使用Express框架将一个PHP应用程序重构为Node.js。

应用程序的一部分是回调URL,由Amazon SNS通知发送POST请求到该URL。

在PHP中,SNS的POST请求体目前以以下方式被读取(这个方法是有效的):

$notification = json_decode(file_get_contents('php://input'));

在 Express 中,我尝试了以下方法:

app.post('/notification/url', function(req, res) {
    console.log(req.body);
});

然而,通过查看控制台,我们发现当发布文章时只会记录以下信息:

{}

那么,重复一下问题:如何使用Express / Node.js访问Amazon SNS帖子正文


1
作为背景:这种(合理的,天真的)方法失败的原因是SNS HTTP设置。 “Content-Type: text/plain; charset=UTF-8” 请参阅:http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html (SNS实际上不理解JSON) - Jason
5个回答

44

另一种方法是修复Content-Type标题。

以下是用于执行此操作的中间件代码:

exports.overrideContentType = function(){
  return function(req, res, next) {
    if (req.headers['x-amz-sns-message-type']) {
        req.headers['content-type'] = 'application/json;charset=UTF-8';
    }
    next();
  };
}

假设在根项目目录下有一个名为util.js的文件,其中包含:

util = require('./util');

在你的app.js中调用,通过包含以下内容:

app.use(util.overrideContentType());

之前

app.use(express.bodyParser());

app.js 文件中。这使得 bodyParser() 能够正确解析请求体...

更加不会干扰,然后你可以正常访问 req.body


3
这是一个很棒的答案,特别是如果你想使用同一个应用程序来处理来自SNS的通知和订阅确认。 - supaseca
AWS为什么使用“text/plain”作为Content-Type?这真的很让人头疼。很高兴有一个解决方案。 - lasec0203
看看这个主题,并得出自己的结论。注意日期。与其试图寻找解决问题的替代方案,AWS最好修复它。 - TaoTao
现在您可以让Amazon SNS设置正确的“Content-Type”标头为“application/json”。为实现此目的,请修改Amazon SNS订阅的“DeliveryPolicy”属性,将“headerContentType”属性设置为“application/json”或任何其他受支持的值。您可以在此处找到所有支持的值:https://docs.aws.amazon.com/sns/latest/dg/sns-message-delivery-retries.html#creating-delivery-policy - Otavio Ferreira

5

以下是基于AlexGad答案的内容。尤其是这条评论:

标准的Express解析器只能处理application/json、application/x-www-form-encoded和multipart/form-data。我在你的body解析器之前添加了一些代码。

app.post('/notification/url', function(req, res) {
    var bodyarr = []
    req.on('data', function(chunk){
      bodyarr.push(chunk);
    })  
    req.on('end', function(){
      console.log( bodyarr.join('') )
    })  
})

从技术上讲,得票更高的帖子是所述问题的“更干净”的解决方案。让经过充分测试的框架来完成工作... - andreas

4

请查看AWS Node.js SDK,它可以访问所有AWS服务端点。

    var sns = new AWS.SNS();

    // subscribe
    sns.subscribe({topic: "topic", Protocol: "https"}, function (err, data) {
      if (err) {
        console.log(err); // an error occurred
      } else {
        console.log(data); // successful response - the body should be in the data
     }
   });


    // publish example
    sns.publish({topic: "topic", message: "my message"}, function (err, data) {
      if (err) {
        console.log(err); // an error occurred
      } else {
        console.log(data); // successful response - the body should be in the data
     }
   });

编辑:问题在于标准的 body 解析器无法处理 SNS 发送的 plain/text 内容类型。以下代码可用于提取原始 body。请将其放置在 body 解析器之前:
app.use(function(req, res, next) {
    var d= '';
    req.setEncoding('utf8');
    req.on('d', function(chunk) { 
        d+= chunk;
    });
    req.on('end', function() {
        req.rawBody = d;
        next();
    });
});

您可以使用以下方法:

JSON.stringify(req.rawBody));

在你的路由中创建一个javascript对象并适当地操作SNS帖子。

你也可以修改body parser以处理text/plain,但修改中间件不是一个好主意。只需使用上面的代码。


我认为这可能是一个较早的阶段。我的应用程序已经订阅了。SNS确实将帖子发布到其中,因为我可以在控制台中看到它,但我无法访问帖子的正文。 - timstermatic
1
啊,我明白了,抱歉。我现在理解你在做什么。我相信SNS会将内容作为text/plain发送,即使文本是以JSON格式呈现的。你如何设置你的body parsers?标准的express parser只能处理application/json、application/x-www-form-encoded和multipart/form-data。我在上面添加了一些代码,在你的body parser之前放置它。它将为您提供来自请求的rawbody,您可以测试以确保从SNS获取数据。记录这个并且你可能需要编写自己的body parser。 - AlexGad
我无法让这个工作起来。但是req.on和分块技术确实指引了我正确的方向,所以非常感谢您的建议。我已将我的解决方案设置为被采纳的答案。 - timstermatic

1
假设您正在使用body-parser,以下是如何完成此操作的方法。只需将以下行添加到您的app.js中:
app.use(bodyParser.json());
app.use(bodyParser.text({ type: 'text/plain' }));

这些信息也可以在body-parser官方文档中找到:
https://github.com/expressjs/body-parser

+1 对于我的使用情况,同时添加这两个选项是有效的,因为我需要将JSON用于发送邮件,而来自AWS反弹回来的返回类型为text/plain。 - Sachin Tanpure

0

这里的问题在于,Amazon SNS默认将Content-Type标头设置为text/plain。现在,Amazon SNS内置了一种解决方案,支持从主题传递的HTTP消息的自定义Content-Type标头。以下是发布帖子:https://aws.amazon.com/about-aws/whats-new/2023/03/amazon-sns-content-type-request-headers-http-s-notifications/

您需要修改Amazon SNS订阅的DeliveryPolicy属性,将headerContentType属性设置为application/json或其他支持的值。您可以在此处找到所有支持的值:https://docs.aws.amazon.com/sns/latest/dg/sns-message-delivery-retries.html#creating-delivery-policy

{
    "healthyRetryPolicy": {
        "minDelayTarget": 1,
        "maxDelayTarget": 60,
        "numRetries": 50,
        "numNoDelayRetries": 3,
        "numMinDelayRetries": 2,
        "numMaxDelayRetries": 35,
        "backoffFunction": "exponential"
    },
    "throttlePolicy": {
        "maxReceivesPerSecond": 10
    },
    "requestPolicy": {
        "headerContentType": "application/json"
    }
}

您可以通过调用 SubscribeSetSubscriptionAttributes API 操作来设置 DeliveryPolicy 属性:

或者,您也可以使用AWS CloudFormation来设置此策略,使用AWS :: SNS :: Subscription资源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接