27

我有一个 Sinatra 应用程序,并且在我的大多数控制器中,json 都会进入并在 params 对象中自动获取。但是,我有一个根本不获取参数的 post 操作,除非我使用 before 方法来拉取 request.body 参数,将它们解析为 JSON 并将它们合并到 params 哈希中。

这是控制器以及过滤器方法:

before do
  if request.request_method == "POST"
    body_parameters = request.body.read
    params.merge!(JSON.parse(body_parameters))
  end
end


post '/locations/new' do
  content_type :json
  puts "params after post params method = #{params.inspect}"
  ... other code ...
end

我看到的输出基本上是控制器动作中的参数实际上是正确的。但是,如果我注释掉之前的调用,则参数为空。

之前本身感觉就像一个黑客。无论如何,我都希望那些参数会进来……我一定在里面做错了什么,但我不知道那是什么。

任何帮助将不胜感激......

4

3 回答 3

36

为了回答这个问题,我们首先要查看一些 HTTP 请求(它们只不过是简单telnet的“消息”;这可以很容易地手动重新创建)。首先,当你提交一个普通的 HTML 时会发生什么<form>?该POST请求看起来与此非常相似(可能带有一些额外的参数,但我们现在不需要担心):

POST /submit-form HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 12

name=JohnDoe

逐个字符地键入该字符(将 替换/sample-form为任何表单操作的 URL,并Host替换为您的 IP 或主机名)将与您的浏览器发送的内容相同。从中学习的重要一点是参数语法:formname=formvalue. Sinatra 使用这种语法将POST请求的主体解释为params散列!因此,与此基本不兼容的 JSON 请求不会因此出现在params哈希中。

但是,您在before块中所做的事情显示了正确的解决方案。而params从上面将是{'name' => 'JohnDoe'}request.body.read将返回原始主体,name=JohnDoe

知道了这一点,就可以理解为什么您的“hacky”解决方案有效:POST请求的原始主体由 解释JSON.parse,然后被插入到空params哈希中。它看起来很hacky的原因是因为params在这个例子中是一个不必要的中间人。以下应该做的工作:

post '/locations/new' do
    @json = JSON.parse(request.body.read)
    # @json now contains a hash of the submitted JSON content
end

但是,实施更好实践的解决方案要么仅在提供 JSON 内容时响应,要么在提交标准表单时以不同方式响应。如上面的 HTTPPOST请求示例所示,HTML 表单用application/x-www-form-urlencodedMIME 类型标识,而 JSON 用application/json. 如果您想了解有关检查POST请求的 MIME 类型的详细信息,请查看这个问题,其中包含有关如何使用 Sinatra 执行此操作的一些很好的答案!

于 2012-08-27T08:46:57.450 回答
15

有一个类似的问题: Posting JSON params from java to sinatra service

我找到了一个更好的解决方案来处理它,通过添加一个中间件来为我做同样的事情。我使用了 rack-contrib gem。以下是我在代码中所做的更改:

编辑:使用 git 获取特定版本,当内容类型为application/json;charset=UTF-8

宝石文件:

gem 'rack-contrib', git: 'git@github.com:rack/rack-contrib', ref: 'b7237381e412852435d87100a37add67b2cfbb63'

配置.ru:

use Rack::PostBodyContentTypeParser

来源:https ://www.jaywiggins.com/2010/03/29/Using-Rack-middleware-to-parse-JSON/

于 2012-09-02T08:17:17.660 回答
2

派对迟到了,但如果还有人需要的话——

添加到 goyalankit 的答案:如果您尝试对此进行测试(例如使用 RSpec),它可能无法正常工作,因为标准测试设置不使用中间件。

要在测试中使用中间件:

# spec_helper.rb

OUTER_APP = Rack::Builder.parse_file("config.ru").first

module RSpecMixin
  include Rack::Test::Methods
  def app
    OUTER_APP # typically this might just be Sinatra::Application
  end
end

RSpec.configure do |config|
  config.include RSpecMixin
end

以及示例用法:

it 'is ok' do
  post '/', { key: 'value' }.to_json, { 'CONTENT_TYPE' => 'application/json' }
  expect(last_response).to be_ok
end

还有我的 config.ru:

require 'rack/contrib'
require './app'

use Rack::PostBodyContentTypeParser
run Sinatra::Application
于 2019-09-06T19:29:36.600 回答