如何向rack/test的put()方法传递原始二进制数据并避免编码错误?
问题
执行以下代码发送二进制数据(比如ZIP归档)时:
require 'rack/test' data = # some binary data, for example, ZIP archive put('/foo', data, 'content_type' => 'application/octet-stream')
原本运行正常,但当data中包含%符号时,会抛出错误:
Invalid query parameters: invalid %-encoding ... (long exception text)
不想对data做Base64或CGI编码,因为生产环境中服务器端代码运行正常,问题出在Rack的使用方式上,需要让它原样发送数据到Sinatra。
以下是可复现问题的代码(直接运行即可查看输出):
require 'sinatra' require 'minitest/autorun' require 'rack/test' put '/foo' do 'OK' end class FooTest < Minitest::Test include Rack::Test::Methods def app Sinatra::Application end def test_me put('/foo', 'hello % world') assert_equal(200, last_response.status, last_response.body) end end
运行后得到结果:
1) Failure: FooTest#test_me [a.rb:18]: Invalid query parameters: invalid %-encoding (hello % world). Expected: 200 Actual: 400
移除%符号后测试就能通过。
解决方案
问题核心是:当使用put(path, data, headers)这种调用方式时,Rack::Test会把第二个参数data当成URL查询参数解析,而非请求体,因此会尝试对%做URL解码,最终触发编码错误。
要让Rack::Test把数据作为请求体原样发送,有两种可行方式:
方式一:使用:input选项指定请求体
修改测试中的put调用,将查询参数位置传空哈希,通过:input选项传递请求体内容:
put('/foo', {}, {'content_type' => 'application/octet-stream'}, input: 'hello % world')
方式二:通过块传递请求体
利用Rack::Test的块语法,将块的返回值作为请求体:
put '/foo', nil, 'content_type' => 'application/octet-stream' do 'hello % world' end
两种方式都能让Rack跳过对请求体的URL解析,直接将数据原样发送给Sinatra。修改后的完整测试代码示例:
require 'sinatra' require 'minitest/autorun' require 'rack/test' put '/foo' do 'OK' end class FooTest < Minitest::Test include Rack::Test::Methods def app Sinatra::Application end def test_me # 任选一种方式即可 put('/foo', {}, {'content_type' => 'application/octet-stream'}, input: 'hello % world') # put '/foo', nil, 'content_type' => 'application/octet-stream' do # 'hello % world' # end assert_equal(200, last_response.status, last_response.body) end end
运行修改后的代码,测试即可正常通过,数据会原样发送到服务端。
内容的提问来源于stack exchange,提问作者yegor256
相关产品推荐
相关产品推荐

