web-dev-qa-db-ja.com

JSONPOSTリクエストでSinatraコントローラーのparamsメソッドが空になります

私はSinatraアプリを持っていて、ほとんどのコントローラーでjsonが入って来て、paramsオブジェクトで自動的に取得されます。ただし、beforeメソッドでrequest.bodyパラメーターをプルしてJSONとして解析し、paramsハッシュにマージしない限り、paramsをまったく取得しないpostアクションがあります。

これがコントローラーとフィルターメソッドです。

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

私が見る出力は、基本的に、コントローラーアクションのパラメーターが実際に正しくそこにあるということです。ただし、before呼び出しをコメントアウトすると、パラメーターは空になります。

前自体はハックのように感じます。私はそれらのパラメータが何があっても入ってくることを期待します...私はそこで何か間違ったことをしているに違いありませんが、それが何であるかわかりません。

どんな助けでも深く感謝します...

26
jaydel

この質問に答えるために、最初にいくつかの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

その文字を1文字ずつ入力する(/sample-formをフォームアクションのURLに置き換え、HostをIPまたはホスト名に置き換える)ことは、ブラウザーが送信するものと同じです。これから学ぶべき重要なことは、パラメータ構文formname=formvalueです。 Sinatraは、この構文を使用してPOSTリクエストの本文をparamsハッシュに解釈します!したがって、これと実質的に互換性のないJSONリクエストはnotは、このためparamsハッシュに表示されません。

ただし、beforeブロックで実行していることは、適切な解決策を示しています。上記のparams{'name' => 'JohnDoe'}になりますが、request.body.readは元の本文name=JohnDoeを返します。

これを知ることで、「ハッキー」ソリューションが機能する理由を理解できるようになります。POSTリクエストの元の本文は、JSON.parseによって解釈され、空のparamsハッシュに挿入されます。ハッキーに見える理由は、この例では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-urlencoded MIMEタイプで識別されますが、JSONはapplication/jsonで識別されます。 POSTリクエストのMIMEタイプの確認に関する詳細が必要な場合は、Sinatraでこれを行う方法について この質問といくつかの優れた回答 を確認してください。

33
Kyle Lacy

同様の問題がありました: JSONパラメーターをJavaからsinatraサービスに投稿

私のために同じことをするミドルウェアを追加することによって、それに対処するためのより良い解決策を見つけました。ラックコントリビュートジェムを使用しました。コードに加えた変更は次のとおりです。

EDIT:gitを使用して特定のバージョンを取得すると、コンテンツタイプがapplication/json;charset=UTF-8の場合の問題が修正されます。

Gemfile:

gem 'rack-contrib', git: '[email protected]:rack/rack-contrib', ref: 'b7237381e412852435d87100a37add67b2cfbb63'

config.ru:

use Rack::PostBodyContentTypeParser

ソース: http://jaywiggins.com/2010/03/using-rack-middleware-to-parse-json/

14
goyalankit

パーティーに遅れるが、それでも誰かがそれを必要とするなら-

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
0
johansenja