Ruby CGI Programming
CGI (Common Gateway Interface) is a standard interface for interaction between web servers and external programs. Although modern web development more commonly uses frameworks like Rails and Sinatra, understanding CGI programming still helps grasp the basic principles of web applications. Ruby provides a powerful CGI library that makes writing CGI programs simple and elegant. This chapter will explain in detail the various methods and best practices of CGI programming in Ruby.
🎯 CGI Basics
What is CGI
CGI is a standard protocol that allows web servers to execute external programs to generate dynamic web content. When a user requests a CGI program, the web server starts the program, passes request data to it, and then returns the program's output to the user.
ruby
# Basic CGI program
#!/usr/bin/env ruby
require 'cgi'
# Create CGI object
cgi = CGI.new
# Output HTTP header and HTML content
cgi.out do
"<html>
<head><title>My First CGI Program</title></head>
<body>
<h1>Hello, World!</h1>
<p>Current time: #{Time.now}</p>
</body>
</html>"
endCGI Environment Variables
ruby
# Access CGI environment variables
#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# Get environment information
puts "Content-Type: text/html\n\n"
puts "<html><body>"
puts "<h1>CGI Environment Information</h1>"
puts "<ul>"
puts "<li>Server software: #{ENV['SERVER_SOFTWARE']}</li>"
puts "<li>Server name: #{ENV['SERVER_NAME']}</li>"
puts "<li>Gateway interface: #{ENV['GATEWAY_INTERFACE']}</li>"
puts "<li>Server protocol: #{ENV['SERVER_PROTOCOL']}</li>"
puts "<li>Server port: #{ENV['SERVER_PORT']}</li>"
puts "<li>Request method: #{ENV['REQUEST_METHOD']}</li>"
puts "<li>Request URI: #{ENV['REQUEST_URI']}</li>"
puts "<li>Script name: #{ENV['SCRIPT_NAME']}</li>"
puts "<li>Path info: #{ENV['PATH_INFO']}</li>"
puts "<li>Path translated: #{ENV['PATH_TRANSLATED']}</li>"
puts "<li>Query string: #{ENV['QUERY_STRING']}</li>"
puts "<li>Remote host: #{ENV['REMOTE_HOST']}</li>"
puts "<li>Remote address: #{ENV['REMOTE_ADDR']}</li>"
puts "<li>Authentication type: #{ENV['AUTH_TYPE']}</li>"
puts "<li>Remote user: #{ENV['REMOTE_USER']}</li>"
puts "<li>Remote ident: #{ENV['REMOTE_IDENT']}</li>"
puts "<li>Content type: #{ENV['CONTENT_TYPE']}</li>"
puts "<li>Content length: #{ENV['CONTENT_LENGTH']}</li>"
puts "<li>HTTP user agent: #{ENV['HTTP_USER_AGENT']}</li>"
puts "<li>HTTP referer: #{ENV['HTTP_REFERER']}</li>"
puts "</ul>"
puts "</body></html>"HTTP Header Handling
ruby
# Set HTTP headers
#!/usr/bin/env ruby
require 'cgi'
require 'json'
cgi = CGI.new
# Set different content types
case cgi.params['format'][0]
when 'json'
cgi.out('type' => 'application/json') do
{ message: "Hello, World!", time: Time.now }.to_json
when 'xml'
cgi.out('type' => 'application/xml') do
"<?xml version='1.0' encoding='UTF-8'?>
<response>
<message>Hello, World!</message>
<time>#{Time.now}</time>
</response>"
end
else
cgi.out('type' => 'text/html') do
"<html>
<head><title>HTML Response</title></head>
<body>
<h1>Hello, World!</h1>
<p>Current time: #{Time.now}</p>
</body>
</html>"
end
end
# Set custom headers
cgi.out(
'type' => 'text/html',
'status' => 'OK',
'cache-control' => 'no-cache',
'expires' => '0'
) do
"<html><body><h1>No Cache Page</h1></body></html>"
end
# Redirect
# cgi.out('status' => 'REDIRECT', 'location' => 'http://example.com') { '' }📥 Handling HTTP Requests
GET Request Handling
ruby
# Handle GET request parameters
#!/usr/bin/env ruby
require 'cgi'
require 'uri'
cgi = CGI.new
# Get GET parameters
name = cgi['name'] || 'Visitor'
age = cgi['age']&.to_i || 0
# Output HTML page
cgi.out do
html = <<-HTML
<html>
<head>
<title>GET Request Example</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Welcome, #{CGI.escapeHTML(name)}!</h1>
<p>Age: #{age > 0 ? age : 'Not provided'}</p>
<h2>All GET Parameters:</h2>
<ul>
HTML
cgi.params.each do |key, values|
html += "<li>#{CGI.escapeHTML(key)}: #{CGI.escapeHTML(values.join(', '))}</li>"
end
html += <<-HTML
</ul>
<h2>Test Form:</h2>
<form method="GET" action="#{ENV['SCRIPT_NAME']}">
<p>
<label>Name: <input type="text" name="name" value="#{CGI.escapeHTML(name)}"></label>
</p>
<p>
<label>Age: <input type="number" name="age" value="#{age > 0 ? age : ''}"></label>
</p>
<p>
<input type="submit" value="Submit">
</p>
</form>
</body>
</html>
HTML
html
endPOST Request Handling
ruby
# Handle POST request parameters
#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
# Check request method
if cgi.request_method == 'POST'
# Get POST parameters
username = cgi['username']
password = cgi['password']
message = cgi['message']
# Process form data
response = process_form_data(username, password, message)
else
# Display form
response = show_form
end
cgi.out do
"<html>
<head>
<title>POST Request Example</title>
<meta charset=\"UTF-8\">
</head>
<body>
#{response}
</body>
</html>"
end
def process_form_data(username, password, message)
# Simple form processing
if username.nil? || username.empty?
return "<h1>Error</h1><p>Username cannot be empty</p><p><a href=\"#{ENV['SCRIPT_NAME']}\">Back</a></p>"
end
# Actual data processing and validation should be done here
# Such as saving to database, sending email, etc.
<<-HTML
<h1>Form Submitted Successfully</h1>
<p>Username: #{CGI.escapeHTML(username)}</p>
<p>Message: #{CGI.escapeHTML(message)}</p>
<p><a href="#{ENV['SCRIPT_NAME']}">Submit Again</a></p>
HTML
end
def show_form
<<-HTML
<h1>POST Form Example</h1>
<form method="POST" action="#{ENV['SCRIPT_NAME']}">
<p>
<label>Username: <input type="text" name="username" required></label>
</p>
<p>
<label>Password: <input type="password" name="password" required></label>
</p>
<p>
<label>Message:<br><textarea name="message" rows="5" cols="50"></textarea></label>
</p>
<p>
<input type="submit" value="Submit">
</p>
</form>
HTML
endFile Upload Handling
ruby
# Handle file uploads
#!/usr/bin/env ruby
require 'cgi'
require 'tempfile'
cgi = CGI.new
if cgi.request_method == 'POST'
# Check if there's a file upload
if cgi.params['upload'] && !cgi.params['upload'][0].nil?
uploaded_file = cgi.params['upload'][0]
# Get file information
filename = uploaded_file.filename
content_type = uploaded_file.type
file_data = uploaded_file.read
# Save file (should be handled more securely in production)
if filename && !filename.empty?
File.open("uploads/#{filename}", "wb") do |f|
f.write(file_data)
end
response = "<h1>File Upload Successful</h1>
<p>Filename: #{CGI.escapeHTML(filename)}</p>
<p>Content type: #{CGI.escapeHTML(content_type)}</p>
<p>File size: #{file_data.length} bytes</p>
<p><a href=\"#{ENV['SCRIPT_NAME']}\">Upload Again</a></p>"
else
response = "<h1>Error</h1><p>Please select a file to upload</p><p><a href=\"#{ENV['SCRIPT_NAME']}\">Back</a></p>"
end
else
response = "<h1>Error</h1><p>No file received</p><p><a href=\"#{ENV['SCRIPT_NAME']}\">Back</a></p>"
end
else
response = show_upload_form
end
cgi.out do
"<html>
<head>
<title>File Upload Example</title>
<meta charset=\"UTF-8\">
</head>
<body>
#{response}
</body>
</html>"
end
def show_upload_form
<<-HTML
<h1>File Upload</h1>
<form method="POST" action="#{ENV['SCRIPT_NAME']}" enctype="multipart/form-data">
<p>
<label>Select file: <input type="file" name="upload" accept="image/*,.pdf,.txt"></label>
</p>
<p>
<input type="submit" value="Upload">
</p>
</form>
<h2>Notes:</h2>
<ul>
<li>Make sure the server is configured to allow file uploads</li>
<li>Check upload_max_filesize and post_max_size settings in php.ini or server config</li>
<li>In production, uploaded files should be security-checked</li>
</ul>
HTML
end📤 Generating HTTP Responses
HTML Response Generation
ruby
# Dynamic HTML generation
#!/usr/bin/env ruby
require 'cgi'
require 'erb'
cgi = CGI.new
# Use ERB template
template = <<-TEMPLATE
<html>
<head>
<title><%= title %></title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
.content { margin-top: 20px; }
.item { border-bottom: 1px solid #ddd; padding: 10px 0; }
</style>
</head>
<body>
<div class="header">
<h1><%= title %></h1>
<p>Current time: <%= current_time %></p>
</div>
<div class="content">
<h2>Product List</h2>
<% products.each do |product| %>
<div class="item">
<h3><%= product[:name] %></h3>
<p>Price: $<%= product[:price] %></p>
<p><%= product[:description] %></p>
</div>
<% end %>
</div>
</body>
</html>
TEMPLATE
# Data
title = "My Online Store"
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
products = [
{ name: "Apple", price: 5.50, description: "Fresh Fuji apples" },
{ name: "Banana", price: 3.00, description: "Imported bananas" },
{ name: "Orange", price: 4.20, description: "Sweet oranges" }
]
# Render template
erb = ERB.new(template)
html_content = erb.result(binding)
cgi.out('type' => 'text/html') do
html_content
endJSON and XML Responses
ruby
# API response generation
#!/usr/bin/env ruby
require 'cgi'
require 'json'