Метод параметров контроллера Sinatra, который пуст по запросу JSON
У меня есть приложение Sinatra, и в большинстве моих контроллеров json приходит и автоматически выбирается в объекте params. Тем не менее, у меня есть пост-действие, которое вообще не получает параметров, если я не сыграю трюк с помощью метода 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
Вывод, который я вижу, в основном состоит в том, что параметры в действии контроллера действительно находятся там правильно. Однако, если я прокомментирую предыдущий вызов, параметры будут пустыми.
Перед собой чувствует себя как взлома. Я ожидал бы, что эти параметры придут независимо от того, что... Я должен делать что-то не так, но я не знаю, что это.
Любая помощь будет глубоко оценена...
Ответы
Ответ 1
Чтобы ответить на этот вопрос, нам сначала нужно посмотреть некоторые 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, будучи в значительной степени несовместимыми с этим, будут отображаться 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, либо отвечать по-другому, если стандартная форма будет отправлена. Как видно из вышеприведенного примера HTTP POST
запроса, HTML-форма идентифицируется с типом application/x-www-form-urlencoded
MIME, тогда как JSON идентифицируется с помощью application/json
. Если вам нужна спецификация проверки типа запроса POST
MIME, проверьте этот вопрос с некоторыми замечательными ответами о том, как это сделать с Sinatra!
Ответ 2
Была похожая проблема:
Отправка параметров JSON из java в службу sinatra
Я нашел лучшее решение для этого, добавив промежуточное программное обеспечение, чтобы сделать то же самое для меня. Я использовал жемчужину Contribute Gem. Ниже приведены изменения, которые я сделал в своем коде:
ОБНОВЛЕНИЕ: использовать 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/
Ответ 3
Поздно на вечеринку, но если кому-то еще это нужно -
Чтобы добавить к 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