Skip to content

Ruby Web Applications

Ruby Web Applications - CGI Programming Fundamentals

Section titled “Ruby Web Applications - CGI Programming Fundamentals”

The Common Gateway Interface (CGI) is a standard protocol that defines how web server software can delegate the generation of web pages to executable files (scripts). While Ruby is a versatile language capable of much more, understanding CGI provides a foundational insight into how web servers and application code can interact. Modern Ruby web development predominantly uses frameworks like Ruby on Rails, Sinatra, or Hanami, which abstract away the low-level details of CGI through interfaces like Rack. However, Ruby’s cgi standard library can still be used for simple CGI scripting or for parsing CGI environment variables and query parameters.

This tutorial focuses on the basics of CGI scripting with Ruby’s cgi library. It’s important to note that direct CGI scripting is rarely used for new, complex web applications due to performance, scalability, and development complexity considerations compared to modern frameworks.

A CGI script must output a valid HTTP header followed by the content (e.g., HTML). The minimal header includes the Content-Type.

#!/usr/bin/env ruby
# Output HTTP header
puts "Content-Type: text/html\n\n" # Double newline is crucial
# Output HTML content
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>"

To run this, you’d place the script (e.g., test.cgi) in a web server’s CGI-enabled directory (like cgi-bin), make it executable (chmod +x test.cgi), and access it via a web browser (e.g., http://yourserver.com/cgi-bin/test.cgi). The web server executes the script, and its output is sent to the browser.

Ruby’s cgi library simplifies CGI scripting by providing methods for handling HTTP headers, query parameters, cookies, and generating HTML.

#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# Output header using cgi.header
# This automatically sets Content-Type to text/html by default
# and handles other potential header needs.
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>"

The cgi library makes it easy to access parameters sent from an HTML form (via GET or POST methods). Parameters are available through the CGI object, which acts like a hash.

Suppose you have an HTML form like this:

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>

The process_form.cgi script could be:

#!/usr/bin/env ruby
# process_form.cgi
require 'cgi'
cgi = CGI.new
username = cgi['username'] # Returns a single value (first if multiple)
email = cgi.params['email'] # Returns an array of values
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'] returns an array, so access its first element
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'] fetches the first value for a parameter. cgi.params returns a hash where keys are parameter names and values are arrays of submitted values (as a parameter name can appear multiple times). Always use CGI.escapeHTML when displaying user-submitted data to prevent Cross-Site Scripting (XSS) vulnerabilities.

The cgi library includes methods for generating HTML tags. You can pass "html5" to CGI.new to indicate HTML5 doctype, though modern practice often involves templating engines for complex HTML.

#!/usr/bin/env ruby
require 'cgi'
# Use 'html5' for HTML5 doctype, or 'html4' for older HTML
# CGI.new by itself defaults to a basic HTML structure.
# For more explicit control for modern HTML5, you might construct it manually or use CGI.new("html5")
cgi = CGI.new("html5") # This helps with doctype and structure
# cgi.out takes a block and outputs the necessary headers and HTML structure
cgi.out do
cgi.html do
cgi.head do
cgi.title { "HTML Generation Test" } +
cgi.style { "body { font-family: sans-serif; }" } # Example inline style
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

This HTML generation approach can become cumbersome for large pages. Templating systems like ERB (part of Ruby’s standard library) or dedicated template gems (e.g., Slim, Haml) are preferred in modern frameworks.

When dealing with URLs or HTML, special characters must be escaped.

  • CGI.escape(string): URL-encodes a string (e.g., spaces become + or %20, / becomes %2F).
  • CGI.unescape(string): Decodes a URL-encoded string.
  • CGI.escapeHTML(string): Escapes characters special in HTML (e.g., < becomes &lt;, & becomes &amp;). Essential for security when displaying user input.
  • CGI.unescapeHTML(string): Converts HTML entities back to characters (use with caution).
#!/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;

The cgi library provides basic support for handling HTTP cookies.

#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# Read existing cookie
user_visits = cgi.cookies['visits'].first.to_i || 0
user_visits += 1
# Set a new cookie (or update existing one)
new_cookie = CGI::Cookie.new(
'name' => 'visits',
'value' => user_visits.to_s,
'path' => '/', # Optional: cookie path
'expires' => Time.now + (3600*24*30) # Optional: expiration (30 days from now)
)
# Output header, including the Set-Cookie instruction
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>"

Managing sessions (tracking user state across multiple requests) with cgi alone is manual. The library cgi/session (if available or needs to be required explicitly) provided some session management capabilities, but it’s often complex. Modern frameworks have robust, built-in session handling.

Direct CGI scripting has significant drawbacks for modern web applications:

  • Performance: A new process is often started for each CGI request, which is resource-intensive.
  • State Management: Maintaining state (like user sessions) across requests is cumbersome.
  • Development Complexity: Lacks the structure, routing, ORM, and templating features of modern frameworks.
  • Scalability: Scaling CGI applications can be challenging.

For any non-trivial web application in Ruby, consider using Rack-based frameworks:

  • Ruby on Rails: A full-featured, convention-over-configuration framework for building large-scale applications.
  • Sinatra: A lightweight DSL for quickly creating web applications with minimal effort.
  • Hanami: A modern, full-stack Ruby framework focusing on clean architecture and maintainability.

These frameworks provide a much more productive, scalable, and maintainable environment for web development.