Perl and Complex SOAP Request - perl

I need to make a somewhat complex soap query using Perl, preferably using SOAP::Lite. I know the service is active and have been successful in getting errors back from the other end. Here is the soap query I need to make:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetCategories xmlns="http://webservices.uship.com">
<token>string</token>
</GetCategories>
</soap:Body>
</soap:Envelope>
I've researched this via Google to no avail.
Update: The code used so far is
use SOAP::Lite;
print SOAP::Lite
-> uri('webservices.uship.com/uShipsvc.asmx?WSDL';)
-> proxy('http:/webservices.uship.com')
-> GetCategories('myToken')
-> result;
This returns
500 bad hostname, 500 Can't connect to :80 (Bad hostname '') at soap2.pl line 2

From SOAP::Lite's Getting Started Guide your code should look something like:
#!perl -w
use SOAP::Lite;
print SOAP::Lite
uri('http://www.soaplite.com/Temperatures')
proxy('http://webservices.uship.com')
GetCategories('string')
result;
Plug in the URI for the returned object in uri()

I had issues making SOAP calls because the server that I was talking to was .NET, which apparently has communication problems with SOAP::Lite:
http://msdn.microsoft.com/en-us/library/ms995764.aspx#soapliteperl_topic3
Even if your server isn't .NET, this is another way to make your call (that works for me):
# proxy and uri strings should NOT have trialing slashes
my $_uri = 'http://youruri.com/whatever';
my $_proxy = 'http://yourproxy.com/something.asmx';
my $methodName = 'GetCategories';
my #params = (
SOAP::Data->name( 'token'=>'string' ),
);
my $handle = SOAP::Lite
->uri( $_uri )
->proxy( $_proxy , timeout => 30, keep_alive => 1 )
->on_action( sub{ $_uri . "/" . $_[1] } );
my $method = SOAP::Data
->name( $methodName )
->attr( {xmlns => $_uri . "/"} );
my $rv = $handle->call( $method=>#params );
if( $rv->fault() ){
print "SOAP Error ($methodName) :: " . $handle->transport()->status() . "\n\t" . $rv->faultcode() . ": " . $rv->faultstring();
} else {
print $rv->result();
}
Also, looking at your comment to one of the answers
codeuse SOAP::Lite; print SOAP::Lite ->
uri('webservices.uship.com/uShipsvc.asmx?WSDL';) ->
proxy('http:/webservices.uship.com') -> GetCategories('myToken') ->
result;
You might have the uri and proxy backwards. I.e., the proxy should be your .asmx (without the "?WSDL"). If you want to you the ?WSDL, it's a completely different method of connecting than using the uri+proxy. See: http://guide.soaplite.com/#access%20with%20service%20description%20%28wsdl%29

You need to correct your URIs, with http:/webservices.uship.com I get 500 No Host option provided at test-soap.pl line 7. Change it to this:
use SOAP::Lite;
print SOAP::Lite
-> uri('http://webservices.uship.com/uShipsvc.asmx?WSDL')
-> proxy('http://webservices.uship.com')
-> GetCategories('myToken')
-> result;

Consider using SOAP::Trace to trace the execution of SOAP calls
You can include this use statement in your lib/script:
use SOAP::Lite +trace => [qw/ debug method fault /];
This can help you debug your SOAP call.

Related

Calling a simple Soap API using Perl's Soap::Lite

I am trying to make simple API call (at least that's what I thought initially when I started ) using SOAP::Lite module. I am using one of the publicly available SOAP API here to add two numbers. I am getting following error:
Server did not recognize the value of HTTP Header SOAPAction:
http://tempuri.org/#Add.
I enabled the debug in SOAP::Lite and it seems my request is not formed correctly. I suspect the type specified (xsi:type="xsd:int") in intA and intB is causing issue.
Request from debug:
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<Add xmlns="http://tempuri.org/">
<intA xsi:type="xsd:int">5</intA>
<intB xsi:type="xsd:int">10</intB>
</Add>
</soap:Body> </soap:Envelope>
Here is my Perl code:
#!/usr/bin/env perl
use strict;
use warnings;
use SOAP::Lite;
#use SOAP::Lite +trace => 'all';
SOAP::Lite->import(trace => 'debug');
#my $uri = 'http://tempuri.org/';
my $proxy = 'http://www.dneonline.com/calculator.asmx';
my $ns = 'http://tempuri.org/';
my $client = SOAP::Lite
->readable(1)
->uri($ns)
->proxy($proxy);
my $param1 = SOAP::Data->name("intA" => $x);
my $param2 = SOAP::Data->name("intB" => $y);
my $response = $client->Add($param1,$param2);
print "Result is $response \n";
Note: I tried loading the WSDL in SOAPUI tool and the API works fine there.
UPDATE
As #simbabque suggested, I tried debuging using LWP::ConsoleLogger
Header looks like this:
.---------------------------------+-----------------------------------------.
| Request (before sending) Header | Value |
+---------------------------------+-----------------------------------------+
| Accept | text/xml, multipart/*, application/soap |
| Content-Length | 549 |
| Content-Type | text/xml; charset=utf-8 |
| SOAPAction | "http://tempuri.org/#Add" |
| User-Agent | SOAP::Lite/Perl/1.27 |
'---------------------------------+-----------------------------------------'
I have no idea where # is coming from. Maybe I will try SOAP::Simple and see if it helps.
Cheers
URI and method name (Add) are concatenated to build an http header
named SOAPAction.
URI must end with "/". SOAP::Lite merge them with "#".
Long time ago I got the same problem. Found that .NET based webservices are disappointed to see "#" and read about this solution - from man pages - by mean of on_action handler which simply concatenates URI and method name.
All this is well documented in man SOAP::Lite
my $client = SOAP::Lite->new(
readable => 1,
# uri is the same as package/class name into cgi file; must end with "/"
uri => $ns,
# on action corrects SOAPAction to make .NET happy. .NET dislike the '#'
# $_[1] will contain method name on $client->call('someMethName')
on_action => sub { return '"'. $ns . $_[1] .'"'; },
# proxy is the full resource URL of aspx/php/cgi/pl wich implements method name
proxy => $proxy);
# rest of your code...

Define namespaces in the "method" element using SOAP::Lite

I am using SOAP::Lite to connect to an outside service. After much trial and error and communication with the other company, I have discovered the problem.
My code looks like this:
$soap = new SOAP::Lite
->service($wsdl_link)
->uri($url_link)
->proxy($proxy_link)
->on_action(sub { sprintf '"%s%s"', shift, shift });
my $resp = $soap->call('CreateAssignment',SOAP::Data->type('xml'=>$xml),
SOAP::Header->type('xml'=>$headXML));
This produces the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
<soap:Header>
...
</soap:Header>
<soap:Body>
<CreateAssignment xmlns="url_link">
...
</CreateAssignment></soap:Body></soap:Envelope>
(where url_link is a valid url)
I need to define additional namespaces. I have done this by adding ->ns(namespace, prefix) to my code. However, this adds the additional namespaces to the "envelope" tag. I have been informed by the company that the namespaces need to be on the "CreateAssignment" tag. Indeed, when I make the appropriate change and run it using SOAP UI, it works beautifully.
I have tried adding the "CreateAssignment" tag to my xml and running the call() function without a method. SOAP::Lite wraps the xml in a generic tag.
I have read the SOAP::Lite documentation, I have asked search engines, I have asked colleagues and no one has an answer.
Is there a way to force SOAP::Lite to put the namespace declarations where I need them?
If not, what is a better module to use?
I ended up sitting down with another coworker and reading the source code of SOAP::Lite - we discovered that the method tag was built in sub envelope. There is an if-statement whereby the module will use the entire object if instead of a string object a SOAP::Data object is passed in as the method:
elsif (UNIVERSAL::isa($method => 'SOAP::Data')) {
$body = $method;
}
I changed from this:
$soap = new SOAP::Lite
->service($wsdl_link)
->uri($url_link)
->proxy($proxy_link)
->on_action(sub { sprintf '"%s%s"', shift, shift });
my $resp = $soap->call('CreateAssignment',SOAP::Data->type('xml'=>$xml),
SOAP::Header->type('xml'=>$headXML));
To this:
$method = SOAP::Data->new(name=>'ns4:CreateAssignment');
$method->attr({'xmlns'=> $namespaceOne,
'xmlns:ns2'=> $namespaceTwo,
'xmlns:ns3'=> $namespaceThree,
'xmlns:ns4'=> $namespaceFour,
'xmlns:ns5'=> $namespaceFive});
$soap = new SOAP::Lite
->service($wsdl_link)
->uri($url_link)
->proxy($proxy_link)
->on_action(sub { sprintf '"%s%s"', shift, shift });
my $resp = $soap->call($method,SOAP::Data->type('xml'=>$xml),
SOAP::Header->type('xml'=>$headXML));
This created my method tag exactly as the company required:
<ns4:CreateAssignment xmlns="namespaceOne" xmlns:ns2="namespaceTwo" xmlns:ns3="namespaceThree" xmlns:ns4="namespaceFour" xmlns:ns5="namespaceFive">
...
</ns4:CreateAssignment>

Extract specific XML element in CDATA taken from SOAP::Lite Response Hash

My code below is connecting to a .asmx web service to get data.
The code extractes the CDATA into %keyHash below.
Rather than parsing the entire CDATA, is it possible to grab a specific data element in the SOAP CDATA by calling out it's path?
I read that I could use $soap->valueof() to get the data, is that correct? and that this would require use of XPATH?
I ask as I am unfamiliar with this and I do not know if I am on the right path, are there other ways to do this?
My $soap->valueof('//Images/Front') attempts have failed, saying that, my first time using XPATH, could be getting it wrong but at this point I am guessing if this is the right way to go.
Any direction on whether I am on the right or wrong path in using valueof() would be appreciated!
Here is the code, it works. I have also included the obscurated CDATA data extracted from %keyHash.
use SOAP::Lite +trace => 'all';
$soap = SOAP::Lite
-> uri('..../')
-> on_action( sub { join '/', '.....', $_[1] } )
-> proxy('......asmx');
$method = SOAP::Data->name('methodName')
->attr({xmlns => ...../'});
#params = (
SOAP::Data->name(tran=> 765) ->type(''),
SOAP::Data->name(token => 0)->type(''),
SOAP::Data->name(type=> 1)->type('')
);
%keyHash = %{ $soap->call($method => #params)->body->{'GetmethodNameResponse'}->{'GetmethodNameResult'} };
# iterate through all fields and print them
foreach my $k (keys %keyHash) {
print "$k=$keyHash{$k}\n";
}
Example of the data output, I want the data in the string "THIS_IS_THE_DATA_I_WANT" (unable to put the path here for some reason)
RequestResult=0
Xml=<?xml version="1.0" encoding="utf-8"?>
<Images>
<Front>THIS_IS_THE_DATA_I_WANT</Front>
</Images>
Thank You,
A
I solved this by using the following, hope it helps someone...
use XML::Simple;
%keyhash = %{ $soap->call($method => #params)->body->{'GetCheckXmlResponse'}->{'GetCheckXmlResult'}};
$getxml= %keyhash->{Xml};
$parsexml = XMLin($getxml);
print Dumper($parsexml); # Use this to point to your data and then grab it as per the line below
$frontside = $parsexml->{Images}->{Front};

Perl wsdl webservice call

I am new to perl webservice call.
I am calling a wsdl webservice function and I am getting the response as 400 bad request error. I tried and googled many sites but could not get successful. Please help me in solving this issue. Below is my code.
use SOAP::Lite;
my $lite = SOAP::Lite -> service('http://localhost:8080/service.svc?wsdl');
my $arg1 ="SRC";
my $arg2 = "ARG";
my #arg3 = ('test1','test2','test3');
my #res = $lite->Func($arg1,$arg2,#arg3);
print "#res";
I just had similar problem and seemed that SOAP::Lite may have a bug concerning handling complex data structures.
Whatever, if your SOAP method (Func) needs 3 arguments, the last one should be reference to array.
Also, with use SOAP::Lite qw(trace) you could debug request envelope.
If you are not restrict at SOAP::Lite, I will request you to please have a look at XML::Compile::SOAP::Client
I'm not sure how much of a difference there is between a WSDL and an ASMX web service. If not much, try this code sample which works for me.
my $soap = SOAP::Lite
-> uri('http://foo.com')
-> on_action( sub { join '/', 'http://foo.com', $_[1] } )
-> proxy('http://foo/services/GetEmailAddress/Service.asmx');
my $method = SOAP::Data->name('GetEmailAddress')
->attr({xmlns => 'http://foo.com/'});
my #params = ( SOAP::Data->name(username => $user) );
my $email = $soap->call($method => #params)->result;
Input is a username, output is an email address. The ASMX web service was created in .NET 3, I believe.
Everything I know about how this works I learned from http://msdn.microsoft.com/en-us/library/ms995764.aspx.

Perl: what kind of data should i feed to delcampe API?

I write soap-client based on Delcampe API. Simple methods work fine, but functions with need on complex data give me an error message like "You must send item's data!". Based on PHP example here i thought, that data should be either hash or hashref, but both give me error mentioned before.
Sample script i use:
use 5.010;
use SOAP::Lite;
use SOAP::WSDL;
use strict;
use warnings;
use Data::Dumper;
my $API_key = 'xyz';
my $service = SOAP::Lite->service('http://api.delcampe.net/soap.php?wsdl');
my $return = $service->authenticateUser($API_key);
if ($return->{status}) {
my $key = $return->{data};
my %data = (description => 'updated description');
my $response = $service->updateItem($key, 123456, \%data);
if ($response->{status}) {
say Dumper $response->{data};
} else {
say $response->{errorMsg};
}
} else {
say "no: " . $return->{status};
}
So, what kind of data structure should i use instead of %data or how could i debug the SOAP-envelope, which is produced as request? (PHP code based on example works fine)
ADDITION
with use SOAP::Lite qw(trace); igot SOAP envelope too:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://api.delcampe.net/soap.php">
<soap:Body>
<tns:updateItem>
<token xsi:type="xsd:string">secret_one</token>
<id_item xsi:type="xsd:int">123456</id_item>
<arrData xsi:nil="true" xsi:type="soap-enc:Array" />
</tns:updateItem>
</soap:Body>
</soap:Envelope>
As seen above, there is no bit of data sent. I tried data also as string, array and arrayref. Maybe it is bug of SOAP::Lite?
May be you'd try to replace
my %data = (description => 'updated description');
with
my $data = SOAP::Data->name(description => 'updated description');
We have similar issues when working on our SOAP API, and it was solved by something like that, wrapping complex data into SOAP::Data. So I hope this'll help. )
UPDATE:
The previous advice didn't help: looks like it's indeed the SOAP::Lite bug, which ignores the 'soap-enc:Array' definition in WSDL file whatsoever.
Have finally found a workaround, though. It's not pretty, but as a final resort it may work.
First, I've manually downloaded the WSDL file from Delcampe site, saved it into local directory, and referred to it as ...
my $service = SOAP::Lite->service('file://...delcampe.wsdl')
... as absolute path is required.
Then I've commented out the 'arrData line' within WSDL updateItem definition.
And, finally, I've made this:
my $little_monster = SOAP::Data->name(arrData =>
\SOAP::Data->value((
SOAP::Data->name(item =>
\SOAP::Data->value(
SOAP::Data->name(key => 'personal_reference'),
SOAP::Data->name(value => 'Some Personal Reference')->type('string'),
)
),
SOAP::Data->name(item =>
\SOAP::Data->value(
SOAP::Data->name(key => 'title'),
SOAP::Data->name(value => 'Some Amazing Title')->type('string'),
)
),
# ...
))
)->type('ns1:Map');
... and, I confess, successfully released it into the wilderness by ...
$service->updateItem($key, 123456, $little_monster);
... which, at least, generated more-o-less likable Envelope.
I sincerely hope that'll save at least some poor soul from banging head against the wall as much as I did working on all that. )