SOAP, Background Tasks, and AJAX

Recently in Rails I’ve been interacting with various SOAP services and running them in the background with Workling. I needed to relay the SOAP response to the client’s web browser, so I decided to use AJAX to poll the status of my background tasks.

This is great if you have < 30 second background tasks running, but don’t want to block a user (and a request).

The Solution

I created a Rails plugin, called AjaxTask, that has two components:

  • Methods to use in your controller to define a task handler and create tasks
  • Javascript library to manage the AJAX between the browser and the handler.

GitHub Link: http://github.com/chrismoos/ajaxtask

In a nutshell, the client initiates a task, the handler responds with a task ID, and the client polls at a user defined interval until the task has finished, or has an error.

The plugin takes the pain out of implementing the handler, as well as the Javascript. All you have to do is run code for your task, and periodically update the status.

I am using Workling to run my background tasks, as well as maintain the status using Workling’s return store.

Okay, enough with the intro, here is the example.

Example

Controller/Routes

The first thing to do is define the handler. This instructs the AjaxTask plugin to create a handler that will respond to ajax requests, as well as dispatch to your actual tasks. The only parameter to ajaxtask_handler is a symbol, which MUST be identical to a named route. This is how a URL gets from Rails to the plugin.

routes.rb:

map.ajaxtask_demo '/ajaxtask/handler/:task', :controller => :demo, :action => :ajaxtask_demo

demo_controller.rb:

ajaxtask_handler :ajaxtask_demo

Now we will define a task:

demo_controller.rb:

ajaxtask :mytask

This tells the plugin to respond to a task named mytask.

By doing this, we must implement two methods in our controller.

mytask_start is called when a browser starts a new task. You should probably fire off your background task in this method.

mytask_start should return a unique ID for the task. By using Workling and calling .async, a unique ID is returned.

def mytask_start
    return MyWorklingWorker.async_mytask
end
  
def mytask_status(uid)
    Workling.return.get(uid)
end

The Worker

The worker is the meat of our background task. In this we will do something that might take a while, and also update the status.

my_workling_worker.rb:

class MyWorklingWorker < Workling::Base
  def mytask(options)
    Workling.return.set(options[:uid], {:pending => 'i am just starting...wait up!'})
    begin
      # your long running task goes here
			sleep 10
    rescue => e
      Workling.return.set(options[:uid], {:error => e.to_s})
      return
    end
    Workling.return.set(options[:uid], {:done => 'i finished!})
  end
end

The important things to note here are what we set the return to. AjaxTask recognizes the following:

  • :error
  • :pending
  • :done

They should be pretty self explanatory. Now let’s see what the client side looks like.

The Client

For the client, we will be interacting with the AjaxTask javascript library. Make sure you copy the ajaxtask.js file to your javascripts directory, and include it in your page. The following will copy the javascript for you:

cd vendor/plugins/ajaxtask
rake ajax_task_js

Here is an example of what an HTML page that uses AjaxTask:

<html>
<head>
	<%= javascript_include_tag 'ajaxtask.js' %>
</head>
<body>
<script>
function mytaskHandler() {
	$(this).bind('onTaskError', onTaskError);
	$(this).bind('onTaskFinished', onTaskFinished);
	$(this).bind('onTaskPending', onTaskPending);
	
	function onTaskError(event, error) {
		alert('error: ' + error);
	}
	
	function onTaskPending(event, data) {
		alert("pending: " + data);
	}
	
	function onTaskFinished(event, data) {
		alert("finished: " + data);
	}
}

$(document).ready(function() {	
	var myTask = new AjaxTask({
		url: "<%= ajaxtask_demo_url :task => :mytask %>",
		handler: new mytaskHandler(),
		taskStatusDiv: $("#taskStatus"),
		taskStatusLoadingMsg: 'Please wait while my task runs...',
		taskStatusLoadingImg: '/images/smallactivity.gif',
		taskStatusErrorMsg: 'Oops...something bad happened.'
	});
	myTask.start();
});
</script>
<div id="taskStatus"></div>

</body>
</html>

Looking at the above client code, you can see how easy it is to present a background task’s processing to a user.

That does it for now, I’ll try to document and post more soon abou AjaxTask.