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

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

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>

Perl soap request retrieving one value?

I have the following script that apparently this will not let me submit with more detail. It basically is pulling basic scheduling details from a SOAP request into XML and i need to retrieve a value from it.:
#!perl
#
# Quick sample script to fetch data from Tribune's Data Direct Service
#
# R. Eden 2/23/08
# (modified from XMLTV's tv_grab_na_dd, which started with a sample)
# script provided by Tribune
use SOAP::Lite;
use strict;
my $USER='username';
my $PASS='password';
my $START='2016-09-27T00:00:00Z';
my $STOP ='2016-09-27T23:59:59Z';
#
# Set login credientials
#
sub SOAP::Transport::HTTP::Client::get_basic_credentials {
return lc($USER) => "$PASS";
}
#
# Deifne SOAP service
#
my $dd_service='http://dd.schedulesdirect.org/tech/tmsdatadirect/schedulesdirect/tvDataDelivery.wsdl';
my $proxy='http://localhost/';
my $soap= SOAP::Lite
-> service($dd_service)
-> outputxml('true')
-> proxy($proxy, options => {compress_threshold => 10000,
timeout => 420});
#
# It's polite to set an agent string
#
$soap->transport->agent("perl/$0");
#
# Now let's get our data
#
my $raw_data=$soap->download($START,$STOP);
print $raw_data;
exit 0;
It outputs:
<?xml version='1.0' encoding='utf-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'>
<SOAP-ENV:Body><ns1:downloadResponse SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:ns1='urn:TMSWebServices'>
<xtvdResponse xsi:type='ns1:xtvdResponse'>
<messages xsi:type='ns1:messages'>
<message>Your subscription will expire: 2017-09-29T21:18:20Z</message>
</messages>
<xtvd from='2016-09-27T00:00:00Z' to='2016-09-27T23:59:59Z' schemaVersion='1.3' xmlns='urn:TMSWebServices' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='urn:TMSWebServices http://dd.schedulesdirect.org/tech/xml/schemas/tmsxtvd.xsd'>
<stations>
</stations>
<lineups>
</lineups>
</xtvd>
</xtvdResponse>
</ns1:downloadResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> [
I strictly want to date/time of the <message> part that says:
<message>Your subscription will expire: 2017-09-29T21:18:20Z</message>
How do I do that?
The most pragmatic way would be to just take the raw result that contains the XML as a string, and parse it with a regular expression. All you want is a fixed pattern, so there is no need to actually use an XML parser (or the built-in stuff that SOAP::Lite brings).
$raw_data =~ m/Your subscription will expire: ([^<]+)</;
my $date_string = $1;
Now you have 2017-09-29T21:18:20Z inside of $date_string and can do with it what you want.

Perl SOAP::Lite unable to call method

I am working on a script to get data from OBIEE Web services. The following shows the correct soap envelope created however i do not get any results.
#!/usr/bin/perl
use warnings;
use strict;
use diagnostics;
use XML::Simple;
use Data::Dumper;
use Log::Log4perl;
use SOAP::Lite 0.65 +trace => 'debug';
#Initialize Logger
Log::Log4perl->init("log.conf");
my $logger = Log::Log4perl->get_logger();
my $outputFormat = "SAWRowsetData";
my $SQL = qq(sql);
my $sessionID = "session id";
my $soap = SOAP::Lite
->readable(1)
->uri('urn://oracle.bi.webservices/v6')
->proxy( 'http://host:port/analytics/saw.dll/wsdl/v6' );
my $serializer = $soap->serializer();
$serializer->register_ns("urn://oracle.bi.webservices/v6","sawsoap");
my $som = $soap->call('executeSQLQuery',
SOAP::Data->name('sawsoap:sql' => $SQL)->type('xsd:string'),
SOAP::Data->name('sawsoap:outputFormat' => $outputFormat)->type('sawso
ap:XMLQueryOutputFormat'),
SOAP::Data->name('sawsoap:executionOptions')->type('sawsoap:XMLQueryEx
ecutionOptions')->value(
\SOAP::Data->value(
SOAP::Data->name("sawsoap:async")->type("xsd:boolean")->va
ue("?"),
SOAP::Data->name("sawsoap:maxRowsPerPage")->type("xsd:int")->value("?"),
SOAP::Data->name("sawsoap:refresh")->type("xsd:boolean")->
value("?"),
SOAP::Data->name("sawsoap:presentationInfo")->type("xsd:boolean")->value("?"),
SOAP::Data->name("sawsoap:type")->type("xsd:string")->value("?"))),
SOAP::Data->name('sawsoap:sessionID' => $sessionID)->type('xsd:string'));
$logger->info(Dumper $som);
here is the soap envelope it produced that works perfectly fine using soapUI
<soap:Envelope
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:sawsoap="urn://oracle.bi.webservices/v6"
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>
<logon xmlns="urn://oracle.bi.webservices/v6">
<sawsoap:name xsi:type="xsd:string">name</sawsoap:name>
<sawsoap:password xsi:type="xsd:string">password</sawsoap:password>
</logon>
</soap:Body>
</soap:Envelope>
I checked the OBIEE server and do not see a request. It is generating the right stuff but the request is not getting across. Any insight is greatly Appreciated.
The issue is resolved by using the endpoint for proxy.

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

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.