Back to tech

n8n で一時的なコンテンツサーバを立てる

4 min read
Table of Contents

お久しぶりです。

またですが、今回も n8n の話です。

n8n を使って一時的なコンテンツサーバを立てる方法についてのメモです。

この話をする理由

n8n のワークフローを作っているときに、
「外部で受け取ったファイルを一時的に保存して、後でダウンロードできるようにしておきたい」と思いました。

コンテンツを公開するためにコンテンツサーバーを立てるべきかと考えましたが、一時的な用途のためにわざわざ Docker を準備するのは面倒だと思いました。

なんとか n8n でできないかなぁと思い、chatGPT と壁打ちしていたところ、良い方法が見つかりました。

中身に入る前に大前提

Docker で n8n を動かしていることを前提に話を進めます。

n8n のインストール方法は公式ドキュメントを参照してください。

n8n のバージョン

Version 1.103.2 で確認しています。

また、セルフホスト版を使っています。

やりたいこと

GET リクエストでファイルをダウンロードしたい。

可能であれば、簡易的なセキュリティも付けたいです。

さらに欲を言えば、POST リクエストでアップロードもできないか。

できたもの

ということで、できたものがこちらになります。

Webhook を使って実現しました。

中身はとても単純です。

https://${YOUR_HOST}/webhook/file に POST リクエストを送ると、ファイルがサーバーに保存されます。

# e.g.
curl -X POST https://${YOUR_HOST}/webhook/file \
  -F "file=@/path/to/image.png"

https://${YOUR_HOST}/webhook/file?filename=${FILE_NAME} に GET リクエストを送ると、保存したファイルをダウンロードできます。

# e.g.
wget "https://${YOUR_HOST}/webhook/file?filename=image.png"

セキュリティ

n8n の Webhook ノードは優秀で、Basic Auth や IP 制限などを設定できます。

好みに応じて設定してください。

セキュリティ設定1セキュリティ設定2

コード

よろしければ参考にしてみてください。

{
  "name": "sample",
  "nodes": [
    {
      "parameters": {
        "path": "file",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -416,
        16
      ],
      "id": "05edf4d6-ec31-4ccc-99b2-55732ed974ae",
      "name": "Get Webhook",
      "webhookId": "bd6aeee2-b26d-4b2a-a2aa-e4063d1f5d5b"
    },
    {
      "parameters": {
        "respondWith": "binary",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        288,
        16
      ],
      "id": "445987d3-83d4-49d2-8ffd-c5fadc2cb93b",
      "name": "Response",
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "file",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -416,
        -144
      ],
      "id": "a943b130-6f49-4afd-b4d3-d5c432fd0583",
      "name": "Post Webhook",
      "webhookId": "bd6aeee2-b26d-4b2a-a2aa-e4063d1f5d5b"
    },
    {
      "parameters": {
        "fileSelector": "=/tmp/{{ $json.filename }}",
        "options": {
          "fileName": "={{ $json.filename }}",
          "mimeType": "={{ $json.mimeType }}"
        }
      },
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        48,
        16
      ],
      "id": "50eb78a6-2f60-49f0-9712-be56ce14597e",
      "name": "Read File From Tmp Folder."
    },
    {
      "parameters": {
        "operation": "write",
        "fileName": "=/tmp/{{ $json.fileName }}",
        "dataPropertyName": "file",
        "options": {}
      },
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        48,
        -144
      ],
      "id": "c6b46191-38ed-464f-9be1-cd73a00cbd63",
      "name": "Read/Write Files from Disk"
    },
    {
      "parameters": {
        "jsCode": "const fileName = $input.first().binary.file.fileName;\n\nreturn [\n  {\n    json: {\n      fileName\n    },\n    binary: $input.first().binary\n  }\n]"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -176,
        -144
      ],
      "id": "94c1ac5b-d4bb-41e3-a6dc-4f7c0d69475b",
      "name": "Get File Name"
    },
    {
      "parameters": {
        "jsCode": "const filename = $input.first().json.query.filename || \"default.jpg\";\nconst ext = filename.split('.').pop().toLowerCase();\nconst mimeMap = {\n  jpg: \"image/jpeg\",\n  jpeg: \"image/jpeg\",\n  png: \"image/png\",\n  gif: \"image/gif\",\n  webp: \"image/webp\",\n};\nconst mimeType = mimeMap[ext] || \"application/octet-stream\";\nreturn [{ json: { filename, mimeType } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -176,
        16
      ],
      "id": "3b2933d1-b904-4993-b1a6-fe61b71f3cc1",
      "name": "Get File Info"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"messsage\": \"success\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        288,
        -144
      ],
      "id": "6637f6e7-4738-4c1c-ad54-4832f52bd844",
      "name": "Response1"
    }
  ],
  "pinData": {},
  "connections": {
    "Get Webhook": {
      "main": [
        [
          {
            "node": "Get File Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read File From Tmp Folder.": {
      "main": [
        [
          {
            "node": "Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Webhook": {
      "main": [
        [
          {
            "node": "Get File Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read/Write Files from Disk": {
      "main": [
        [
          {
            "node": "Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get File Name": {
      "main": [
        [
          {
            "node": "Read/Write Files from Disk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get File Info": {
      "main": [
        [
          {
            "node": "Read File From Tmp Folder.",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d20798f2-748c-4887-a83e-33ccc41948aa",
  "meta": {
    "instanceId": "f1ea10e2477ea0ca0966ce19137d7a6d60d042c865a530a9bc6a8f603af2cc15"
  },
  "id": "A2Knxh6bn6YQp5QD",
  "tags": []
}

一応注意

主に上記のコードをそのまま使う方への注意点です。

ファイルの保存先

ファイルのアップロード先が /tmp になっています。

そのため、n8n のコンテナが再起動されるとファイルは消えます。

一時的なコンテンツサーバとして使うことを想定しているので、特に問題はないと思いますが、必要に応じて変更してください。

tmpフォルダには他の一時ファイルも含まれる

/tmp フォルダ内にはさまざまなログファイルが生成されています。

GETリクエストによって、意図しないファイルがダウンロードされる可能性があります。

そのため、Check Extension ノードを使って、ファイルの拡張子を制限するなどの対策を推奨します。

例えば、以下のようになります。

全体像
Check Extension の設定
画像ファイルのみを許可する場合

あとは、セキュリティの設定をしておけば、ある程度は安心して使えると思います。

まとめ

n8n を使って一時的なコンテンツサーバを立てる方法についてのメモでした。

n8n の Webhook ノードを使うことで、簡単に一時的なファイルサーバを構築できることがわかりました。

これを使えば、Docker を準備することなく、手軽に一時的なコンテンツサーバを立てることができます。

もし、n8n を使っている方で一時的なコンテンツサーバを立てたいと思っている方がいれば、ぜひ試してみてください。