Savon 2.13 or later + Workday SOAP API が壊れる
Workday の SOAP API を Savon で叩くと (SOAP-ENV:Client.validationError) Validation error occurred. Namespace not found: urn:com.workday/bsvc/Xxx
というエラーが出ます。
Savon は WSDL に入っている namespaces を全部ルートエレメントに xmlns 属性として追加するような仕様になっています。Workday の SOAP API は解決できない Namespace が xmlns として指定されているとバリデーションエラーを発します(前述のエラー)。このかみ合わせによって死にます。
Workday の SOAP API に実際に送信する HTTP Request Body のサンプルは以下のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:wsdl="urn:com.workday/bsvc" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
<wsse:Username>{username}@{tenant}</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">{password}</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</env:Header>
<env:Body>
<wsdl:Get_Workers_Request wsdl:version="v27.2">
<wsdl:Request_References>
<wsdl:Worker_Reference>
<wsdl:ID wsdl:type="Employee_ID">{id}</wsdl:ID>
</wsdl:Worker_Reference>
</wsdl:Request_References>
</wsdl:Get_Workers_Request>
</env:Body>
</env:Envelope>
Workday の SOAP API を触るのに必要な Namespace は xmlns:wsdl="urn:com.workday/bsvc"
のみです。Savon を使うと沢山の Namespace が追加されてしまいます。
解決策は Savon を使わないことです。SOAP API はリクエストが巨大なので、Savon で組み立てるより HTTP Request Body のテンプレートを準備しておいてデータを注入したほうが楽です。ただ、パラメータの在不在が切り替わるとか、真に動的にリクエストを投げたいという場合は、Savon を使う価値もあるかもしれません。その場合でも、なんらかの Builder を書いておくと便利そうです。
さて、Savon のコードをもう少し掘り下げてみます。
- HTTP Request Body を組み立てているのは Savon::Builder
- Savon::Builder のメンバである @wsdl は Wasabi::Document のインスタンス
- Savon::Builder#namespaces は @wsdl.parser.namespaces を参照している
- @wsdl.parser は Wasabi::Parser のインスタンスで、@namespaces は parse_namespaces で作られている
- parse_namespaces で使われている @document は Nokogiri::XML::Document
- @document.namespaces は @document.root.namespaces と同じ
最終的に Nokogiri になりましたね。それはそう。みんな大好き Nokogiri 。
require 'nokogiri'
require 'uri'
wsdl = Nokogiri::XML(URI.open('https://wd3-services1.myworkday.com/ccx/service/cookpad/Human_Resources/v40.1?wsdl'))
puts wsdl.root.namespaces
結果がこれ。
{"xmlns:wsdl"=>"http://schemas.xmlsoap.org/wsdl/",
"xmlns:wd-wsdl"=>"urn:com.workday/bsvc/Human_Resources",
"xmlns:wd"=>"urn:com.workday/bsvc",
"xmlns:nyw"=>"urn:com.netyourwork/aod",
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
"xmlns:soapbind"=>"http://schemas.xmlsoap.org/wsdl/soap/",
"xmlns:httpbind"=>"http://schemas.xmlsoap.org/wsdl/http/",
"xmlns:mimebind"=>"http://schemas.xmlsoap.org/wsdl/mime/"}
これ全部が追加されるわけです。OptOut できないのかよ Savon。
このコードが追加された修正はこちら。
最近追加された仕様なんですね。確かに複雑な WSDL の Namespace を全部読みたいというのは分からないでもない。SOAP とか WSDL とか詳しくないけど、なんとなくそういうユースケースは想像できる。しかし OptOut できるようにしててくれ。Whitelist 式でもいい。
最終的に生まれた XML を Nokogiri して不要な xmlns を削除しても良いのかもしれない。と思ったけどフックするポイントがなかった。Savon 基本的にちゃんと private にしてくれているのでいじれなくて悲しい。
Discussion