Python + Zeepにて、SOAPのinline(Base64エンコード)でファイルを送信する

SOAPでファイルを送信する方法を調べたところ、5つほど見つかりました。

  1. inline (Base64 エンコード)
  2. SOAP with Attachments (SwA)
  3. swaRef
  4. wsi:swaRef
  5. MTOM

参考:SOAP Attachments and Files | SoapUI

 
Zeepではどれが実装されているかを調べたところ、elementに直接送信ファイルを埋め込む形であるinlineはあるものの、他は実装されていなさそうでした。

ただ、MTOMについては、issueやPRがありました。

また、以下のcommitが取り込まれているため、将来的にはサポートされるかもしれません。
https://github.com/mvantellingen/python-zeep/commit/e346e91d6d8b0a37a84ab2bd9b423c25ddfd88f1#diff-40cce0d06cb2448df518c74200e882ae

 
そこで今回は、inline (Base64エンコード) でのファイル送信を試してみました。

なお、前提は以下です。

 
目次

 

環境

 

WSDLでの実装

inlineでデータを送信する場合、型(type)として xsd:base64Binary を使った定義を用意します。

<!-- xmlns:xsd="http://www.w3.org/2001/XMLSchema" -->
<xsd:element name="RequestInterface">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element minOccurs="0" name="image" type="xsd:base64Binary" />
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>

 
残りの部分は、以前作成したものと同じです。
Python + Zeep + SOAP UI + 自作WSDLで、SOAPのリクエストとレスポンスを試してみた - メモ的な思考的な

全体像はこんな感じです。

inline.wsdl

<?xml version="1.0" encoding="UTF-8"?>
<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>
        <xsd:schema elementFormDefault="qualified" targetNamespace="http://example.com/HelloWorld">
            <xsd:element name="RequestInterface">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element minOccurs="0" name="image" type="xsd:base64Binary" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <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:part name="parameters" element="ns0:RequestInterface" />
    </wsdl:message>
    <wsdl:message name="messageOut">
        <wsdl:part name="parameters" element="ns0:ResponseInterface" />
    </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 name="InlineAttachmentBindingSoap11" 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 name="HelloService">
        <wsdl:port name="InlineAttachmentServicePort" binding="ns0:InlineAttachmentBindingSoap11">
            <soap11:address location="http://localhost:9400/inlineAttachmentBindingSoap11"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

 

Zeepでの実装

Zeepでは、以下の流れで実装すれば良さそうです。

コードだとこんな感じです。

# バイナリ形式でファイルを読み込み
with ATTACHMENT.open(mode='rb') as f:
    attachment_data = f.read()

# ファイルをBase64エンコード
encoded_data = base64.b64encode(attachment_data)

client = Client(str(WSDL))

# ZeepクライアントにBase64エンコードしたデータを渡す
# この場合、上記で定義した `image` elementに渡している
response = client.service.requestMessage(image=encoded_data)

 

動作確認

SOAP UIにて動作確認します。

そこで、SOAP UIのMockServiceをSOAPサーバとして動作させます。

  • SOAP UIにて、上記の inline.wsdl を指定してプロジェクトを作成します。
  • エンドポイント inlineAttachmentBindingSoap11 を右クリックし、 Generate SOAP Mock Service を選択
  • Generate MockService ダイアログでは、以下を指定
    • Pathに、WSDLと同じ /inlineAttachmentBindingSoap11
    • Portに、WSDLと同じ 9400
    • Starts the MockService immediately にチェックを入れ、MockServiceを起動
  • Generate SOAP MockService ダイアログでは、以下を指定
    • Specify name of MockService to createは、デフォルト値
  • MockServiceダイアログが表示されるため、実行ボタンを押す
    • 右側に run on port 9400 が表示されればOK

 
あとは、Zeepで作成したSOAPクライアントを実行します。

なお、送信時のSOAPエンベロープの内容を確認するため、History pluginを使います。
Python + Zeep + History pluginで、SOAPのリクエストとレスポンスを確認してみた - メモ的な思考的な

 
全体像は以下の通りです。

import base64
import pathlib

from lxml import etree
from zeep import Client
from zeep.plugins import HistoryPlugin

BASE_PATH = pathlib.Path(__file__).resolve().parents[0]
WSDL = BASE_PATH.joinpath('inline.wsdl')
ATTACHMENT = BASE_PATH.joinpath('shinanogold.png')


def run():
    with ATTACHMENT.open(mode='rb') as f:
        attachment_data = f.read()
    encoded_data = base64.b64encode(attachment_data)

    history_plugin = HistoryPlugin()
    client = Client(str(WSDL), plugins=[history_plugin])
    response = client.service.requestMessage(image=encoded_data)

    print('--- request body ---')
    print(etree.tostring(history_plugin.last_sent['envelope'], pretty_print=True, encoding='unicode'))

    print('--- response ---')
    print(response)


if __name__ == '__main__':
    run()

 
上記のPythonスクリプトを実行したところ、image要素に画像のBase64エンコードデータが含まれて送信されました(スペースの関係上、途中は省略しました)。

responseで ? が返ってきていますが、SOAP UIで何も設定していないため、デフォルトのレスポンス値の ? となっています。

$ python inline_runner.py 
--- request body ---
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld">
    
      <!-- Base64エンコードした画像データ(途中省略) -->
      <ns0:image>aVZ...PT0=</ns0:image>
      
    </ns0:RequestInterface>
  </soap-env:Body>
</soap-env:Envelope>

--- response ---
?

 
MockServiceダイアログを見ると、アクセスした時のログが追加されているため、ダブルクリックで表示します。

Request Messageタブを見ると、Zeepからリクエストした時のデータが表示されていました。

f:id:thinkAmi:20190101115607p:plain:w300

上記の内容を以下に転記しますが、History pluginの内容と一致しているため、うまく送信できたと考えられました。

<?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:image>aVZ...PT0=</ns0:image></ns0:RequestInterface></soap-env:Body></soap-env:Envelope>

 
本当に一致しているかはBase64デコードして確認する必要がありますが、今回は省略します。

 

ソースコード

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