Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Using SOAP::Lite with a WSDL

by lahfordie (Initiate)
on May 20, 2017 at 02:34 UTC ( [id://1190692]=perlquestion: print w/replies, xml ) Need Help??

lahfordie has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to convert this known working SOAP call to SOAP::Lite in Perl and having difficulty doing so. The examples and tutorials I'm seeing are all for simpler use cases, and I haven't found what I need in the perldoc. So many options... I have a WSDL available to use, and the method to call seems to be RunLocator. This is an example of a request I would make in SoapUI:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envel +ope/" xmlns:was="http://tempuri.org/AWEService/API/WASAPI"> <soapenv:Header/> <soapenv:Body> <was:RunLocator> <was:userIdentification> <was:WAS_userName>WS_Decom</was:WAS_userName> <was:WAS_password>PASSWORD</was:WAS_password> </was:userIdentification> <was:locatorSearchFieldsIdentification> <was:DEVICE_NAME>prodh20</was:DEVICE_NAME> </was:locatorSearchFieldsIdentification> </was:RunLocator> </soapenv:Body> </soapenv:Envelope>

Note that in SoapUI (or sending that request body with curl), I would fill out the authentication and DEVICE_NAME and leave the rest empty. It will send back a response in the format:

<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envel +ope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http:// +www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <ns1:RunLocatorResponse xmlns="urn:RunLocator" xmlns:ns1="http://tempu +ri.org/AWEService/API/WASAPI" xmlns:xsd="http://www.w3.org/2001/XMLSc +hema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ns1:RunLocatorResult> <ns1:DEVICE_NAME>prodh20</ns1:DEVICE_NAME> <ns1:MANUFACTURER>HEWLETT PACKARD</ns1:MANUFACTURER> <ns1:MODEL>PROLIANT DL380P GEN8</ns1:MODEL> <ns1:ASSET_NUMBER></ns1:ASSET_NUMBER> <ns1:BARCODE_NUMBER></ns1:BARCODE_NUMBER> <ns1:SERIAL_NUMBER>(SERIAL)</ns1:SERIAL_NUMBER> <ns1:BUILDING>(BUILDING)</ns1:BUILDING> <ns1:FLOOR>(FLOOR)</ns1:FLOOR> <ns1:SPACE_ID>(SPACE_ID)</ns1:SPACE_ID> <ns1:GRID_LOCATION>(LOC)/ns1:GRID_LOCATION> <ns1:RACK_NAME>(RACK)</ns1:RACK_NAME> <ns1:RACK_MOUNT_SIDE>FRONT</ns1:RACK_MOUNT_SIDE> <ns1:BOTTOM_RACK_UNIT>7</ns1:BOTTOM_RACK_UNIT> <ns1:HEIGHT>3.5</ns1:HEIGHT> </ns1:RunLocatorResult> </ns1:RunLocatorResponse> </soapenv:Body> </soapenv:Envelope>

Parenthesized fields contain values which I've removed for posting here. I've tried many different techniques with SOAP::Lite adapted from examples online, the latest involving SOAP::Header and SOAP::Data. Can someone please point me in the right direction for how I should be approaching this? And I presume I'm going to need SOM to parse the result, if I can get that far.

Here is the WSDL, with the actual server name replaced by www.example.com:

<definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns: +soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.o +rg/2001/XMLSchema" xmlns:s0="http://tempuri.org/AWEService/API/WASAPI +" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm= +"http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://sch +emas.xmlsoap.org/wsdl/mime/" xmlns="http://schemas.xmlsoap.org/wsdl/" + targetNamespace="http://tempuri.org/AWEService/API/WASAPI"> <types> <s:schema elementFormDefault="qualified" targetNamespace="http://tempu +ri.org/AWEService/API/WASAPI"> <s:element name="RunLocator"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="userIdentification" type= +"s0:UserIdentificationStructure"/> <s:element minOccurs="1" maxOccurs="1" name="locatorSearchFieldsIdenti +fication" type="s0:LocatorSearchFieldsStructure"/> </s:sequence> </s:complexType> </s:element> <s:complexType name="UserIdentificationStructure"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="WAS_userName" type="s:str +ing"/> <s:element minOccurs="0" maxOccurs="1" name="WAS_password" type="s:str +ing"/> </s:sequence> </s:complexType> <s:complexType name="LocatorSearchFieldsStructure"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="RACK_NAME" type="s:string +"/> <s:element minOccurs="0" maxOccurs="1" name="DEVICE_NAME" type="s:stri +ng"/> <s:element minOccurs="0" maxOccurs="1" name="MANUFACTURER" type="s:str +ing"/> <s:element minOccurs="0" maxOccurs="1" name="MODEL" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="ASSET_NUMBER" type="s:str +ing"/> <s:element minOccurs="0" maxOccurs="1" name="BARCODE_NUMBER" type="s:s +tring"/> <s:element minOccurs="0" maxOccurs="1" name="SERIAL_NUMBER" type="s:st +ring"/> <s:element minOccurs="0" maxOccurs="1" name="BUILDING" type="s:string" +/> <s:element minOccurs="0" maxOccurs="1" name="FLOOR" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="SPACE_ID" type="s:string" +/> <s:element minOccurs="0" maxOccurs="1" name="GRID_LOCATION" type="s:st +ring"/> </s:sequence> </s:complexType> <s:element name="RunLocatorResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="10000" name="RunLocatorResult" typ +e="s0:LocatorResultInfo"/> </s:sequence> </s:complexType> </s:element> <s:complexType name="LocatorResultInfo"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="DEVICE_NAME" type="s:stri +ng"/> <s:element minOccurs="0" maxOccurs="1" name="MANUFACTURER" type="s:str +ing"/> <s:element minOccurs="0" maxOccurs="1" name="MODEL" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="ASSET_NUMBER" type="s:str +ing"/> <s:element minOccurs="0" maxOccurs="1" name="BARCODE_NUMBER" type="s:s +tring"/> <s:element minOccurs="0" maxOccurs="1" name="SERIAL_NUMBER" type="s:st +ring"/> <s:element minOccurs="0" maxOccurs="1" name="BUILDING" type="s:string" +/> <s:element minOccurs="0" maxOccurs="1" name="FLOOR" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="SPACE_ID" type="s:string" +/> <s:element minOccurs="0" maxOccurs="1" name="GRID_LOCATION" type="s:st +ring"/> <s:element minOccurs="0" maxOccurs="1" name="RACK_NAME" type="s:string +"/> <s:element minOccurs="0" maxOccurs="1" name="RACK_MOUNT_SIDE" type="s: +string"/> <s:element minOccurs="0" maxOccurs="1" name="BOTTOM_RACK_UNIT" type="s +:double"/> <s:element minOccurs="0" maxOccurs="1" name="HEIGHT" type="s:double"/> </s:sequence> </s:complexType> </s:schema> </types> <message name="RunLocatorSoapIn"> <part name="parameters" element="s0:RunLocator"/> </message> <message name="RunLocatorSoapOut"> <part name="parameters" element="s0:RunLocatorResponse"/> </message> <portType name="RunLocatorSoap"> <operation name="RunLocator"> <documentation> This function provides the XML interface to create a form </documentation> <input message="s0:RunLocatorSoapIn"/> <output message="s0:RunLocatorSoapOut"/> </operation> </portType> <binding name="RunLocatorSoap" type="s0:RunLocatorSoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style=" +document"/> <operation name="RunLocator"> <soap:operation soapAction="http://tempuri.org/AWEService/API/WASAPI/R +unLocator" style="document"/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> <service name="RunLocator"> <port name="RunLocator" binding="s0:RunLocatorSoap"> <soap:address location="https://www.example.com/WAS/API/AWEService.asm +x?w_v=1&w_s=VISTA&w_o=LocateDevice"/> </port> </service> </definitions>

EDIT: I think a big part of what's tripping me up is the two separate element areas under the RunLocator element. The method requires "userIdentification" and "locatorSearchFieldsIdentification" separately.

This is the current iteration of what I'm attempting:

my $wsdl = 'https://www.example.com/VISTA/wsdl/LocateDevi +ce_RunLocator.wsdl'; my $lite = SOAP::Lite ->service($wsdl) ->ns('was'); my @params = (SOAP::Data->name(WAS_userName => 'MY_USER'), +SOAP::Data->name(WAS_password => 'MY_PASSWD'), SOAP::Data->name(DEVIC +E_NAME => 'MY_SERVER')); SOAP::Lite->import(+trace => 'all'); my $result = $lite->RunLocator(@params);

Here's the trace:

SOAP::Transport::HTTP::Client::new: () SOAP::Lite::call: () SOAP::Serializer::envelope: () SOAP::Serializer::envelope: RunLocator SOAP::Data=HASH(0x2063270) SOAP +::Data=HASH(0x2063150) SOAP::Data=HASH(0x1b2a2a0) SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Transport::HTTP::Client::send_receive: HTTP::Request=HASH(0x2046 +3b8) SOAP::Transport::HTTP::Client::send_receive: POST https://www.example. +com/WAS/API/AWEService.asmx?w_v=1&w_s=VISTA&w_o=LocateDevice HTTP/1.1 Accept: text/xml Accept: multipart/* Accept: application/soap Content-Length: 832 Content-Type: text/xml; charset=utf-8 SOAPAction: "http://tempuri.org/AWEService/API/WASAPI/RunLocator" <?xml version="1.0" encoding="UTF-8"?><soap:Envelope soap:encodingStyl +e="http://schemas.xmlsoap.org/soap/encoding/" xmlns:http="http://sche +mas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/ws +dl/mime/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:s0="http:/ +/tempuri.org/AWEService/API/WASAPI" xmlns:soap="http://schemas.xmlsoa +p.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/enco +ding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns: +xsi="http://www.w3.org/2001/XMLSchema-instance"><soap:Body><s0:RunLoc +ator><userIdentification xsi:nil="true" xsi:type="s0:UserIdentificati +onStructure" /><locatorSearchFieldsIdentification xsi:nil="true" xsi: +type="s0:LocatorSearchFieldsStructure" /><DEVICE_NAME xsi:type="xsd:s +tring">prodh20</DEVICE_NAME></s0:RunLocator></soap:Body></soap:Envelo +pe> SOAP::Transport::HTTP::Client::send_receive: HTTP::Response=HASH(0x203 +6400) SOAP::Transport::HTTP::Client::send_receive: HTTP/1.1 500 Internal Ser +ver Error Cache-Control: private,no-cache, no-store, must-revalidate Date: Sat, 20 May 2017 19:56:56 GMT Server: Microsoft-IIS/7.5 Content-Length: 837 Content-Type: text/xml; charset=utf-8 ... <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http: +//schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/20 +01/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">< +soap:Header><soap12:Upgrade xmlns:soap12="http://www.w3.org/2003/05/s +oap-envelope"><soap12:SupportedEnvelope qname="soap:Envelope" xmlns:s +oap="http://schemas.xmlsoap.org/soap/envelope/" /><soap12:SupportedEn +velope qname="soap12:Envelope" xmlns:soap12="http://www.w3.org/2003/0 +5/soap-envelope" /></soap12:Upgrade></soap:Header><soap:Body><soap:Fa +ult><faultcode>soap:VersionMismatch</faultcode><faultstring>Possible +SOAP version mismatch: Envelope namespace http://schemas.xmlsoap.org/ +wsdl/soap/ was unexpected. Expecting http://schemas.xmlsoap.org/soap/ +envelope/.</faultstring><detail /></soap:Fault></soap:Body></soap:Env +elope> SOAP::Deserializer::deserialize: () SOAP::Parser::decode: () SOAP::SOM::new: () SOAP::SOM::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Lite::DESTROY: () SOAP::Deserializer::DESTROY: () SOAP::Parser::DESTROY: () SOAP::Transport::DESTROY: () SOAP::Transport::HTTP::Client::DESTROY: () SOAP::Serializer::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: ()

Yes, I see the version mismatch messages, but I'm more concerned about the generated envelope not having the correct structure.

SOLVED thanks to poj. Code is below, slightly adapted for public posting:

#!/usr/bin/perl use strict; use warnings; use SOAP::Lite; use v5.16; my $client = SOAP::Lite ->proxy('https://www.example.com/WAS/API/AWEService.asmx?ENDPOINT' +) ->on_action(sub { join '/', @_ }) ->ns('http://tempuri.org/AWEService/API/WASAPI','was'); my %auth_parms = ( WAS_userName => 'USER', WAS_password => 'PASSWORD', ); my %search_parms = ( DEVICE_NAME => 'SERVER', ); my @auth_data; for (keys %auth_parms){ my $data = SOAP::Data->name( $_ => $auth_parms{$_} )->prefix('was'); push @auth_data, SOAP::Data->type('data' => $data); } my @search_data; for (keys %search_parms){ my $data = SOAP::Data->name( $_ => $search_parms{$_} )->prefix('was'); push @search_data, SOAP::Data->type('data' => $data); } my $auth = SOAP::Data->name( "userIdentification" => \@auth_data )->prefix('was'); my $search = SOAP::Data->name( "locatorSearchFieldsIdentification" => \@search_data )->prefix('was'); my $header = SOAP::Data->type('xml' => ''); my $response = $client->call("RunLocator", SOAP::Header->type('data' => $header), SOAP::Data->type('data' => $auth), SOAP::Data->type('data' => $search) ); if ($response->valueof('//RunLocatorResult')) { my $serial = $response->valueof('//SERIAL_NUMBER'); my $model = join(' ', scalar $response->valueof('//MANUFACTURER'), scalar $response->valueof('//MODEL') ); my $location = join(' ', scalar $response->valueof('//BUILDING'), scalar $response->valueof('//FLOOR'), scalar $response->valueof('//SPACE_ID'), scalar $response->valueof('//GRID_LOCATION'), scalar $response->valueof('//RACK_NAME') ); return ($serial, $model, $location); }

Replies are listed 'Best First'.
Re: Using SOAP::Lite with a WSDL
by poj (Abbot) on May 20, 2017 at 20:00 UTC

    Not sure which part you are having trouble with but try this

    #!perl use strict; use SOAP::Lite; # soap my $client = SOAP::Lite->uri('') ->proxy("loopback://"); $client->autotype(0)->readable(1); $client->outputxml(1); $client->ns("http://schemas.xmlsoap.org/soap/envelope/","soapenv"); $client->ns("http://tempuri.org/AWEService/API/WASAPI","was"); my $auth_data = { WAS_userName => 'WS_Decom', WAS_pasword => 'PASSWORD', }; my $search_data = { RACK_NAME => '', DEVICE_NAME => 'prodh20', MANUFACTURER => '', MODEL => '', ASSET_NUMBER => '', BARCODE_NUMBER => '', SERIAL_NUMBER => '', BUILDING => '', FLOOR => '', SPACE_ID => '', GRID_LOCATION => '', }; my $auth = SOAP::Data->name( "userIdentification" => $auth_data )->prefix('was'); my $search = SOAP::Data->name( "locatorSearchFieldsIdentification" => $search_data )->prefix('was'); my $response = $client->call("RunLocator", SOAP::Data->type('data' => $auth), SOAP::Data->type('data' => $search) ); #open OUT,'>','output.xml' or die "$!"; print $response; #close OUT;
    poj

      Thank you, poj. That introduces some namespace pieces I wasn't sure how to work with. However, replacing proxy with the API endpoint, I get this in response:

      <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http: +//schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/20 +01/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">< +soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring> +Server did not recognize the value of HTTP Header SOAPAction: http:// +tempuri.org/AWEService/API/WASAPI#RunLocator.</faultstring><detail /> +</soap:Fault></soap:Body></soap:Envelope>

      That is using the soap:address location from the wsdl file, as the proxy. Running it with loopback of course just spits the envelope back out. On the bright side, the envelope looks better now:

      <soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:was="http://tempuri.org/AWEService/API/WASAPI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <was:RunLocator> <was:userIdentification> <WAS_userName>WS_Decom</WAS_userName> <WAS_password>PASSWORD</WAS_password> </was:userIdentification> <was:locatorSearchFieldsIdentification> <DEVICE_NAME>SERVER</DEVICE_NAME> </was:locatorSearchFieldsIdentification> </was:RunLocator> </soapenv:Body> </soapenv:Envelope>

      Only issue I'm seeing with the envelope anymore is no prefix assigned to the lowest children, which will cause problems. I've corrected that issue manually, and I'm still left with the HTTP header faultstring.

        Ok, try this, it adds the prefixes and an empty header.

        #!perl use strict; use SOAP::Lite; # soap my $client = SOAP::Lite->uri('') ->proxy("loopback://"); $client->autotype(0)->readable(1); $client->outputxml(1); $client->ns("http://schemas.xmlsoap.org/soap/envelope/","soapenv"); $client->ns("http://tempuri.org/AWEService/API/WASAPI","was"); my %auth = ( WAS_userName => 'WS_Decom', WAS_password => 'PASSWORD', ); my %search = ( DEVICE_NAME => 'prodh20', RACK_NAME => '', # etc ); my @auth_data; for (keys %auth){ my $data = SOAP::Data->name( $_ => $auth{$_} )->prefix('was'); push @auth_data, SOAP::Data->type('data' => $data), } my @search_data; for (keys %search){ my $data = SOAP::Data->name( $_ => $search{$_} )->prefix('was'); push @search_data, SOAP::Data->type('data' => $data), } my $auth = SOAP::Data->name( "userIdentification" => \@auth_data )->prefix('was'); my $search = SOAP::Data->name( "locatorSearchFieldsIdentification" => \@search_data )->prefix('was'); my $header = SOAP::Data->type('xml' => ''); my $response = $client->call("RunLocator", SOAP::Header->type('data'=>$header), SOAP::Data->type('data' => $auth), SOAP::Data->type('data' => $search) ); #open OUT,'>','output.xml' or die "$!"; print $response; #close OUT;
        poj
Re: Using SOAP::Lite with a WSDL
by Anonymous Monk on May 20, 2017 at 06:00 UTC

      I'll check that out if I can get my company to add it to our yum repos. CPAN is blocked.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1190692]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2024-04-19 21:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found