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


SOAP Cookbook

According to The Feynman Problem Solving Algorithm steps you need to undertake to solve any problem are really simple:

  1. Write down the problem.
  2. Think real hard.
  3. Write down the answer.

In most cases you can't avoid step two, but sometimes you need to get the answer as soon as possible. The Cookbook won't teach you, but it might give you an help if you are felling lost working with SOAP or SOAP::Lite.

Citing Larry Wall (foreword for excellent Perl Cookbook written by Tom Christiansen & Nathan Torkington from O'Reilly): 'Easy things should be easy, and hard things should be possible'.


Making SOAP

SOAP acronym

Problem

You want to know what SOAP stands for.

Solution

Sometimes SOAP stands for Simple Object Access Protocol, and sometimes for Services Oriented Access Protocol, depending on your view on simplicity.

Information about SOAP protocol

Problem

You want to find more information about the SOAP protocol.

Solution

Check list of resources on http://www.soaplite.com/#LINKS where you can find references to specifications, articles, tutorials and presentations about SOAP and related technologies.

Information about SOAP toolkits

Problem

You are looking for the SOAP toolkit for your favorite language.

Solution

Take a look at http://www.soaplite.com/#TOOLKITS

Discussion

With more than fifty toolkits available (as of May, 2001) in more than 15 languages (Ada, C#, C++, Java, JavaScript, Perl, PHP, Python, Ruby, Smalltalk, Tcl, VB, XSLT, Delphi, Orchard, Smalltalk, K) you shouldn't have a problem finding a toolkit for your language of choice. If you can't find what you are looking for, join the soapbuilders list at http://groups.yahoo.com/group/soapbuilders and start writing your own implementation.

See Also

The SOAP directory at http://www.soapware.org/

The SOAP services directory at http://www.xmethods.net/

Install in a custom directory

Problem

You want to install SOAP::Lite, but don't have root/administrator privileges.

Solution

Install SOAP::Lite into a custom directory using CPAN module:

  # perl -MCPAN -e shell
  > o conf make_arg -I~/lib
  > o conf make_install_arg -I~/lib
  > o conf makepl_arg LIB=~/lib PREFIX=~ INSTALLMAN1DIR=~/man/man1 INSTALLMAN3DIR=~/man/man3
  > install SOAP::Lite

Discussion

Setup PERL5LIB environment variable. Depending on your shell it may look like:

  PERL5LIB=/you/home/directory/lib; export PERL5LIB

lib here is the name of directory where all libraries will be installed under your home directory.

Run CPAN module with

  perl -MCPAN -e shell

and run three commands from CPAN shell

  > o conf make_arg -I~/lib
  > o conf make_install_arg -I~/lib
  > o conf makepl_arg LIB=~/lib PREFIX=~ INSTALLMAN1DIR=~/man/man1 INSTALLMAN3DIR=~/man/man3

LIB will specify directory where all libraries will reside.

PREFIX will specify prefix for all directories (like lib, bin, man, though it doesn't work in all cases for some reason).

INSTALLMAN1DIR and INSTALLMAN3DIR specify directories for manuals (if you don't specify them, install will fail because it'll try to setup it in default directory and you don't have permissions for that).

Then run:

  > install SOAP::Lite

Now in your scripts you need to specify:

  use lib '/your/home/directory/lib';

somewhere before 'use SOAP::Lite;'


Client SOAP

Writing a client

Problem

You want to write a SOAP client.

Solution

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

Discussion

There is some information you need to provide for every SOAP call:

address
address of endpoint that will handle you call (specified with proxy() method, can be http:, mailto:, tcp: or something else, depending on what kind of SOAP server will handle your request);

namespace
namespace (URI) of method element (specified with uri() method), which will help the SOAP server in handling your request;

method and parameters
method name with parameters (hi() in our example);

SOAPAction
optional SOAPAction header (specified with on_action() method, omitted in this example).

Accessing envelope with autodispatch

Problem

You want to access envelope returned by an autodispatched call.

Solution

 Accessing envelope with autodispatch  
  #!perl -w
  use SOAP::Lite +autodispatch =>
    uri => 'http://www.soaplite.com/Temperatures',
    proxy => 'http://services.soaplite.com/temper.cgi';
  c2f(37.5);
  print SOAP::Lite->self->call->result;

Discussion

One of the differences between autodispatched call (AD) and call with object interface (OO) is that the result of OO call is an envelope (SOAP::SOM object) and the result of AD call is the pure result only, so you don't have direct access to envelope element and hence need to have some other way to access headers, returned parameters and attributes. SOAP::Lite->self returns the SOAP::Lite object that handles autodispatched calls, SOAP::Lite->self->call returns the envelope (SOAP::SOM) object for the last call and SOAP::Lite->self->call->result method accesses the result element of this envelope. Following this scheme, you can access any of the elements of the returned envelope, such as ->fault, ->faultcode, ->faultstring, ->headers, and others.

Overriding serializer (client)

Problem

You need to override the serializer to alter data serialization

Solution

 Overriding serializer (client)  
  #!perl -w
  use SOAP::Lite;
  use XMLRPC::Lite;
  my $soap = SOAP::Lite
    -> serializer(XMLRPC::Serializer->new);

Creating custom serializer

Problem

You want to customize the serializer on client side.

Solution

 Creating custom serializer  
  #!perl -w
  use SOAP::Lite;
  # define serializer
  BEGIN {
    package My::Serializer; 
    @My::Serializer::ISA = 'SOAP::Serializer';
    # methods of My::Serializer
    # ....
  }
  # register serializer
  my $soap = SOAP::Lite
    -> serializer(My::Serializer->new);

Overriding deserializer (client)

Problem

You want to customize the deserializer on the client side.

Solution

 Overriding deserializer (client)  
  #!perl -w
  use SOAP::Lite;
  use XMLRPC::Lite;
  my $soap = SOAP::Lite
    -> deserializer(XMLRPC::Deserializer->new);

Customizing SOAPAction header

Making class or object calls with OO interface

Internationalization and encoding

Problem

You want to send string that has an international characters inside. What to do and what encoding to specify?

Solution

  1. use iso-8859-1 encoding

     Internationalization and encoding  
      #!perl -w
      use SOAP::Lite;
      # specify type explicitly and it won't be encoded as base64
      my $string = SOAP::Data->type(string => 'tštš');
      my $result = SOAP::Lite
        -> proxy (...)
        -> uri (...)
           # specify encoding, because default is utf-8
        -> encoding('iso-8859-1')
        -> hello($string)
        -> result;

  2. use UTF-8 encoding

     Internationalization and encoding  
      #!perl -w
      use SOAP::Lite;
      # convert to UTF (if you're using Perl 5.6)
      (my $utf = 'tštš') =~ tr/\0-\x{ff}//CU;
      # specify type explicitly and it won't be encoded as base64
      my $string = SOAP::Data->type(string => $utf);
      my $result = SOAP::Lite
        -> proxy (...)
        -> uri (...)
        -> hello($string)
        -> result;

Discussion

Hard topic. Intent is simple, represent you data on wire, so other side will understand you. You have your data encoded using some encoding in your application, so you should either specify this encoding on wire, or convert your encoding to something you can use on wire. You best bet is to stay with iso-8859-1 or UTF-8 (and maybe UTF-16), because every implementation should understand those encodings.

How to do it? The first example shows how to specify encoding on wire (if you do have this encoding in your application). The second one shows how to convert your values into UTF-8 and use this (default) encoding. Convertion itself may not be so straightforward if you use older versions of Perl. You may need to use Unicode::String or Unicode::Map8 to convert from your encoding to UTF-8.

If you have strings with non-ASCII values you need to specify type string explicitly, otherwise autotyping will encode this string as base64 value. Hopefully next version (after v0.50) will be more tolerant and autotyping won't encode those strings as base64, so you won't need to specify type explicitly.

On the server side you'll always get your data encoded as UTF-8 regardless of encoding specified on wire (data will be converted by XML::Parser).

See Also

UTF-8 and Unicode FAQ for Unix/Linux at http://www.cl.cam.ac.uk/~mgk25/unicode.html

Iteration over the list of endpoints

Problem

You have a list of endpoints that provide the same functionality. You want to use the next endpoint from this list if call failes and do it transparently for the application. Is it possible?

Solution

 Iteration over the list of endpoints  
  BEGIN {
    package My::SOAP::Lite;
    use SOAP::Lite;
    @My::SOAP::Lite::ISA = 'SOAP::Lite';
    my @alternatives = qw(
      http://somethingelse/ 
      http://and.another.one/ 
      http://services.soaplite.com/hibye.cgi
    );
    sub call {
      my $self = shift;
      return $self->SUPER::call unless @_;
      my $result = $self->SUPER::call(@_);
      return $result if $self->transport->is_success;
      # ok, try the next endpoint (if any) till our call succeed
      while (my $next = shift @alternatives) {
        $result = $self->endpoint($next)->SUPER::call(@_);
        last if $self->transport->is_success;
      }
      return $result;
    }
  }
  my $soap = My::SOAP::Lite
    -> proxy('http://wrong/')
    -> uri('Demo')
    -> on_fault(sub{})
  ;
  print $soap->hi->result;
  # it should use the last successful endpoint
  print $soap->hi->result;

Discussion

You can modify porvided logic and, for example, initialize @alternatives array when it becomes empty, so calls will be restarted from the beginning. You can also use the different lists for the different methods.

Specifying attributes for method

Accessing service with service description (WSDL)


Server SOAP

Writing a server

Accessing specific modules

Returning headers

Problem

You need to add some headers in Response message

Solution

 Returning headers  
  return 1, SOAP::Header->name(a => 1)->uri('http://my.namespace/'), 2;

Discussion

All SOAP::Header elements will be encoded as headers in serialized message. You can use SOAP::Header object exactly as you use SOAP::Data and provide required name, value, namespace and attributes. Position of headers among other parameters is not significant. Headers will be serialized in the same order as they provided, but this behavior might change in future versions.

Returning null value

Problem

You want to return undef value

Solution

 Returning headers  
  return undef;
  # -- OR --
  return {key => undef};
  # -- OR --
  return [1, undef, 3];
  # -- OR --
  return SOAP::Data->name(name => undef);

Discussion

Nothing fancy here, variable that has undefined value, or literal undef will be serialized, and client side gets it as the undef value.

Accessing envelope

Overriding serializer (server)

Problem

You need to override the serializer to alter data serialization

Solution

 Overriding serializer (server)  
  use SOAP::Transport::HTTP;
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 80)
    # register serializer
    -> serializer(MySerializer->new)
    -> dispatch_to(...) 
  ;
  print "Contact to SOAP server at ", $daemon->url, "\n";
  $daemon->handle;
  BEGIN {
    package MySerializer; 
    @MySerializer::ISA = 'SOAP::Serializer';
    # methods of MySerializer
    # ....
  }

Discussion

Everything looks very similar to what is done on a client side (Overriding serializer (client)). You can also share the same serializer between client and server side.

See also

Changing method name in response and Changing attributes for method element in response for useful examples.

Overriding deserializer (server)

Problem

You need to override the deserializer to alter data serialization

Solution

 Overriding deserializer (server)  
  use SOAP::Transport::HTTP;
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 80)
    # register serializer
    -> deserializer(MyDeserializer->new)
    -> dispatch_to(...) 
  ;
  print "Contact to SOAP server at ", $daemon->url, "\n";
  $daemon->handle;
  BEGIN {
    package MyDeserializer; 
    @MyDeserializer::ISA = 'SOAP::Deserializer';
    # methods of MyDeserializer
    # ....
  }

Changing method name in response

Problem

You want to change method name in response message.

Solution

 Changing method name  
  use SOAP::Transport::HTTP;
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 80) 
    -> serializer(MySerializer->new)
    -> dispatch_to(...) 
  ;
  print "Contact to SOAP server at ", $daemon->url, "\n";
  $daemon->handle;
  BEGIN {
    package MySerializer; @MySerializer::ISA = 'SOAP::Serializer';
    sub envelope {
      $_[2] =~ s/Response$// if $_[1] =~ /^(?:method|response)$/;
      shift->SUPER::envelope(@_);
    }
  }

Discussion

Overriding the serializer give you full control over serialization process and envelope generation. envelope() method will be called every time when message is generated, and this code will drop 'Response' suffix from method name. Keep in mind, however, that in future versions method name ($_[2]) in this case might be represented not only as a string, but also as a SOAP::Data object, so perfectly correct code might look like:

  sub envelope {
    UNIVERSAL::isa($_[2] => 'SOAP::Data') 
      ? do { (my $method = $_[2]->name) =~ s/Response$//; $_[2]->name($method) }
      : $_[2] =~ s/Response$// 
      if $_[1] =~ /^(?:method|response)$/;
    shift->SUPER::envelope(@_);
  }

Changing attributes for method element in response

Problem

You want to change attributes for method element in response message.

Solution

 Changing attributes for method  
  use SOAP::Transport::HTTP;
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 80) 
    -> serializer(MySerializer->new)
    -> dispatch_to(...) 
  ;
  print "Contact to SOAP server at ", $daemon->url, "\n";
  $daemon->handle;
  BEGIN {
    package MySerializer; @MySerializer::ISA = 'SOAP::Serializer';
    sub envelope {
      $_[2] = SOAP::Data->name($_[2])
        ->encodingStyle("http://xml.apache.org/xml-soap/literalxml";)
        if $_[1] =~ /^(?:method|response)$/;
      shift->SUPER::envelope(@_);
    }
  }

Discussion

This example will put encodingStyle attribute directly on method element. Considering comment for Changing method name in response, perfectly valid code might look like:

  sub envelope {
    (UNIVERSAL::isa($_[2] => 'SOAP::Data') ? $_[2] : SOAP::Data->name($_[2]))
      ->encodingStyle("http://xml.apache.org/xml-soap/literalxml";)
      if $_[1] =~ /^(?:method|response)$/;
    shift->SUPER::envelope(@_);
  }

Can be used to tell ApacheSOAP that enclosed XML has literal encoding, as well as in many other cases.


SOAP Faults

Returning fault

Problem

You want to return fault from the method on the server side.

Solution

 Returning fault  
  sub my_method {
    # do something here
    return $result if $everything_is_ok;
    die "Something bad happened\n";
  }

Discussion

SOAP processor will catch every die on the server side and will package your error as a Fault message, providing you message as the faultstring and Client as the faultcode. Use Customizing fault advice if you need more control on returned Fault.

Customizing fault

Problem

You want to return fault from the method on the server side, but you also need to have a control on what to specify as a faultcode, faultdetail and probably faultactor.

Solution

 Customizing fault  
  sub die_with_object {
    die SOAP::Data
      -> name(something => 'value')
      -> uri('http://www.soaplite.com/');
  }
  sub die_with_fault {
    die SOAP::Fault->faultcode('Server.Custom') # will be qualified
                   ->faultstring('Died in server method')
                   ->faultdetail(bless {code => 1} => 'BadError')
                   ->faultactor('http://www.soaplite.com/custom');
  }

Discussion

You can use die in three different modes: die with string (as in Returning fault, will be reported as faultstring); die with object (as in die_with_object() method, will be reported as detail); die with SOAP::Fault object (as in die_with_fault() method), allows you to specify all parameters of provided Fault. Both faultcode and faultstring are required, so library will specify them for you even if you omit them.

Handling faults


Transporting SOAP

Using different transports

Specifying proxy

Problem

You are behind a proxy firewall and want to configure SOAP::Lite to take it into account.

Solution

Global

Use environment variable HTTP_proxy to specify proxy like this:

  HTTP_proxy=http://proxy.my.com/

Syntax can be different depending on your command processor. You may also specify this variable in your script:

  $ENV{HTTP_proxy} = "http://proxy.my.com/";;
Local

 Specifying proxy  
 my $soap = SOAP::Lite->proxy('http://endpoint.server/', 
                              proxy => ['http' => 'http://my.proxy.server/']);
 # -- OR --
 my $soap = SOAP::Lite->proxy('http://endpoint.server/', 
                              proxy => 'http://my.proxy.server/');
 # -- OR --
 my $soap = SOAP::Lite->proxy('http://endpoint.server/');
 $soap->transport->proxy(http => 'http://my.proxy.server/');

Discussion

The proxy() method can accept transport-specific parameters that can be passed as name => value pairs. If value is represented by more than one element (as in the first example) it should be wrapped into an array.

Alternatively, the transport() method gives you access to the underlying module (LWP::UserAgent for HTTP protocol), so any options supported by that transport module can be specified this way also.

Specifying timeout

Problem

You want to change the timeout value, so your SOAP calls will timeout sooner.

Solution

 Specifying timeout  
 my $soap = SOAP::Lite->proxy('http://endpoint.server/', 
                              timeout => 5);
 # -- OR --
 my $soap = SOAP::Lite->proxy('http://endpoint.server/');
 $soap->transport->timeout(5);

Accessing service with basic authentication

Problem

You want to access endpoint that uses basic authentication.

Solution

 Accessing service with basic authentication  
  #!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;

Discussion

Since HTTP client functionality is based on LWP::UserAgent you may use the same techniques as you use with this module. Embedding username:password in URL is one of the options. Another option is to specify the get_basic_credentials() method that will be called when asked for credentials:

 Accessing service with basic authentication  
  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples', 
    proxy => 'http://services.soaplite.com/auth/examples.cgi', 
  ;
  sub SOAP::Transport::HTTP::Client::get_basic_credentials { 
    return 'soaplite' => 'authtest';
  }
  print getStateName(21);

Accessing service with proxy authentication

Problem

You want to access endpoint that uses proxy authentication.

Solution

You have several options (hope you are not surprised):

  1. You may specify HTTP_proxy_user and HTTP_proxy_pass environment variables for user and password and SOAP::Lite should know how to handle it properly

  2. Combine knowledge about Specifying proxy and Accessing service with basic authentication and specify both proxy and information for proxy authentication:

     Accessing service with proxy authentication  
     my $soap = SOAP::Lite->proxy('http://user:password@endpoint.server/', 
                                  proxy => 'http://my.proxy.server/');

Accessing service with SSL

Problem

You want to access SOAP server using SSL.

Solution

Specify in your client https protocol instead of http:

 Accessing service with SSL  
  #!perl -w
  use SOAP::Lite +autodispatch => 
    uri => 'http://www.soaplite.com/My/Examples',
    proxy => 'https://localhost/cgi-bin/soap.cgi';
  print getStateName(21);

Discussion

Is there anything to discuss?

Using cookies

Problem

You want to accept cookies from SOAP response and provide it in the next request, probably using cookie-based authentication or for any other reason.

Solution

 Using cookies  
  #!perl -w
  use HTTP::Cookies;
  use SOAP::Lite;
  # you may also add 'file' if you want to keep cookie between sessions
  my $soap->proxy('http://localhost/', 
                  cookie_jar => HTTP::Cookies->new(ignore_discard => 1));

Discussion

Cookies will be taken from the response and provided for the request. You may always add another cookie (or extract what you need after response) using the HTTP::Cookies interface.

Enabling compression

Problem

You want to enable SOAP::Lite's support for compression on the wire.

Solution

Discussion

SOAP::Lite provides you with the option for enabling compression on the wire (for HTTP transport only). Both server and client should support this capability, but this should be absolutely transparent to your application. The Server will respond with an encoded message only if the client can accept it (indicated by client sending an Accept-Encoding header with 'deflate' or '*' values) and client has fallback logic, so if server doesn't understand specified encoding (Content-Encoding: deflate) and returns proper error code (415 NOT ACCEPTABLE) client will repeat the same request without encoding and will store this server in a per-session cache, so all other requests will go there without encoding.

Compression will be enabled on the client side if the threshold is specified and the size of current message is bigger than the threshold and the module Compress::Zlib is available.

The Client will send the header 'Accept-Encoding' with value 'deflate' if the threshold is specified and the module Compress::Zlib is available.

Server will accept the compressed message if the module Compress::Zlib is available, and will respond with the compressed message only if the threshold is specified and the size of the current message is bigger than the threshold and the module Compress::Zlib is available and the header 'Accept-Encoding' is presented in the request.

Reusing sockets on restart

Problem

You want to restart Daemon or TCP server implementation, but on restart sockets go into TIME_WAIT for quite some time before expiring. What to do?

Solution

Use Reuse => 1 as in:

 Reusing sockets on restart  
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 10000,
            Reuse => 1)

Thanks to Michael Brutsch <mbrutsch@intrusion.com>, Sean Meisner <Sean.Meisner@verizonwireless.com> and others.

See Also

http://www.perlfect.com/articles/sockets.shtml

Several Daemon interfaces

Problem

You want to start Daemon server, but access it through different hostnames or aliases.

Solution

Do not specify LocalAddr in new() method for HTTP::Daemon:

 Several Daemon interfaces  
  my $daemon = SOAP::Transport::HTTP::Daemon
    -> new (LocalPort => 10000)

Discussion

If you do not specify LocalAddr then you can access it with any hostname/IP alias, including localhost or 127.0.0.1. If you do specify LocalAddr in ->new() then you can only access it from that interface.

Thanks to Michael Percy <mpercy@portera.com>.

Changing HTTP protocol

Problem

You want to talk to a server that accepts only HTTP/1.1 requests. How do you tell the client to send HTTP/1.1 header?

Solution

Write handler that will modify request to required protocol:

 Changing HTTP protocol  
  use SOAP::Lite +trace => 
    transport => sub { 
      $_[0]->protocol('HTTP/1.1') if $_[0]->isa('HTTP::Request') 
    }
  ;


Copyright

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


Author

Paul Kulchenko (paulclinger@yahoo.com)


Copyright (C) 2001 Paul Kulchenko
2001/06/18 15:09:01