前回、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のリクエストとレスポンスを試してみました。
目次
環境
SOAP・WSDLとも他のバージョンがありましたが、Web上に資料の多い上記のバージョンとしました。
なお、Zeep・SOUP UI とも、上記のバージョンでも動作します。
リクエストパラメータなし・レスポンスデータありのSOAP通信
WSDLの作成
WSDLを書いたことがないため、以下の記事を参考に、ステップを追って書いてみることにしました。
WSDL:Webサービスのインターフェイス情報:Webサービスのキホン(4) - @IT
手始めに、まずはパラメータなしのリクエストを送ると Hello, world!
が返ってくるWSDLを書くことにしました。
以降、ざっくりとしたメモ書きです。
wsdl:definitions要素
関係する要素のうち
は定型のため、参考記事をそのまま書きました。
他に
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
は定型wsdl:operation
には、 wsdl:portTypeで定義したメソッド名requestMessage
を指定- 別の値にすると、リクエストがうまくいかない
wsdl:input
とwsdl: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の方にも、左側にリクエストデータが記録されていました。
全体が見えないため、内容を貼っておきます。
<?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:types
と wsdl: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を元にモックを作成します。
今回はリクエストパラメータを取得して返すので、
- 変数の追加
- スクリプトの作成
を行います。
方法については以下が参考になりました。
まずは、レスポンスのXMLに Hello, ${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>
参考資料
- Web Service Definition Language (WSDL)
- WSDL文書が持つ二層構造の前段部:Webサービスのキホン(5) - @IT
- HelloWorld WSDL
- XML/Webサービス入門
- SOAPのWSDLについて調べてみた | もふもふ技術部
- SOAP バインディング要素 (プロジェクトでの HTTP バインディングコンポーネントの使用)
- デベロッパーズコーナー:エンジニアのためのXMLスキーマ講座「基礎4:名前空間とXML Schema-その2」(4) - XML Square
ソースコード
GitHubに上げました。 wsdls_and_client
ディレクトリ以下が今回のものです。
https://github.com/thinkAmi-sandbox/python_zeep-sample