如何在Ruby Sinatra中读取GZIP有效负载

问题描述:

在远程主机上,我有一个bash脚本,将一个简单的gzip压缩YAML文件发送到我的Ruby Sinatra端点:

On a remote host I have a bash script sending a simple gzipped YAML file to my Ruby Sinatra endpoint:

#!/bin/bash

/bin/gzip -c /tmp/test.yaml > /tmp/test.gz

curl -i <hostname>:<port>/last_run_report -H "Content-Type: application/xml" -H "Content-Encoding: gzip" --data-binary @/tmp/test.gz

我的示例Ruby应用程序是:

My sample Ruby app is:

require 'sinatra'
require 'zlib'
require 'stringio'

set :port, <port>
set :bind, "<ip>"

post '/last_run_report' do
  sio = StringIO.new(request.body.to_s)
  gz = Zlib::GzipReader.new(sio).read
  test_yaml = YAML.load(gz)
end

这给了我以下错误:

Zlib::GzipFile::Error: not in gzip format

如果我需要'base64'并将端点定义更改为:

If I require 'base64' and change the endpoint definition to:

post '/last_run_report' do
  raw = Base64.decode64(request.body)
  sio = StringIO.new(raw)
  gz = Zlib::GzipReader.new(sio).read
  test_yaml = YAML.load(gz)
end

我收到以下错误:

NoMethodError: undefined method `unpack1' for #<StringIO:0x000055713e2d51b8>

我无法确定是发送错误的数据还是读取错误的数据.请帮忙.

I can't figure out if I'm sending the data wrong or reading it wrong. Please help.

假定如下所示的YAML示例:

Assuming a YAML sample like the following:

martin:
    name: Martin D'vloper
    job: Developer
    skill: Elite

您不需要所有多余的 StringIO 东西. request.body 已经是一个 StringIO 实例,因此无需将其转换为字符串然后再将其转换为 StringIO :

You don't need all the excess StringIO stuff. request.body is already a StringIO instance, so converting it to a string then converting that to a StringIO is unnecessary:

require 'sinatra'
require 'zlib'

post '/last_run_report' do
  gz = Zlib::GzipReader.new(request.body).read
  puts YAML.load(gz)
end

现在提出您的请求:

curl -i localhost:4567/last_run_report -H "Content-Type: application/xml" -H "Content-Encoding: gzip" --data-binary @test.gz

并查看sinatra输出:

And view the sinatra output:

== Sinatra (v2.0.4) has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.7.2 codename Bachmanity)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
{"martin"=>{"name"=>"Martin D'vloper", "job"=>"Developer", "skill"=>"Elite"}}
::1 - - [14/Jan/2019:23:24:28 -0700] "POST /last_run_report HTTP/1.1" 200 - 0.0048

请注意, puts 已编写 {"martin" => {"name" =>"Martin D'vloper","job" =>"Developer",技能" =>精英"}} 到控制台.

Note that puts has written {"martin"=>{"name"=>"Martin D'vloper", "job"=>"Developer", "skill"=>"Elite"}} to the console.

我应该指出,在您的示例中,以下代码无法按您期望的方式工作:

I should point out that in your example the following code does not work the way you expect:

sio = StringIO.new(request.body.to_s)

您希望能够调用 sio.read 并获得如下信息:

You expect to be able to call sio.read and get something like this:

\x1F\x8B\b\b\xA7z=\\\x00\x03test.yaml\x00SVp\xCCSH\xCD-\xC8\xC9\xAFLMU(JM\xCE/J\xE1\xCAM,*\xC9\xCC\xB3\xE2R\x00\x82\xBC\xC4\xDCT+\x05_\xB0\x88\x82\x8BzYN~Aj\x11X&+?\xC9J\xC1%\xB5,\x15!T\x9C\x9D\x99\x93c\xA5\xE0\x9A\x93Y\x92\n\x00\xFC\xA0\x83yZ\x00\x00\x00

您实际上得到的是:

#<StringIO:0x00007ffd8184bdf0>

请注意,这是文字字符串"#< StringIO:0x00007ffd8184bdf0> ",而不是对 StringIO 对象的引用,因为这是调用时返回的内容像 request.body 这样的 StringIO 对象上的 .to_s ,因此随后对 sio.read (即在对 Zlib :: GzipReader.new 的调用中隐式包含)将返回该原义字符串,并且不会返回您希望其返回的gzip压缩数据,这将导致错误 Zlib :: GzipFile::错误:不是gzip格式.

Note that this is the literal string "#<StringIO:0x00007ffd8184bdf0>" and not a reference to a StringIO object, because that is what is returned when calling .to_s on a StringIO object like request.body, so any subsequent call to sio.read (which is implicit in the call to Zlib::GzipReader.new) will return that literal string and will not return the gzipped data that you expect it to return, which leads to the error Zlib::GzipFile::Error: not in gzip format.

当您要返回 StringIO 的字符串表示形式时,应调用 .read ,而不是 .to_s .考虑到这一点,使您的第一个示例工作所需的最小更改是更改此内容:

When you want to return the string representation of a StringIO you should call .string or .read, not .to_s. With that in mind, the minimal change required to make your first example work is to change this:

sio = StringIO.new(request.body.to_s)

对此:

sio = StringIO.new(request.body.string)

但是同样,这是将 StringIO 转换为字符串然后再转换为 StringIO 的不必要操作.

But again, this is an unnecessary conversion of a StringIO to a string and back to a StringIO.