node-redで画像をhttp postで中継送信

# 1. はじめに

Node-Redが中継役として、http postされた画像を別サーバにhttp postする方法についてまとめました。
この方法を対向系を作成しながら模索した[記事](https://qiita.com/dzonesasaki/items/a61cd362b3383cafc7e4)は別にしています。

# 2. 全体の概要

想定しているのは、カメラノードからhttp post でアップロードされた画像をラズパイが一旦引き取り、クラウド側へhttp postでアップロードするという構成です。次の図のような構成です。


![diagramOverall.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/e68f2e10-dfeb-4686-1a2f-f2168f3179ce.png)


カメラノードとしてM5Cameraを想定しています。M5Cameraがhttp postする方法は、[こちら](https://qiita.com/dzonesasaki/items/88deb9a2acb8be2d7cfb)に挙げています。 M5CameraはIoTでいうところのエッジですので、いきなりクラウド側に上げる役割を持たせるのは、能力的にも、開発工数的にも、不安があります。その点、ラズパイは、パワーがあり、node-redで開発も容易ですので、フォグコンピューティングとしての役割を持たせるのがとても都合が良いです。

クラウド側も最近は、Node-Redが動くサーバがほとんどです。そうなると、今回の内容はそのままクラウド側にも流用できそうですが、今回は、旧来のCGIを対象として考えてみます。


# 3. フローの構築

## 3.1. 全体

全体のフローは次の通りです。

![01b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/b09bca89-426e-cef6-a1ca-1d1e96dcf8a3.png)


このフローのjsonは次です。（三角マークをクリックして展開して利用してください）

<details><summary>RelayImagehttpPost.json</summary><div>

```json
[
    {
        "id": "46a5b4e9.1028e4",
        "type": "tab",
        "label": "フロー 2",
        "disabled": false,
        "info": ""
    },
    {
        "id": "e4365738.d8df28",
        "type": "http in",
        "z": "46a5b4e9.1028e4",
        "name": "",
        "url": "/mypost01",
        "method": "post",
        "upload": true,
        "swaggerDoc": "",
        "x": 120,
        "y": 120,
        "wires": [
            [
                "39c6a8ed.47b6",
                "99b3be79.f601c8"
            ]
        ]
    },
    {
        "id": "7e14122.70cf86c",
        "type": "http response",
        "z": "46a5b4e9.1028e4",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 460,
        "y": 60,
        "wires": []
    },
    {
        "id": "39c6a8ed.47b6",
        "type": "template",
        "z": "46a5b4e9.1028e4",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "received",
        "output": "str",
        "x": 310,
        "y": 60,
        "wires": [
            [
                "7e14122.70cf86c"
            ]
        ]
    },
    {
        "id": "a06d87ec.8581c8",
        "type": "function",
        "z": "46a5b4e9.1028e4",
        "name": "wrap",
        "func": "//let myb64 = msg.payload.toString('base64');\n\nlet mymsg2={};\n\nmymsg2.headers = {\n    \"Content-Type\": \"multipart/form-data; boundary=------------------------1234567890987654\"\n}\n\nmymsg2.payload = '--------------------------1234567890987654\\r\\n'+\n'Content-Disposition: form-data; name=\"imagefile\"; filename=\"10x10.jpg\"\\r\\n'+\n'Content-Type: image/jpeg\\r\\n'+\n'Content-Transfer-Encoding: base64\\r\\n'+\n'\\r\\n'+\nmsg.payload +\n'\\r\\n'+\n'--------------------------1234567890987654--\\r\\n';\n\n\nlet mymsg1={};\n\nmymsg1.headers = {\n    \"Content-Type\": \"multipart/form-data\"\n}\n\nmymsg1.payload = \n{\n    \"imagefile\": {\n        \"value\": msg.payload,\n        \"options\": {\n            \"filename\": \"FILENAME\"\n        }\n    }\n};\n\nreturn(mymsg1);\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 490,
        "y": 120,
        "wires": [
            [
                "b29cd1d6.842e9"
            ]
        ]
    },
    {
        "id": "b29cd1d6.842e9",
        "type": "http request",
        "z": "46a5b4e9.1028e4",
        "name": "",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "localhost:1880/cgi-bin/imgpost01.cgi",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 690,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "99b3be79.f601c8",
        "type": "change",
        "z": "46a5b4e9.1028e4",
        "name": "pick file",
        "rules": [
            {
                "t": "move",
                "p": "req.files[0].buffer",
                "pt": "msg",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 310,
        "y": 120,
        "wires": [
            [
                "682c93b2.3ecacc",
                "a06d87ec.8581c8"
            ]
        ]
    },
    {
        "id": "682c93b2.3ecacc",
        "type": "file",
        "z": "46a5b4e9.1028e4",
        "name": "",
        "filename": "img01.jpg",
        "appendNewline": true,
        "createDir": false,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 480,
        "y": 200,
        "wires": [
            []
        ]
    }
]
```

</div> </details>

## 3.2. 各ノードの設定

では、左のノードから設定を説明します。

### 3.2.1. http inノード

http inノードを編集します。メソッドを **POST** にして、**ファイルのアップロード**にチェックを入れます。今回は、カメラノードから送付するURLは、mypost01という名前にしてあります。ここは適宜変更してください(カメラノードのプログラムと一致させます）。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/35b9c27f-3e43-2a24-5907-a31d3c463da0.png)

### 3.2.2. Templateノード
postした側への返答をテンプレートノードで作成します。文面は何でも良いです。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/1bd00e31-36c2-81b4-fb81-1d3c9eb87b0b.png)

### 3.2.3. HTTP-Responseノード
コード200を返すのがhttp responseです。特に何も設定する必要がありません。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/87ae5c62-262c-802f-6d8a-d8c0811205a8.png)


### 3.2.4. changeノード

ここからが、中継としての処理になります。

changeノードで、http inで受け取った画像ファイルを取り出し、このデータ列をpayloadとして後続のノードへ渡します。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/922be452-fdb3-efc5-7278-3127b6ad031c.png)

なお、このchangeノードの役割が、http postで受け取ったファイルの取り扱いとして重要です。
http-inノードは、POSTメソッドでファイルを受信すると、ファイルpayloadではなく、req.files[]にその情報が入ります。ファイル自体は、bufferに配列として格納されます。
フローよりもプログラム志向な方は、このchangeノードと等価な動きをする、次のスクリプトをfunctionノードで書いた方が馴染みやすいかもしれません。

```javascript
let mymsg={};
mymsg.payload=msg.req.files[0].buffer;
return mymsg;
```



### 3.2.6. Functionノード

今回の肝、functionノードでファイルを指定された構造で包みます。 http-responseのヘルプに書いてある内容そのままです。

![07b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/449396d2-9b00-ad65-ba24-1c971c93922e.png)

内容は次の通りです。相手のCGIで抽出する名前とキー名を一致させる必要があります。今回は、```"imagefile"```としています。なお、参考にしたのは次のページです。 https://discourse.nodered.org/t/http-post-image-image/32669/2


```javascript
let mymsg1={};

mymsg1.headers = {
    "Content-Type": "multipart/form-data"
}

mymsg1.payload = 
{
    "imagefile": {
        "value": msg.payload,
        "options": {
            "filename": "FILENAME"
        }
    }
};

return(mymsg1);

```

### 3.2.7. HTTP-Requestノード

最後に、http requestノードで、payloadに画像とともに載せた情報をサーバに渡します。サーバのURLを記述します。ここでは、サンプルとして、このホスト内のサービスに投げています。

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/aa540ba8-7bd1-fbef-9089-b2629bbf6278.png)


# 4. 重要な点
node-redで画像をhttp postで受信し、http postで送信する場合の重要な点を挙げます。

- 送信では送信内容を指定された構造で包みます。
- バイナリのまま送信できます。(Base64にする必要がありません）
- 受信のファイルは、```msg.req.files[0].buffer```に格納されています。

これらを知っていれば、戸惑う事は無いと思います。

# 5. おわりに

今回は、Node-Redで画像をhttp postで中継送信するためのフローを挙げました。 冒頭でM5Cameraは能力的に不安があるという書き方をしましたが、実際は問題無いと思います。しかし、開発工数的には、圧倒的にラズパイの方に歩があると思います。
これを模索するために、色々と試しましたので、それについても[記事](https://qiita.com/dzonesasaki/items/a61cd362b3383cafc7e4)として上げました。