我在我的rails 3.0应用程序中设置端点,以接收来自Amazon SNS服务的推送通知。
Amazon发布的请求具有JSON有效负载,但是它们在请求上将内容类型设置为" text / plain",这导致Rails无法解析正文。
来自Amazon's docs的帖子请求示例:
POST / HTTP/1.1
x-amz-sns-message-type: Notification
x-amz-sns-message-id: 22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324
x-amz-sns-topic-arn: arn:aws:sns:us-west-2:123456789012:MyTopic
x-amz-sns-subscription-arn: arn:aws:sns:us-west-2:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96
Content-Length: 773
Content-Type: text/plain; charset=UTF-8
Host: myhost.example.com
Connection: Keep-Alive
User-Agent: Amazon Simple Notification Service Agent
{
"Type" : "Notification",
"MessageId" : "22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324",
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
"Subject" : "My First Message",
"Message" : "Hello world!",
"Timestamp" : "2012-05-02T00:54:06.655Z",
"SignatureVersion" : "1",
"Signature" : "EXAMPLEw6JRNwm1LFQL4ICB0bnXrdB8ClRMTQFGBqwLpGbM78tJ4etTwC5zU7O3tS6tGpey3ejedNdOJ+1fkIp9F2/LmNVKb5aFlYq+9rk9ZiPph5YlLmWsDcyC5T+Sy9/umic5S0UQc2PEtgdpVBahwNOdMW4JPwk0kAJJztnc=",
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96"
}
所以在我的控制器中,我可以使用请求对象并读取request.body并自行解析,如下所示:
def receive_notification
if request.content_type =~ /text\/plain/
body = request.body.read.force_encoding("UTF-8")
params.merge(JSON.parse(body))
request.body.rewind
end
# ... go on with rest of controller stuff
end
有人有更好的方法吗?我们可以将它移回链中,这样我的控制器就不用担心请求对象吗?编写在参数解析之前运行的中间件并识别出来自Amazon(通过User-Agent或那些自定义标头)的请求,并将内容类型更改为" application / json&#34,这是一个坏主意。 ;? (你怎么做的?)
答案 0 :(得分:2)
我最近遇到过这个问题。网上提到的大多数方法都不适合我,所以我创建了一个中间件。中间件检测消息是否来自SNS(通过查找类型标头)并强制content_type为application/json
。
在我的情况下,message参数也是作为字符串传递的JSON(来自S3存储桶通知的SNS消息)。这不能处理这种情况,但可以很容易地扩展到这样做。
# app/middleware/sns_content_type.rb
class SnsContentType
def initialize(app, message = "Response Time")
@app = app
end
def call(env)
env['CONTENT_TYPE'] = 'application/json' if env['HTTP_X_AMZ_SNS_MESSAGE_TYPE'].present?
@app.call(env)
end
end
创建中间件后,您需要安装它:
# config/application.rb
config.middleware.insert_before ActionDispatch::ParamsParser, "SnsContentType"
这会在解析参数之前插入中间件,这意味着params解析器将看到JSON内容类型。