Perl SOAP::Lite unable to call method - perl

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.

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>

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

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.