XML::C...Client(3pm)

πŸ“¦
version 3.28 Β· 2 versions
  • 3.28 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 3.27 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ‘‘ XML::Compile::SOAP::Client β€” NAME πŸ‘‘

okay so bestie, gather round, because this is XML::Compile::SOAP::Client β€” the SOAP message initiators. That's the whole vibe. These are the things that kick a SOAP message off into the world. πŸ’Œ


🌸 INHERITANCE 🌸

XML::Compile::SOAP::Client is extended by

  • XML::Compile::SOAP11::Client
  • XML::Compile::SOAP12::Client

so like, this is the blueprint, and those two are the actual glow-ups that get used. πŸ’…


✨ SYNOPSIS ✨

# never used directly, only via XML::Compile::SOAP1[12]::Client

fr fr. you never touch this one directly, it's giving "backstage parent energy". 🎭


πŸ¦‹ DESCRIPTION πŸ¦‹

This class defines the methods that each client side of the SOAP message exchange protocols must implement. It's the contract, the rules, the "you must have these methods or you're not a real client" checklist. periodt. πŸ’–


🌈 METHODS 🌈

πŸŽ€ Constructors

This object can not be instantiated, bestie β€” it's only used as a secundary base class. The primary one is the one that has to contain the new. So no, you can't just ->new this and expect a whole personality. The main character carries the new. πŸ‘‘

πŸ’œ Handlers

⭐ $obj->compileClient(%options)

Here's the option table, and yes, we're keeping it exactly as it is β€” -Option --Default and all:

-Option   --Default
 async      <false>
 decode     <required>
 encode     <required>
 kind       request-response
 name       <undef>
 soap       'SOAP11'
 transport  <required>

Now let's break each one down, bestie, because these defaults are doing a LOT of heavy lifting: πŸ’ͺ

  • async => BOOLEAN β€” if true, you get a whole different code-reference back. Every time you call it, the call goes out but the function returns immediately. As an extra parameter to the call you gotta pass a _callback parameter, which is a code-reference that handles the result later. it's giving "don't wait up, I'll text you". πŸ“±
  • decode => CODE β€” this CODE reference comes from XML::Compile::SOAP::compileMessage(), and it must be a RECEIVER: it translates a SOAP message into Perl data. And even in one-way operation you should still provide this decode, because some servers may pass back some XML in case of errors. πŸ‘€ (yes, even when you said you didn't want a reply.)
  • encode => CODE β€” also produced by XML::Compile::SOAP::compileMessage(), but this one must be a SENDER: it translates Perl data structures into the SOAP message in XML. πŸ“€
  • kind => STRING β€” which kind of client is this? WSDL11 defines four kinds of client-server interaction. Only request-response (the default) and one-way are currently supported. so two out of four, no cap. 🎯
  • name => STRING β€” a name. that's it, that's the description. πŸ’«
  • soap => OBJECT|'SOAP11'|'SOAP12' β€” when the transport parameter is an object, this is used to wrap the SOAP content according to the matching transporter regulation. 🎁
  • transport => CODE|OBJECT β€” the CODE reference is produced by an extensions of XML::Compile::Transport::compileClient() (usually XML::Compile::Transport::SOAPHTTP::compileClient().

If you pass a XML::Compile::Transport::SOAPHTTP object, the compileClient will be called for you. This is possible in case you do not have any configuration options to pass with the compileClient(). 🚚

example:

Normal call:

my $call = $wsdl->compileClient('myOp');
my ($answer, $trace) = $call->(@params);
#do something with $answer

Async call:

my $call = $wsdl->compileClient('myOp', async => 1);
sub cb
{  my ($answer, $trace) = @_;
   #do something with $answer
};
$call->(@params, _callback => \&cb);

πŸ’€ DETAILS πŸ’€

🌸 Client side SOAP 🌸

πŸ’… Calling the server (Document style)

First, you compile the call β€” either via a WSDL file (see XML::Compile::WSDL11), or in a few manual steps (which are described in the next section). Either way, you end up with a CODE reference that you can call multiple times. compile once, call often, that's the whole aesthetic. ✨

# compile once
my $call   = $soap->compileClient(...);

# and call often
my $answer = $call->(%request);  # list of pairs
my $answer = $call->(\%request); # same, but HASH
my $answer = $call->(\%request, 'UTF-8');  # same

# or with trace details, see XML::Compile::SOAP::Trace
my ($answer, $trace) = $call->...

But bestie, what IS the structure of %request and $answer?? 😭 well, there are various syntaxes possible: from structurally perfect, to user-friendly. so basically from "formal" to "chill".

First, find out which data structures can be present: when you compiled your messages explicitly, you picked your own names. When the call was initiated from a WSDL file, then you have to find the names of the message parts which can be used: the part names for header blocks, body blocks, headerfaults, and (body) faults. Do not worry too much, you will get (hopefully understandable) run-time error messages when the structure is incorrect. 🚨

Let's say that the WSDL defines this (ignoring all name-space issues)

<definitions xmlns:xx="MYNS"
  <message name="GetLastTradePriceInput">
   <part name="count" type="int" />
   <part name="request" element="xx:TradePriceRequest"/>
  </message>

  <message name="GetLastTradePriceOutput">
   <part name="answer" element="xx:TradePrice"/>
  </message>

  <binding
   <operation
    <input>
     <soap:header message="GetLastTradePriceInput" part="count"
     <soap:body message="GetLastTradePriceInput" parts="request"
    <output>
     <soap:body message="GetLastTradePriceOutput"

The input message needs explicitly named parts in this case, where the output message simply uses all defined in the body. So, the input message has one header part count, and one body part request. The output message only has one part named answer, which is all defined for the message and therefore its name can be omitted. see? one part = no need to name it. slay. πŸŽ€

Then, the definitions of the blocks:

<schema targetNamespace="MYNS"
  <element name="TradePriceRequest">
   <complexType>
    <all>
     <element name="tickerSymbol" type="string"/>

  <element name="TradePrice">
   <complexType>
    <all>
     <element name="price" type="float"/>
</schema>

Now, calling the compiled function can be done like this:

my $got
   = $call->(  count => 5, request => {tickerSymbol => 'IBM'}  );
   = $call->({ count => 5, request => {tickerSymbol => 'IBM'} });
   = $call->({ count => 5, request => {tickerSymbol => 'IBM'} }
      , 'UTF-8');

If the first arguments for the code ref is a HASH, then there may be a second which specifies the required character-set. The default is "UTF-8", which is very much advised. don't fight it, just use UTF-8. 🫢

πŸ¦‹ Parameter unpacking (Document Style)

In the example situation of the previous section, you may simplify the call even further. To understand how, we need to understand the parameter unpacking algorithm. πŸ”

The structure which we need to end up with, looks like this

$call->(\%data, $charset);
%data = ( Header => {count => 5}
        , Body   =>
           { request => {tickerSymbol => 'IBM'} }
        );

The structure of the SOAP message is directly mapped on this nested complex HASH. But it's inconvenient to write each call like this, therefore the $call parameters are transformed into the required structure according to the following rules:

  1. if called with a LIST, then that will become a HASH
  2. when a "Header" and/or "Body" are found in the HASH, those are used
  3. if there are more parameters in the HASH, then those with names of known header and headerfault message parts are moved to the "Header" sub-structure. Body and fault message parts are moved to the "Body" sub-structure.
  4. If the "Body" sub-structure is empty, and there is only one body part expected, then all remaining parameters are put in a HASH for that part. This also happens if there are not parameters: it will result in an empty HASH for that block.

So, in our case this will also do, because count is a known part, and request gets all the left-overs, being the only body part. πŸ†

my $got = $call->(count => 5, tickerSymbol => 'IBM');

This does not work if the block element is a simple type. In most existing Document style SOAP schemas, this simplification probably is possible. πŸ™ˆ

πŸ’– Understanding the output (Document style)

The $got is a HASH, which will not be simplified automatically: it may change with future extensions of the interface. The return is a complex nested structure, and Data::Dumper is your friend. 🀝

$got = { answer => { price => 16.3 } }

To access the value use

printf "%.2f US\$\n", $got->{answer}->{price};
printf "%.2f US\$\n", $got->{answer}{price};   # same

or

my $answer = $got->{answer};
printf "%.2f US\$\n", $answer->{price};

⭐ Calling the server (SOAP-RPC style literal)

SOAP-RPC style messages which have "<use=literal"> cannot be used without a little help. However, one extra definition per procedure call suffices. one lil extra thing and you're golden. ✨

This is a complete code example, although you need to fill in some specifics about your environment. If you have a WSDL file, then it will be a little simpler, see XML::Compile::WSDL11::compileClient().

# You probably need these
use XML::Compile::SOAP11::Client;
use XML::Compile::Transport::SOAPHTTP;
use XML::Compile::Util  qw/pack_type/;

# Literal style RPC
my $outtype = pack_type $MYNS, 'myFunction';
my $intype  = pack_type $MYNS, 'myFunctionResponse';

# Encoded style RPC (see next section on these functions)
my $outtype = \&my_pack_params;
my $intype  = \&my_unpack_params;

# For all RPC calls, you need this only once (or have a WSDL):
my $transp  = XML::Compile::Transport::SOAPHTTP->new(...);
my $http    = $transp->compileClient(...);
my $soap    = XML::Compile::SOAP11::Client->new(...);
my $send    = $soap->compileMessage('SENDER',   style => $style, ...);
my $get     = $soap->compileMessage('RECEIVER', style => $style, ...);

# Per RPC procedure
my $myproc = $soap->compileClient
  ( name   => 'MyProc'
  , encode => $send, decode => $get, transport => $http
  );

my $answer = $myproc->(@parameters);   # as document style

Actually, the @paramers are slightly less flexible as in document style SOAP. If you use header blocks, then the called CODE reference will not be able to distinguish between parameters for the RPC block and parameters for the header blocks. 😭

my $answer = $trade_price
  ->( {symbol => 'IBM'}    # the RPC package implicit
    , transaction => 5     # in the header
    );

When the number of arguments is odd, the first is indicating the RPC element, and the other pairs refer to header blocks. (odd number of args = first one is the RPC element, the rest are header pairs. iykyk πŸ’…)

The $answer structure may contain a "Fault" entry, or a decoded datastructure with the results of your query. One call using Data::Dumper will show you more than I can explain in a few hundred words. πŸ”₯

πŸ‘‘ Calling the server (SOAP-RPC style, encoded)

SOAP-RPC is a simplification of the interface description: basically, the interface is not described at all, but left to good communication between the client and server authors. In strongly typed languages, this is quite simple to enforce: the client side and server side use the same method prototypes. However, in Perl we are blessed to go without these strongly typed prototypes. πŸ™

and that's where this part wraps β€” more coming, bestie, don't go anywhere πŸ’–

okay so real talk bestie: SOAP::Lite's whole thing is guessing the types of the parameters you pass. πŸ’… Like, "42" gets passed as an Integer. And that? That can lead to nasty problems: a float parameter "2.0" gets passed as integer "2", or a string representing a house number "8" gets passed as a number. And the SOAP server might just not accept that. 😭

So when you use SOAP-RPC in XML::Compile::SOAP, it's gonna ask a little more effort from you: you have to state parameter types explicitly. In the examples/namesservice/ directory, you'll find a detailed example. You have to create a CODE ref which produces the message, using methods defined provided by XML::Compile::SOAP11::Encoding. 🫢

πŸ’… Faults (Document and RPC style) πŸ’…

Faults and headerfaults are a slightly different story: the type which is specified with them is not of the fault XML node itself, but of the "detail" sub-element within the standard fault structure. πŸ‘‘

When producing the data for faults, you must be aware of the fact that the structure is different for SOAP1.1 and SOAP1.2. When interpreting faults, the same problems are present, although the implementation tries to help you by hiding the differences. ✨

Check whether SOAP1.1 or SOAP1.2 is used by looking for a "faultcode" (SOAP1.1) or a "Code" (SOAP1.2) field in the data:

if(my $fault = $got->{Fault})
{  if($fault->{faultcode}) { ... SOAP1.1 ... }
   elsif($fault->{Code})   { ... SOAP1.2 ... }
   else { die }
}

In either protocol case, the following will get you at a compatible structure in two steps:

if(my $fault = $got->{Fault})
{   my $decoded = fault->{_NAME}};
    print $got->{$decoded}->{code};
    ...
}

See the respective manuals XML::Compile::SOAP11 and XML::Compile::SOAP12 for the hairy details. But one thing can be said: when the fault is declared formally, then the "_NAME" will be the name of that part. πŸ¦‹

🌸 SOAP without WSDL (Document style) 🌸

See the manual page of XML::Compile::WSDL11 to see how simple you can use this module when you have a WSDL file at hand. The creation of a correct WSDL file is NOT SIMPLE. πŸ’€

When using SOAP without WSDL file, it gets a little bit more complicate to use: you need to describe the content of the messages yourself. The following example is used as test-case "t/10soap11.t", directly taken from the SOAP11 specs section 1.3 example 1. πŸ‘‘

# for simplification
my $TestNS   = 'http://test-types';
use XML::Compile::Util qw/SCHEMA2001/;
my $SchemaNS = SCHEMA2001;

First, the schema (hopefully someone else created for you, because they can be quite hard to create correctly) is in file "myschema.xsd":

<schema targetNamespace="$TestNS"
  xmlns="$SchemaNS">

<element name="GetLastTradePrice">
  <complexType>
     <all>
       <element name="symbol" type="string"/>
     </all>
  </complexType>
</element>

<element name="GetLastTradePriceResponse">
  <complexType>
     <all>
        <element name="price" type="float"/>
     </all>
  </complexType>
</element>

<element name="Transaction" type="int"/>
</schema>

Ok, now the program you create the request:

use XML::Compile::SOAP11;
use XML::Compile::Util  qw/pack_type/;

my $soap   = XML::Compile::SOAP11->new;
$soap->schemas->importDefinitions('myschema.xsd');

my $get_price = $soap->compileMessage
  ( 'SENDER'
  , header =>
     [ transaction => pack_type($TestNS, 'Transaction') ]
  , body  =>
     [ request => pack_type($TestNS, 'GetLastTradePrice') ]
  , mustUnderstand => 'transaction'
  , destination    => [ transaction => 'NEXT http://actor' ]
  );

"INPUT" is used in the WSDL terminology, indicating this message is an input message for the server. This $get_price is a WRITER. Above is done only once in the initialization phase of your program. πŸ’–

At run-time, you have to call the CODE reference with a data-structure which is compatible with the schema structure. (See XML::Compile::Schema::template() if you have no clue how it should look) So: let's send this:

# insert your data
my %data_in = (transaction => 5, request => {symbol => 'DIS'});
my %data_in = (transaction => 5, symbol => 'DIS'); # alternative

# create a XML::LibXML tree
my $xml  = $get_price->(\%data_in, 'UTF-8');
print $xml->toString;

And the output is:

<SOAP-ENV:Envelope
   xmlns:x0="http://test-types"
   xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <x0:Transaction
      mustUnderstand="1"
      actor="http://schemas.xmlsoap.org/soap/actor/next http://actor">
        5
    </x0:Transaction>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <x0:GetLastTradePrice>
      <symbol>DIS</symbol>
    </x0:GetLastTradePrice>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Some transport protocol will sent this data from the client to the server. See XML::Compile::Transport::SOAPHTTP, as one example. πŸš€

On the SOAP server side, we will parse the message. The string $soap contains the XML. The program looks like this:

my $server = $soap->compileMessage # create once
 ( 'RECEIVER'
 , header => [ transaction => pack_type($TestNS, 'Transaction') ]
 , body   => [ request => pack_type($TestNS, 'GetLastTradePrice') ]
 );

my $data_out = $server->($soap);   # call often

Now, the $data_out reference on the server, is stucturally exactly equivalent to the %data_in from the client. ✨

✨ SEE ALSO ✨

This module is part of XML-Compile-SOAP distribution version 3.28, built on August 01, 2022. Website: http://perl.overmeer.net/CPAN/ 🌈

πŸ’– LICENSE πŸ’–

Copyrights 2007-2022 by [Mark Overmeer <markov@cpan.org>]. For other contributors see ChangeLog. πŸ‘‘

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://dev.perl.org/licenses/ 🫢


signing off with perl v5.34.0 vibes, dated 2022-08-04, from XML::C...Client(3pm) πŸ’–

πŸ“¦ where this page comes from