如何在 ruby CGI 脚本中访问原始请求正文?

How can I access the raw request body in ruby CGI scripts?

在我 运行 作为 CGI 程序的 ruby 脚本中,我需要访问 HTTP POST 请求的主体。请求正文包含 JSON 数据:

{"data":"a"}

我想取全身用JSON.parse解析处理。执行此操作的规范方法是什么? Ruby docs 不要提及请求正文。

我只在 blog post 中找到一个提示

CGI tries to parse the request body as form parameters so a blob of JSON awkwardly ends up as the one and only parameter key.

这种方法似乎有效

puts cgi.params.keys.first # prints {"data":"a"}

但一旦 data 的值是包含 = 用于填充的 base64 编码字符串,就会失败:使用此正文

{"data":"a="}

结果如下(末尾缺少字符):

puts cgi.params.keys.first # prints {"data":"a

解决这个问题的正确方法是什么?

您可能已经知道,当参数及其值被 urlencoded 时,它们用 = 分隔:name=Theo&language=ruby 等等。

这就是第一个参数的名称停在=之前的字符的原因。如该博客 post 所述,使用第一个密钥的方法并不可靠。

相反,在 CGI 脚本中,您可以直接从标准输入读取请求正文,例如

request_body = $stdin.read

请注意,当您实例化一个 CGI 对象时,它将从标准输入中读取所有内容并尝试将其解析为 params 哈希。

这意味着,如果您仍想使用 cgi 库来构建您的响应,则需要在代码的前面读取标准输入,before 创建 CGI 对象。例如

# minimal example that just outputs the request body
require 'cgi'

request_body = $stdin.read

cgi = CGI.new

cgi.out("status" => "OK", "type" => "text/plain", "connection" => "close") do
  request_body
end

显然 Ruby 对此没有简单的解决方案。

但是有两种方法可以实现这一点。

  1. 重新定义CGI::parse(params)方法。 CGI 模块中的此方法负责将 POST 和 GET 参数解析为 params 哈希。您可以在代码中重新定义此方法,以便它在 params 哈希中添加一个名为 RAW_DATA 的额外参数。

    def CGI::parse(query)
       params = {}
       query.split(/[&;]/).each do |pairs |
       key, value = pairs.split('=', 2).collect {
         | v | CGI::unescape(v)
        }
    
       next unless key
    
       params[key] || = []
       params[key].push(value) if value
     end
    
     #Add RAW_DATA to params
     params[:RAW_DATA] = query
    
     params.default = [].freeze
     params
    end
    
  2. 在创建 CGI 实例之前使用 $stdin.read()。 但这可能会阻止您使用其他 CGI 功能。

    因此您可以暂时将 $stdin 替换为 StringIO 对象。

    require 'cgi'
    require 'stringio'
    
    raw_data  = $stdin.read()
    
    real_stdin = $stdin
    $stdin     = StringIO.new(raw_data)
    STDIN      = $stdin
    
    cgi = CGI.new
    
    #Your CGI code here
    
    #........
    
    $stdin = real_stdin
    STDIN  = $stdin