Node-RED: joinノードでタイムアウト処理

# はじめに
Node-REDで通信をする際のタイムアウト処理は、ユーザディレクトリ（~/.node-red）のsetting.jsで変更することができます。例えば、httpリクエストのタイムアウトは、httpRequestTimeoutで変更できます。

しかし、全体の処理時間に制限がある場合など、呼び出し先などによって変更したい場合は対応できません。オーソドックスな方法はファンクションノードで頑張ることかもしれませんが、それではデバッグノードで簡単にデバッグするというNode-REDらしい開発ができません。そこで、標準ノードであるjoinノードを使って、呼び出しの際のタイムアウトを処理する方法を考えてみました。

# joinノード
joinノードはsplitノードと組み合わされることが多く、配列などをsplitノードで分割し、それぞれを非同期に処理した後にjoinノードでまとめるという使い方をされます。実はjoinノードは単体でも利用可能で、複数のmsgオブジェクトをタイムアウト時間内にまとめて出力することができます。これを利用するわけです。

具体的には、httpリクエストの直前でmsgオブジェクトをリクエスト処理用とタイムアウト処理用に作成して利用します。joinノードでは、まとめる単位ごとに共通のidが必要で、これにはmsg._msgidを使っています。また、総数のcount、それぞれのmsgオブジェクトを識別するindexが必要です。

joinノードでは結合対象のプロパティ以外には破壊的ですので、結合対象のプロパティに保存しておきたいデータをコピーしておきます。

# フロー
実際のフローが下記になります。テスト用サーバーがあるので複雑に見えますが、主要な処理は上部の7つほどのノードだけです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/147007/feb681de-c726-7a96-cd1f-5ca1e0608a97.png)
ポイントは、joinの前に必要なプロパティをセットすることです。また、タイムアウトの際にはチェック用のメッセージが流れた後でhttpのレスポンスが返ってきますのでこれを捨てる必要があることです。

上部のcatchノードはタイムアウトの際に例外が発生するのでつけていますが、この辺はお好みで変更してください。

# 注意点
更新処理の場合、クライアント側でタイムアウトしてもサーバで処理が行われる可能性がありますので注意してください。

# コード
下記をインポート（読み込み）して使ってください。
```[{"id":"75ee1c7.c0e5464","type":"join","z":"967c5dd7.dee1a","name":"","mode":"custom","build":"merged","property":"data","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"3","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":610,"y":220,"wires":[["61b5c8ec.15b63"]]},{"id":"a63fd121.9be3","type":"debug","z":"967c5dd7.dee1a","name":"時間内","active":true,"console":false,"complete":"true","x":770,"y":260,"wires":[]},{"id":"4d768f00.8ead18","type":"function","z":"967c5dd7.dee1a","name":"チェック用msg作成","func":"let ret1 = {};\nlet ret2 = {};\nret1.id = ret2.id = msg._msgid;\n// ret1.backup = msg.payload;\nret2.data = {backup: msg.payload};\n\nreturn [ret1, ret2];","outputs":2,"noerr":0,"x":160,"y":200,"wires":[["d5f777c.c4eba88"],["b07e9ae5.b5df28"]]},{"id":"61b5c8ec.15b63","type":"function","z":"967c5dd7.dee1a","name":"タイムアウト判定","func":"if (msg.data.req && msg.data.check ) {\n    return [msg.data, null]; // OK;\n}\nelse if (msg.data.check ) {\n    return [null, msg.data]; // timeOut\n} // ret1.data.req のみは捨てる \n","outputs":2,"noerr":0,"x":590,"y":280,"wires":[["a63fd121.9be3"],["3e7cdc8a.0b0f14"]]},{"id":"95a03086.c8a628","type":"debug","z":"967c5dd7.dee1a","name":"タイムアウト","active":true,"console":false,"complete":"true","x":800,"y":340,"wires":[]},{"id":"acf1e79b.8e7a18","type":"function","z":"967c5dd7.dee1a","name":"Join用リクエストmsg","func":"let ret1 = {};\nret1.data =  msg;\nret1.data.req = true;\nret1.count = 2;\nret1.index = 0;\nret1.id = msg.id;\nret1.complete = true;\n        // ch - 文字列またはバッファの場合、メッセージを文字列またはバイトの配列として分割するために使用されるデータ\n        // len - 固定長の値を使って分割したときの各メッセージの長さ\n\nreturn ret1;","outputs":1,"noerr":0,"x":420,"y":160,"wires":[["75ee1c7.c0e5464"]]},{"id":"d5f777c.c4eba88","type":"http request","z":"967c5dd7.dee1a","name":"","method":"GET","ret":"txt","url":"http://localhost:1880/test","tls":"","x":370,"y":100,"wires":[["acf1e79b.8e7a18"]]},{"id":"3e7cdc8a.0b0f14","type":"change","z":"967c5dd7.dee1a","name":"タイムアウト設定","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"Timeout","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"NG","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":340,"wires":[["95a03086.c8a628"]]},{"id":"c6d5d3a3.b1ad4","type":"inject","z":"967c5dd7.dee1a","name":"タイムアウト","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":380,"wires":[["16990334.237f15"]]},{"id":"ae91ea01.fc484","type":"inject","z":"967c5dd7.dee1a","name":" 正常","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":130,"y":420,"wires":[["67212cfb.acd604"]]},{"id":"16990334.237f15","type":"change","z":"967c5dd7.dee1a","name":"","rules":[{"t":"set","p":"timeout","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":380,"wires":[["413690a7.c84b7"]]},{"id":"7ccc98fd.be9938","type":"comment","z":"967c5dd7.dee1a","name":"テスト設定","info":"","x":100,"y":340,"wires":[]},{"id":"9e1e9675.3edc1","type":"comment","z":"967c5dd7.dee1a","name":"テストサーバー","info":"","x":120,"y":480,"wires":[]},{"id":"e4a3e329.67a9e","type":"http in","z":"967c5dd7.dee1a","name":"","url":"/test","method":"get","upload":false,"swaggerDoc":"","x":140,"y":520,"wires":[["912e543.afb19a8"]]},{"id":"912e543.afb19a8","type":"switch","z":"967c5dd7.dee1a","name":"timeout==false","property":"timeout","propertyType":"global","rules":[{"t":"false"}],"checkall":"true","repair":false,"outputs":1,"x":330,"y":520,"wires":[["8c956349.51fd8"]]},{"id":"9961a69b.3ac7d","type":"http response","z":"967c5dd7.dee1a","name":"","statusCode":"","headers":{},"x":690,"y":520,"wires":[]},{"id":"67212cfb.acd604","type":"change","z":"967c5dd7.dee1a","name":"","rules":[{"t":"set","p":"timeout","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":420,"wires":[["413690a7.c84b7"]]},{"id":"8c956349.51fd8","type":"change","z":"967c5dd7.dee1a","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"OK","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":520,"wires":[["9961a69b.3ac7d"]]},{"id":"287d1512.5e9a62","type":"inject","z":"967c5dd7.dee1a","name":"","topic":"","payload":"DATA","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[["4d768f00.8ead18"]]},{"id":"b07e9ae5.b5df28","type":"function","z":"967c5dd7.dee1a","name":"join用チェックmsg","func":"msg.count = 2;\nmsg.index = 1;\nmsg.data.check = true;\n\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":220,"wires":[["75ee1c7.c0e5464"]]},{"id":"413690a7.c84b7","type":"debug","z":"967c5dd7.dee1a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":530,"y":400,"wires":[]},{"id":"9fa34a45.09784","type":"catch","z":"967c5dd7.dee1a","name":"","scope":["d5f777c.c4eba88"],"x":360,"y":40,"wires":[["87b6e72b.bf2598"]]},{"id":"87b6e72b.bf2598","type":"debug","z":"967c5dd7.dee1a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","x":540,"y":40,"wires":[]}]
```