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 Basic CGI Script:
Section titled “A Basic CGI Script:”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 headerputs "Content-Type: text/html\n\n" # Double newline is crucial
# Output HTML contentputs "<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.
Using the cgi Library:
Section titled “Using the cgi Library:”Ruby’s cgi library simplifies CGI scripting by providing methods for handling HTTP headers, query parameters, cookies, and generating HTML.
#!/usr/bin/env rubyrequire '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>"Processing Form Data:
Section titled “Processing Form Data:”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:
<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.cgirequire '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.headerputs "<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 elementputs "<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.
Generating HTML with cgi:
Section titled “Generating HTML with cgi:”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 rubyrequire '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 structurecgi.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 endendThis 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.
Escaping Special Characters:
Section titled “Escaping Special Characters:”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<,&becomes&). Essential for security when displaying user input.CGI.unescapeHTML(string): Converts HTML entities back to characters (use with caution).
#!/usr/bin/env rubyrequire '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}" #=> <script>alert('XSS')</script>Cookies and Sessions with cgi:
Section titled “Cookies and Sessions with cgi:”The cgi library provides basic support for handling HTTP cookies.
#!/usr/bin/env rubyrequire 'cgi'
cgi = CGI.new
# Read existing cookieuser_visits = cgi.cookies['visits'].first.to_i || 0user_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 instructionputs 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.
Limitations and Modern Alternatives:
Section titled “Limitations and Modern Alternatives:”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.