🫧

Savon 2.13 or later + Workday SOAP API が壊れる

2023/05/24に公開

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>

https://community.workday.com/sites/default/files/file-hosting/productionapi/Human_Resources/v40.1/Get_Workers.html

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。

このコードが追加された修正はこちら。

https://github.com/savonrb/savon/pull/943
https://github.com/savonrb/savon/issues/224

最近追加された仕様なんですね。確かに複雑な WSDL の Namespace を全部読みたいというのは分からないでもない。SOAP とか WSDL とか詳しくないけど、なんとなくそういうユースケースは想像できる。しかし OptOut できるようにしててくれ。Whitelist 式でもいい。

最終的に生まれた XML を Nokogiri して不要な xmlns を削除しても良いのかもしれない。と思ったけどフックするポイントがなかった。Savon 基本的にちゃんと private にしてくれているのでいじれなくて悲しい。

Discussion