The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

SOAP::WSDL - SOAP with WSDL support

SYNOPSIS

 use SOAP::WSDL;

 my $soap = SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' );
 $soap->wsdlinit();
 $soap->servicename( 'myservice' );
 $soap->portname( 'myport' );

 my $som = $soap->call( 'method' =>  (
                   name => 'value' ,
                   name => 'value' ) );

DESCRIPTION

This is mainly a bugfix update to 1.22, which was a small update to 1.21 - autodetection of servicename and portname have been are re-introduced, so users of 1.20 have no need to change their scripts.

1.21 was a new version of SOAP::WSDL, mainly based on the work of Giovanni S Fois.

SOAP::WSDL provides WSDL support for SOAP::Lite. It is built as a add-on to SOAP::Lite, and will sit on top of it, forwarding all the actual request-response to SOAP::Lite - somewhat like a pre-processor.

WSDL support means that you don't have to deal with those bitchy namespaces some web services set on each and every method call parameter.

It also means an end to that nasty

 SOAP::Data->name( 'Name' )->value(
    SOAP::Data->name( 'Sub-Name')->value( 'Subvalue' )
 );

encoding of complex data. (Another solution for this problem is just iterating recursively over your data. But that doesn't work if you need more information [e.g. namespaces etc] than just your data to encode your parameters).

And it means that you can use ordinary hashes for your parameters - the encording order will be derived from the WSDL and not from your (unordered) data, thus the problem of unordered perl-hashes and WSDL >sequence< definitions is solved, too. (Another solution for the ordering problem is tying your hash to a class that provides ordered hashes - Tie::IxHash is one of them).

Why should I use this ?

SOAP::WSDL eases life for webservice developers who have to communicate with lots of different web services using a reasonable big number of method calls.

If you just want to call a hand full of methods of one web service, take SOAP::Lite's stubmaker and modify the stuff by hand if it doesn't work right from the start. The overhead SOAP::WSDL imposes on your calls is not worth the time saving.

If you need to access many web services offering zillions of methods to you, this module should be your choice. It automatically encodes your perl data structures correctly, based on the service's WSDL description, handling even those complex types SOAP::Lite can't cope with.

SOAP::WSDL also eliminates most perl <-> .NET interoperability problems by qualifying method and parameters as they are specified in the WSDL definition.

USAGE

 my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' );

 # or
 my $soap=SOAP::WSDL->new()
 $soap->wsdl('http://server.com/ws.wsdl');

 # or
 # without dispatching calls to the WebService
 #
 # useful for testing
 my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl',
                no_dispatch => 1 );

 # never forget to call this !in order to start the parsing procedure
 $soap->wsdlinit();

 # with caching enabled:don't forget the cache directory
 $soap->wsdlinit( caching => 1, cache_directory =>"/tmp/cachedir");

 # optional, set to a false value if you don't want your
 # soap message elements to be typed
 $soap->autotype(0);

 # you may specify a service and port - if not, SOAP::WSDL will search for
 # the first one appearing in the WSDL
 $soap->servicename('myservice');
 $soap->portname('myport');

 my $som=$soap->call( 'method' ,
                   name => 'value' ,
                   name => 'value'  );


 # with the method overloaded (got it from the standard)
 my $som=$soap->call( 'method' ,
                   wsdl_input_name => unique_input_message_name
                   name => 'value' ,
                   name => 'value'  );

  # with headers (see the SOAP documentation)

 #first define your headers
 @header = (SOAP::Header->name("FirstHeader")->value("FirstValue"),
    SOAP::Header->name("SecontHeader")->value("SecondValue"));

 #and then do the call. please note the backslash
 my $som=$soap->call( 'method' ,
                   name => 'value' ,
                   name => 'value' ,
                   "soap_headers" => \@header);

How it works

SOAP::WSDL takes the wsdl file specified and looks up the service and the specified port.

On calling a SOAP method, it looks up the message encoding and wraps all the stuff around your data accordingly.

Most pre-processing is done in wsdlinit, the rest is done in call, which overrides the same method from SOAP::Lite.

wsdlinit

SOAP::WSDL loads the wsdl file specified by the wsdl parameter / call using SOAP::Lite's schema method. It sets up a XPath object of that wsdl file, and subsequently queries it for namespaces, service, and port elements.

SOAP::WSDL automatically uses the first service and port found in the WSDL.

If you want to chose a different one, you can specify the service by calling

 $soap->servicename('ServiceToUse');
 $soap->portname('PortToUse');

call

The call method examines the wsdl file to find out how to encode the SOAP message for your method. Lookups are done in real-time using XPath, so this incorporates a small delay to your calls (see "Memory consumption and performance" below.

The SOAP message will include the types for each element, unless you have set autotype to a false value by calling

 $soap->autotype(0);

After wrapping your call into what is appropriate, SOAP::WSDL uses the call() method from SOAP::Lite to dispatch your call.

call takes the method name as first argument, and the parameters passed to that method as following arguments.

Example:

 $som=$soap->call( "SomeMethod" , "test" => "testvalue" );

 $som=$soap->call( "SomeMethod" => %args );

Caching

SOAP::WSDL uses a two-stage caching mechanism to achieve best performance.

First, there's a pretty simple caching mechanisms for storing XPath query results.

They are just stored in a hash with the XPath path as key (until recently, only results of "find" or "findnodes" are cached). I did not use the obvious Cache or Cache::Cache module here, because these use Storable to store complex objects and thus incorporate a performance loss heavier than using no cache at all.

Second, the XPath object and the XPath results cache are be stored on disk using the Cache::FileCache implementation.

A filesystem cache is only used if you

 1) enable caching
 2) set wsdl_cache_directory

The cache directory must be, of course, read- and writeable.

XPath result caching doubles performance, but increases memory consumption - if you lack of memory, you should not enable caching (disabled by default).

Filesystem caching triples performance for wsdlinit and doubles performance for the first method call.

The file system cache is written to disk when the SOAP::WSDL object is destroyed.

It may be written to disk any time by calling the "wsdl_cache_store" method.

Using both filesystem and in-memory caching is recommended for best performance and smallest startup costs.

Sharing cache between applications

Sharing a file system cache among applications accessing the same web service is generally possible, but may under some circumstances reduce performance, and under some special circumstances even lead to errors. This is due to the cache key algorithm used.

SOAP::WSDL uses the WSDL's URL to store the XML::XPath object of the wsdl file. If you're using more than one WSDL definition on the same URL, this may lead to errors when two or more applications using SOAP::WSDL share a file system cache.

SOAP::WSDL stores the XPath results in-memory-cache in the filesystem cache, using the URL wsdl file with _cache appended. Two applications sharing the file system cache and accessing different methods of one web service could overwrite each others in-memory-caches when dumping the XPath results to disk, resulting in a slight performance drawback (even though this only happens in the rare case of one app being started before the other one has had a chance to write its cache to disk).

Controlling the file system cache

If you want full controll over the file system cache, you can use wsdl_init_cash to initialize it. wsdl_init_cash will take the same parameters as Cache::FileCache->new(). See Cache::Cache and Cache::FileCache for details.

Notes

If you plan to write your own caching implementation, you should consider the following:

The XPath results cache must not survive the XPath object SOAP::WSDL uses to store the WSDL file in (this could cause memory holes - see XML::XPath for details). This never happens during normal usage - but note that you have been warned before trying to store and re-read SOAP::WSDL's internal cache.

Methods

Frequently used methods

wsdl

 $soap->wsdl('http://my.web.service.com/wsdl');

Use this to specify the WSDL file to use. Must be a valid (and accessible !) url.

You must call this before calling "wsdlinit".

For time saving's sake, this should be a local file - you never know how much time your WebService needs for delivering a wsdl file.

wsdlinit

 $soap->wsdlinit( caching => 1,
        cache_directory => '/tmp/cache' );

Initializes the WSDL document for usage.

wsdlinit will die if it can't set up the WSDL file properly, so you might want to eval{} it.

On death, $@ will (hopefully) contain some error message like

 Error processing WSDL: no <definitions> element found

to give you a hint about what went wrong.

wsdlinit will accept a hash of parameters with the following keys:

  • caching

    enables caching if true

  • cache_directory

    The cache directory to use for FS caching

  • url

    URL to derive port and service name from. If url is given, wsdlinit will try to find a matching service and port in the WSDL definition.

  • servicename

    like setting the servicename directly. See <servicename|servicename> below.

  • portname

    like setting the servicename directly. See <portname|portname> below.

call

 $soap->call($method, %data);

See above.

call will die if it can't find required elements in the WSDL file or if your data doesn't meet the WSDL definition's requirements, so you might want to eval{} it. On death, $@ will (hopefully) contain some error message like

 Error processing WSDL: no <definitions> element found

to give you a hint about what went wrong.

Configuration methods

servicename

 $soap->servicename('Service1');

Use this to specify a service by name. Your wsdl contains definitions for one or more services - hou have to tell SOAP::WSDL which one to use.

You can call it before each method call.

portname

 $soap->portname('Port1');

Your service can have one or many ports attached to it. Each port has some operation defined in it trough a binding. You have to tell which port of your service should be used for the method you are calling.

You can call it before each method call.

wsdl_checkoccurs

Enables/disables checks for correct number of occurences of elements in WSDL types. The default is 1 (on).

Turning off occurance number checking results in a sligt performance gain.

To turn off checking for correct number of elements, call

 $soap->wsdl_checkoccurs(0);

wsdl_encoding

The encoding style for the SOAP call.

cache_directory

enables filesystem caching (in the directory specified). The directory given must be existant, read- and writeable.

wsdl_cache_directory

 $soap->wsdl_cache_directory( '/tmp/cache' );

Sets the directory used for filesystem caching and enables filesystem caching. Passing the cache_directory parameter to wsdlinit has the same effect.

Seldomly used methods

The following methods are mainly used internally in SOAP::WSDL, but may be useful for debugging and some special purposes (like forcing a cache flush on disk or custom cache initializations).

no_dispatch

Gets/Sets the no_dispatch flag. If no_dispatch is set to true value, SOAP::WSDL will not dispatch your calls to a remote server but return the SOAP::SOM object containing the call instead.

encode

        # this is how call uses encode
        # $xpath contains a XPath object of the wsdl document

        my $def=$xpath->find("/definitions")->shift;
        my $parts=$def->find("//message[\@name='$messageName']/part");

        my @param=();

        while (my $part=$parts->shift) {
                my $enc=$self->encode($part, \%data);
                push @param, $enc if defined $enc;
        }

Does the actual encoding. Expects a XPath::NodeSet as first, a hashref containing your data as second parameter. The XPath nodeset must be a node specifying a WSDL message part.

You won't need to call encode unless you plan to override call or want to write a new SOAP server implementation.

* wsdl_cache_init

Initialize the WSDL file cache. Normally called from wsdlinit. For custom cache initailization, you may pass the same parameters as to Cache::FileCache->new().

wsdl_cache_store

 $soap->wsdl_cache_store();

Stores the content of the in-memory-cache (and the XML::XPath representation of the WSDL file) to disk. This will not have any effect if cache_directory is not set.

Notes

Why another SOAP module ?

SOAP::Lite provides only some rudimentary WSDL support. This lack is not just something unimplemented, but an offspring of the SOAP::Schema class design. SOAP::Schema uses some complicated format to store XML Schema information (mostly a big hashref, containing arrays of SOAP::Data and a SOAP::Parser-derived object). This data structure makes it pretty hard to improve SOAP::Lite's WSDL support.

SOAP::WSDL uses XPath for processing WSDL. XPath is a query language standard for XML, and usually a good choice for XML transformations or XML template processing (and what else is WSDL-based en-/decoding ?). Besides, there's an excellent XPath module (XML::XPath) available from CPAN, and as SOAP::Lite uses XPath to access elements in SOAP::SOM objects, this seems like a natural choice.

Fiddling the kind of WSDL support implemented here into SOAP::Lite would mean a larger set of changes, so I decided to build something to use as add-on.

Memory consumption and performance

SOAP::WSDL uses around twice the memory (or even more) SOAP::Lite uses for the same task (but remember: SOAP::WSDL does things for you SOAP::Lite can't). It imposes a slight delay for initialization, and for every SOAP method call, too.

On my 1.4 GHz Pentium mobile notebook, the init delay with a simple WSDL file (containing just one operation and some complex types and elements) was around 50 ms, the delay for the first call around 25 ms and for subsequent calls to the same method around 7 ms without and around 6 ms with XPath result caching (on caching, see above). XML::XPath must do some caching, too - don't know where else the speedup should come from.

Calling a method of a more complex WSDL file (defining around 10 methods and numerous complex types on around 500 lines of XML), the delay for the first call was around 100 ms for the first and 70 ms for subsequent method calls. wsdlinit took around 150 ms to process the stuff. With XPath result caching enabled, all but the first call take around 35 ms.

Using SOAP::WSDL on an idiotically complex WSDL file with just one method, but around 100 parameters for that method, mostly made up by extensions of complex types (the heaviest XPath operation) takes around 1.2 s for the first call (0.9 with caching) and around 830 ms for subsequent calls (arount 570 ms with caching).

The actual performance loss compared to SOAP::Lite should be around 10 % less than the values above - SOAP::Lite encodes the data for you, too (or you do it yourself) - and encoding in SOAP::WSDL is already covered by the pre-call delay time mentioned above.

If you have lots of WebService methods and call each of them from time to time, this delay should not affect your perfomance too much. If you have just one method and keep calling it ever & ever again, you should cosider hardcoding your data encoding (maybe even with hardcoded XML templates - yes, this may be a BIG speedup).

CAVEATS

API change between 1.20 and 1.21

Giovanni S. Fois has implemented a new calling convention, which allows to specify the port type used by SOAP::WSDL.

While this allows greater flexibillity (and helps around the still missing bindings support), the following lines have to be added to existing code:

 $soap->servicename( $servicename);
 $soap->portname( $porttype );

Both lines must appear after calling

 $soap->wsdlinit();

API change between 1.13 and 1.14

The SOAP::WSDL API changed significantly between versions 1.13 and 1.14. From 1.14 on, call expects the following arguments: method name as scalar first, method parameters as hash following.

The call no longer recognizes the dispatch option - to get the same behaviour, pass no_dispatch = 1> to new or call

 $soap->no_dispatch(1);

Unstable interface

This is alpha software - everything may (and most things will) change. But you don't have to be afraid too much - at least the call synopsis should be stable from 1.14 on, and that is the part you'll use most frequently.

BUGS

  • Arrays of complex types are not checked for the correct number of elements

    Arrays of complex types are just encoded and not checked for correctness etc. I don't know if I do this right yet, but output looks good. However, they are not checked for the correct number of element (does the SOAP spec say how to specify this ?).

  • +trace (and other SOAP::Lite flags) don't work

    This may be an issue with older versions of the base module (before 2.?), or with activestate's activeperl, which do not call the base modules import method with the flags supplied to the parent.

    There's a simple workaround:

     use SOAP::WSDL;
     import SOAP::Lite +trace;
  • nothing else known

    But I'm sure there are some serious bugs lurking around somewhere.

TODO

Allow use of alternative XPath implementations

XML::XPath is a great module, but it's not a race-winning one. XML::LibXML offers a promising-looking XPath interface. SOAP::WSDL should support both, defaulting to the faster one, and leaving the final choice to the user.

CHANGES

See CHANGES file.

COPYRIGHT

This library is free software, you can distribute / modify it under the same terms as perl itself.

AUTHOR>

Replace whitespace by '@' in E-Mail addresses.

 Martin Kutter <martin.kutter fen-net.de>
 Giovanni S. Fois <giovannisfois tiscali.it>

SVN information

 $LastChangedBy: kutterma $
 $HeadURL: https://soap-wsdl.svn.sourceforge.net/svnroot/soap-wsdl/SOAP-WSDL/branches/1.21/lib/SOAP/WSDL.pm $
 $Rev: 20 $