ザネリは列車を見送った

ブログという名の備忘録

SOAP Webサービスクライアントを作ろう(Apache Axis 編)

Webサービスを用意する(ASP.NET)

今回は Java のライブラリである Apache Axis を利用してクライアントを作成するため、
せっかくなので繋ぎ先は Java 以外で。
Visual Studio 2010 で[新しいプロジェクト] -> [ASP.NET Web アプリケーション]を作成し、
ソリューション エクスプローラーで適当なディレクトリを作成し右クリック -> [追加] -> [新しい項目] -> [Web サービス]を選択。
文字列と数値を受け取って文字列を返すWebサービスを作成。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace WebServices.WS
{
 [WebService(Namespace = "http://www.zaneli.com/")]
 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 [System.ComponentModel.ToolboxItem(false)]
 public class ZaneliWS : System.Web.Services.WebService
 {
  [WebMethod(Description="テスト用Webサービスオペレーション")]
  public string GetMessage(String text, int num)
  {
   return "text=" + text + ", num=" + num;
  }
 }
}

自動生成される WSDL (http://localhost:49175/WS/ZaneliWS.asmx?WSDL)は以下の通り。

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
   xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
   xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
   xmlns:tns="http://www.zaneli.com/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
   xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
   targetNamespace="http://www.zaneli.com/"
   xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://www.zaneli.com/">
      <s:element name="GetMessage">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="text" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name="num" type="s:int" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="GetMessageResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="GetMessageResult" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </wsdl:types>
  <wsdl:message name="GetMessageSoapIn">
    <wsdl:part name="parameters" element="tns:GetMessage" />
  </wsdl:message>
  <wsdl:message name="GetMessageSoapOut">
    <wsdl:part name="parameters" element="tns:GetMessageResponse" />
  </wsdl:message>
  <wsdl:portType name="ZaneliWSSoap">
    <wsdl:operation name="GetMessage">
      <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">テスト用Webサービスオペレーション</wsdl:documentation>
      <wsdl:input message="tns:GetMessageSoapIn" />
      <wsdl:output message="tns:GetMessageSoapOut" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="ZaneliWSSoap" type="tns:ZaneliWSSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="GetMessage">
      <soap:operation soapAction="http://www.zaneli.com/GetMessage" style="document" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:binding name="ZaneliWSSoap12" type="tns:ZaneliWSSoap">
    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="GetMessage">
      <soap12:operation soapAction="http://www.zaneli.com/GetMessage" style="document" />
      <wsdl:input>
        <soap12:body use="literal" />
      </wsdl:input>
      <wsdl:output>
        <soap12:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="ZaneliWS">
    <wsdl:port name="ZaneliWSSoap" binding="tns:ZaneliWSSoap">
      <soap:address location="http://localhost:49175/WS/ZaneliWS.asmx" />
    </wsdl:port>
    <wsdl:port name="ZaneliWSSoap12" binding="tns:ZaneliWSSoap12">
      <soap12:address location="http://localhost:49175/WS/ZaneliWS.asmx" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

ライブラリのダウンロード

Apache Axis サイトから axis-bin-1_4 をダウンロードする。
2006 年時点で開発は停止し、Axis2 への移行が行われているため今後新規に Axis を選ぶことはあまり無いだろう。
(SOAP Web サービス自体が…というのは置いておいて。)

スタブを使用して Web サービス実行

WSDL2Java コマンドで WSDL からスタブを生成する。

java org.apache.axis.wsdl.WSDL2Java http://localhost:49175/WS/ZaneliWS.asmx?WSDL

以下の 5 クラスが自動生成される。

  • com.zaneli.www.ZaneliWS
  • com.zaneli.www.ZaneliWSLocator
  • com.zaneli.www.ZaneliWSSoap
  • com.zaneli.www.ZaneliWSSoap12Stub
  • com.zaneli.www.ZaneliWSSoapStub

以下のように実行。

import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import com.zaneli.www.ZaneliWS;
import com.zaneli.www.ZaneliWSLocator;
import com.zaneli.www.ZaneliWSSoap;

public class AxisExecutor1 {

 public String execute(String text, int num) throws ServiceException, RemoteException {
  ZaneliWS service = new ZaneliWSLocator();
  ZaneliWSSoap port = service.getZaneliWSSoap();
  return port.getMessage(text, num);
 }

 public static void main(String[] args) throws ServiceException, RemoteException {
  System.out.println(new AxisExecutor1().execute("Axisで実行", 111));
 }
}

「text=Axisで実行, num=111」が出力されることを確認。

スタブを使用せず Web サービス実行

WSDL の URL, サービス名, ポート名, オペレーション名を直接指定する場合は以下のようにする。

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;

import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ServiceException;

import org.apache.axis.client.Service;

public class AxisExecutor2 {

 private static final String WSDL_URI = "http://localhost:49175/WS/ZaneliWS.asmx?WSDL";
 private static final String NAMESPACE = "http://www.zaneli.com/";
 private static final QName SERVICE_NAME = new QName(NAMESPACE, "ZaneliWS");
 private static final QName PORT_NAME = new QName(NAMESPACE, "ZaneliWSSoap");
 private static final String OPERATION_NAME = "GetMessage";

 public String execute(String text, int num) throws MalformedURLException, ServiceException, RemoteException {
  Service service = new Service(new URL(WSDL_URI), SERVICE_NAME);
  Call call = service.createCall(PORT_NAME, OPERATION_NAME);
  return (String) call.invoke(new Object[]{text, num});
 }
 public static void main(String[] args) throws ServiceException, RemoteException, MalformedURLException {
  System.out.println(new AxisExecutor2().execute("Axisで動的実行", 222));
 }
}

「text=Axisで動的実行, num=222」が出力されることを確認。

エンジンを入れ替える

Axis では HTTP1.0 で SOAP リクエストを送信しているが、エンジンを入れ替えることで HTTP1.1 での送信が可能になる。
CommonsHTTPSender を使用する場合、commons-httpclientcommons-codec をパスに通す必要がある。

import java.rmi.RemoteException;
import java.util.Hashtable;

import javax.xml.rpc.ServiceException;

import org.apache.axis.AxisEngine;
import org.apache.axis.SimpleTargetedChain;
import org.apache.axis.client.AxisClient;
import org.apache.axis.configuration.SimpleProvider;
import org.apache.axis.transport.http.CommonsHTTPSender;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.transport.http.HTTPTransport;

import com.zaneli.www.ZaneliWS;
import com.zaneli.www.ZaneliWSLocator;
import com.zaneli.www.ZaneliWSSoap;

public class AxisExecutor1 {

 public String execute(String text, int num) throws ServiceException, RemoteException {
  ZaneliWS service = new ZaneliWSLocator();
  ((org.apache.axis.client.Service) service).setEngine(getEngine());
  ZaneliWSSoap port = service.getZaneliWSSoap();
  return port.getMessage(text, num);
 }

 private AxisEngine getEngine() {
  CommonsHTTPSender sender = new CommonsHTTPSender();
  SimpleProvider clientConfig = new SimpleProvider();
  clientConfig.deployTransport(
    HTTPTransport.DEFAULT_TRANSPORT_NAME,
    new SimpleTargetedChain(sender));
  AxisClient engine = new AxisClient(clientConfig);
  engine.setOption(HTTPConstants.REQUEST_HEADERS, getChunkedOffHeader());
  return engine;
 }

 private Hashtable<String, Boolean> getChunkedOffHeader() {
  Hashtable<String, Boolean> httpHeaders = new Hashtable<String, Boolean>();
  httpHeaders.put(HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED, false);
  return httpHeaders;
 }

 public static void main(String[] args) throws ServiceException, RemoteException {
  System.out.println(new AxisExecutor1().execute("Axisで実行", 111));
 }
}
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.Hashtable;

import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ServiceException;

import org.apache.axis.AxisEngine;
import org.apache.axis.SimpleTargetedChain;
import org.apache.axis.client.AxisClient;
import org.apache.axis.client.Service;
import org.apache.axis.configuration.SimpleProvider;
import org.apache.axis.transport.http.CommonsHTTPSender;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.transport.http.HTTPTransport;

public class AxisExecutor2 {

 private static final String WSDL_URI = "http://localhost:49175/WS/ZaneliWS.asmx?WSDL";
 private static final String NAMESPACE = "http://www.zaneli.com/";
 private static final QName SERVICE_NAME = new QName(NAMESPACE, "ZaneliWS");
 private static final QName PORT_NAME = new QName(NAMESPACE, "ZaneliWSSoap");
 private static final String OPERATION_NAME = "GetMessage";

 public String execute(String text, int num) throws MalformedURLException, ServiceException, RemoteException {
  Service service = new Service(new URL(WSDL_URI), SERVICE_NAME);
  service.setEngine(getEngine());
  Call call = service.createCall(PORT_NAME, OPERATION_NAME);
  call.setProperty(HTTPConstants.REQUEST_HEADERS, getChunkedOffHeader());
  return (String) call.invoke(new Object[]{text, num});
 }

 private AxisEngine getEngine() {
  SimpleProvider clientConfig = new SimpleProvider();
  clientConfig.deployTransport(
    HTTPTransport.DEFAULT_TRANSPORT_NAME,
    new SimpleTargetedChain(new CommonsHTTPSender()));
  return new AxisClient(clientConfig);
 }

 private Hashtable<String, Boolean> getChunkedOffHeader() {
  Hashtable<String, Boolean> httpHeaders = new Hashtable<String, Boolean>();
  httpHeaders.put(HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED, false);
  return httpHeaders;
 }

 public static void main(String[] args) throws ServiceException, RemoteException, MalformedURLException {
  System.out.println(new AxisExecutor2().execute("Axisで動的実行", 222));
 }
}

getEngine() で CommonsHTTPSender を設定するようにしている。
getChunkedOffHeader() で何をやっているのかは次回 Axis2 編で触れる予定。

WS-Securityに対応する

Apache WSS4Jを使用し、エンジン設定の際に

 private AxisEngine getEngine() {
  CommonsHTTPSender sender = new CommonsHTTPSender();
  SimpleProvider clientConfig = new SimpleProvider();
  clientConfig.deployTransport(
    HTTPTransport.DEFAULT_TRANSPORT_NAME,
    new SimpleTargetedChain(sender));
  AxisClient engine = new AxisClient(clientConfig);
  engine.setOption(HTTPConstants.REQUEST_HEADERS, getChunkedOffHeader());
  clientConfig.setGlobalRequest(getSecuritySender());
  return engine;
 }

と指定すれば設定可能。
getSecuritySender() の詳細は今後気が向けばまとめよう。