Home  |  Products  |  Projects  |  Articles  |  Contact  

Articles

Delphi subclassable
singleton class
« Apache/mod_perl
and XML-RPC »

Apache/mod_perl and XML-RPC

by Craig Manley, 8 december 2002.

This article describes how one can create an XML-RPC server using Apache's mod_perl. If you do not know what XML-RPC is and what it can be used for, then please visit the XML-RPC website first before reading any further. All the Perl code examples below require the perl module Frontier::RPC2.

A blocking standalone server (not Apache/mod_perl)

Creating a standalone XML-RPC server is very easy using perl. See the example below.

#!/usr/bin/perl -w
use strict;
use warnings;
use Frontier::Daemon;

my $d = Frontier::Daemon->new('methods' => {'sum' => \&sum},
			      'LocalAddr' => '127.0.0.1',
			      'LocalPort' => 1080);

sub sum {
 my ($arg1, $arg2) = @_;
 sleep 5;
 return $arg1 + $arg2;
}

As you can see, all you need is a few lines of code to make your script's functions accessable to the outside world using XML-RPC. All requests are served from the same process. If the server receives multiple requests at the same time, then it'll only handle one request at a time and block the rest until it's ready to handle them. This disadvantage can be overcome by using a forking mechanism, but why reinvent the wheel when you can use Apache as your server and extend it a little to turn it into an XML-RPC server?

Three methods of extending Apache into an XML-RPC server using Perl

  1. Write a CGI script that handles XML-RPC requests. I won't go into this here as examples exist on the XML-RPC website.
  2. Write an Apache::Registry (mod_perl) script that handles XML-RPC requests.
  3. Write an Apache::Request (mod_perl) module that handles XML-RPC requests.

Apache::Registry (mod_perl) script that handles XML-RPC requests

Apache::Registry scripts are executed in a similar fashion to CGI scripts. For details, see the perl.apache.org website.
#!/usr/bin/perl -w
use strict;
use Apache::Constants qw(:methods :http);
use Frontier::RPC2;

##### Store XML-RPC functions in a global hash that is re-used over multiple requests
our %xml_rpc_functions;
unless(%xml_rpc_functions) {

 $xml_rpc_functions{'sum'} =
  sub($$) {
   my ($x, $y) = @_;
   return $x + $y;
  };

 $xml_rpc_functions{'diff'} =
  sub($$) {
   my ($x, $y) = @_;
   return abs($x - $y);
  };
}
#####

##### Frontier::RPC2 object re-used over multiple requests
our $coder;
unless(defined($coder)) {
 $coder = Frontier::RPC2->new();
}
#####

my $r = shift;


# Check that method is POST
unless($r->method_number() == M_POST) {
 $r->allowed($r->allowed | (1<<M_POST));
 return $r->status(HTTP_METHOD_NOT_ALLOWED);
}

# Check the content type
my $content_type = $r->header_in('Content-Type');
unless(defined($content_type)) {
 return $r->status(HTTP_BAD_REQUEST);
}
unless($content_type  =~ m|^text/xml\b|) {
 return $r->status(HTTP_BAD_REQUEST);
}

# Check the content length
my $content_length = $r->header_in('Content-Length');
unless(defined($content_length)) {
 return $r->status(HTTP_BAD_REQUEST);
}
unless(($content_length =~ /^\d{1,10}$/) && $content_length) {
 return $r->status(HTTP_BAD_REQUEST);
}

# Get the content
my $content;
if (1) {
 my $buf;
 while ($r->read($buf, $content_length)) {
  $content .= $buf;
 }
}
unless(defined($content) && length($content)) {
 return $r->status(HTTP_NO_CONTENT);
}

# Handle XML-RPC request.
my $response_xml = $coder->serve($content, \%xml_rpc_functions);
$r->send_http_header('text/xml');
$r->print($response_xml);

If you save the script above as rpcxml.pl and it's URL is http://localhost/perl/xmlrpc.pl, then you could call it's sum function from the PHP script below. See the XML-RPC website for more PHP examples and downloads.

<html><body>
<?php
require('../include/xmlrpc.inc');
$x = 5;
$y = 3;
$request = new xmlrpcmsg('sum', array(new xmlrpcval($x,'int'), new xmlrpcval($y,'int')));
$client = new xmlrpc_client('/perl/xmlrpc.pl','localhost',80);
$response = $client->send($request);
$value = $response->value();
if (!$response->faultCode()) {
 print 'Sum of x + y = '. $value->scalarval() . "<br>\n";
}
else {
 print "Fault:<br>\n";
 print "\tCode: " . $response->faultCode() . "<br>\n";
 print "\tReason: " . $response->faultString() . "<br>\n";
}
?>
</body></html>

Apache::Request (mod_perl) module that handles XML-RPC requests

For details on what an Apache::Request is, see the perl.apache.org website. The example below is of an Apache::Request module called MyCompany::MyApplication::XMLRPC2.
package MyCompany::MyApplication::XMLRPC2;
use strict;
use Apache::Constants qw(:methods :http);
use Frontier::RPC2;
use Carp;
our $VERSION = '0.01';

##### Globals
# The exported XML-RPC functions
our %xml_rpc_functions = (
                          'sum' => sub($$) {
                                    my ($x, $y) = @_;
                                    return $x + $y;
                                   },

                          'diff' => sub($$) {
                                     my ($x, $y) = @_;
                                     return abs($x - $y);
                                    },
);
# Frontier::RPC2 object
our $coder = Frontier::RPC2->new();
#####


##### The handler method, called automatically by Apache for each request.
sub handler {
 my $r = shift;

 # Check that method is POST
 unless($r->method_number() == M_POST) {
  $r->allowed($r->allowed | (1<<M_POST));
  return HTTP_METHOD_NOT_ALLOWED;
 }

 # Check the content type
 my $content_type = $r->header_in('Content-Type');
 unless(defined($content_type)) {
  return HTTP_BAD_REQUEST;
 }
 unless($content_type  =~ m|^text/xml\b|) {
  return HTTP_BAD_REQUEST;
 }

 # Check the content length
 my $content_length = $r->header_in('Content-Length');
 unless(defined($content_length)) {
  return HTTP_BAD_REQUEST;
 }
 unless(($content_length =~ /^\d{1,10}$/) && $content_length) {
  return HTTP_BAD_REQUEST;
 }

 # Get the content
 my $content;
 if (1) {
  my $buf;
  while ($r->read($buf, $content_length)) {
   $content .= $buf;
  }
 }
 unless(defined($content) && length($content)) {
  return HTTP_NO_CONTENT;
 }

 # Handle XML-RPC request.
 my $response_xml = $coder->serve($content, \%xml_rpc_functions);
 $r->send_http_header('text/xml');
 $r->print($response_xml);
 return HTTP_OK;
}
#####

1;

You can map a URI path to this handler by adding a few lines to the httpd.conf file or by creating an .htaccess file. Below is an example of the .htaccess file created in the RPC2 subdirectory of the document root of my webserver. Make sure the module MyCompany::MyApplication::XMLRPC2 can be found by Perl in it's modules search path.

SetHandler perl-script

# Line below is not necessary, but very useful during development. If the
# timestamp of the perl module changes, then Apache will reload and recompile it.
PerlInitHandler Apache::Reload

# Define the handler to be called whenever requests are made to this URI path.
PerlHandler MyCompany::MyApplication::XMLRPC2