今更ですが、Ruby で SOAP をやってみる

いやホント、今更感たっぷりですけど (^^

## SOAP について
かつて WebAPI といえば SOAP という時代がありました。
現在は、JSON形式による REST が主流になっていますが、XML形式によるリモートサーバとのやりとりの一つの形式として注目を集めていました。

データのやりとりをXMLで行うために、通信のデータ量が増えたり、コンピュータで扱う時にパースしたりと、ちょっと手間がかかるので、面倒といえば面倒なんですが、WSDLという書式でデータの型とか決めれるので親切といえば親切な方式なんですけどね。
SOAP(Simple Object Access Protocol) って言うくらいですから。
＃現在は違うらしい。。。

## Savon
で、ネットで探してみると、Savon という gem が見つかりました。
　→ http://savonrb.com
　
というわけで、使ってみる。

```
$ gem install savon
```

### テスト用 WSDL を作る
　→ http://www.wakhok.ac.jp/~sakamoto/webservice/web_service_c3.html
　→ http://itdoc.hitachi.co.jp/manuals/link/cosmi_v0870/APWK/EU310265.HTM#ID00735

一般的な感じで作って見ます (^^

```wsdl:test.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
  targetNamespace="https://web-service"

  xmlns:impl="https://web-service"
  xmlns:intf="https://web-service"
  xmlns:tns="https://hoge.web-service"

  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:apachesoap="http://xml.apache.org/xml-soap"    
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    
  <wsdl:types>
    <schema targetNamespace="https://hoge.web-service"
            xmlns="http://www.w3.org/2001/XMLSchema">
      <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
      <complexType name="UserInfo">
        <sequence>
          <element name="name" nillable="false" type="xsd:string"/>
          <element name="age" type="xsd:int"/>
          <element name="birthday" nillable="true" type="xsd:string"/>
          <element name="favorite" nillable="true" type="xsd:string"/>
        </sequence>
      </complexType>
    </schema>
  </wsdl:types>

  <wsdl:message name="getInfoRequest">
    <wsdl:part name="username" type="xsd:string"/>
    <wsdl:part name="password" type="xsd:string"/>
  </wsdl:message>  
  <wsdl:message name="getInfoResponse">
    <wsdl:part name="userinfo" type="tns:UserInfo"/>
  </wsdl:message>

  <wsdl:portType name="TestService">
    <wsdl:operation name="getInfo" parameterOrder="username password">
      <wsdl:input message="impl:getInfoRequest" name="getInfoRequest"/>
      <wsdl:output message="impl:getInfoResponse" name="getInfoResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  
  <wsdl:binding name="TestServiceSoapBinding" type="impl:TestService">
    <wsdlsoap:binding style="rpc"
		      transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getInfo">
      <wsdlsoap:operation soapAction=""/>
      <wsdl:input name="getInfoRequest">
        <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
		       namespace="https://web-service" use="encoded"/>
      </wsdl:input>
      <wsdl:output name="getInfoResponse">
        <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
		       namespace="https://web-service" use="encoded"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>

  <wsdl:service name="TestServiceService">
    <wsdl:port binding="impl:TestServiceSoapBinding" name="TestService">
      <wsdlsoap:address
	  location="https://web-service/test_service"/>
    </wsdl:port>
  </wsdl:service>
  
</wsdl:definitions>
```

先頭の ```xmlns:xxxx``` はおまじない的ななにか (^^
前半は 各WebService に合わせて変更します。後半はおまじない (^^ 

```<wsdl:types>``` は任意の型の定義。
ここでは、UserInfo という型を定義しています。
UserInfo には name（string型） と age（int型） と birthday（string型） と favorite（string型） があります。

```<wsdl:message>``` は 構造を定義します。
\<wsdl:types\> と同じ感じですね。
getInfoRequest には username（string型） と password（string型） があって、
getInfoResponse には、先ほど上で定義した userinfo（UserInfo型） がある、と定義しています。

```<wsdl:portType>``` は 入出力の操作の組み合わせを定義します。
getInfo という 操作（operation） には 入力（input）に getInfoRequest、出力（output）には getInfoResponse があるよ、と定義しています。

```<wsdl:binding>``` は portType で定義した組み合わせの具体的な定義をします。
getInfoRequest や getInfoResponse が先頭で定義したどの WebService の範囲にあるのか、とかとか。

```<wsdl:service>``` は 上で定義した binding が実際にどのアドレスにアクセスするのかを定義します。
location がそれですね。

### ruby でやってみる

```
$ irb
```

```
> require "savon"
> client = Savon.client(:wsdl => "test.wsdl")
```
エラーがなければとりあえず読み込みは成功ということで。

確認してみましょう。

```
> client.operations
=> [:get_info]
```
メソッドが認識されているようです。
実行は以下。

```
> response = client.call(:get_info, :message => { "username" => "hoge", "password" => "tara" })
```
まあエラーになります。WebService がないので。。。

## ConvertTemperature WebService
これは困った、ということで、とりあえず Savon のソースをみてみると、Test がありました。
　→ https://github.com/savonrb/savon/blob/master/spec/integration/temperature_example_spec.rb
　
これをやってみることにします。

```
$ irb

> client = Savon.client(:wsdl => "http://www.webservicex.net/ConvertTemperature.asmx?WSDL")
=> #<Savon::Client:0x007fd535c241a8 @globals=#<Savon::GlobalOptions:0x007fd535c24180 @option_type=:global, @options={:encoding=>"UTF-8", :soap_version=>1, :namespaces=>{}, :logger=>#<Logg ... c1fc20 @document="http://www.webservicex.net/ConvertTemperature.asmx?WSDL", @adapter=nil, @request=#<HTTPI::Request:0x007fd535c1fbd0 @follow_redirect=false>>>

> client.operations
=> [:convert_temp]
```

はい、ConvertTemperature WebService には convert_temp という関数があることがわかりましたが、これだけでは引数とか戻り値とかがわかりません。
ということで、少し詳しく見て見ます。

```
> client.wsdl.operations[:convert_temp]
=> {:action=>"http://www.webserviceX.NET/ConvertTemp", :input=>"ConvertTemp", :output=>"ConvertTempResponse", :namespace_identifier=>"tns", :parameters=>{:Temperature=>{:name=>"Temperature", :type=>"double"}, :FromUnit=>{:name=>"FromUnit", :type=>"TemperatureUnit"}, :ToUnit=>{:name=>"ToUnit", :type=>"TemperatureUnit"}}}
```

なんとなく、見えてきたような気がします。
入力として ConvertTemp があって、戻りは ConvertTempResponse で、（入力の）パラメータには Temperature、FromUnit、ToUnit があるということがわかります。
がしかし、これだけでは詳細がわからないので、やはり WSDL 自体を覗いて見ます。

```wsdl:www.webservicex.net/ConvertTemperature.asmx?WSDL
＜前略＞

<wsdl:types>
  <s:schema elementFormDefault="qualified" targetNamespace="http://www.webserviceX.NET/">
    <s:element name="ConvertTemp">
      <s:complexType>
        <s:sequence>
          <s:element minOccurs="1" maxOccurs="1" name="Temperature" type="s:double"/>
          <s:element minOccurs="1" maxOccurs="1" name="FromUnit" type="tns:TemperatureUnit"/>
          <s:element minOccurs="1" maxOccurs="1" name="ToUnit" type="tns:TemperatureUnit"/>
        </s:sequence>
      </s:complexType>
    </s:element>
    <s:simpleType name="TemperatureUnit">
      <s:restriction base="s:string">
        <s:enumeration value="degreeCelsius"/>
        <s:enumeration value="degreeFahrenheit"/>
        <s:enumeration value="degreeRankine"/>
        <s:enumeration value="degreeReaumur"/>
        <s:enumeration value="kelvin"/>
      </s:restriction>
    </s:simpleType>
    <s:element name="ConvertTempResponse">
      <s:complexType>
        <s:sequence>
          <s:element minOccurs="1" maxOccurs="1" name="ConvertTempResult" type="s:double"/>
        </s:sequence>
      </s:complexType>
    </s:element>
    <s:element name="double" type="s:double"/>
  </s:schema>
</wsdl:types>

＜後略＞
```

というわけで、実行してみます。

```
> response = client.call(:convert_temp, :message => {"Temperature" => 27.0, "FromUnit" => "degreeCelsius", "ToUnit" => "kelvin"})
=> #<Savon::Response:0x007fd535b444e0 @http=#<HTTPI::Response:0x007fd535b44850 @code=200, @headers={"cache-control"=>"private, max-age=0", "content-type"=>"text/xml; charset= ... Celsius", "ToUnit"=>"kelvin"}, :soap_action=>"http://www.webserviceX.NET/ConvertTemp"}>>

> response.body
=> {:convert_temp_response=>{:convert_temp_result=>"300.15", :@xmlns=>"http://www.webserviceX.NET/"}}
```
なんか正しく返ってきているみたいですね。

## とっても素敵
簡単にできました。Savon って素敵！
ネット上を探すのですが、だいたいが 2006年とか2009年とかそれくらいの記事しかなくて、やはり今時 SOAP 使ってるところってないよなぁ、自前で作らんといかんのか、って思ってたのでとても助かりました。ありがとう！

## 認証のある WebSerice にアクセスする場合
上記は普通にアクセスする場合なのですが、https で Basic認証 とか クライアント認証 を行なっているサービスにアクセスする場合は最初のコードが少し違うのでメモしておきます。

### Basic認証の場合
　→ https://qiita.com/niku4i/items/0a262abb4666f834b6af

### クライアント認証の場合
前項のサイトを参考に、savon のソースを覗いてみました。

```
require "openssl"
require "savon"

@client_cert_file = "client.pfx"
@client_password = "client_password"
@ca_cert_file = "ca.pem"
pkcs = OpenSSL::PKCS12.new(File.read(@client_cert_file),
                                     @client_password)

client = Savon.client(:wsdl => "test.wsdl",
                      :ssl_verify_mode => :peer,
                      :ssl_ca_cert_file => @ca_cert_file,
                      :ssl_cert_key => pkcs.key,
                      :ssl_cert => pkcs.certificate)
```

これでいける感じです。

```@client_cert_file``` は クライアント証明書のファイル名
```@client_password``` は クライアント証明書のパスワード
```@ca_cert_file``` は CA局の証明書ファイル
```test.wsdl``` は WSDLファイル（又はアドレス）

## XMLRPC（おまけ）
自作しないとダメかもと思って色々探してたときですが、XMLを使った通信がある、ということだったので、使って見ました。

gem インストールする必要があるみたいなので、インストール

```
$ gem 'xmlrpc'
```

使い方は以下な感じらしい。

```ruby
# -*- coding: utf-8 -*-
require "xmlrpc/client"

client = XMLRPC::Client.new2("http://web-service.server")
result = client.call("method", 1, 2)
```
ネット上ではなんか簡単にかいてあるので、本当に XML になってるかちょっと確認してみました。

```
<?xml version=\"1.0\" ?>
<methodCall>
  <methodName>method</methodName>
  <params>
    <param><value><i4>1</i4></value></param>
    <param><value><i4>2</i4></value></param>
  </params>
</methodCall>
```
嗚呼なってます、なってます。

ですが、どうやら定型なので任意の型は使用できないみたいです。
ちょっとあれですが、定型で使用するなら大丈夫かな？？