Perl SOAP :: Lite与.NET

时间:2016-09-01 03:56:19

标签: perl soap namespaces

经常读者,第一次提问。

我正在努力将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。

2 个答案:

答案 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::TransportSOAP::Transport::HTTP::ClientLWP::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的解决方案。

我希望这篇文章对其他人也有用。