Ruby is a Gem
I’m speaking of the scripting language, of course. It is an absolute pleasure to code in.
I’m almost done with the rewrite/port of my Perl-based randomizer web services. The Perl version had to use a C-compiled program to fill the bit pool files. Now, that code is a Ruby extension, complete with catchable exceptions on error.
The architecture that I’m building is based on the following files:
- extensions/bbs
- extensions/sysv_semaphore
- Settings.rb
- TaggedData.rb
- BitPool.rb
- LogFileManager.rb
- SemaphoreManager.rb
- ServiceManager.rb
- RandomService.rb
- XmlCGI.rb
- config/semaphores.system
- config/XmlCGI.settings
- crons/purgeLogs.rb
The extensions are compiled C code that adheres to Ruby’s C interface. The source code is compiled into a shared library that is dynamically loaded by any Ruby script that requires it. The bbs extension is my implementation of the Blum Blum Shub cryptographically secure random bit generator. This extension is the bit generator engine. A seed pair must be supplied as input. The result of the bit generator will be a new seed and an array of random bits organized as bytes. This extension is required by the BitPool class.
The sysv_semaphore extension provides a Ruby interface into the system-level System V IPC semaphore API. This extension is required by the SemaphoreManager module.
Settings is a Ruby class that reads and writes to Window .INI format or XML files. The files themselves are read into an internal hash and written from said hash. Partial file I/O is not supported. Neither is file locking used. Instead, cooperating processes need to use the SemaphoreManager to arbitrate access to the same settings file.
TaggedData is a Ruby class that reads and writes data based on the Window .INI format. Instead of key, value pairs under each section, free-flowing text is stored instead. As with Settings, files are read and written in their entirety. Cooperating processes must use the SemaphoreManager to arbitrate access.
The BitPool class manages bit pool files. These files are filled with management data as well as the random bits-n-bytes supplied by the bbs extension. There are two uses for bit pool files. One is as a reusable, refillable repository of random data. The other is for use as throw-away one-time pads for data encryption/decryption purposes. I’ve not fully implemented the one-time pad feature support yet.
LogFileManager is a ‘mix-in’ module that provides log file management services to any class that includes it. It can handle multiple log files based on a “system” identifier. It also handles the purging of old log files based on a hash-based rule set.
SemaphoreManager is another mix-in module that provides a “named” semaphore system. It requires the sysv_semaphore extension. It also requires the config/semaphores.system settings file. Each semaphore can have from one to N “channels”. A semaphore which has more than one channel can be used to either allow, at most, N processes accessing the same shared resource or each channel could be associated with a single process resource. For example, the RandomService has access to 32 BBS seed pairs. This allows it to run up to 32 random bit generators in parallel. Therefore, a semaphore assigned to the RandomService can have 32 channels, one for each bit generator. The config/semaphores.system file contains all the system identifier, name and channel property associations.
ServiceManager is a mix-in module that provides a set of common service routines for all the web services. The Perl version of this module was an amalgamation of the LogFileManager and SemaphoreManager modules and included an e-mail messaging functionality as well. For the Ruby rewrite, having such disparate functions contained in one all-encompassing class just didn’t make sense. Frex, the purgeLogs.rb script only needs the LogFileManager. The Perl version also suffered from the fact that some game-oriented services needed a randomizer, which was also a service in and of itself. This multiple inheritance possibility made web service construction difficult. Using mixins alleviates this problem by providing common API and implementation pieces and including them into whatever class definitions need that code. Very powerful and they make my coding life a bit easier.
RandomService is a web service class that includes ServiceManager and others. It requires BitPool and other managers. It provides an API to random number functions, like get_random_uniform_integers(), shuffle_indices() and shuffle_items(). It also provides an XmlCGI interface into its core functions. XmlWhat?
XmlCGI is a web service class launcher that was inspired by XmlRPC. XmlCGI differs from XmlRPC in many ways; I will go into detail in a later post. The most important thing to know is that its primary use is as an AJAX interface point. Javascript within web pages will request web services, like a dice roller or card shuffler in the RandomService, and update the page’s content (game state) accordingly. Requests and responses use XML documents as the transport mechanism. XmlCGI will parse incoming XML request documents and act accordingly. The Javascript routines on the client are responsible for parsing the XML response documents.
The Perl version of XmlCGI used a home-grown XmlParser module, which used a SAX-like parsing scheme. Ruby comes with the REXML parser, which suited my needs better than a ported XmlParser.
config/semaphores.system is used by the SemaphoreManager to define the semaphore properties needed by the web services system.
config/XmlCGI.settings is used by the XmlCGI class to define the service function prototypes and their availability. At any point in time, the settings file can be used to enable/disable either entire services or certain API calls within each service. The function prototypes define the input and output parameters expected and produced by each service call. XmlCGI supports the use of SystemService calls to query the service call prototypes in order to provide the correct XML document data.
crons/purgeLogs.rb is a cron job that purges old log files. It requires the LogFileManager mixin and provides the rules for that module’s purge_logs method.
This morning I finished the XmlCGI interface calls for RandomService.GetDiceRoll, RandomService.GetDiceRolls, RandomService.ShuffleIndices and RandomService.ShuffleItems working. The interfaces themselves are very simple. Consider the following code snippet:
begin
{ ‘result’ => ‘Success’, ‘roll’ => get_dice_roll(input_params[’sides’], input_params[’numdice’], input_params[’mods’]) }
rescue StandardError
{ ‘result’ => ‘Error: ‘ + $!, ‘roll’ => nil }
end
end
All inputs to XmlCGI services are parsed into a hash named input_params. The output is a hash as well. The indices of these hashes correspond with the prototype definitions found in config/XmlCGI.settings.
The prototype entry for RandomService.GetDiceRoll in config/XmlCGI.settings is:
Input=sides:int,numdice:int,mods:int
Output=result:string,roll:int