私は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呼び出しをコメントアウトすると、パラメーターは空になります。
前自体はハックのように感じます。私はそれらのパラメータが何があっても入ってくることを期待します...私はそこで何か間違ったことをしているに違いありませんが、それが何であるかわかりません。
どんな助けでも深く感謝します...
この質問に答えるために、最初にいくつかの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でこれを行う方法について この質問といくつかの優れた回答 を確認してください。
同様の問題がありました: 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/
パーティーに遅れるが、それでも誰かがそれを必要とするなら-
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