经常读者,第一次提问。
我正在努力将Perl与我们的某个供应商提供的SOAP API(LogRhythm)连接起来,似乎已经遇到了一些障碍。
我已经能够与API接口,并克服了我最初遇到的身份验证挑战。现在,当我需要为SOAP调用提供参数时,我遇到了SOAP信封体中名称空间标记的问题。
供应商提供的API是围绕.NET和WCF构建的。
我的原型代码目前如下所示:
use strict;
no strict "refs";
use Data::Dumper;
use IO::Socket::SSL qw (SSL_VERIFY_NONE) ;
use SOAP::Lite;
use Tie::IxHash;
# Username and Password
my $sUID = "<API UserID>";
my $sPWD = "<API Password>";
# WSDL definition URI. Using a cached local copy using file:/... also works
my $LookupService_wsdl = 'https://melcapi01.soc.ipsec.net.au/LogRhythm.API/Services/LookupServiceBasicAuth.svc?singleWsdl';
my $lrns = 'http://www.logrhythm.com/webservices';
# Ensure that a consistent xmlns:soap value is used, without this we get inconsistent results.
$SOAP::Constants::PREFIX_ENV = 'SOAP-ENV';
# Don't validate SSL Certificate while testing
IO::Socket::SSL::set_defaults(SSL_verify_mode => "SSL_VERIFY_NONE");
# Construct the security header
my %authHash;
tie %authHash, "Tie::IxHash";
%authHash = (
Username => SOAP::Data->type( '' => $sUID )->prefix('wsse'),
Password => SOAP::Data->type( '' => $sPWD )->prefix('wsse'),
);
my $wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
my $securityHeader = SOAP::Header->new(
name => 'Security',
uri => $wsse,
prefix => 'wsse',
value => \SOAP::Data->new(
name => 'UsernameToken',
prefix => 'wsse',
value => \%authHash,
)
);
# Error handling
on_fault => sub { my($soap, $res) = @_;
die ref $res ? $res->faultstring : $soap->transport->status;
};
# Define the SOAP Instance
my $lrapi = SOAP::Lite
-> readable (1)
-> service($LookupService_wsdl)
-> on_action( sub {return $action});
# Set the default Namespace
$lrapi->default_ns($lrns);
# Actually get data from the LookupService
my $result;
# Build up the parameters
my @classificationType = ( SOAP::Data->new(name =>'classificationType', value => 2000));
$result = $lrapi->GetClassificationsByType($securityHeader);
print Dumper $result;
这会产生以下SOAP请求:
SOAPAction: "http://www.logrhythm.com/webservices/LookupService/GetClassificationsByType"
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://www.logrhythm.com/webservices"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsa10="http://www.w3.org/2005/08/addressing"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>reporting</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:GetClassificationsByType>
<classificationType xsi:type="xsd:int">2000</classificationType>
<classificationTypeSpecified xsi:type="xsd:boolean">true</classificationTypeSpecified>
</tns:GetClassificationsByType>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
返回没有结果。我已经使用.NET WebService Studio进行了一些挖掘和请求医生,并且已经确定问题是由SOAP Body中元素上缺少xmlns标识符引起的。
SOAP请求应如下所示:
SOAPAction: "http://www.logrhythm.com/webservices/LookupService/GetClassificationsByType"
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://www.logrhythm.com/webservices"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsa10="http://www.w3.org/2005/08/addressing"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>reporting</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:GetClassificationsByType xmlns="http://www.logrhythm.com/webservices">
<classificationType xsi:type="xsd:int">2000</classificationType>
<classificationTypeSpecified xsi:type="xsd:boolean">true</classificationTypeSpecified>
</tns:GetClassificationsByType>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
各种支持请求表明应该通过在SOAP :: Lite对象上手动设置default_ns
来解决这个问题,但这对我不起作用。
我已经深入挖掘了SOAP::Lite(1.20)的源代码,看看发生了什么,似乎是{'_use_default_ns'}
属性,在创建SOAP之前设置为1
方法调用似乎在处理呼叫期间重置为0
。
我还试图手动构建SOAP :: Lite对象,目的是使用call()
方法来获得所需的结果,但是,我想我遇到了名称空间数量问题由于我目前使用的service()
机制而自动添加。如果我想使用call()
方法,我似乎无法使用service()
设置对象。
有没有人有任何其他的建议我可以尝试,因为我现在不知所措?
非常感谢任何协助。
免责声明:我不是程序员/开发人员,我是高级安全工程师/顾问/架构师,具有一定的编程能力,所以如果我的代码结构糟糕,我会提前道歉。这也是我第一次尝试使用SOAP。
答案 0 :(得分:1)
这是一个hacky解决方案。它挂钩到SOAP :: Lite客户端的传输层,并修改每个传出请求。
我使用了通过Google搜索找到的随机网络服务,因此您可以复制并粘贴示例代码并在没有凭据的情况下运行它。 The service由美国国家气象局运营。在我的示例中,我使用其端点返回美国邮政编码的纬度/经度坐标。
use strict;
use warnings 'all';
use feature 'say';
use SOAP::Lite;
my $client = SOAP::Lite->new(
proxy => 'http://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php'
);
$client->service('http://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl');
# the transport layer is a subclass of LWP::UserAgent
$client->transport->add_handler(
request_prepare => sub {
my ( $request, $ua, $h ) = @_;
my $content = $request->content;
# this is the content before modification
say 'before: ' . $content;
# modify the content with a simple regex substitution
$content =~ s{<LatLonListZipCode>}{<LatLonListZipCode xmlns="http://example.org">};
$request->content($content);
# so it doesn't throw a 'Content-Length header value
# was wrong, fixed' warning
$request->header( 'Content-Length' => length $content );
# this is the content after modification
say 'after: ' . $content;
# the return value is ignored
}
);
my $res = $client->LatLonListZipCode('90210');
say $res->result;
<强>解释强>
这是有效的,因为此类SOAP通信的SOAP::Transport层SOAP::Transport::HTTP::Client是LWP::UserAgent的子类。 UserAgent非常灵活,并为某些事件提供了大量different handlers。我们使用的是request_prepare
,它允许我们在通过线路(或无线或其他)发送之前更改HTTP::Request。
在发送请求之前调用处理程序,并且可以以任何方式修改请求。例如,这可以用于向特定请求添加某些标头。
在我们添加的处理程序中,我们获取请求的内容并使用正则表达式替换将xmlns添加到<LatLongListZipCode>
标记。虽然parsing XML with regex is not a good idea改变了一些看起来像XML的文本很好,但是因为我们当时并不在意它是什么。
由于已经构建了请求,我们需要更新 Content-Length 的标头字段。它会自动更新,但同时会发出警告。我们可以通过简单地更新它来省略它。
这是程序的输出,分为三个部分。
更改前请求的内容
<?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><LatLonListZipCode><c-gensym3 xsi:type="xsd:int">90210</c-gensym3></LatLonListZipCode></soap:Body></soap:Envelope>
更改后请求的内容
<?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><LatLonListZipCode xmlns="http://example.org"><c-gensym3 xsi:type="xsd:int">90210</c-gensym3></LatLonListZipCode></soap:Body></soap:Envelope>
回复内容
<?xml version='1.0'?><dwml version='1.0' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='http://graphical.weather.gov/xml/DWMLgen/schema/DWML.xsd'><latLonList>34.0995,-118.414</latLonList></dwml>
显然,更改工作正常,请求仍在处理中,我选择的服务并不关心xmlns属性。
采用您的网络服务
对于您的用例,您必须确保只有开箱即用的请求才会发生变化。如果所有这些都应该改变,它可能就像这样简单。在构建客户端后,只需执行一次。
$lrapi->transport->add_handler(
request_prepare => sub {
my ( $request, $ua, $h ) = @_;
my $content = $request->content;
# modify the content with a simple regex substitution
$content =~ s{<tns:GetClassificationsByType>}{<tns:GetClassificationsByType xmlns="http://www.logrhythm.com/webservices">};
$request->content($content);
# so it doesn't throw a 'Content-Length header value was wrong, fixed' warning
$request->header( 'Content-Length' => length $content );
}
);
<强>声明强>
正如我所说,这是一个务实的解决方案。但是SOAP非常复杂,并且很少有客户能够把一切都搞定。还有很多服务器做了很多不太正确的事情。有时实用主义是最好的行动方针。当然,服务器中的行为可能会改变,在这种情况下,这可能会中断。但这是值得我相信的风险。
答案 1 :(得分:1)
作为我原始帖子和@simbabque的后续内容,我对此进行了测试,并确认@ simbabque的建议有效。
为了满足可用方法的扩展列表(有6种不同的服务定义,共有59种方法可用。这可能会在API的未来版本中增加......),我已经实现了建议的{ {1}}如下:
add_handler
我还必须在$lrapi->transport->add_handler(
request_prepare => sub {
my ( $request, $ua, $h ) = @_;
my $content = $request->content;
# Get the SOAPAction Header from the current request
my $soapAction = $request->header('SOAPAction');
# Parse the SOAPAction Header, and determine the method being called
$soapAction =~ m/^\"(http:\/\/[\w\.\/]+)\/(\w+)\/(\w+)\"$/;
my ($lruri, $lrsvc, $lrmethod) = ($1, $2, $3);
# Modify the content with a simple regex substitution
$content =~ s{<tns:$lrmethod>}{<tns:$lrmethod xmlns="$lruri">};
$request->content($content);
# So it doesn't throw a 'Content-Length header value was wrong, fixed' warning
$request->header( 'Content-Length' => length $content );
}
);
上设置一个属性,以确保它有一些值。我只需在传输上设置代理属性即可实现此目的。出于某种原因,当使用WSDL服务导致错误时,传输属性的构建方式不同。
在调用$lrapi->transport
之前,我使用类似于以下的内容来实现此目的:
add_handler
这会导致$lrapi->proxy ("https://melcapi01.soc.ipsec.net.au/LogRhythm.API/Services/LookupServiceBasicAuth.svc");
对象正确构建,从而使$lrapi->transport
能够正常运行。
再次感谢@simbabque的解决方案。
我希望这篇文章对其他人也有用。