guide.soaplite.com
SOAP::Lite for Perl
The Power Of Simplicity
| Home | User Guide | new! Cookbook | FAQ |
 


Quick Start Guide with SOAP and SOAP::Lite

SOAP (Simple Object Access Protocol) is a way for you to remotely make method calls upon classes and objects that exist on a remote server. It's the latest in a long series of similar projects like CORBA, DCOM, and XML-RPC.

SOAP specifies a standard way to encode parameters and return values in XML, and standard ways to pass them over some common network protocols like HTTP (web) and SMTP (email). This guide is not about those meaty technical aspects of SOAP, though, it's a very quick introduction in writing SOAP servers and clients. We will hardly scratch the surface of what's possible.

All examples will use SOAP::Lite module. Don't be mislead by the 'Lite' suffix -- this refers to the effort it takes to use the module, not its capabilities.


Writing a CGI-based Server

Here's a simple CGI-based SOAP server:

 1.a. server  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI   
    -> dispatch_to('Demo')     
    -> handle;
  package Demo;
  sub hi {                     
    return "hello, world";     
  }
  sub bye {                    
    return "goodbye, cruel world";
  }

There are basically two parts to this: the first four lines set up a SOAP wrapper around a class. Everything from 'package Demo' onward is the class being wrapped.

In the previous version of the SOAP specification (1.0), SOAP over HTTP was supposed to use a new HTTP method, M-POST. In practice, there are many web servers that don't understand the M-POST method so this requirement was weakened and now it's common to try a normal POST first and then use M-POST if the server needs it. If you don't understand POST and M-POST, don't worry, you don't need to know all about it to use the module.


Writing a Client

This program prints the results of the hi() method call:

 1.a. client  
  #!perl -w
  use SOAP::Lite;
  print SOAP::Lite
    -> uri('http://www.soaplite.com/Demo')
    -> proxy('http://services.soaplite.com/hibye.cgi')
    -> hi()
    -> result;

The uri() identifies the class on the server, and the proxy() identifies the CGI script that provides access to the class. Since both look like URLs, I'll take a minute to explain the difference, as it's quite important.

proxy()
proxy() is simply the address of the server to contact that provides the methods. You can use http:, mailto:, even ftp: URLs here.

uri()
Each server can offer many different services through the one proxy() URL. Each service has a unique URI-like identifier, which you specify to SOAP::Lite through the uri() method. If you get caught up in the gripping saga of the SOAP documentation, the 'namespace' corresponds to the uri() method.

Run your client and you should see:

 1.a. result  
  hello, world

That's it (assuming you're connected to the Internet).

If your method returns multiple values:

 1.b. server  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI      
    -> dispatch_to('Demo')        
    -> handle;
  package Demo;
  sub hi {                        
    return "hello, world";        
  }
  sub bye {                       
    return "goodbye, cruel world";
  }
  sub languages {                 
    return ("Perl", "C", "sh");   
  }

Then the result() method will only return the first. To access the rest, use the paramsout() method:

 1.b. client  
  #!perl -w
  use SOAP::Lite;
  $soap_response = SOAP::Lite
    -> uri('http://www.soaplite.com/Demo')
    -> proxy('http://services.soaplite.com/hibye.cgi')
    -> languages();
  @res = $soap_response->paramsout;
  $res = $soap_response->result;                               
  print "Result is $res, outparams are @res\n";

This code will produce:

 1.b. result  
  Result is Perl, outparams are C sh


Passing Values

Methods can take arguments. Here's a SOAP server that translates between Fahrenheit and Celsius:

 2.a. server  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('Temperatures')
    -> handle;
  package Temperatures;
  sub f2c {
      my ($class, $f) = @_;
      return 5/9*($f-32);
  }
  sub c2f {
      my ($class, $c) = @_;
      return 32+$c*9/5;
  }

And here's a sample query:

 2.a. client  
  #!perl -w
  use SOAP::Lite;
  print SOAP::Lite                                            
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi')
    -> c2f(37.5)
    -> result;

You can also create an object representing the remote class, and then make method calls on it:

 2.b. client  
  #!perl -w
  use SOAP::Lite;
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');
  print $soap
    -> c2f(37.5)
    -> result;

Check your math, it should give you:

 2. result  
  99.5


Autodispatching

This being Perl, there's more than one way to do it:

 2.c. client  
  #!perl -w
  use SOAP::Lite +autodispatch =>
    uri => 'http://www.soaplite.com/Temperatures',
    proxy => 'http://services.soaplite.com/temper.cgi';
  print c2f(37.5);

After you specify the uri and proxy parameters, you are able to call remote functions with the same syntax as local ones (e.g., c2f). This is done with UNIVERSAL::AUTOLOAD, which catches all unknown method calls. Be warned that all calls to undefined methods will result in an attempt to use SOAP.


Objects access (it's 'simple OBJECT access protocol', isn't it?)

Methods can also return real objects. Let's extend our Temperatures class with an object-oriented interface.

 2.b. server  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('Temperatures')
    -> handle;
  package Temperatures;
  sub f2c {
      my ($class, $f) = @_;
      return 5/9*($f-32);
  }
  sub c2f {
      my ($class, $c) = @_;
      return 32+$c*9/5;
  }
  sub new {
      my $self = shift;
      my $class = ref($self) || $self;
      bless {_temperature => shift} => $class;
  }
  sub as_fahrenheit {
      return shift->{_temperature};
  }
  sub as_celsius {
      return 5/9*(shift->{_temperature}-32);
  }

Here is a client to access this class:

 2.d. client  
  #!perl -w
  use SOAP::Lite;
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');
  my $temperatures = $soap
    -> call(new => 100) # accept Fahrenheits 
    -> result;
  print $soap
    -> as_celsius($temperatures)
    -> result;

Similar code with autodispatch is shorter and easier to read.

 2.e. client  
  #!perl -w
  use SOAP::Lite +autodispatch =>
    uri => 'http://www.soaplite.com/Temperatures',
    proxy => 'http://services.soaplite.com/temper.cgi';
  my $temperatures = Temperatures->new(100);
  print $temperatures->as_fahrenheit;


Error handling

A SOAP call may fail for numerous reasons, such as: transport error, incorrect parameters, an error on the server. Transport errors (which may occur if, for example, there is a network break between the client and the server) are dealt with below (examples 2.g, 2.h, 2.j and 2.k). All other errors are indicated by the fault() method:

 2.f. client  
  #!perl -w
  use SOAP::Lite;
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');
  my $result = $soap->c2f(37.5);
  unless ($result->fault) {
    print $result->result();
  } else {
    print join ', ', 
      $result->faultcode, 
      $result->faultstring;
  }

faultcode() gives you information about the main reason for the error. Possible values may be:

Client: you provided incorrect information in the request.
This error may occur when parameters for remote call are incorrect, for example, for service that returns name of state in US based on number of this state you provide negative or too big number. Or type of parameter is incorrect (specified int instead of string), or likewise.

Server: something is wrong on the server side.
This means that provided information is correct, but server couldn't handle the request because of temporary difficulties, for example, unavailable database.

MustUnderstand: Header elements has mustUnderstand attribute, but wasn't understood by server.
Basically that means that the server was able to parse the request but that the client is requesting functionality that can't be provided. For example, request requires execution of SQL statement and client wants to be sure that several requests will be executed in one transaction. It could be implemented as three differents calls with common TransactionID.

In this case, the SOAP header may be extended with a new header element 'TransactionID' which carries the transaction ID across the 3 separate invocations. However, the server may not understand what a 'TransactionID'. If the server does not have this understanding and tries to process the request anyway, problems may arise if there is a problem with processing the 3 invocations together, as the server will not maintain transactional integrity across the group of 3. To guard against this, the client may indicate that the server 'mustUnderstand' the element 'TransactionID'. If the server sees this and does NOT understand the meaning of the element, it will not try and process the requests in the first place.

This functionality makes services more reliable and distributed system more robast.

VersionMismatch: the server can't understand the version of SOAP used by the client.
This is provided for (possible) future extensions, when new versions of SOAP will have different functionality and only clients that are knowledgeable about it will be able to use it.

Other errors
The server is allowed to create its own errors, like Client.Authentication.

faultstring() provides a readable explanation, whereas faultdetail() gives access to more detailed information, which may be an object, or complex structure.

For example, if you change uri to something else (let's try with 'Test' instead of 'Temperatures'), this code will generate:

 2.f. result  
  Client, Bad Class Name, Failed to access class (Test)

By default client will die with diagnostic on transport errors and do nothing for faulted calls, so, you'll be able to get fault info from result. You can alter this behavior with on_fault() handler either per object (will die on both transport errors and SOAP faults):

 2.g. client  
  #!perl -w
  use SOAP::Lite;
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi')
    -> on_fault(sub { my($soap, $res) = @_; 
         die ref $res ? $res->faultstring : $soap->transport->status, "\n";
       });

or globally:

 2.h. client  
  #!perl -w
  use SOAP::Lite
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultstring : $soap->transport->status, "\n";
    };
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');

Now you wrap your SOAP call into eval {} block and catch both transport errors and SOAP faults:

 2.j. client  
  #!perl -w
  use SOAP::Lite
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultstring : $soap->transport->status, "\n";
    };
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');
  eval { 
    print $soap->c2f(37.5)->result; 
  1 } or die;

You may also consider this variant that will return empty envelope and setup $@ on failure:

 2.k. client  
  #!perl -w
  use SOAP::Lite
    on_fault => sub { my($soap, $res) = @_; 
      eval { die ref $res ? $res->faultstring : $soap->transport->status };
      return ref $res ? $res : new SOAP::SOM;
    };
  my $soap = SOAP::Lite
    -> uri('http://www.soaplite.com/Temperatures')
    -> proxy('http://services.soaplite.com/temper.cgi');
  defined (my $temp = $soap->c2f(37.5)->result) or die;
  print $temp;

And finally, if you want to ignore errors (however, you can still check for them with the fault() method call):

 2. fragment  
  use SOAP::Lite
    on_fault => sub {};

or

 2. fragment  
  my $soap = SOAP::Lite
    -> on_fault(sub{})
    ..... other parameters


Service dispatch (different services on one server)

So far our CGI programs have had a single class to handle incoming SOAP calls. But we might have one CGI program that dispatches SOAP calls to many classes. This section shows you how to do that.

Static dispatch is when you hard-code the name of the module that the SOAP requests go to. That module can be defined by your program or loaded when needed.

First, what is this dispatch? When server gets SOAP request it binds it to class specified in request. This class could be already loaded on server side (on server startup or as result of previous calls) or will be loaded on demand according to server configuration. Dispatch is the process of determining of what class should handle this request and loading of this class. Static means that name of the class is specified in configuration and dynamic means that only directory is specified and any class from this particular directory can be accessed.

Now imagine you want to give access to two different classes on server side, and want to provide the same address for both. What should you do? Several options are available (surprised?):

Static internal
That is something you already are familiar with:
 3.a. server (Static internal)  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI   
    -> dispatch_to('Demo')     
    -> handle;
  package Demo;
  sub hi {                     
    return "hello, world";     
  }
  sub bye {                    
    return "goodbye, cruel world";
  }
  1;

Static external
Similar to Static internal, but module is somewhere outside of server code:
 3.b. server (Static external)  
  #!perl -w
  use SOAP::Transport::HTTP;
  use Demo;
  SOAP::Transport::HTTP::CGI   
    -> dispatch_to('Demo')     
    -> handle;

Following file should be somewhere in @INC directory:

 3.b. module (Static external)  
  package Demo;
  sub hi {                     
    return "hello, world";     
  }
  sub bye {                    
    return "goodbye, cruel world";
  }
  1;

Dynamic
As you can see in both Static internal and Static external modes the name of the module is hardcoded in the server's code. But what if you want to be able to add new modules dynamically without changing the code? Dynamic dispatch allows you to do that. Specify the directory and any module in this directory becomes available for dispatching:
 3.c. server (Dynamic)  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('/home/soaplite/modules')
    -> handle;

Then put Demo.pm in /home/soaplite/modules directory:

 3.c. module (Dynamic)  
  package Demo;
  sub hi {                     
    return "hello, world";     
  }
  sub bye {                    
    return "goodbye, cruel world";
  }
  1;

That's it. The any module you put in /home/soaplite/modules is now available, but don't forget that the URI on the client side should match the module/class name you want to dispatch your call to.

Mixed
Why do we need this? Unfortunately, both dynamic and static dispatch have disadvantages. During dynamic dispatch access to @INC is disabled (due to security reasons) and static dispatch loads modules on startup, but this may not be what we want if we have a bunch of modules we want to access. To avoid this, you can combine the dynamic and static approaches.

Let's assume you have 10 modules in /home/soaplite/modules directory, and want to provide access, but don't want to load all of them on startup. All you need to do is this:

 3.d. server (Mixed)  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('/home/soaplite/modules', 'Demo', 'Demo1', 'Demo2')
    -> handle;

Now access to all of these modules is enabled and they'll be loaded on a demand basis, only when needed. And, more importantly, all these modules now have access to @INC array, so can do any use they want.


Types and Names

Since Perl is a typeless language (in the sence that there is no difference between an integer 123 and a string '123') the transformation process from a SOAP message to Perl data is very simple. For most simple types we can just ignore types during this stage. However there are drawbacks also: we need to provide additional information during the generation of our SOAP message, because another side (server) may expect to get type information. SOAP::Lite tries hard to do this job for you and doesn't force you to type every parameter explicitly. It tries to guess the datatype based on the actual value stored in the variable, and behave appropriately (according to another Perl's motto, DWIM, 'Do What I Mean').

For example, the variable that has value 123 becomes element with type int in the SOAP message, and a variable that has value 'abc' gets type string. There are several more complex cases. For example, a variable that has a value with binary zeroes "\0" will be encoded with type base64 and objects (blessed references) will have type and name (unless specified) according to their types.

It may not work in all cases though. There is no way to make (by default) element with type string or type long from value 123, because autotyping will always make type int for this variable.

You may alter this behavior in several ways. You may disable it completely (with autotype(0)), you may change autotyping for different types, or you may explicitly specify type for your variable:

  my $var = SOAP::Data->type(string => 123);

$var is encoded as an element with type string and value 123. You may use this variable in ANY place where you use usual variables in SOAP calls. You may also provide not only a specific data types, but also name and attributes.

Since many services count on names of parameters (instead of positions) you may specify the name for your parameters through the same syntax. To specify the name for the $var variable you may use $var->name('myvar'), or make it in one line:

  my $var = SOAP::Data->type(string => 123)->name('myvar');
  # -- OR --
  my $var = SOAP::Data->type('string')->name(myvar => 123);
  # -- OR --
  my $var = SOAP::Data->type('string')->name('myvar')->value(123);

You may always get/set the value of this variable with the value() method:

  $var->value(321);            # set new value
  my $realvalue = $var->value; # store it in variable


More complex server (daemon, mod_perl and mod_soap)

You shouldn't have many problems with the CGI-based SOAP server you created; however, performance could be significantly better. The next logical step might be to implement SOAP services using accelerators (like PerlEx or VelociGen) or persistent technologies (like mod_perl). Another lightweight solution might be to implement the SOAP service as an HTTP daemon; in that case you don't need to use a separate web server. This might be useful in a situation where a client application accepts SOAP calls, or for internal usage.

HTTP daemon
For HTTP daemon implementation you may write this code:
 4.a. server (HTTP daemon)  
  #!perl -w
  use SOAP::Transport::HTTP;
  use Demo;
  # don't want to die on 'Broken pipe' or Ctrl-C
  $SIG{PIPE} = $SIG{INT} = 'IGNORE';
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 80)
    -> dispatch_to('/home/soaplite/modules')
  ;
  print "Contact to SOAP server at ", $daemon->url, "\n";
  $daemon->handle;

Not much difference from the CGI server (Dynamic), huh? And it makes the same interface accessible, only through the different endpoint. This code is all you need to run the SOAP server on your computer without anything else.

HTTP daemon in VBScript
Similar code in VBScript may look like:
 4.b. server (HTTP daemon, VBScript)  
  call CreateObject("SOAP.Lite") _
   .server("SOAP::Transport::HTTP::Daemon", _
     "LocalPort", 80) _
   .dispatch_to("/home/soaplite/modules") _
   .handle

That is all that you need to run SOAP server on Microsoft platform (and it will run on Win9x/Me/NT/2K as soon as you register Lite.dll with regsvr32 Lite.dll).

ASP/VB
ASP server could be created with VBScript or PerlScript code:
 4.c. server (ASP server, VBScript)  
  <%
    Response.ContentType = "text/xml"
    Response.Write(Server.CreateObject("SOAP.Lite") _
      .server("SOAP::Server") _ 
      .dispatch_to("/home/soaplite/modules") _
      .handle(Request.BinaryRead(Request.TotalBytes)) _
    )
  %>

Apache::Registry
One of the easiest ways to significantly speed up your CGI-based SOAP server is to wrap it with mod_perl Apache::Registry module. You need to configure it in httpd.conf file:
 4.d. server (Apache::Registry, httpd.conf)  
  Alias /mod_perl/ "/Apache/mod_perl/"
  <Location /mod_perl>
    SetHandler perl-script
    PerlHandler Apache::Registry
    PerlSendHeader On
    Options +ExecCGI
  </Location>

Put CGI script soap.mod_cgi in /Apache/mod_perl/ directory mentioned above:

 4.d. server (Apache::Registry, soap.mod_cgi)  
  #!perl -w
  use SOAP::Transport::HTTP;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('/home/soaplite/modules')
    -> handle
  ;

mod_perl
Let's consider mod_perl-based server now. To run it server you'll need to put SOAP::Apache module (Apache.pm) in any directory from @INC:
 4.e. server (mod_perl, Apache.pm)  
  package SOAP::Apache;
  use SOAP::Transport::HTTP;
  my $server = SOAP::Transport::HTTP::Apache
    -> dispatch_to('/home/soaplite/modules')
  sub handler { $server->handler(@_) }
  1;

Then modify your httpd.conf file:

 4.e. server (mod_perl, httpd.conf)  
  <Location /soap>
    SetHandler perl-script
    PerlHandler SOAP::Apache
  </Location>

mod_soap
mod_soap allows you to create a SOAP server by simply configuring the httpd.conf or .htaccess file.
 4.f. server (mod_soap, httpd.conf)  
  # directory-based access
  <Location /mod_soap>
    SetHandler perl-script
    PerlHandler Apache::SOAP
    PerlSetVar dispatch_to "/home/soaplite/modules"
    PerlSetVar options "compress_threshold => 10000"
  </Location>
  # file-based access
  <FilesMatch "\.soap$">
    SetHandler perl-script
    PerlHandler Apache::SOAP
    PerlSetVar dispatch_to "/home/soaplite/modules"
    PerlSetVar options "compress_threshold => 10000"
  </FilesMatch>

Directory-based access turns a directory into SOAP endpoint. For example, you may point your request to http://localhost/mod_soap (there is no need to create this directory).

File-based access turns a file with a specified name (or mask) into a SOAP endpoint. For example, http://localhost/somewhere/endpoint.soap.

Alternatively, you may turn a existing directory into a SOAP server if you put .htaccess file inside it:

 4.g. server (mod_soap, .htaccess)  
  SetHandler perl-script
  PerlHandler Apache::SOAP
  PerlSetVar dispatch_to "/home/soaplite/modules"
  PerlSetVar options "compress_threshold => 10000"


Access to Remote Services

It's time now to reuse what has already done and to try and call services available on the Internet. After all, the most interesting part in SOAP is operability between systems where communicating parts are created in different languages, running on different platforms or in different environments, and providing interfaces with service descriptions or documentation. XMethods.net can be a perfect starting point

Name of state based on state's number (in alphabetical order)
Frontier implementation has test server that returns the name of a state based on number you provided. By default, SOAP::Lite generates a SOAPAction header with the structure of [URI]#[method]. Frontier, however, expects SOAPAction to be just the URI, so we have to use on_action to modify it... In our example we specify on_action(sub { sprintf '"%s"', shift }), so the resulting SOAPAction will contain only the URI (and don't forget double quotes there).
 5.a. client  
  #!perl -w
  use SOAP::Lite;
  # Frontier http://www.userland.com/
  $s = SOAP::Lite 
    -> uri('/examples')
    -> on_action(sub { sprintf '"%s"', shift })
    -> proxy('http://superhonker.userland.com/')
  ;
  print $s->getStateName(SOAP::Data->name(statenum => 25))->result;

You should get the output:

 5.a. result  
  Missouri

Whois
We will target services with different implementations and this service is running on Windows platform:
 5.b. client  
  #!perl -w
  use SOAP::Lite;
  # 4s4c (aka Simon's SOAP Server Services For COM) http://www.4s4c.com/
  print SOAP::Lite 
    -> uri('http://www.pocketsoap.com/whois')
    -> proxy('http://soap.4s4c.com/whois/soap.asp')
    -> whois(SOAP::Data->name('name' => 'yahoo'))
    -> result;

Nothing fancy here, 'name' is the name of the field and 'yahoo' is the value. That should give you the output:

 5.b. result  
  The Data in Network Solutions' WHOIS database is provided by Network
  Solutions for information purposes, and to assist persons in obtaining
  information about or related to a domain name registration record.
  Network Solutions does not guarantee its accuracy.  By submitting a
  WHOIS query, you agree that you will use this Data only for lawful
  purposes and that, under no circumstances will you use this Data to:
  (1) allow, enable, or otherwise support the transmission of mass
  unsolicited, commercial advertising or solicitations via e-mail
  (spam); or  (2) enable high volume, automated, electronic processes
  that apply to Network Solutions (or its systems).  Network Solutions
  reserves the right to modify these terms at any time.  By submitting
  this query, you agree to abide by this policy.
  Yahoo (YAHOO-DOM)                                                  YAHOO.COM
  Yahoo Inc. (YAHOO27-DOM)                                           YAHOO.ORG
  Yahoo! Inc. (YAHOO4-DOM)                                           YAHOO.NET
  To single out one record, look it up with "!xxx", where xxx is the
  handle, shown in parenthesis following the name, which comes first.

Book price based on ISBN
In many cases the SOAP interface is just a frontend that requests information, parses response and formats it and returns according to your request. It may not be doing that much, but it saves you time on the client side and fixes this interface, so you don't need to update it every time your service provider changes format or content. In addition to that, the major players are moving quickly toward XML; for example, Google already has XML-based interface for their search engine. Here is the service that returns book price based on ISBN:
 5.c. client  
  #!perl -w
  use SOAP::Lite;
  # Apache SOAP http://xml.apache.org/soap/ (running on XMethods.net)
  $s = SOAP::Lite                             
    -> uri('urn:xmethods-BNPriceCheck')                
    -> proxy('http://services.xmethods.net/soap/servlet/rpcrouter');
  my $isbn = '0596000278'; # Programming Perl, 3rd Edition
  print $s->getPrice(SOAP::Data->type(string => $isbn))->result;

Here is the result for 'Programming Perl, 3rd Edition':

 5.c. result  
  39.96

Note that we explicitly specified 'string' type, because ISBN looks like number and will be serialized by default as number, but SOAP server we work with requires it to be the string.

Currency Exchange rates
This service returns value of 1 unit of country1's currency converted into country2's unit currency:
 5.d. client  
  #!perl -w
  use SOAP::Lite;
  # GLUE http://www.themindelectric.com/ (running on XMethods.net)
  my $s = SOAP::Lite                             
    -> uri('urn:xmethods-CurrencyExchange')                
    -> proxy('http://services.xmethods.net/soap');
  my $r = $s->getRate(SOAP::Data->name(country1 => 'England'), 
                      SOAP::Data->name(country2 => 'Japan'))
            ->result;
  print "Currency rate for England/Japan is $r\n";

Which gives you (as of 2001/03/11):

 5.d. result  
  Currency rate for England/Japan is 175.4608

NASDAQ quotes
This service returns delayed stock quote based on stock symbol:
 5.e. client  
  #!perl -w
  use SOAP::Lite;
  # GLUE http://www.themindelectric.com/ (running on XMethods.net)
  my $s = SOAP::Lite                             
    -> uri('urn:xmethods-delayed-quotes')                
    -> proxy('http://services.xmethods.net/soap');
  my $symbol = 'AMZN';
  my $r = $s->getQuote($symbol)->result;
  print "Quote for $symbol symbol is $r\n";

It may (or may not, depending on how Amazon is doing) give you:

 5.e. result  
  Quote for AMZN symbol is 12.25


Access with service description (WSDL)

Although support for WSDL 1.1 is limited in SOAP::Lite for now (service description may work in some cases, but hasn't been extensively tested), you can access services that don't have complex types in their description:

 6.a. client  
  #!perl -w
  use SOAP::Lite;
  print SOAP::Lite
    -> service('http://www.xmethods.net/sd/StockQuoteService.wsdl')
    -> getQuote('MSFT');

If we take a look under the hood we'll find that SOAP::Lite makes the request for service description, parses it, builds the stub (object that make available the same methods as remote service) and returns it to you. As the result, you can run several requests using the same service description:

 6.b. client  
  #!perl -w
  use SOAP::Lite;
  my $service = SOAP::Lite
    -> service('http://www.xmethods.net/sd/StockQuoteService.wsdl');
  print 'MSFT + ORCL = ', 
        $service->getQuote('MSFT') + $service->getQuote('ORCL');

The service description doesn't need to be on the Internet, you can access it from your local drive also:

 6.c. client  
  #!perl -w
  use SOAP::Lite
    service => 'http://www.xmethods.net/sd/StockQuoteService.wsdl',
    # service => 'file:/your/local/path/StockQuoteService.wsdl',
    # service => 'file:./StockQuoteService.wsdl',
  ;
  print getQuote('MSFT'), "\n";

This code works in a similar way to the previous example (in OO style), but loads description and imports all methods, so you can use the function interface.

And finally, a couple of one-liners for those who like to do something short and simple (albeit useful and powerful):

 6.d. client  
  # following command is splitted for readability
  perl "-MSOAP::Lite service=>'http://www.xmethods.net/sd/StockQuoteService.wsdl'" 
       -le "print getQuote('MSFT')"
  perl "-MSOAP::Lite service=>'file:./quote.wsdl'" -le "print getQuote('MSFT')"

Last example (second one-liner) seems to be the shortest SOAP method invocation.


Security (SSL, basic/digest authentication, cookie-based authentication, ticket-based authentication, access control)

Though SOAP itself doesn't impose any security mechanisms (unless you'll count SOAP Security Extensions: Digital Signature specification), the extensibility of protocol allows you to leverage many security methods that are available for different protocols, like SSL over HTTP or S/MIME. We'll consider how SOAP can be used together with SSL, basic authentication, cookie-based authorization, and access control.

SSL
Let's start with SSL. Surprisingly there is nothing SOAP-specific you need to do on the server side, and there is only a minor modification on the client side: just specify https: instead of http: as the protocol for your endpoint and everything else will be done for you. Obviously, endpoint should support this functionality and server should be properly configured.
 7.a. client  

  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples',
    proxy => 'https://localhost/cgi-bin/soap.cgi',
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultstring : $soap->transport->status, "\n";
    }
  ;
  print getStateName(21);

Basic authentication
The situation gets even more interesting with authentication. Consider this code that accesses an endpoint that requires authentication.
 7.b. client  

  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples', 
    proxy => 'http://services.soaplite.com/auth/examples.cgi', 
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultstring : $soap->transport->status, "\n";
    }
  ;
  print getStateName(21);

Keep in mind that the password will be in clear text during the transfer (not exactly in clear text; it will be base64 encoded, but that's almost the same) unless the user uses https (ie, authentication doesn't mean encryption).

The server configuration (for an Apache webserver) with authentication may look like this (can be specified in .conf or in .htaccess file):

 7.b. server (.htaccess)  
  AuthUserFile /path/to/users/file/created/with/htpasswd
  AuthType Basic
  AuthName "SOAP::Lite authentication tests"
  require valid-user

If you run example 7.b against this endpoint, you'll probably get error like this:

 7.b. result  
  401 Authorization Required

You may provide required credentials on client side (user soaplite, and password authtest) overriding function get_basic_credentials() in class SOAP::Transport::HTTP::Client:

 7.c. client  

  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples', 
    proxy => 'http://services.soaplite.com/auth/examples.cgi', 
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultstring : $soap->transport->status, "\n";
    }
  ;
  sub SOAP::Transport::HTTP::Client::get_basic_credentials { 
    return 'soaplite' => 'authtest';
  }
  print getStateName(21);

That gives you the correct result:

 7.c. result  
  Massachusetts

Alternatively you may provide this information with credentials() functions, but you need to specify host and realm also:

 7.d. client  

  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples',
    proxy => [
      'http://services.soaplite.com/auth/examples.cgi', 
      credentials => [
        'services.soaplite.com:80',        # host:port
        'SOAP::Lite authentication tests', # realm
        'soaplite' => 'authtest',          # user, password
      ]
    ],
    on_fault => sub { my($soap, $res) = @_; 
      die ref $res ? $res->faultdetail : $soap->transport->status, "\n";
    }
  ;
  print SOAP->getStateName(21);

Under modern Perl you MAY get the warning about 'deprecated usage of inherited AUTOLOAD'. To avoid it use the full syntax: SOAP->getStateName(21) instead of getStateName(21).

The simplest and most convenient way would probably be to provide the user and password embedded in a URL. Surprisingly, this works:

 7.e. client  

  #!perl -w
  use SOAP::Lite;
  print SOAP::Lite
    -> uri('http://www.soaplite.com/My/Examples')
    -> proxy('http://soaplite:authtest@services.soaplite.com/auth/examples.cgi')
    -> getStateName(21)
    -> result;

Cookie-based authentication
Cookie-based authentication also doesn't require a lot of work on the client side. Usually, it means that you need to provide credentials in some way, and if everything is OK, the server will return a cookie on success, then will check it for all subsequent requests. Using available functionality you may not only support this behavior on the client side in one session, but even store cookies in a file and use the same server session for several runs. All you need to do is:
 7.f. client  

  #!perl -w
  use SOAP::Lite; 
  use HTTP::Cookies;
  my $soap = SOAP::Lite
    -> uri('urn:xmethodsInterop')
    -> proxy('http://services.xmethods.net/soap/servlet/rpcrouter', 
             cookie_jar => HTTP::Cookies->new(ignore_discard => 1));
  print $soap->echoString('Hello')->result;

All the magic is in the cookie jar :). You may even add or delete cookies between calls, but the underlying module does everything you need by default. Add file => 'filename' option to new() method to save/restore cookies between sessions. Not much work, huh? Kudos to Gisle Aas on that!

Ticket-based authentication
Ticket-based authentication is a little bit more complex. The logic is similar to cookie-based authentication, but it is executed on the application level, instead of at the transport level. The advantage is that it works for any SOAP transport (not only for HTTP) and gives you a little bit more flexibility. As a result, you won't get support from webserver and you'll have to do everything manually. No big deal, right?

First step is ticket generation. We'll build ticket that contains email, time, and signature.

 7.g. server (TicketAuth)  
  package TicketAuth;
  # we will need to manage Header information to get a ticket
  @TicketAuth::ISA = qw(SOAP::Server::Parameters);
  # ----------------------------------------------------------------------
  # private functions
  # ----------------------------------------------------------------------
  use Digest::MD5 qw(md5);
  my $calculateAuthInfo = sub {
    return md5(join '', 'something unique for your implementation', @_);
  };
  my $checkAuthInfo = sub {
    my $authInfo = shift;
    my $signature = $calculateAuthInfo->(@{$authInfo}{qw(email time)});
    die "Authentication information is not valid\n" 
      if $signature ne $authInfo->{signature};
    die "Authentication information is expired\n" 
      if time() > $authInfo->{time};
    return $authInfo->{email};
  };
  my $makeAuthInfo = sub {
    my $email = shift;
    my $time = time()+20*60; # signature will be valid for 20 minutes
    my $signature = $calculateAuthInfo->($email, $time);
    return +{time => $time, email => $email, signature => $signature};
  };
  # ----------------------------------------------------------------------
  # public functions
  # ----------------------------------------------------------------------
  sub login { 
    my $self = shift;
    pop; # last parameter is envelope, don't count it
    die "Wrong parameter(s): login(email, password)\n" unless @_ == 2;
    my($email, $password) = @_;
    # check credentials, write your own is_valid() function
    die "Credentials are wrong\n" unless is_valid($email, $password);
    # create and return ticket if everything is ok
    return $makeAuthInfo->($email);
  }
  sub protected { 
    my $self = shift;
    # authInfo is passed inside the header
    my $email = $checkAuthInfo->(pop->valueof('//authInfo'));
    # do something, user is already authenticated 
    return;
  }
  1;

It would be very careless (and insecure) to create calculateAuthInfo() as a normal, exposed function because then client could invoke it and generate valid ticket without providing valid credentials (unless you forbid it in SOAP server configuration, but we'll show another way). We will create calculateAuthInfo(), checkAuthInfo() and makeAuthInfo() as 'private' functions, so only other functions inside the same file may access it. It effectively prevents clients from accessing them directly.

The login() function returns hash that has email and time inside as well as an MD5 signature that disallows the user from altering this information. Since the server used a secret string during signature generation, the user is not able to tamper with the resulting signature. To access protected methods, client has to provide the obtained ticket in the header:

 7.g. fragment  

  # login
  my $authInfo = login(email => 'password');
  # convert it into the Header
  $authInfo = SOAP::Header->name(authInfo => $authInfo);
  # invoke protected method
  protected($authInfo, 'parameters');

This is just a fragment, but it should give you some ideas on how to implement ticket-based authentication on application level. You MAY even get the ticket in one place (via HTTP for example) and then access SOAP server via SMTP providing this ticket (ideally you should use PKI [public key infrastructure] for that matter).

Access control
Why would you need access control? Imagine that you have a class and want to give access to it selectively, for example, read access to one person and read/write access to another person (or list of persons). At a low level, read and write access means access to specific functions/methods in class.

You may put this check in at the application level (for example with ticket-based authentication), or you may split your class into two different classes and give one person access only to one of them, but it's not an optimal solution. We consider a different approach, where you create two different endpoints that refer to the same class on the server side, but have different access options.

 7.e. server (first endpoint)  
  use SOAP::Transport::HTTP;
  use Protected;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('Protected::readonly')
    -> handle
  ;

This endpoint will have access only to readonly() method in Protected class.

 7.e. server (second endpoint)  
  use SOAP::Transport::HTTP;
  use Protected;
  SOAP::Transport::HTTP::CGI
    -> dispatch_to('Protected')
    -> handle
  ;

This endpoint will have unrestricted access to all methods/functions in Protected class. Now you may put it under basic, digest or some other kind of authentication preventing access to it.

Thus, by combining the capabilities of webserver with the SOAP server you can create an application that best suites your needs.


Handling LoLs (List of Lists, Structs, Objects, or something else)

Processing of complex data structures isn't different in any aspect from usual processing in your programming language. General rule is simple: 'Treat the result of SOAP call as variable of specified type'.

Next example shows service that works with array of structs (strictly speaking, Perl has no structs. Structs are often emulated with hashes, and that is exactly what is happening here):

 8.a. client  

  #!perl -w
  use SOAP::Lite;
  my $result = SOAP::Lite
        -> uri('urn:xmethodsServicesManager')
        -> proxy('http://www.xmethods.net/soap/servlet/rpcrouter')
        -> getAllSOAPServices();
  if ($result->fault) {
    print $result->faultcode, " ", $result->faultstring, "\n";
  } else {
    # reference to array of structs is returned
    my @listings = @{$result->result};
    # @listings is the array of structs
    foreach my $listing (@listings) {
      print "-----------------------------------------\n";
      # print description for every listing
      foreach my $key (keys %{$listing}) {
        print $key, ": ", $listing->{$key} || '', "\n";
      }        
    }
  }

Exactly the same thing is true about structs inside of other structs, list of objects, objects that have lists inside, etc. 'What you return on server side is what you get on client side, and let me know if you get something else.'

(Ok, not always. You MAY get a blessed array even when you return a simple array on the other side and you MAY get a blessed hash when you return a simple one, but it won't change anything in your code, just access it as you usually do).


XML processing


In/Out parameters


How to write SOAP services


COM interface


Service packages (stubmaker)


Headers and attributes


Handling parameters on server side


Custom data types


Custom serializer and deserializer


Different transports (SMTP/POP3, IO, TCP)


Debugging and Troubleshooting


Global settings


Toolkits and Interoperability


SOAP shell


UDDI requests


Oneliners


Glossary


References


Copyright

Copyright (C) 2001 Paul Kulchenko. All rights reserved.


Author and contributors

Paul Kulchenko (paulclinger@yahoo.com)

Major contributors:

Nathan Torkington
Basically started this work and pushed the whole process.

Tony Hong
Invaluable comments, fixes and input help me keep this material correct, fresh and simple.


Copyright (C) 2001 Paul Kulchenko
2001/05/15 11:20:01