先日、SOAPを使ったAPIサーバを構築する機会がありSOAP1.1と1.2の違いやWSDLを使用したSOAPクライアントの挙動でハマったのでメモ。
最近のWebAPIといえばほとんどがRESTで、SOAPとRESTのIFが用意されていれば殆どの場合RESTを使うことが多いと思います。 自分自身もSOAPなんて名前を聞くのも久々で仕様をいまいち理解していなかったこともあり手間取ってしまいました。
ハマったポイントはSOAP1.1と1.2の仕様の違い、特にWSDLでsoapActionを指定した場合のSOAPクライアントの挙動の違いの部分。
SOAPにおけるWSDL
サービス利用者側でSOAPクライアントを作成する場合、Webサービスのインタフェース仕様を記述するための規格であるWSDLを作成しておくと、利用者側はアプリやツールでWSDLを読み込みSOAPメッセージを自動生成することができます。 今回使用したWSDLの構造は簡略化すると下のようになります。
<definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://example.com/api/" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://example.com/api/">
<wsdl:types>
<xsi:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/api/" elementFormDefault="qualified">
<xsi:element name="InterfaceA">
<xsi:complexType>
<xsi:sequence>
<xsi:element name="paramA" type="xsi:string" />
<xsi:element name="paramB" type="xsi:string" />
</xsi:sequence>
</xsi:complexType>
</xsi:element>
<xsi:element name="InterfaceB">
...
</xsi:element>
</xsi:schema>
</wsdl:types>
<wsdl:message />
<wsdl:portType>
<wsdl:operation name="InterfaceA">
<input message=""/>
<output message=""/>
</wsdl:operation>
<wsdl:operation name="InterfaceB">
<input message=""/>
<output message=""/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding>
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="InterfaceA">
<soap:operation soapAction="http://example.com/api/InterfaceA" style="document"/>
</operation>
<operation name="InterfaceB">
<soap:operation soapAction="http://example.com/api/InterfaceB" style="document"/>
</operation>
</wsdl:binding>
</wsdl:definitions>
このWSDLではInterfaceAとInterfaceBという2つのインターフェースが定義されています。
各インターフェースのデータ送信先を指定しているのがwsdl:binding>operation>soap:operationの部分。
soapActionでアクションを指定した場合の挙動
SOAPメッセージをHTTPで送信する場合、soapActionでインターフェース毎の値を指定したらPOST先が変わるものだと思っていました。
上記のWSDLを使用した場合、
InterfaceAはPOST先がhttp://example.com/api/InterfaceA
InterfaceBはPOST先がhttp://example.com/api/InterfaceB
になるイメージ。
ところが実際にSOAPメッセージを送信してみると全インターフェース共にPOST先が http://example.com/api/ と同じになってしまい、Not Foundになってしまいました。
この挙動が理解できずに数時間悩んでいてふとWiresharkでパケットキャプチャしてみてようやく気づいた。 soapActionで指定した場合、HTTPリクエストヘッダの別のパラメータが変化するじゃん!
が、ここがちょっとややこしくてSOAP1.1とSOAP1.2ではSOAPメッセージのプロトコルバインディングヘッダの内容が異なるようです。
SOAP1.1の場合
POST /api/ HTTP/1.1
Content-Type: text/xml; Charset=UTF-8
SOAPAction: “http://example.com/api/InterfaceA”
SOAP1.2の場合
POST /api/ HTTP/1.1
Content-Type: application/soap+xml; charset=utf-8; action=”http://dev-amt.dev.konaminet.jp/amt/api/public/InformPublicFrame”
SOAP1.1ではSOAPActionというパラメータが作成されるが、SOAP1.2ではContent-Typeのaction属性に指定される。 なのでSOAP API側を実装する場合SOAPのバージョンに応じて見るヘッダのパラメータが違ってくるという面倒な仕様。
今回の案件はSOAP1.1での構築だったためHTTPリクエストのSOAPActionヘッダを取得して処理を分岐するように実装することで解決。
最後に
最初にも書きましたが最近はREST全盛期で、B2B以外の一般のWeb開発者向けに公開されているAPIでSOAPを使っているというのはあまり聞かない気がします。
とはいえB2BのEDIなどではまだまだ使われていくと思います。 今後SOAP1.2が主流になっていくと思いますが、1.1と1.2の仕様の違いは抑えておきたいなあと。
おしまい。