SOAP - WRITING A SOAP CLIENT

 
WRITING A SOAP CLIENT ^

This chapter guides you to writing a SOAP client by example.

The SOAP service to be accessed is a simple variation of the well-known hello world program. It accepts two parameters, a name and a given name, and returns "Hello $given_name $name".

We will use "Martin Kutter" as the name for the call, so all variants will print the following message on success:

 Hello Martin Kutter!

SOAP message styles

There are three common (and one less common) variants of SOAP messages.

These address the message style (positional parameters vs. specified message documents) and encoding (as-is vs. typed).

The different message styles are:

    rpc/encoded

    Typed, positional parameters. Widely used in scripting languages. The type of the arguments is included in the message. Arrays and the like may be encoded using SOAP encoding rules (or others).
    rpc/literal

    As-is, positional parameters. The type of arguments is defined by some pre-exchanged interface definition.
    document/encoded

    Specified message with typed elements. Rarely used.
    document/literal

    Specified message with as-is elements. The message specification and element types are defined by some pre-exchanged interface definition.

As of 2008, document/literal has become the predominant SOAP message variant. rpc/literal and rpc/encoded are still in use, mainly with scripting languages, while document/encoded is hardly used at all.

You will see clients for the rpc/encoded and document/literal SOAP variants in this section.
Example implementations
RPC/ENCODED

Rpc/encoded is most popular with scripting languages like perl, php and python without the use of a WSDL. Usual method descriptions look like this:

 Method: sayHello(string, string)
 Parameters:
    name: string
    givenName: string

Such a description usually means that you can call a method named "sayHello" with two positional parameters, "name" and "givenName", which both are strings.

The message corresponding to this description looks somewhat like this:

 <sayHello xmlns="urn:HelloWorld">
   <s-gensym01 xsi:type="xsd:string">Kutter</s-gensym01>
   <s-gensym02 xsi:type="xsd:string">Martin</s-gensym02>
 </sayHello>

Any XML tag names may be used instead of the "s-gensym01" stuff - parameters are positional, the tag names have no meaning.

A client producing such a call is implemented like this:

 use SOAP::Lite;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:81/soap-wsdl-test/helloworld.pl');
 $soap->default_ns('urn:HelloWorld');
 my $som = $soap->call('sayHello', 'Kutter', 'Martin');
 die $som->faultstring if ($som->fault);
 print $som->result, "\n";

You can of course use a one-liner, too...

Sometimes, rpc/encoded interfaces are described with WSDL definitions. A WSDL accepting "named" parameters with rpc/encoded looks like this:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:s0="urn:HelloWorld"
   targetNamespace="urn:HelloWorld"
   xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
     </s:schema>
   </types>
   <message name="sayHello">
     <part name="name" type="s:string" />
     <part name="givenName" type="s:string" />
   </message>
   <message name="sayHelloResponse">
     <part name="sayHelloResult" type="s:string" />
   </message>

   <portType name="Service1Soap">
     <operation name="sayHello">
       <input message="s0:sayHello" />
       <output message="s0:sayHelloResponse" />
     </operation>
   </portType>

   <binding name="Service1Soap" type="s0:Service1Soap">
     <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
         style="rpc" />
     <operation name="sayHello">
       <soap:operation soapAction="urn:HelloWorld#sayHello"/>
       <input>
         <soap:body use="encoded"
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
       </input>
       <output>
         <soap:body use="encoded"
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
       </output>
     </operation>
   </binding>
   <service name="HelloWorld">
     <port name="HelloWorldSoap" binding="s0:Service1Soap">
       <soap:address location="http://localhost:81/soap-wsdl-test/helloworld.pl" />
     </port>
   </service>
 </definitions>

The message corresponding to this schema looks like this:

 <sayHello xmlns="urn:HelloWorld">
   <name xsi:type="xsd:string">Kutter</name>
   <givenName xsi:type="xsd:string">Martin</givenName>
 </sayHello>

A web service client using this schema looks like this:

 use SOAP::Lite;
 my $soap = SOAP::Lite->service("file:say_hello_rpcenc.wsdl");
 eval { my $result = $soap->sayHello('Kutter', 'Martin'); };
 if ($@) {
     die $@;
 }
 print $som->result();

You may of course also use the following one-liner:

 perl -MSOAP::Lite -e 'print SOAP::Lite->service("file:say_hello_rpcenc.wsdl")\
   ->sayHello('Kutter', 'Martin'), "\n";'

A web service client (without a service description) looks like this.

 use SOAP::Lite;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:81/soap-wsdl-test/helloworld.pl');
 $soap->default_ns('urn:HelloWorld');
 my $som = $soap->call('sayHello',
    SOAP::Data->name('name')->value('Kutter'),
    SOAP::Data->name('givenName')->value('Martin')
 );
 die $som->faultstring if ($som->fault);
 print $som->result, "\n";

RPC/LITERAL

SOAP web services using the document/literal message encoding are usually described by some Web Service Definition. Our web service has the following WSDL description:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:s0="urn:HelloWorld"
   targetNamespace="urn:HelloWorld"
   xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
       <s:complexType name="sayHello">
         <s:sequence>
           <s:element minOccurs="0" maxOccurs="1" name="name"
              type="s:string" />
           <s:element minOccurs="0" maxOccurs="1" name="givenName"
              type="s:string" nillable="1" />
         </s:sequence>
       </s:complexType>

       <s:complexType name="sayHelloResponse">
         <s:sequence>
           <s:element minOccurs="0" maxOccurs="1" name="sayHelloResult"
              type="s:string" />
         </s:sequence>
       </s:complexType>
     </s:schema>
   </types>
   <message name="sayHello">
     <part name="parameters" type="s0:sayHello" />
   </message>
   <message name="sayHelloResponse">
     <part name="parameters" type="s0:sayHelloResponse" />
   </message>

   <portType name="Service1Soap">
     <operation name="sayHello">
       <input message="s0:sayHello" />
       <output message="s0:sayHelloResponse" />
     </operation>
   </portType>

   <binding name="Service1Soap" type="s0:Service1Soap">
     <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
         style="rpc" />
     <operation name="sayHello">
       <soap:operation soapAction="urn:HelloWorld#sayHello"/>
       <input>
         <soap:body use="literal" namespace="urn:HelloWorld"/>
       </input>
       <output>
         <soap:body use="literal" namespace="urn:HelloWorld"/>
       </output>
     </operation>
   </binding>
   <service name="HelloWorld">
     <port name="HelloWorldSoap" binding="s0:Service1Soap">
       <soap:address location="http://localhost:80//helloworld.pl" />
     </port>
   </service>
  </definitions>

The XML message (inside the SOAP Envelope) look like this:

 <ns0:sayHello xmlns:ns0="urn:HelloWorld">
    <parameters>
      <name>Kutter</name>
      <givenName>Martin</givenName>
    </parameters>
 </ns0:sayHello>

 <sayHelloResponse xmlns:ns0="urn:HelloWorld">
    <parameters>
        <sayHelloResult>Hello Martin Kutter!</sayHelloResult>
    </parameters>
 </sayHelloResponse>

This is the SOAP::Lite implementation for the web service client:

 use SOAP::Lite +trace;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:80/helloworld.pl');

 $soap->on_action( sub { "urn:HelloWorld#sayHello" });
 $soap->autotype(0)->readable(1);
 $soap->default_ns('urn:HelloWorld');

 my $som = $soap->call('sayHello', SOAP::Data->name('parameters')->value(
    \SOAP::Data->value([
        SOAP::Data->name('name')->value( 'Kutter' ),
        SOAP::Data->name('givenName')->value('Martin'),
    ]))
);

 die $som->fault->{ faultstring } if ($som->fault);
 print $som->result, "\n";

DOCUMENT/LITERAL

SOAP web services using the document/literal message encoding are usually described by some Web Service Definition. Our web service has the following WSDL description:

 <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:s="http://www.w3.org/2001/XMLSchema"
    xmlns:s0="urn:HelloWorld"
    targetNamespace="urn:HelloWorld"
    xmlns="http://schemas.xmlsoap.org/wsdl/">
   <types>
     <s:schema targetNamespace="urn:HelloWorld">
       <s:element name="sayHello">
         <s:complexType>
           <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="name" type="s:string" />
               <s:element minOccurs="0" maxOccurs="1" name="givenName" type="s:string" nillable="1" />
           </s:sequence>
          </s:complexType>
        </s:element>

        <s:element name="sayHelloResponse">
          <s:complexType>
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="sayHelloResult" type="s:string" />
            </s:sequence>
        </s:complexType>
      </s:element>
    </types>
    <message name="sayHelloSoapIn">
      <part name="parameters" element="s0:sayHello" />
    </message>
    <message name="sayHelloSoapOut">
      <part name="parameters" element="s0:sayHelloResponse" />
    </message>

    <portType name="Service1Soap">
      <operation name="sayHello">
        <input message="s0:sayHelloSoapIn" />
        <output message="s0:sayHelloSoapOut" />
      </operation>
    </portType>

    <binding name="Service1Soap" type="s0:Service1Soap">
      <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
          style="document" />
      <operation name="sayHello">
        <soap:operation soapAction="urn:HelloWorld#sayHello"/>
        <input>
          <soap:body use="literal" />
        </input>
        <output>
          <soap:body use="literal" />
        </output>
      </operation>
    </binding>
    <service name="HelloWorld">
      <port name="HelloWorldSoap" binding="s0:Service1Soap">
        <soap:address location="http://localhost:80//helloworld.pl" />
      </port>
    </service>
 </definitions>

The XML message (inside the SOAP Envelope) look like this:

 <sayHello xmlns="urn:HelloWorld">
   <name>Kutter</name>
   <givenName>Martin</givenName>
 </sayHello>

 <sayHelloResponse>
   <sayHelloResult>Hello Martin Kutter!</sayHelloResult>
 </sayHelloResponse>

You can call this web service with the following client code:

 use SOAP::Lite;
 my $soap = SOAP::Lite->new( proxy => 'http://localhost:80/helloworld.pl');

 $soap->on_action( sub { "urn:HelloWorld#sayHello" });
 $soap->autotype(0);
 $soap->default_ns('urn:HelloWorld');

 my $som = $soap->call("sayHello",
    SOAP::Data->name('name')->value( 'Kutter' ),
    SOAP::Data->name('givenName')->value('Martin'),
);

 die $som->fault->{ faultstring } if ($som->fault);
 print $som->result, "\n";

Differences between the implementations

You may have noticed that there's little difference between the rpc/encoded, rpc/literal and the document/literal example's implementation. In fact, from SOAP::Lite's point of view, the only differences between rpc/literal and document/literal that parameters are always named.

In our example, the rpc/encoded variant already used named parameters (by using two messages), so there's no difference at all.

You may have noticed the somewhat strange idiom for passing a list of named parameters in the rpc/literal example:

 my $som = $soap->call('sayHello', SOAP::Data->name('parameters')->value(
    \SOAP::Data->value([
        SOAP::Data->name('name')->value( 'Kutter' ),
        SOAP::Data->name('givenName')->value('Martin'),
    ]))
 );

While SOAP::Data provides full control over the XML generated, passing hash-like structures require additional coding.

 

source: http://search.cpan.org/~phred/SOAP-Lite-1.20/lib/SOAP/Lite.pm#WRITING_A_SOAP_CLIENT