Perl から JAX-WS な Web サービスへの通信方法

前回の続き。
Perl をクライアントとして、JAX-WS な Web サービスにアクセスする。

Perl

PerlSOAP::Lite を使用すれば良いらしいが、中々一筋縄でいかず一番ハマった。
まずはドキュメントにあるように素直にやってみた。

#!/usr/bin/env perl
use strict;
use warnings;
use SOAP::Lite;
my $client = SOAP::Lite->service('http://localhost:8084/CalculatorWSApplication/CalculatorWS?wsdl');
my $result = $client->add(1, 2);
warn Dumper $result;
$VAR1 = undef;

デバッグオプションをつけてみる。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use SOAP::Lite +trace => [qw(debug)];
my $client = SOAP::Lite->service('http://localhost:8084/CalculatorWSApplication/CalculatorWS?wsdl');
$client->readable(1);
my $result = $client->add(1, 2);
warn Dumper $result;
SOAP::Transport::HTTP::Client::send_receive: POST http://localhost:8084/CalculatorWSApplication/CalculatorWS HTTP/1.1
Accept: text/xml
Accept: multipart/*
Accept: application/soap
Content-Length: 850
Content-Type: text/xml; charset=utf-8
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://www.w3.org/ns/ws-policy"
    soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
    xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="http://org.me.calculator/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <tns:add>
      <c-gensym3 xsi:type="xsd:int">1</c-gensym3>
      <c-gensym5 xsi:type="xsd:int">2</c-gensym5>
    </tns:add>
  </soap:Body>
</soap:Envelope>
SOAP::Transport::HTTP::Client::send_receive: HTTP/1.1 500 Internal Server Error
Connection: close
Date: Tue, 03 Aug 2010 09:43:23 GMT
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Client-Date: Tue, 03 Aug 2010 09:43:23 GMT
Client-Peer: 127.0.0.1:8084
Client-Response-Num: 1

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
  <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
    <faultcode>S:VersionMismatch</faultcode>
    <faultstring>
      Couldn't create SOAP message. Expecting Envelope in namespace http://schemas.xmlsoap.org/soap/envelope/, but got http://schemas.xmlsoap.org/wsdl/soap/
    </faultstring>
  </S:Fault>
</S:Body>
</S:Envelope>
$VAR1 = undef;

Internal Server Error を起こしている…。
送信しているリクエストヘッダの名前空間がおかしいとエラーが出てる。
色々調べて結果以下のように名前空間を追加すれば良いらしい。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use SOAP::Lite +trace => [qw(debug)];
my $client = SOAP::Lite->service('http://localhost:8084/CalculatorWSApplication/CalculatorWS?wsdl');
$client->readable(1);
$client->ns('http://schemas.xmlsoap.org/soap/envelope/', 'ns');
my $result = $client->add(1, 2);
warn Dumper $result;
SOAP::Transport::HTTP::Client::send_receive: POST http://localhost:8084/CalculatorWSApplication/CalculatorWS HTTP/1.1
Accept: text/xml
Accept: multipart/*
Accept: application/soap
Content-Length: 898
Content-Type: text/xml; charset=utf-8
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<ns:Envelope_
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://www.w3.org/ns/ws-policy"
    xmlns:ns="http://schemas.xmlsoap.org/soap/envelope/"
    ns:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
    xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="http://org.me.calculator/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ns:Body>
    <tns:add>
      <c-gensym3 xsi:type="xsd:int">1</c-gensym3>
      <c-gensym5 xsi:type="xsd:int">2</c-gensym5>
    </tns:add>
  </ns:Body>
</ns:Envelope>
SOAP::Transport::HTTP::Client::send_receive: HTTP/1.1 200 OK
Connection: close
Date: Tue, 03 Aug 2010 09:47:01 GMT
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Client-Date: Tue, 03 Aug 2010 09:47:01 GMT
Client-Peer: 127.0.0.1:8084
Client-Response-Num: 1

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
  <ns2:addResponse xmlns:ns2="http://org.me.calculator/">
    <return>0</return>
  </ns2:addResponse>
</S:Body>
</S:Envelope>
$VAR1 = '0';

Internal Server Error は消えたが、期待する結果とは異った。

<tns:add>
  <c-gensym3 xsi:type="xsd:int">1</c-gensym3>
  <c-gensym5 xsi:type="xsd:int">2</c-gensym5>
</tns:add>

原因は上記のように という風にタグ名を付けられて、JAX-WS にはそんなタグに対応したパラメータがないから。
PHP ではどのようなリクエストを送っているか比較してみた。

<?php
ini_set('soap.wsdl_cache_enabled', '0');
$uri    = 'http://localhost:8084/CalculatorWSApplication/CalculatorWS?wsdl';
$client = new SoapClient($uri, array('trace' => 1));

$result = $client->add(array('i' => 1, 'j' => 10));
echo $client->__getLastRequest();
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:ns1="http://org.me.calculator/">
  <SOAP-ENV:Body>
    <ns1:add>
      <i>1</i>
      <j>10</j>
    </ns1:add>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

シンプルな形で送信している。

色々調査とトライアンドエラーを繰り返した結果、SOAP::Lite を使用した場合、以下のようにすればいけた。

#!/usr/bin/env perl
use strict;
use warnings;

use Data::Dumper;
use SOAP::Lite +trace => [qw(debug)];
my $client = SOAP::Lite->service('http://localhost:8084/CalculatorWSApplication/CalculatorWS?wsdl');
$client->readable(1);
$client->ns('http://schemas.xmlsoap.org/soap/envelope/', 'ns');

my $args1 = SOAP::Data->name('i')->value(1);
my $args2 = SOAP::Data->name('j')->value(2);
my $result = $client->add($args1, $args2);
warn Dumper $result;
SOAP::Transport::HTTP::Client::send_receive: POST http://localhost:8084/CalculatorWSApplication/CalculatorWS HTTP/1.1
Accept: text/xml
Accept: multipart/*
Accept: application/soap
Content-Length: 870
Content-Type: text/xml; charset=utf-8
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<ns:Envelope
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://www.w3.org/ns/ws-policy"
    xmlns:ns="http://schemas.xmlsoap.org/soap/envelope/"
    ns:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
    xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="http://org.me.calculator/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ns:Body>
    <tns:add>
      <i xsi:type="xsd:int">1</i>
      <j xsi:type="xsd:int">2</j>
     </tns:add>
  </ns:Body>
</ns:Envelope>
SOAP::Transport::HTTP::Client::send_receive: HTTP/1.1 200 OK
Connection: close
Date: Tue, 03 Aug 2010 10:38:22 GMT
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Client-Date: Tue, 03 Aug 2010 10:38:22 GMT
Client-Peer: 127.0.0.1:8084
Client-Response-Num: 1

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
  <ns2:addResponse xmlns:ns2="http://org.me.calculator/">
    <return>3</return>
  </ns2:addResponse>
</S:Body>
</S:Envelope>
$VAR1 = '3';

ちゃんと add の箇所のタグが、i と j という風に Java のパラメータと同じ値になっている。
なお、配列を(Java の List)を送りたい場合は、以下のようにする。

my $args3 = SOAP::Data->name('params')->value(100, 200);
my $result2 = $client->addList($args3);
warn Dumper $result2;

リクエストはこんな感じになった。

<?xml version="1.0" encoding="UTF-8"?>
<ns:Envelope
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://www.w3.org/ns/ws-policy"
    xmlns:ns="http://schemas.xmlsoap.org/soap/envelope/"   
    ns:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
    xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="http://org.me.calculator/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ns:Body>
    <tns:addList>
      <params xsi:type="xsd:int">100</params>
      <params xsi:type="xsd:int">200</params>
    </tns:addList>
  </ns:Body>
</ns:Envelope>
$VAR1 = '300';

まとめ

SOAP::Lite という名前の割に色々やっている。その為か一番ハマった。
なによりもう LL な世界じゃ SOAP という技術自体全然使われてないよう。
ググっても古い情報しか出てこなくて困った。
Java .NET の世界じゃまだまだ使われてるのかな?
まぁセキュリティとかエンタープライズな使い方を考えると色々やりやすいのかも。
JAX-WS にもセキュリティ周りが実装されてるっぽいし。


蛇足だけど、今回は Perl, Ruby, Python, PHP でクライアントを書いたが、JavaNetBeans を使ってクライアントを作るのが一番簡単だった。