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.
To implement SOAP services using MoSeS, you need to have the following installed on your machine:
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.
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.
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.
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.
At the command prompt, issue the following command:
You should see text like the following:
Generating services from samples.jmoby.net: Done.
Basically, we used the script moses-generate-services.pl to:
We are now ready to implement the business logic of our service!
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!
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.
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.
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:
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>
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.
There are 2 things that you need to successfully update the status of your service:
- service invocation id
- 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:
- Heartbeat progress event
- Percent progress event
- State changed event (default)
- Step progress event
- Time progress event
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.
- pid is the property id for our service,
- input is the input to our service,
- status is the current event state for our running task, and
- result holds the result for our running task and is obtained once our state is set to completed.
# 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!