Node.js スクリプトを CGI として動かしてみる
CGI という仕組みは、Perl・Ruby・PHP などの言語に限らず、標準入力と標準出力を扱える言語なら何でもいいらしい。ということは、Node.js をランタイムにした CGI も可能だと思われる。
と、ふと思い立って実際にやってみた。
目次
前例がある : CGI-Node
同じようなことを考えていた人がいた。
$ cat cgi-bin/node_test.cgi
#!/usr/bin/node
console.log('Content-type: text/plain');
console.log('');
console.log('Hello World');
$ chmod 755 cgi-bin/node_test.cgi
シンプルなコードが掲載されていたが、コレでやれるみたいだ。
1行目の Shebang は node
(Windows なら node.exe
) へのフルパスを渡してやる必要があり、/usr/bin/node
や /usr/bin/env node
なんかだとうまく行かないことがあるみたい。Nodebrew なんかを使っている場合はそもそも ~/.nodebrew/
配下にあったりするので、この辺は実行環境に合わせてフルパスを書いてやる必要がありそうだ。
Node.js の組み込みパッケージである fs
なんかは使えそうだが、それ以外の npm パッケージを require()
するのはできないんじゃないかな。試してないけど。
CGI として動かせる一式が組まれた、CGI-Node というスクリプトも存在した。
- CGI-Node
- GitHub - DEDAjs/cgi-node: CGI Node.js complete module to replace PHP on shared hosting such as GoDaddy
リクエストヘッダとかを処理してくれるのは分かったが、.htaccess
にて cgi-node.js
を挟んで処理するよう設定する必要があるらしい。若干面倒臭い。
Node.js ならサーバを立てて動かせばいいじゃん、とも思うだろうが、Apache や nginx から、localhost:3000
とかで動いてるサーバにリクエストをフォワードしてやるのも面倒臭い気がする。既存の CGI サーバで、CGI スクリプトとして動かしたい時に、開発言語として Node.js を選びたい、という時のための検証である。w
前提条件
今回の前提として、Apache サーバで CGI が動かせるようにしてあって、拡張子は .cgi
を対象としている。お好みで .jss
とかを CGI として動かせるようにしておくと良いかと (.js
を対象にしてしまうと通常の JS ファイルと混在して辛い)。
Node.js は Nodebrew でインストールし、$ type node
コマンドで確認できたフルパスを Shebang に書くこととする。
最低限 CGI として動かせるコード
Node-CGI は大仰なので、一切の依存を持たず、完全なシングルファイルで CGI として動作するスクリプトを組んでみる。最低限以下を書けば、CGI として動かせる。
example-node-cgi.js.cgi
- 拡張子は CGI として動かせるモノなら何でも良い
- 実行権限を付与しておく
#!/usr/bin/node
// ↑実行パスは適宜調整する
(async () => {
let postBody = ''; // POST 時にリクエストボディを取得する
if(process.env.REQUEST_METHOD === 'POST') for await(const chunk of process.stdin) postBody += chunk;
// HTTP ヘッダを出力する
console.log('Content-Type: text/html; charset=UTF-8\n\n');
// 以下、任意の処理
console.log('<h1>Hello Node.js CGI</h1>');
})();
async
・await
を使っている他、for await of
を使っているので Node.js v12 以降が対象。それ以前の古いバージョンで動かすには、次のようなコードにすれば動かせる。
#!/usr/bin/node
new Promise((resolve) => {
// POST 時にリクエストボディを取得する
if(process.env.REQUEST_METHOD !== 'POST') return resolve();
let postBody = '';
process.stdin.on('data', (chunk) => { postBody += chunk; });
process.stdin.on('end', () => { resolve(postBody); });
}).then((postBody) => {
// HTTP ヘッダを出力する
console.log('Content-Type: text/html; charset=UTF-8\n\n');
// 以下、任意の処理
console.log('<h1>Hello Node.js CGI</h1>');
});
POST 時のリクエストボディのみ、process.stdin
から取得するため、このような処理を入れている。GET でしか処理しないのであれば、これらの前処理すら省いていきなり「HTTP ヘッダの出力」から実装しても良い。
Node.js を CGI として使うためのアレコレ
POST 時のリクエストボディのみ、取得方法が特殊なので上のとおり実装しておいたが、それ以外の情報はどのように受け取れば良いか。
GET 時のクエリ文字列や、サーバの情報、リクエスト情報などは、全て process.env
を見ることで確認できる。代表的なモノは以下のとおり。
環境変数名 | 内容 |
---|---|
HTTP_HOST |
サーバの Host Name or Public IP |
SERVER_NAME |
〃 |
SERVER_ADDR |
サーバの Private IP |
SERVER_PORT |
サーバのポート (80 とか 443 とか) |
DOCUMENT_ROOT |
Apache サーバ等のドキュメントルート |
CONTEXT_DOCUMENT_ROOT |
〃 |
REMOTE_ADDR |
リクエスト元の Public IP |
HTTP_USER_AGENT |
リクエスト元の User Agent |
HTTPS |
HTTPS 接続時は on が設定される |
REQUEST_SCHEME |
プロトコル (http や https ) |
REQUEST_METHOD |
メソッド (GET や POST ) |
SCRIPT_FILENAME |
CGI ファイルのフルパス |
SCRIPT_NAME |
CGI ファイルへのルート相対パス (/ 始まり) |
REQUEST_URI |
リクエストパス (/ 始まり、クエリ文字列を含む) |
QUERY_STRING |
クエリ文字列 (? は含まない) |
レスポンスは process.stdout.write()
か console.log()
で書けば良い。
console.log('<pre>', process.env, '</pre>');
などとコーディングすれば、リクエスト時の環境変数一覧が出力できる (セキュリティ的に注意が必要だが)。
その他、エラー発生時の処理やプロセス終了時の処理を process.on
で定義しておくと安全であろう。
// エラー発生時に行う処理
process.on('uncaughtException', (error) => {
// HTTP ヘッダを出力してあるかどうか確認できるようフラグ変数を管理しておくと良いだろう
console.log('Error :', error);
});
// 終了時の処理を予め定義する
process.on('exit', () => {
// 全ての処理が終わった後に以下が実行される。ちゃんとレスポンスに乗る
console.log('Exit');
});
ボイラープレートを作った
こうしたノウハウをまとめた、ボイラープレートプロジェクトを作った。以下の GitHub にある index.js
をベースとして利用してもらえればと思う。
以上
ちゃんと Node.js でも CGI として動かせた。外部 npm パッケージが使えなさそうだが、単一ファイルとして動かせるようにビルドしてやればイケるかも?