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

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. )

Related

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 Pod::WSDL: wrong return type

I have a problem with Pod::WSDL perl module and didn't find an answer, yet...
I use a method like this:
=pod
=begin WSDL
_DOC
Method hopefully returning a string
_IN parameter1 $string The first STRING parameter
_RETURN $string Returns a string
=end WSDL
=cut
sub go {
my ($this, $parameter1) = #_;
return($parameter1);
}
The problem is this: calling this method with a string (say:
go("abc");
), everything is fine.
Calling it with a value which is a number (say:
go("123");
), produces a return type of "xsd:int", instead of "xsd:string", as I would expect from "_RETURN $string"...
This is the full SOAP Envelop built, if it can help:
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><goResponse xmlns="http://192.168.10.21/TestService"><s-gensym3 xsi:type="xsd:int">1</s-gensym3></goResponse></soap:Body></soap:Envelope>
Returning:
return("" . $parameter1);
doesn't help. Returning something like this:
return(" " . $parameter1);
does help, but it's not what I want to return... :-)

How can I change what order the xml elements of a SOAP::Lite request are generated with in perl?

I'm trying to make a request to a SOAP::Lite server and the vendor wants me to send a request where MessageSource comes before MessageContent, but when I pass my hash to SOAP::Lite it always makes it the other way around.
I've tried using Tie::IxHash to no avail.
I'm thinking about just hand-writing the XML with SOAP::Data->type('xml' => $xml_content), but it really feels like a workaround that will get really annoying to support.
I have personally found that I prefer to use SOAP::Data::Builder for building the SOAP::Data and then passing it to SOAP::Lite.
#!/usr/bin/perl
use 5.006;
use strict;
use warnings;
use SOAP::Lite +trace => [ 'debug' ];
use SOAP::Data::Builder;
my $req1 = SOAP::Lite->new(
readable => 1,
autotype => 0,
proxy => 'https://example.com/mysoapuri',
);
my $sb = SOAP::Data::Builder->new;
$sb->autotype(0);
$sb->add_elem(
name => 'clientLibrary',
value => 'foo',
);
$sb->add_elem(
name => 'clientLibraryVersion',
value => 'bar',
);
$sb->add_elem(
name => 'clientEnvironment',
value => 'baz',
);
my $ret = $req1->requestMessage( $sb->to_soap_data );
this generates the following SOAP
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<requestMessage>
<clientLibrary>foo</clientLibrary>
<clientLibraryVersion>bar</clientLibraryVersion>
<clientEnvironment>baz</clientEnvironment>
</requestMessage>
</soap:Body>
</soap:Envelope>
Note: I realize that adding another dependency may not be in the cards... unfortunately I have never really figured out how else to get my data right.

Perl and Complex SOAP Request

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.