如何在 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 对此没有简单的解决方案。
但是有两种方法可以实现这一点。
重新定义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
在创建 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
在我 运行 作为 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 对此没有简单的解决方案。
但是有两种方法可以实现这一点。
重新定义
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
在创建 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