How to develop an Asynchronous CGI based BioMoby service using Perl MoSeS

This document summarises steps in order to develop (to implement) an asynchronous CGI based BioMoby service using Perl MoSeS.

The main thing to understand is that MoSeS does not give you a full implementation of your service. You still need to program the business logic (e.g. to extract data from your database) - but you do not need to worry about the BioMoby and CGI details.

Table of Contents

Step 1: What is needed
Step 2: Service registration
Step 3: Service generation
Step 4: Service implementation
Step 5: Service testing
Step 6: Service deployment
Step 7: Service testing using CGI
How to update polling status (optional)

What is needed

To implement SOAP services using MoSeS, you need to have the following installed on your machine:

  1. Perl - perl has to be installed on your machine
  2. A web server - this document assumes that you are using Apache
  3. Perl MoSeS - available on cpan
  4. MOBY-Client / MOBY-Server - either one will do; also available on cpan

Once you have installed Perl MoSeS and all of its dependencies on your machine, you will have to run through the MoSeS set up. This is only done once per user of MoSeS. For more information, please view the MOSES::MOBY documentation.

Service registration

In order to implement a Biomoby service, we must first register one in the registry of our choosing.

We can register a service using a perl script or one of the many GUIs created by the Biomoby developers. This tutorial will be using a simple perl script, but you are invited to try GUIs such as Dashboard or The Moby Registration Pages.

The service that we will be registering is going to be a simple echo service. To spice things up a little bit, our service will echo the reverse string that it receives.

The script that we will use for registering our service is shown below:

#!/usr/bin/perl -w
use strict;
use MOBY::Client::Central;

# instantiate a client to mobycentral
my $m = MOBY::Client::Central->new(
    Registries => { mobycentral => {
                       URL => "http://moby.ucalgary.ca/moby/MOBY-Central.pl",
                       URI => "http://moby.ucalgary.ca/MOBY/Central",
                     },
                  }
);

# give our new service a name
my $serviceName = "getReverseEchoString";

# give our service a service type
my $serviceType = "TutorialService";

# the uri of the service provider
my $authURI = "samples.jmoby.net";

# the email address of the service provider
my $email = 'your.email@domain.com';

# the url to our service cgi file
my $URL = "http://localhost/cgi-bin/getReverseEchoString.cgi";

# a small description of what our service does
my $description = "This service consumes a string of text and returns the reverse string back to the caller";

# the inputs to our service
my @input_namespaces = ();
my @input_simples = ('String', \@input_namespaces);
my @input_articles = ('my_input_string', \@input_simples);
my @all_inputs = (\@input_articles);

# the outputs to our service
my @output_namespaces = ();
my @output_simples = ('String', \@output_namespaces);
my @output_articles = ('my_reverse_string', \@output_simples);
my @all_outputs = (\@output_articles);

# register the service
my $REG = $m->registerService(
          serviceName  => $serviceName,
          serviceType  => $serviceType,
          authURI      => $authURI,
          contactEmail => $email,
          description  => $description,
          category     =>  "cgi-async",
          URL          =>  $URL,
          input        => \@all_inputs,
          output       => \@all_outputs,
          secondary    => undef,
      );

# where we successful?
$REG->success?print "Success!\n":print "Failure: ",$REG->message,"\n";

# END of Script

  

The above script will register the service getReverseEchoString into the registry. Further discussion on the registering of servics is beyond the scope of this tutorial. For more information on that topic, please visit the Biomoby homepage.

Service generation

Now that we have registered our service, we can now use MoSeS to generate the basis for our service.

Before we generate any code, we have to ensure that our cache (the one created when running the moses-install.pl script) is up to date.

We can update our cache by issuing the following commands:

  1. moses-generate-services.pl -u
  2. moses-generate-datatypes.pl -u

If you see a message like the following:

There was a problem updating the cache. Did you create it first?
  

Followed by an exception, use the -f option instead of the -u option.

Now we are ready to generate the basis of our service

At the command prompt, issue the following command:

  1. moses-generate-services.pl -v -C samples.jmoby.net getReverseEchoString

You should see text like the following:

Generating services from samples.jmoby.net:
Done.

Basically, we used the script moses-generate-services.pl to:

  1. automatically generate a perl module called getReverseEchoString.pm in the directory /your_home_dir/Perl-MoSeS/services/Service (if it already exists, it won't overwrite it).
    This file contains the implementation for our service and is what we edit to inject our business logic.
  2. automatically generates a cgi script called getReverseEchoString.cgi in the directory /your_home_dir/Perl-MoSeS/cgi/samples/jmoby/net/.
    This file is what we deploy on our web server that redirects cgi requests to our service to the getReverseEchoString.pm module above.

We are now ready to implement the business logic of our service!

Service implementation

Now that we are ready to implement the business logic, we will have to find, open and edit the module getReverseEchoString.pm.

MoSeS automatically created this file for you and left just the subroutine process_it for you to code your implementation. Fortunately, MoSeS provides some sample code for you to see how the datatypes are manipulated, how to get/set service notes, etc.

my $my_input_string = eval { $request->my_input_string };

The above snippet of code illustrates how MoSeS extracts the input data to our service. If you recall, when we registered our service, we stated that our service consumes a String and gave that String the name (in Biomoby speak 'ArticleName') 'my_input_string'.

If you had more than one input to a service, you would be able to access them simply by doing a

$request->articleName_of_input

Where articleName_of_input is the name that you gave the input when you registerd the service.

In the Biomoby world only datatypes that are considered primitives have values. Primitives include types such as String, Integer, Boolean, DateTime, Float, etc. In MoSeS, these values can be get/set using the method 'value'.

So in our service implementation, we have to extract the value of the String in order to reverse echo it back to a user of our service.

my $string = $my_input_string->value if defined $my_input_string;
$string = "" unless defined $my_input_string;

Now that we have our string, we can reverse it,

$string = reverse $string;

Now we can set the output of our service to have the value of our string by replacing:

# fill the response
my $my_reverse_string = new MOSES::MOBY::Data::String(
        value => "this is a value ",   # TO BE EDITED
);

with,

# fill the response
my $my_reverse_string = new MOSES::MOBY::Data::String(
        value => "$string",   # TO BE EDITED
);

Since this is an asynchronous service, we are going to make our service take its time in returning its response. So lets make it wait a little bit! Add the following

# sleep for a minute
sleep(60);

above
# fill the response
my $my_reverse_string = new MOSES::MOBY::Data::String(
        value => "$string",   # TO BE EDITED
);

That is all there is to it. Now we can test our service!

Service testing

To test the service, issue the following command:

moses-testing-service.pl Service::getReverseEchoString

This tells MoSeS to obtain the module Service::getReverseEchoString and call it. The service should output something like the following:

<moby:MOBY xmlns:moby="http://www.biomoby.org/moby">
<moby:mobyContent moby:authority="samples.jmoby.net">
<moby:serviceNotes>
<moby:Notes>Response created at Thu May 1 18:57:13 2008 (GMT), by the service 'getReverseEchoString'.</moby:Notes>
</moby:serviceNotes>
<moby:mobyData moby:queryID="job_0">
<moby:Simple moby:articleName="my_reverse_string">
<moby:String moby:id="" moby:namespace=""/>
</moby:Simple>
</moby:mobyData>
</moby:mobyContent>
</moby:MOBY>

The output is nothing special because we didn't provide the service with an input string to reverse! On the plus side, we now know that the service at least works!

Let's provide input to the service to test whether or not our functionality works as expected! Copy the following bit of XML and save it to a file (with the name input.xml). We will then use that file as input to our service.

<moby:MOBY xmlns:moby="http://www.biomoby.org/moby">
<moby:mobyContent>
<moby:mobyData moby:queryID="job_0">
<moby:Simple moby:articleName="my_input_string">
<moby:String moby:id="" moby:namespace="">I want this string reversed!</moby:String>
</moby:Simple>
</moby:mobyData>
</moby:mobyContent>
</moby:MOBY>

To test the service again, this time with some input, issue the following command:

moses-testing-service.pl Service::getReverseEchoString input.xml

This tells MoSeS to obtain the module Service::getReverseEchoString and call it with the text from input.xml. The service should output something like the following:

<moby:MOBY xmlns:moby="http://www.biomoby.org/moby">
<moby:mobyContent moby:authority="samples.jmoby.net">
<moby:serviceNotes>
<moby:Notes>Response created at Thu May 1 18:57:13 2008 (GMT), by the service 'getReverseEchoString'.</moby:Notes>
</moby:serviceNotes>
<moby:mobyData moby:queryID="job_0">
<moby:Simple moby:articleName="my_reverse_string">
<moby:String moby:id="" moby:namespace="">!desrever gnirts siht tnaw I</moby:String>
</moby:Simple>
</moby:mobyData>
</moby:mobyContent>
</moby:MOBY>

Wow, our service works as expected! We will test it once again using CGI, after we deploy it on our machine.

Service deployment

Deploying CGI based Perl MoSeS services is very straight forward!

The only thing you need to do is to tell your Web Server where the cgi script that we generated is located.

If you recall, our services' cgi script was called getReverseEchoString.cgi (the file generated using moses-generate-services.pl with the -C option)

Make a symbolic link from the cgi-bin directory of your Web Server (e.g on some Linux distributions, using Apache Web server, the cgi-bin directory is /usr/lib/cgi-bin) to the cgi-bin script.

For example:

cd /usr/lib/cgi-bin  
sudo ln -s /home/ekawas/Perl-MoSeS/cgi/samples/jmoby/net/getReverseEchoString.cgi .  

Every time that you generate a cgi service using Perl MoSeS, you will have to perform an operation similar to this one for the service that you created in order to deploy it.

Service testing using asynchronous CGI

Now that the service has been deployed, you can test it using CGI with the following command:

moses-testing-service.pl -C http://localhost/cgi-bin/getReverseEchoString.cgi input.xml

When we call the script with the -C option, we tell the moses-testing-service.pl script that we would like to call our service using asynchronous CGI. We then must provide the script with 1 (or an optional second) parameter:

  1. the url to the service
  2. an optional file containing the input to our service

The expected output should be very similar to the output we saw above when we tested our service:

HTTP/1.1 200 OK
Connection: close
Date: Tue, 06 May 2008 16:54:26 GMT
Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5 with Suhosin-Patch
Vary: Accept-Encoding
Content-Type: text/xml; charset=ISO-8859-1
Client-Date: Tue, 06 May 2008 16:54:31 GMT
Client-Peer: 127.0.0.1:80
Client-Response-Num: 1
Client-Transfer-Encoding: chunked

<?xml version="1.0"?>
<moby:MOBY xmlns:moby="http://www.biomoby.org/moby">
 <moby:mobyContent moby:authority="samples.jmoby.net">
   <moby:serviceNotes>
     <moby:Notes>Response created at Tue May  6 16:54:31 2008 (GMT), by the service 'getReverseEchoString'.</moby:Notes>
   </moby:serviceNotes>
   <moby:mobyData moby:queryID="job_0">
     <moby:Simple moby:articleName="my_reverse_string">
       <moby:String moby:id="" moby:namespace="">!desrever gnirts siht tnaw I</moby:String>
     </moby:Simple>
   </moby:mobyData>
 </moby:mobyContent>
</moby:MOBY>

How to update polling status (optional)

If you have a really long running service and would like to ensure that users know exactly how far along the service is to completion, you can create more detailed status messages.

Before we continue, it would be useful for you to get acquainted with the perl module, MOBY::Async::LSAE.

Updating the status of our service

There are 2 things that you need to successfully update the status of your service:

  1. service invocation id
  2. the job id for the current task

The service invocation id is very simple to obtain:

my $ID = $ENV{ID};  

The job id is equally as easy to obtain!

my $jid = $request->jid;

Now that we have those 2 pieces of information, all that is left is for you to choose what type of status update you would like your service to create:

We will be using the State changed event in this example.

In order to save the state information for our task, we need certain variables like, pid, input, status, and result.

# some variables we need to access/store our event information

my $property_pid    = "pid_$jid";
my $property_input = "input_$jid";
my $property_status = "status_$jid";
my $property_result = "result_$jid";

To provide updated state information for our event, we need to first remember what our previous state was:

# construct an LSAE object based on the old event block
my $old_status = 
         LSAE::AnalysisEventBlock->new(
                 $WSRF::WSRP::ResourceProperties{$property_status}
         );

We now instantiate a new LSAE::AnalysisEventBlock and set its various fields. For instance, we will take the previous state information from $old_status and then provide new state information for our event:

# construct a new state changed event
my $status = LSAE::AnalysisEventBlock->new();
$status->type(LSAE_STATE_CHANGED_EVENT); # set the previous state
$status->previous_state( $old_status->new_state() ); # set the new state
$status->new_state('running'); # set our job id for this event
$status->id($jid);

Once we have created our new analysis event block, we need to save it so that the client that made the job request to our service can access the state information:

# create a file based resource that we will store our event information
my $lock = WSRF::MobyFile->new( undef, $ID );
$WSRF::WSRP::ResourceProperties{$property_status} = $status->XML(); # here we leave the result empty since the service is still running
$WSRF::WSRP::ResourceProperties{$property_result} = ''; # save the event so that our clients can access the information $lock->toFile();

Here is the complete code that we put into our process_it subroutine where ever you feel like you should update the state of our long running task!

# our service invocation id
my $ID = $ENV{ID};

# our job id
my $jid = $request->jid;


# some variables we need to access/store our event information
my $property_pid    = "pid_$jid";
my $property_input = "input_$jid";
my $property_status = "status_$jid";
my $property_result = "result_$jid"; # construct an LSAE object based on the old event block my $old_status = LSAE::AnalysisEventBlock->new( $WSRF::WSRP::ResourceProperties{$property_status} ); # construct a new state changed event my $status = LSAE::AnalysisEventBlock->new();
$status->type(LSAE_STATE_CHANGED_EVENT); # set the previous state
$status->previous_state( $old_status->new_state() ); # set the new state
$status->new_state('running'); # set our job id for this event
$status->id($jid); # create a file based resource that we will store our event information my $lock = WSRF::MobyFile->new( undef, $ID );
$WSRF::WSRP::ResourceProperties{$property_status} = $status->XML(); # here we leave the result empty since the service is still running
$WSRF::WSRP::ResourceProperties{$property_result} = ''; # save the event so that our clients can access the information $lock->toFile();

 

That's all there is to constructing asynchronous CGI based Perl MoSeS services!