Skip to content

Ruby Web 应用

通用网关接口(Common Gateway Interface,CGI)是一个标准协议,定义了 Web 服务器软件如何将网页生成委托给可执行文件(脚本)。虽然 Ruby 是一种功能强大的语言,可以做更多事情,但理解 CGI 能提供关于 Web 服务器和应用程序代码如何交互的基础认识。现代 Ruby Web 开发主要使用诸如 Ruby on Rails、Sinatra 或 Hanami 等框架,这些框架通过 Rack 等接口抽象了 CGI 的底层细节。然而,Ruby 的 cgi 标准库仍然可用于简单的 CGI 脚本编写或解析 CGI 环境变量和查询参数。

本教程重点介绍使用 Ruby 的 cgi 库进行 CGI 脚本编写的基础知识。需要注意的是,由于与现代框架相比,直接的 CGI 脚本编写在性能、可伸缩性和开发复杂性方面存在劣势,因此很少用于新建复杂的 Web 应用程序。

CGI 脚本必须输出一个有效的 HTTP 头部,后跟内容(例如 HTML)。最小的头部包括 Content-Type。

#!/usr/bin/env ruby
# 输出 HTTP 头部
puts "Content-Type: text/html\n\n" # 双换行非常重要
# 输出 HTML 内容
puts "<html>"
puts " <head><title>My First CGI Script</title></head>"
puts " <body>"
puts " <h1>Hello from Ruby CGI!</h1>"
puts " <p>Current time: #{Time.now}</p>"
puts " </body>"
puts "</html>"

要运行它,你需要将脚本(例如 test.cgi)放在 Web 服务器支持 CGI 的目录中(比如 cgi-bin),使其可执行(chmod +x test.cgi),并通过 Web 浏览器访问它(例如 http://yourserver.com/cgi-bin/test.cgi)。Web 服务器会执行该脚本,其输出将被发送到浏览器。

Ruby 的 cgi 库通过提供处理 HTTP 头部、查询参数、Cookie 和生成 HTML 的方法,简化了 CGI 脚本编写。

#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# 使用 cgi.header 输出头部
# 这会自动将 Content-Type 默认设置为 text/html
# 并处理其他可能的头部需求。
puts cgi.header
puts "<html>"
puts " <head><title>CGI Library Test</title></head>"
puts " <body>"
puts " <h1>Using Ruby's cgi Library</h1>"
puts " </body>"
puts "</html>"

cgi 库可以轻松访问从 HTML 表单发送的参数(通过 GET 或 POST 方法)。参数可以通过 CGI 对象访问,该对象表现得像一个 hash。

假设你有一个像这样的 HTML 表单:

form.html
<html>
<body>
<form method="POST" action="/cgi-bin/process_form.cgi">
Your Name: <input type="text" name="username" value=""><br>
Your Email: <input type="text" name="email" value=""><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

process_form.cgi 脚本可以是:

#!/usr/bin/env ruby
# process_form.cgi
require 'cgi'
cgi = CGI.new
username = cgi['username'] # 返回单个值(如果存在多个则返回第一个)
email = cgi.params['email'] # 返回值的数组
puts cgi.header
puts "<html><head><title>Form Data</title></head><body>"
puts "<h1>Submitted Data:</h1>"
puts "<p>Username: #{CGI.escapeHTML(username.to_s)}</p>"
# cgi.params['email'] 返回一个数组,因此访问其第一个元素
puts "<p>Email: #{CGI.escapeHTML(email.first.to_s)}</p>"
puts "<h2>All Parameters:</h2>"
cgi.params.each do |key, values|
puts "<p>**#{CGI.escapeHTML(key)}:** #{CGI.escapeHTML(values.join(', '))}</p>"
end
puts "</body></html>"

cgi['param_name'] 获取参数的第一个值。cgi.params 返回一个 hash,其中键是参数名,值是提交值的数组(因为参数名可以出现多次)。在显示用户提交的数据时,务必始终使用 CGI.escapeHTML 以防止跨站脚本攻击(Cross-Site Scripting,XSS)漏洞。

cgi 库包含用于生成 HTML 标签的方法。你可以向 CGI.new 传递 "html5" 来指示 HTML5 doctype,尽管现代实践通常涉及模板引擎来生成复杂的 HTML。

#!/usr/bin/env ruby
require 'cgi'
# 使用 'html5' 表示 HTML5 doctype,或使用 'html4' 表示较旧的 HTML
# 单独使用 CGI.new 会默认生成一个基本的 HTML 结构。
# 对于现代 HTML5 更精确的控制,你可以手动构建或使用 CGI.new("html5")
cgi = CGI.new("html5") # 这有助于 doctype 和结构
# cgi.out 接受一个块,并输出必要的头部和 HTML 结构
cgi.out do
cgi.html do
cgi.head do
cgi.title { "HTML Generation Test" } +
cgi.style { "body { font-family: sans-serif; }" } # 内联样式示例
end +
cgi.body do
cgi.h1 { "Generated HTML Page" } +
cgi.p { "This page was generated using Ruby's cgi library." } +
cgi.form(method: "post", action: "self_script.cgi") do
cgi.label("Your message: ", for: "message_id") +
cgi.textarea(name: "message", id: "message_id") { "Default text" } +
cgi.br +
cgi.submit(value: "Send")
end
end
end
end

这种 HTML 生成方法对于大型页面来说可能会变得笨拙。现代框架中更倾向于使用模板系统,如 ERB(Ruby 标准库的一部分)或专门的模板 gem(例如 Slim, Haml)。

处理 URL 或 HTML 时,特殊字符必须被转义。

  • CGI.escape(string): 对字符串进行 URL 编码(例如,空格变为 + 或 %20,/ 变为 %2F)。
  • CGI.unescape(string): 解码 URL 编码的字符串。
  • CGI.escapeHTML(string): 转义 HTML 中的特殊字符(例如,< 变为 &lt;,& 变为 &amp;)。在显示用户输入时,这对于安全性至关重要。
  • CGI.unescapeHTML(string): 将 HTML 实体转换回字符(谨慎使用)。
#!/usr/bin/env ruby
require 'cgi'
raw_url_param = "A value with / and &"
encoded_param = CGI.escape(raw_url_param)
puts "Encoded for URL: #{encoded_param}" #=> A+value+with+%2F+and+%26
html_input = "<script>alert('XSS')</script>"
escaped_html = CGI.escapeHTML(html_input)
puts "Escaped for HTML: #{escaped_html}" #=> &lt;script&gt;alert('XSS')&lt;/script&gt;

cgi 库提供了处理 HTTP Cookie 的基本支持。

#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# 读取现有 cookie
user_visits = cgi.cookies['visits'].first.to_i || 0
user_visits += 1
# 设置一个新 cookie(或更新现有 cookie)
new_cookie = CGI::Cookie.new(
'name' => 'visits',
'value' => user_visits.to_s,
'path' => '/', # 可选:cookie 路径
'expires' => Time.now + (3600*24*30) # 可选:过期时间(从现在起 30 天)
)
# 输出头部,包括 Set-Cookie 指示
puts cgi.header('cookie' => [new_cookie])
puts "<html><body>"
puts "<h1>Cookie Test</h1>"
puts "<p>You have visited this page #{user_visits} times.</p>"
puts "</body></html>"

单独使用 cgi 管理会话(跟踪跨多个请求的用户状态)是手动的。cgi/session 库(如果可用或需要显式 require)提供了一些会话管理功能,但通常很复杂。现代框架具有健壮的内置会话处理能力。

直接的 CGI 脚本编写对于现代 Web 应用程序有显著缺点:

  • 性能: 通常每个 CGI 请求都会启动一个新的进程,这会占用大量资源。
  • 状态管理: 跨请求维护状态(如用户会话)很麻烦。
  • 开发复杂性: 缺乏现代框架的结构、路由、ORM 和模板特性。
  • 可伸缩性: 扩展 CGI 应用程序可能很困难。

对于任何非简单的 Ruby Web 应用程序,考虑使用基于 Rack 的框架:

  • Ruby on Rails: 一个功能齐全、基于约定优于配置的框架,用于构建大型应用程序。
  • Sinatra: 一个轻量级的 DSL,用于快速创建 Web 应用程序,只需极少的努力。
  • Hanami: 一个现代的全栈 Ruby 框架,专注于清晰的架构和可维护性。

这些框架为 Web 开发提供了更高效、可伸缩且更易于维护的环境。