Node.js + ExpressでSOAP通信するAPIを作ろう

#🔶はじめに
最初は[talend](https://jp.talend.com/)を使って、SOAP⇔REST変換するAPIを作ろうと思ったのですが、難しそうだったのでNode.jsのモジュールの[node-soap](https://github.com/vpulim/node-soap)を使って、SOAP通信を行うREST APIを作ることにしました。
##🔷node-soap
SOAP通信を行うモジュールです。
WSDLファイルを読み込んで、SOAP通信を行うクライアントを作れます。
[ドキュメント](https://github.com/vpulim/node-soap/blob/master/Readme.md)を読む限り、SOAPサーバーも作成できるらしいです。

node-soap
https://github.com/vpulim/node-soap

#🔶実装
まず、expressでスケルトンを作り、[node-soap](https://github.com/vpulim/node-soap)をインストールします。
スケルトンの作り方は以下を参照。  
[Node.js + ExpressでREST API開発を体験しよう for Windows［準備編］] 
 (https://qiita.com/tamura_D/items/98bbd4a3143da960faf5#-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%81%AE%E7%94%9F%E6%88%90)

routerフォルダ配下に、test.jsファイルを作り、app.jsにパス等を記載します。

```javascript:test.js
const express = require('express');
const router = express.Router();
const soap = require('soap');
const wsdlUri = 'http://hogehoge/fuga?wsdl'; // WSDLファイルのURL

/* SOAPサービスに渡すパラメータを作成 */
let args = {
  param01: 'hoge',
  param02: 'fuga',
  param03: 'piyo'
};

router.get('/', function(req, res, next) {

  /* WSDLからSOAPクライアントを作成 */
  soap.createClient(wsdlUri, {}, function(err, client) {

    /* SOAPサービスの定義の確認 */
    let describe = client.describe();
    console.log(`🔴🔴describe🔴🔴\n${JSON.stringify(describe, replacer)}`);

    /* リクエスト時にSOAPヘッダーのセット */
    client.addSoapHeader({
      HeaderName: {
        testSoapHeader: 'hogehoge'
      }
    });

    /* リクエスト時にHTTPヘッダーのセット */
    client.addHttpHeader('x-hoge', 'fuga');

    /* SOAPサービスの指定のメソッドへリクエスト */
    client.HogeFugaPiyo(args, function(err, result, rawResponse, soapHeader, rawRequest) {
      console.log(`😈😈err😈😈\n${err}`);
      console.log(`🔶🔶result🔶🔶\n${JSON.stringify(result)}`);
      console.log(`🔶🔶rawResponse🔶🔶\n${JSON.stringify(rawResponse)}`);
      console.log(`🔶🔶soapHeader🔶🔶\n${JSON.stringify(soapHeader)}`);
      console.log(`🔶🔶rawRequest🔶🔶\n${JSON.stringify(rawRequest)}`);
    });

    /* HTTP通信のリクエスト/レスポンス情報を取得 */
    client.on('response', function(envelope, message) {
      console.log(`🔷🔷message🔷🔷\n${JSON.stringify(message)}`);
      res.send(message);
    });
  });

  /* JSON.stringify()の循環構造対策 */
  let seen = [];
  let replacer = (key, value) => {
    if (value != null && typeof value == "object") {
      if (seen.indexOf(value) >= 0) {
        return;
      }
      seen.push(value);
    }
    return value;
  };
});

module.exports = router;

```

#🔶解説

##🔷SOAPクライアントを作成
WSDLファイルのURLを渡して、SOAP通信を行うクライアントを作成します。
[ドキュメント](https://github.com/vpulim/node-soap/blob/master/Readme.md)には、WSDLファイルはローカルファイルでも良いと書かれています。

```js
  /* WSDLからSOAPクライアントを作成 */
  soap.createClient(wsdlUri, {}, function(err, client) {
```

##🔷SOAPサービスの定義を確認
`client.describe()`で、WSDLに書かれた定義情報を、JSONで出力します。

```js

    /* SOAPサービスの定義の確認 */
    let describe = client.describe();
```

##🔷SOAP通信時のSOAPヘッダーをセット
`client.addSoapHeader()`で、SOAPヘッダーをセットできます。

```js
    /* リクエスト時にSOAPヘッダーのセット */
    client.addSoapHeader({
      HeaderName: {
        testSoapHeader: 'hogehoge'
      }
    });
```

上記の記載で、以下のようにSOAPヘッダーがセットされます。

```xml
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://hogehoge/fuga" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/">
  <soap:Header>
    <HeaderName>
      <testSoapHeader>hogehoge</testSoapHeader>
    </HeaderName>
  </soap:Header>
  <soap:Body>
  ...（以下略）
```
##🔷SOAP通信時のHTTPヘッダーをセット
SOAP通信は、実際にはHTTP通信のPOSTメソッドで行われるが、`client.addHttpHeader()`で、その際のHTTPヘッダーをセットします。

```js
    /* リクエスト時にHTTPヘッダーのセット */
    client.addHttpHeader('x-hoge', 'fuga');
```
##🔷SOAPサービスの任意のメソッドをリクエスト
`client.任意のメソッド名(args, function(err, result, rawResponse, soapHeader, rawRequest)`で、SOAPサービスのメソッドにリクエストを送ります。
リクエスト結果は、以下のように渡されます。

- result
  - SOAPサービスからのレスポンス本文
- rawRespons
  - SOAPサービスからのレスポンスとして渡されるXML
- soapHeader
  - SOAPサービスからの、レスポンスのSOAPヘッダー
- rawRequest
  - SOAPサービスへのリクエスト時に渡すXML

下の例では、HogeFugaPiyoメソッドにリクエストを投げています。

```js
    /* SOAPサービスの指定のメソッドへリクエスト */
    client.HogeFugaPiyo(args, function(err, result, rawResponse, soapHeader, rawRequest) {
      console.log(`😈😈err😈😈\n${err}`);
      console.log(`🔶🔶result🔶🔶\n${JSON.stringify(result)}`);
      console.log(`🔶🔶rawResponse🔶🔶\n${JSON.stringify(rawResponse)}`);
      console.log(`🔶🔶soapHeader🔶🔶\n${JSON.stringify(soapHeader)}`);
      console.log(`🔶🔶rawRequest🔶🔶\n${JSON.stringify(rawRequest)}`);
    });
```

##🔷HTTP通信のリクエスト/レスポンス情報を取得
`client.on('response', function(envelope, message)`で、HTTP通信の全ての情報を、`message`に出力します。

```js

    /* HTTP通信のリクエスト/レスポンス情報を取得 */
    client.on('response', function(envelope, message) {
      console.log(`🔷🔷message🔷🔷\n${JSON.stringify(message)}`);
      res.send(message);
    });
```
例えば、SOAPサービスからのレスポンス時に渡される、Cookie情報を取得したい場合は、`message.headers['set-cookie']`で取得できます。

##🔷未解決
今回は`createClient`という同期処理のメソッドを使いましたが、`createClientAsync`という非同期処理のメソッドもあります。
しかし、試してみたところ`TypeError: callback is not a function`が出て、うまく実装できませんでした。
一応、調べてみると、色々なQ＆Aサイトに同様の質問は上がってましたが、どこも解決方法は書かれていませんでしたので、今回は使わないので諦めました。

#🔶まとめ
このように、[node-soap](https://github.com/vpulim/node-soap)を使えば、「SOAP通信はXMLのやりとりで処理を行う」くらいの知識しかなくても、簡単にSOAP通信を行うREST APIを作る事ができます。

今回のサンプルは、node-soapの機能の一部です。[ドキュメント](https://github.com/vpulim/node-soap/blob/master/Readme.md)を見ると、他にも色々と機能や設定があるようなので、試してみてはいかがでしょうか。

#🔶参考
-  [Node.jsでSOAP通信する際の細かい設定](https://qiita.com/dhythm/items/6856446c922dcc2a2296)
-  [nodejsでSOAP通信する](https://qiita.com/kuniken/items/e82514922f72b9c3db4f)
-  [NodeJS - How to get the HTTP header of a SOAP webservice's response](https://stackoverflow.com/questions/38709527/nodejs-how-to-get-the-http-header-of-a-soap-webservices-response)