The dRuby Book

3.5 Changing Process Allocation

Before we finish this chapter, let’s look at some different ways of using CGI and dRuby. We’ll change the layout of objects between processes and see a slightly different world.

So far, you have two processes, the Reminder server and the CGI client. The Reminder process lives for a longer time, while the CGI process lives for a short time. It bothers me that CGI has a relatively shorter life yet has so much work to do. To help with that, let’s split CGI into a very small CGI process and CGI server.

First we’ll modify the CGI script and turn it into a CGI server. Do you remember the last part of CGI?

  if __FILE__ == $0​
  ​ reminder = DRbObject.new_with_uri('druby://localhost:12345')​
  ​ ReminderCGI.new(reminder).start()​
  end

Let’s rewrite this to be a ReminderCGI server, rather than creating ReminderCGI, and call the start method.

cgi_reminder_d.rb
  ​#!/usr/local/bin/ruby​
  ​require 'cgi_reminder'
  ​require 'drb/drb'
  ​​
  ​reminder = DRbObject.new_with_uri('druby://localhost:12345')​
  ​cgi = ReminderCGI.new(reminder)​
  ​DRb.start_service('druby://localhost:12346', cgi)​
  ​DRb.thread.join​

All the CGI server does is generate ReminderCGI and publish druby://localhost:12346 as a front object. Now let’s move on to a very small CGI process.

cgi_reminder_s.rb
  ​#!/usr/local/bin/ruby​
  ​require 'drb/drb'
  ​​
  ​DRb.start_service('druby://localhost:0')​
  ​ro = DRbObject.new_with_uri('druby://localhost:12346')​
  ​ro.start(ENV.to_hash, $stdin, $stdout)​

The script first starts its own dRuby server, creates a remote object of the CGI server, and calls its start method. The arguments for start contain all the magic. start takes an ENV variable (converted to a hash), standard input, and standard output. When the remote ReminderCGI#start receives these parameters, then ReminderCGI behaves as if it were in the context of this CGI.

This trick relates to Chapter 4, Pass by Reference, Pass by Value, but here’s a brief explanation. Both $stdin and $stdout are File objects. When these variables are sent via Remote Method Invocation (RMI), they get passed by reference (the value of DRbObject) automatically. ENV is a singleton object, and it cannot be passed by value. To work around this, we converted ENV into a Hash with the to_hash method.

Let’s start test_reminder_2.rb in one terminal and cgi_reminder_d.rb from another terminal and then execute the CGI from the browser.

This process layout gives us an object that lives across multiple CGI requests, and therefore it runs setup tasks only once. In this example, you can hold the ReminderCGI object across multiple CGI requests, so you need to convert ERB into a Ruby script only once.

We’ll conclude this chapter by combining the Reminder server and the ReminderCGI server into one process. Let’s move ReminderCGI into test_reminder_2.rb.

Here is the revised version:

test_reminder_2.rb
  ​require './reminder0'
  ​require 'drb/drb'
  ​require 'pp'
  ​require 'cgi_reminder'
  ​​
  ​reminder = Reminder.new​
  ​reminder.add('Apply to RubyKaigi 2011')​
  ​reminder.add('Buy a pomodoro timer')​
  ​reminder.add('Display <a>')​
  ​​
  ​cgi = ReminderCGI.new(reminder)​
  ​DRb.start_service('druby://localhost:12346', cgi)​
  ​​
  while true​
  ​ sleep 10​
  ​ pp cgi.to_a​
  end

Let’s think about what this structure means. This architecture looks as if the Reminder application has ReminderCGI as a user interface. This architecture is very similar to a GUI application. CGI is equivalent to a user interface in a GUI, and an HTTP request is similar to any event (such as a button click) that happens to the GUI interface.