Node.js を CGI として動かしてファイルをレスポンスする
最近なぜかハマっている、Node.js で CGI を作るアレ。Node.js だろうと結局のところ CGI は標準入力と標準出力が使えれば何でもできる。
例えば download-file.js.cgi
にアクセスして、任意の画像ファイルを表示させたければ、こんな風に書けば良い。
download-file.js.cgi
#!/usr/bin/node
const fs = require('fs').promises;
(async () => {
const imgFile = await fs.readFile('./img.gif');
process.stdout.write('Content-Type: image/gif\n\n');
process.stdout.write(imgFile);
})();
ファイルを読み込んで、Content-Type
ヘッダを指定して、ファイルの中身を流すだけ。
console.log()
は末尾に改行コードが入ってしまうので、安全のために改行コードが入らない process.stdout.write()
を使っている。
他にどんなファイルがレスポンスできるかというと、大抵のファイルはレスポンスできる。ファイルの拡張子を基に Content-Type
を決めてやれば良い。
- テキストファイル系は
Content-Type: text/plain
で返せばテキストとして表示できる - HTML として表示したかったら
Content-Type: text/html
とする - 画像系は
image/jpeg
やらimage/png
やら
それ以外はバイナリファイルで、ブラウザ上で表示したりは出来ないだろうから、ダウンロードさせることになる。
ファイルをダウンロードさせるなら、Content-Type: application/octet-stream
を指定すれば、何らかのバイナリと表現できる。
また、ダウンロードするファイルの名前を指定するなら、Content-Disposition
というレスポンスヘッダを入れる。
#!/usr/bin/node
const fs = require('fs').promises;
(async () => {
// 例えば Zip ファイルをダウンロードさせるなら
const zipFile = await fs.readFile('./example.zip');
process.stdout.write('Content-Type: application/octet-stream\n');
process.stdout.write('Content-Disposition: attachment; filename=HOGE.zip\n\n');
// ↑ example.zip ではなく HOGE.zip としてダウンロードさせる
process.stdout.write(zipFile);
})();
ところで、冒頭のスクリプトで画像ファイルをブラウザ上で表示させた時に、その画像をダウンロードしようとすると、ファイル名が download-file.js.cgi
になってしまう。
コレを解消するのも Content-Disposition
が使える。attachment
ではなく inline
を指定すれば良い。
#!/usr/bin/node
const fs = require('fs').promises;
(async () => {
const imgFile = await fs.readFile('./img.gif');
process.stdout.write('Content-Type: image/gif\n');
process.stdout.write('Content-Disposition: inline; filename=HOGE.gif\n\n');
process.stdout.write(imgFile);
})();
コレにより、
- アドレスバーには
http://example.com/download-file.js.cgi
と表示され、 - ブラウザには GIF ファイルが表示されており、
- その GIF ファイルを右クリックしてダウンロードしようとすると
HOGE.gif
というファイル名が既定で設定されている
という動きになる。
結構たやすくファイル表示・ファイルダウンロードが実装できてしまった〜。