Online/Offline Application Take Two (Part 3) - Bringing in CFML

In my earlier posts (way back in April 2010) I outlined how our Air client would work by using the HTMLHost with a couple special workarounds.  Since then I've been pulled in many different directions, and its suffice to say that I am back on this project.  In this post I will focus on setting up the CFML environment inside of the Flex/Air environment.  If you've read my previous posts (Part 1 and Part 2), you'll see what we are trying to accomplish.  In Part 2 I described how to setup the Flex/Air application to be much like a browser, albeit a very limited one.  This worked well enough (many pitfalls which I will document in a future post), so the next step was to get it to render my CFML app.

Bringing in CFML...

As you know, CFML applications require a CFML engine to process the code and return HTML (or xml, json, etc...) that the "browser" can render.  You also know that Adobe CF aint cheap if you are considering installing it on client PCs.  There is a free developer version, however this usage may violate the licensing if you develop a commercial app that deploys or relies on it, and it does things like watermarking PDFs which is not desireable.  It also requires an installation and on Windows runs as a service, which we cannot interact with directly using Air's nativeProcess methods.  This will not do for most folks.

Fortunately there's Railo.  Railo 3.1 and later is open source and FREE.  Best yet it comes in an "Express" version that requires no installation at all.  You can download the zip file from http://www.getrailo.org and  execute the start.bat (Windows) and you have a living, breathing CFML engine.  Also the very nature of Railo is very conducive to client installs as it allows you to set per context locales, datasources, etc...  Brilliant!

Now how do we leverage that in Air?  I did so by using a simple class I named RailoService.as.  This class has the functions to start and stop Railo as the Air browser loads/unloads.  Before I show how that is done, here's how I organized the Railo files in my project:

  1. I downloaded the Railo Express with Jetty from http://www.getrailo.org/index.cfm/download/ (making sure to get the one With JRE)
  2. Created a folder in my Air project called "src/www/railo/" and extracted the Railo Express archive into that directory.
  3. I edited the start.ini file in the src/www/railo/ folder by commenting out all of the config files in the # Configuration files section (at the end of the file).  For single instance applications this is not necessary, but for my case I need multiple instances so I pass those in when I start Railo within RailoService.as
Now that Railo is in place it will be compiled into the Air application when you compile and we can interact via Air's nativeProcess.  

Before we can run Railo, it is important to understand how Railo and Jetty are configured, as this will dictate how and where you setup your CF app.  First off, in my application I am using Railo Express 3.2, which ships with Jetty 7.2.2.  In Railo Express 3.1 they packaged an earlier version Jetty, which has a slightly different config file structure.  If you choose to deploy Railo 3.1 you'll have to read the docs on how to setup the web contexts and such (not hard, just different).  Ok, by default Railo Express ships with a subfolder called "webroot" and as the name suggests is where you should place your CFML application.  If you want it in another location you'll need to modify the /src/www/railo/contexts/railo.xml file to change the resourceBase element to point to the desired location.  Also by default Jetty is setup to listen on port 8888, if you wish to change this just edit the /srs/www/railo/etc/jetty.xml file and change the value of the port element in the addConnector node.

Ok, that about sums it up for the Jetty config.  Now I'll show you how to start/stop Railo from within your Air app...

In my main application mxml (created in Part 2) I added two functions: startRailo() and stopRailo().  The startRailo() function is called onCreationComplete and simply invokes the RailoService.start() function which looks like:
public function start():void{
      trace("starting Railo...");
      var processArgs:Vector.<String> = new Vector.<String>();
      processArgs.push("-DSTOP.PORT=8887");
      processArgs.push("-DSTOP.KEY=railo");
      processArgs.push("-Xms256M");
      processArgs.push("-Xmx512M");
      processArgs.push("-jar");
      processArgs.push("lib/start.jar");

      processArgs.push('etc/jetty.xml');
     processArgs.push('etc/jetty-deploy.xml');
    
    execute(processArgs);
}
If you look at the contents of the start.bat (Windows) file included with Railo Express you'll notice that I'm basically duplicating that here.  I add the xml config files at the end of my arguments because I commented them out in start.ini.  In my real app, this is where I setup multiple instances to listen on different ports and direct to different context roots.
The last call in that function is a custom function in that class that actually executes the "lib/start.jar" file, passing in the above arguments.  Here's how that looks:
private function execute(processArgs:Vector.<String>):void{
   // Get a file reference to the JVM
   var file:File = File.applicationDirectory;
   var javafile:File = new File(file.nativePath);
   if (Capabilities.os.toLowerCase().indexOf("win") » -1){
      javafile = file.resolvePath("www/railo/jre/bin/javaw.exe");
   }else{
      javafile = file.resolvePath("Home/bin/java");
   }
   // Start the process
   try{
      var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
      nativeProcessStartupInfo.executable = javafile;
      nativeProcessStartupInfo.workingDirectory = file.resolvePath("www/railo/");
      nativeProcessStartupInfo.arguments = processArgs;
      process = new NativeProcess();
      process.start(nativeProcessStartupInfo);
   }catch (e:Error){
      Alert.show(e.message, "Error");
   }
}

There you go.  Here we are looking for the correct javaw.exe and then using that as our nativeProcess executable to which we pass the arguments from start().  This is where we differ from the start.bat.  If you notice start.bat uses java.exe and we use javaw.exe.  The difference is java.exe will open a command window and javaw.exe will allow Railo to run in the background.

Now once your Air app is launched it will start Railo.

To stop railo, I use the WindowedApplication's close event to fire RailoService.stop(), which mimics the stop.bat:

public function stop():void{
   trace("stopping Railo...");
   var processArgs:Vector.<String> = new Vector.<String>();
   processArgs.push("-DSTOP.PORT=8887");
   processArgs.push("-DSTOP.KEY=railo");
   processArgs.push("-jar");
   processArgs.push("lib/start.jar");
   processArgs.push("--stop");
   execute(processArgs);            
}

To make this work with the example I created in part 2, you'll just need to add the start(), stop() functions mentioned earlier and change the: 

browser.location = "http://www.myapplication.com";
to
browser.location = "http://localhost:8888";

Then copy your CFML app into the webroot folder.  If you run the application the browser should render your home page (that is if you don't require a database... I'll cover that in Part 4).

Closing notes:  

It is fair to note that once your Air app is running, one could simply pop open any browser and navigate to http://localhost:8888 and gain access to the application. So if you are looking at using Air as a way of preventing users from using a web browser (which has view source, a back button, etc...) you'll need to tweak your CFML code to only run if the cgi user agent is the Air browser. Another noteworthy item along the same lines is that in a regular browser you can also access the Railo admin, by default it would be located at http://localhost:8888/railo-context/admin/server.cfm (or web.cfm for context specific admin). This is handy, but in my app I configure all my Railo settings up using a remote object call to a webservice (hosted on the client) that performs a series of calls to setup the database, locale, etc...

Comments

Leave a comment

Tell us about yourself
(required field)
(required field)
Comment and preferences
Leave this field empty: