Python + Zeep + SOAP UI + 自作WSDLで、SOAPのリクエストとレスポンスを試してみた

前回、Python + Zeepで、SOAP APIクライアントを作成しました。
Python + Zeep で SOAP API クライアントを作ってみた - メモ的な思考的な

 
そんな中、自分でもWSDLを書いてみたくなりました。

ただ、Zeepを使ってSOAP通信するには、WSDLの他にSOAPサーバが必要です。

何かいいものがないか考えたところ、同僚より SOAP UI を教わりました。SOAP UIにWSDLを食わせるとといろいろと自動生成してくれるとのことです。
The World's Most Popular Automated API Testing Tool | SoapUI

 
そのため、今回は、Python + Zeep + SOAP UI + 自作のWSDLで、SOAPのリクエストとレスポンスを試してみました。

 
目次

 

環境

 
SOAPWSDLとも他のバージョンがありましたが、Web上に資料の多い上記のバージョンとしました。

なお、Zeep・SOUP UI とも、上記のバージョンでも動作します。

 

リクエストパラメータなし・レスポンスデータありのSOAP通信

WSDLの作成

WSDLを書いたことがないため、以下の記事を参考に、ステップを追って書いてみることにしました。
WSDL:Webサービスのインターフェイス情報:Webサービスのキホン(4) - @IT

 
手始めに、まずはパラメータなしのリクエストを送ると Hello, world! が返ってくるWSDLを書くことにしました。

以降、ざっくりとしたメモ書きです。

 

wsdl:definitions要素

関係する要素のうち

  • xmlns:wsdl
  • xmlns:soap11
  • xmlns:http
  • xmlns:mime
  • xmlns:xsd

は定型のため、参考記事をそのまま書きました。

他に

  • xmlns:ns0
    • WSDL内で定義を参照するのに使う
  • targetNamespace

を追加しました。

なお、上記2つは適当な値で良さそうだったため、 http://example.com/HelloWorld をセットしました。

<wsdl:definitions
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:soap11="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
        xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:ns0="http://example.com/HelloWorld"
        targetNamespace="http://example.com/HelloWorld">

 

wsdl:types要素

基本のデータ型は xmlns:xsd に定義されています。

しかし、実際には、基本データ型を組み合わせることが多そうなので、今回は wsdl:types 要素を定義しました。

ResponseInterface型を用意し、その中にはstring型の returnMessage フィールドを用意しました。

なお、wsdl:types要素でも targetNamespace が必要になるため、wsdl:definitions要素と同じ値を設定しました。

<wsdl:types>
    <xsd:schema elementFormDefault="qualified" targetNamespace="http://example.com/HelloWorld">
        <xsd:element name="ResponseInterface">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element minOccurs="0" name="returnMessage" type="xsd:string" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    </xsd:schema>
</wsdl:types>

 

wsdl:message 要素

ここではリクエスト・レスポンスに使われる型を定義します。

今回はレスポンスデータだけがあります。

そこで、messageOutと名付けたmessageに対し、element属性にて先ほど定義したwsdl:types要素を名前空間付きで ns0:ResponseInterface と指定しました。
(以降も、同じファイル内を参照する場合は、名前空間 ns0: を先頭に付けます)

なお、nameについては、慣例的に使われていそうな parameters としました。Zeepのソースコード上には表れない名前なので、何でも良さそうです。

<wsdl:message name="messageIn">
</wsdl:message>
<wsdl:message name="messageOut">
    <wsdl:part name="parameters" element="ns0:ResponseInterface" />
</wsdl:message>

 

wsdl:portType要素

ここではリクエスト時に使われるメソッド名と、その時のリクエスト・レスポンス型を指定するようです。

今回はリクエスト時のメソッド名を requestMessage としました。

message属性については、wsdl:message 要素を参照します。

<wsdl:portType name="HelloPort">
    <wsdl:operation name="requestMessage">
        <wsdl:input message="ns0:messageIn"/>
        <wsdl:output message="ns0:messageOut"/>
    </wsdl:operation>
</wsdl:portType>

 

wsdl:binding要素

今までの定義を取りまとめる要素のようです。今回は以下の内容で設定しました。

  • wsdl:bindingのtype属性にて、 wsdl:port 要素を参照
  • soap11:binding は定型
    • soapAction は適当で良さそう
    • styleは rpcdocument のどちらかだが、今回は wsdl:types 要素で定義した型を使ってリクエスト・レスポンスするため、 document を設定
      • 基本型だけであれば rpc なのかな...
  • wsdl:operation には、 wsdl:portTypeで定義したメソッド名 requestMessage を指定
    • 別の値にすると、リクエストがうまくいかない
  • wsdl:inputwsdl:output については、定型
<wsdl:binding name="HelloBindingSoap11" type="ns0:HelloPort">
    <soap11:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <wsdl:operation name="requestMessage">
        <soap11:operation soapAction="http://example.com/HelloWorld/requestMessage" />
        <wsdl:input>
            <soap11:body use="literal"/>
        </wsdl:input>
        <wsdl:output>
            <soap11:body use="literal"/>
        </wsdl:output>
    </wsdl:operation>
</wsdl:binding>

 

wsdl:service要素

実際にアクセスする時の情報をセットします。

  • binding 属性には、 wsdl:binding のname属性をセット
  • location 属性には、実際にアクセスするURL (今回の場合は、SOAP UIのモックURL) をセット
<wsdl:service name="HelloService">
    <wsdl:port name="HelloServicePort" binding="ns0:HelloBindingSoap11">
        <soap11:address location="http://localhost:8088/mockHelloBindingSoap11"/>
    </wsdl:port>
</wsdl:service>

 

WSDL全体

以上でWSDLが書けたため、全体を載せておきます。

<?xml version="1.0" encoding="UTF-8"?>
<!--
 ns0 は、WSDL内で定義を参照するのに使われる
 targetNamespaceは、とりあえず適当に設定しておく
 -->
<wsdl:definitions
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:soap11="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
        xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:ns0="http://example.com/HelloWorld"
        targetNamespace="http://example.com/HelloWorld">

    <wsdl:types>
        <!-- ここのtargetNamespaceも適当に設定(先ほどのと同じでもOK) -->
        <xsd:schema elementFormDefault="qualified" targetNamespace="http://example.com/HelloWorld">
            <xsd:element name="ResponseInterface">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element minOccurs="0" name="returnMessage" type="xsd:string" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
        </xsd:schema>
    </wsdl:types>

    <wsdl:message name="messageIn">
    </wsdl:message>
    <wsdl:message name="messageOut">
        <!--
        独自定義の型を使用するため、element属性にて上記のelementのname属性をセット
        name属性の値は、慣例的に parameters っぽい(他の名称にしても動作する)
        -->
        <wsdl:part name="parameters" element="ns0:ResponseInterface" />
    </wsdl:message>

    <wsdl:portType name="HelloPort">
        <wsdl:operation name="requestMessage">
            <!-- リクエスト(input)とレスポンス(output)の型を特定するため、上記messageのname属性をセット -->
            <wsdl:input message="ns0:messageIn"/>
            <wsdl:output message="ns0:messageOut"/>
        </wsdl:operation>
    </wsdl:portType>

    <!-- 上記のportTypeを使うため、type属性にはportTypeのname属性をセット -->
    <wsdl:binding name="HelloBindingSoap11" type="ns0:HelloPort">
        <!-- 独自の型定義を使っているため、styleには document をセット -->
        <soap11:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <!-- portTypeの中にあるoperationのnameと同じ値をセット(今回の場合、requestMessage) -->
        <wsdl:operation name="requestMessage">
            <!-- soapAction は適当な値で良さそう -->
            <soap11:operation soapAction="http://example.com/HelloWorld/requestMessage" />
            <wsdl:input>
                <soap11:body use="literal"/>
            </wsdl:input>
            <wsdl:output>
                <soap11:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="HelloService">
        <!-- binding属性には、上記bindingのname属性をセット -->
        <wsdl:port name="HelloServicePort" binding="ns0:HelloBindingSoap11">
            <!-- 実際にアクセスするURL(今回はSOAP UI のモックURL)をセット -->
            <soap11:address location="http://localhost:8088/mockHelloBindingSoap11"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

 

SOAP UI によるモックの作成

モックの作り方については以下が参考になりました。ありがとうございます。
SOAP UIのMock Serviceを使った効率的なWebサービスのテスト - そごうソフトウェア研究所

 
現在の SOAP UIでは、プロジェクト作成時にはモックを作れないようですが、後でコンテキストメニューから追加しました。

 
モックとして返す内容は、以下の通り Hello, world! としました。

<soapenv:Envelope
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:hel="http://example.com/HelloWorld">
   <soapenv:Header/>
   <soapenv:Body>
      <hel:ResponseInterface>
         <!--Optional:-->
         <hel:returnMessage>Hello, world!</hel:returnMessage>
      </hel:ResponseInterface>
   </soapenv:Body>
</soapenv:Envelope>

 

Zeepによる実装

前回試した通り、Zeepで実装します。なお、 get_type() は使わない方の実装としました。

import pathlib

from zeep import Client

WSDL = pathlib.Path.cwd().joinpath('Hello.wsdl')


client = Client(str(WSDL))
response = client.service.requestMessage()

print(type(response))
print(response)

 

動作確認

Pythonスクリプトを実行すると、レスポンスがありました。

$ python run_hello.py
<class 'str'>
Hello, world!

 
SOAP UIの方にも、左側にリクエストデータが記録されていました。

f:id:thinkAmi:20181104174053p:plain:w350

全体が見えないため、内容を貼っておきます。

<?xml version='1.0' encoding='utf-8'?>
<soap-env:Envelope 
    xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body/>
</soap-env:Envelope>

 

リクエストパラメータあり・レスポンスデータありのSOAP通信

続いて、リクエストパラメータがあるバージョンも作ってみます。

ユーザ名を送信すると Hey, <ユーザ名> と返すものを作ってみます。

 

WSDLの作成

変更があるのは、 wsdl:typeswsdl:message です。

<wsdl:types>
    <xsd:schema elementFormDefault="qualified" targetNamespace="http://example.com/HelloWorld">
        <xsd:element name="RequestInterface">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element minOccurs="0" name="userName" type="xsd:string" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
        <xsd:element name="ResponseInterface">
        ...
</wsdl:types>

<wsdl:message name="messageIn">
    <wsdl:part name="parameters" element="ns0:RequestInterface" />
</wsdl:message>
<wsdl:message name="messageOut">
...
</wsdl:message>

あとは同様です。

 

SOAP UIのモックを修正

修正したWSDLを元にモックを作成します。

今回はリクエストパラメータを取得して返すので、

を行います。

方法については以下が参考になりました。

 
まずは、レスポンスのXMLHello, ${userName} を追加します。

<soapenv:Envelope
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:hel="http://example.com/HelloWorld">
   <soapenv:Header/>
   <soapenv:Body>
      <hel:ResponseInterface>
         <!--Optional:-->
         <hel:returnMessage>Hello, ${userName}</hel:returnMessage>
      </hel:ResponseInterface>
   </soapenv:Body>
</soapenv:Envelope>

 
また、Scriptにも以下のGroovyコードを追加します。

def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
def userName = holder.getNodeValue("//*:userName")
requestContext.userName = userName

 

Zeepの実装

引数に userName を追加するだけです。今回は taro をリクエスト値として追加します。

import pathlib

from zeep import Client

WSDL = pathlib.Path.cwd().joinpath('RequestResponse.wsdl')


client = Client(str(WSDL))
response = client.service.requestMessage(userName='taro')

print(type(response))
print(response)

 

実行結果

リクエストとレスポンスが成功しました。

$ python run_request_response.py
<class 'str'>
Hello, taro

 
SOAP UI にもログが記録されていました。

<?xml version='1.0' encoding='utf-8'?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld">
      <ns0:userName>taro</ns0:userName>
    </ns0:RequestInterface>
  </soap-env:Body>
</soap-env:Envelope>

 

参考資料

 

ソースコード

GitHubに上げました。 wsdls_and_client ディレクトリ以下が今回のものです。
https://github.com/thinkAmi-sandbox/python_zeep-sample