お久しぶりです。最近Node.jsばかり書いているwakです。
さて、最近こういうことがありました。
というわけで、こういう流れになりました。
つまり、Node.jsからPOSTでファイルアップロードを行いたいのですが、ちょっと探した範囲では 素の https.request でこれを行っているサンプルが見つかりませんでした。各種ライブラリが何をしているか分からないのも気持ちが悪いので、自分で書いたコードを残しておきます。

猫パンチを送信しようとしている猫
やりたいこと
export token="xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx" export channel="channel_name" export filename="filename" curl -F file=@test.zip -F channels=$channel -F token=$token https://slack.com/api/files.upload -F title=$filename
と同じデータを送信したい。内容はバイナリかもしれない。
結論
コードは次の通りです。
"use strict"
const https = require("https");
let binaryArray = [0x00, 0x40, 0x01, 0x41, 0x02, 0x42, 0x03, 0x43]; // ファイルの内容のバイト配列
let buffer = Buffer.from(binaryArray); // バイト配列をそのままBufferに
let content = buffer.toString("ascii"); // 1バイトずつstringへ変換
let options = {
host: "slack.com",
port: 443,
path: "/api/files.upload",
method: "POST",
headers: {
"Content-Type": "multipart/form-data; boundary=------------------------deadbeefdeadbeef"
}
};
let req = https.request(options, res => {
res.setEncoding("utf8");
res.on("data", (chunk) => {
// レスポンスを受け取って何か処理をしたいならここでやる
});
});
req.on("error", (e) => {
console.error(e.message);
});
req.write(`--------------------------deadbeefdeadbeef\r
Content-Disposition: form-data; name="file"; filename="filename.dat"\r
Content-Type: application/octet-stream\r
\r
${content}\r
--------------------------deadbeefdeadbeef\r
Content-Disposition: form-data; name="channels"\r
\r
channel_name\r
--------------------------deadbeefdeadbeef\r
Content-Disposition: form-data; name="token"\r
\r
xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx\r
--------------------------deadbeefdeadbeef\r
Content-Disposition: form-data; name="title"\r
\r
filename\r
--------------------------deadbeefdeadbeef--\r
`);
req.end();
ここではあえてパラメーターの名前や値を直接ベタ書きしていますが、動的にやるのも別に難しくないでしょう。簡単ですね。
なお、
- テンプレートリテラルの改行コードはプラットフォームによらずLF(
\n)に統一される - multipartでデータを送信する際に使用する改行コードはCRLF(
\r\n)とされている
という違いから、面倒だとは思いながらも各行の末尾に \r を書いています*1。 replace(/\n/g, "\r\n") などとやって一括置換をかけると content の中身まで置換される可能性があるのでやめましょう。
説明
上で示したように
curl -F file=@test.zip -F channels=$channel -F token=$token https://slack.com/api/files.upload -F title=$filename
こんなPOSTを送信すると、サーバーには以下に示すようなデータが送信されます。
ヘッダ部
重要な値として Content-Type があります(というか重要なのはこれだけです)。
Content-Type: multipart/form-data; boundary=------------------------deadbeefdeadbeef
これは、ボディ部にmultipartで複数のデータ(ファイルの内容、その他のパラメーター)が送信されること、そしてそれぞれの区切り目(境界区切子)が何かを示しています(RFC 2046)。 boundary の書式にはややこしい制約があるようなのですが、ハイフンをたくさん並べた後ろにランダムな英数字*2を書けば間違いはありません。
ボディ部
前半はファイル名とその内容、後半はその他のパラメーターです。それぞれの値は「ハイフン2個 + boundary の値 + CRLF」で区切ることとされているので、よく見ると boundary の値とはハイフンの数が違います。また、一番最後にはハイフンを2個付けます。
--------------------------deadbeefdeadbeef Content-Disposition: form-data; name="file"; filename="test.zip" Content-Type: application/octet-stream <バイナリデータ> --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="channels" channel_name --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="token" xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="title" filename --------------------------deadbeefdeadbeef--
テキストファイルの場合は text/plain にできます。文字エンコーディングはコード中で指定している通り(この場合はUTF-8)です。
--------------------------deadbeefdeadbeef Content-Disposition: form-data; name="file"; filename="test.txt" Content-Type: text/plain テキストデータ
オチ
Lambdaの環境には curl コマンドも入っているので、ファイルを /tmp あたりに作ってそのまま実行すれば済んだりします。ただしコンテナは使い回される可能性が高いため、作成したファイルは必ず削除しておきましょう。