Skip to content

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>"
end

CGI 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
end

POST 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
end

File 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
end

JSON and XML Responses

ruby
# API response generation
#!/usr/bin/env ruby

require 'cgi'
require 'json'

Content is for learning and research only.