『node.js での、「http.server@python3」相当』微メモ

「微」がつかないと結構なボリュームになるネタだけどそういうことがしたいんではないの。

「ファイル公開サーバ」のような http サービスは、python でも簡単に作れて、その部品が python3 なら http.server にある。これの node.js 版は、てハナシなんだけれど、まぁ w3schools が至れて尽くせてたわ、欲しい情報が全部あった。まぁ誰にとっても真っ先に知りたくなる・やりたくなる、てことなんだろうね:

  1. Node.js HTTP Module
  2. Node.js File System Module
  3. Node.js URL Module

1.、2.、3. にある EXAMPLE を順に読めば「ROAD TO おれのやりたいこと, STEP BY STEP」となって読みやすいと思うが、3. の EXAMPLE から読むなら一撃で「リスティング以外はおk」に辿り着く(つまり同じディレクトリ内のファイル名(ただし例のままだとhtml専用)に応答して中身を返すサーバになる)。この 3. の EXAMPLE の「ポート番号可変」にしたのがこれ:

servfiles.js
 1 var http = require('http');
 2 var url = require('url');
 3 var fs = require('fs');
 4 var listenport = parseInt(process.argv[2], 10) || 8080;
 5 
 6 http.createServer(function (req, res) {
 7     var q = url.parse(req.url, true);
 8     var filename = "." + q.pathname;
 9     fs.readFile(filename, function(err, data) {
10         if (err) {
11             res.writeHead(404, {'Content-Type': 'text/html'});
12             return res.end("404 Not Found");
13         } 
14         res.writeHead(200, {'Content-Type': 'text/html'});
15         res.write(data);
16         return res.end();
17     });
18 }).listen(listenport);
実行例
1 [me@host: somedir]$ node servfiles.js 8081

リスティングは今のワタシには必要ないので、まぁいいかな。

ここからいわゆる動的な生成の世界に行きたいということ。ひとまず「テンプレーティング」については、組み込みでは Template Literals があるので最低限のものはすぐに出来るだろう:

servfiles.js (Template Literals を使ってみてるだけの、実用にはならん例)
 1 var http = require('http');
 2 var url = require('url');
 3 var fs = require('fs');
 4 var listenport = parseInt(process.argv[2], 10) || 8080;
 5 
 6 http.createServer(function (req, res) {
 7     var q = url.parse(req.url, true);
 8     var filename = "." + q.pathname;
 9     var _eval = function (s) { return s; }
10     if (/\.(html)t$/.test(q.pathname)) {
11         _eval = function (s) { return eval(s); }
12     }
13     fs.readFile(filename, function(err, data) {
14         if (err) {
15             res.writeHead(404, {'Content-Type': 'text/html'});
16             return res.end("404 Not Found");
17         } 
18         res.writeHead(200, {'Content-Type': 'text/html'});
19         let cont = _eval(data.toString());
20         res.write(cont);
21         return res.end();
22     });
23 }).listen(listenport);

ということだが、この先。これが「実用にならん」のは、本当にやりたいのは「.htmlt をユーザがリクエストする」のではないからね。たとえば検索キーワードなどを受け取って、検索結果のデータ+テンプレートに基づいてページをレンダリングする、という動的なことをばしたい、てのがゴールなわけで。そもそも「text/html」固定なのもダメだしね。で、こういう一連をインフラが補助してくれたらいいなぁ、てわけだね。ゆえに流れとして「すんげー WEB アプリケーションフレームワーク」を見繕う道もある。例によって「すんげー node.js」。ここに、サードパーティのテンプレートエンジン、WEB アプリケーションフレームワークが列挙されてる。これらの評価がまた大変だなぁと思うが、こうやって列挙されてるだけでありがてーとしておくか。

何がしたいかってさ、こういうのって、やっぱりサーバの助けを借りれれば一気に発展出来るのよね。クライアントサイドだけに頼ってるだけでは、出来ないことが多いの。して、なんで python だけでも出来るのに node.js かは、まぁ今となってはあんまり意味はないのかも、「個人的に知っといたほうがいい」と思った以上のものはない。で、最終的にはこういうのを WSL2 や Docker で動かすようなことを考えたい、というのが今思ってるロードマップ。そういうのがサクっと作れるようになると、いろいろ捗ることも増えるかなと。


「サードパーティの WEB アプリケーションフレームワーク」に頼るなら必要なくなるだろうと思う、ので、まぁ無駄になるだろうとは思うものの「html専用」はさすがに気持ち悪いので解決しときたい、と思うわけだが、「最小限の手動ディスパッチ」でないちゃんとしたもの、は…、node.js 組み込みではないのかしらね、サードパーティのパッケージとしてとしか見つからなかった。ひとまず mime-types でいいのかな:

servfiles.js (Template Literals を使ってみてるだけの、実用にはならん例、のまま)
 1 var http = require('http');
 2 var url = require('url');
 3 var fs = require('fs');
 4 var listenport = parseInt(process.argv[2], 10) || 8080;
 5 var mime = require('mime-types');
 6 
 7 http.createServer(function (req, res) {
 8     var q = url.parse(req.url, true);
 9     var filename = "." + q.pathname;
10     var _eval = function (s) { return s; }
11     var conttype = mime.lookup(q.pathname) || 'text/plain';
12     if (/\.(html)t$/.test(q.pathname)) {
13         _eval = function (s) { return eval(s); }
14         conttype = mime.lookup(q.pathname.slice(0, -1))
15     }
16     fs.readFile(filename, function(err, data) {
17         if (err) {
18             res.writeHead(404, {'Content-Type': 'text/html'});
19             return res.end("404 Not Found");
20         }
21         res.writeHead(200, {'Content-Type': conttype});
22         let cont = _eval(data.toString());
23         res.write(cont);
24         return res.end();
25     });
26 }).listen(listenport);

「動的なことをばしたい」が node.js に限らず「何か外部プロセスに頼る」(いわゆる CGI に限らず)を採用することを考えるならば、「How to execute external commands in node.js?」が知りたいこととなるわけなんだけれど、ふむ、こっちは node.js に組み込まれてる、のかな? あるいは既に持ってたてことかしら。ワタシの今の環境だと、npm install 不要で以下が使えた:

cpexam.js
 1 /*
 2  * 本日 2022-05-22 時点での
 3  *   https://nodejs.org/api/child_process.html
 4  * は node.js v18.2.0 を指していて、ワタシが今使ってる v14.17.5 のものと既に
 5  * インターフェイスが違ってるらしい。v18.2 のものだと
 6  *     require('node:child_process'); 。
 7  */
 8 const subprocess = require('child_process');
 9 
10 /*
11  * あえて DOS コマンドを呼び出すの巻。
12  */
13 const dosattrib = subprocess.spawn("ATTRIB", ["/L"]);
14 dosattrib.stdout.on("data", (data) => {
15     console.log(data.toString());
16 });
17 dosattrib.stderr.on("data", (data) => {
18     console.log(data.toString());
19 });

exec も使えるんだけど、思ったより使いにくくないかこれ。Python の subprocess で言うところの「shell=True」相当のことしか出来ないみたいよね。こういうこと:

 1 /*
 2  * 本日 2022-05-22 時点での
 3  *   https://nodejs.org/api/child_process.html
 4  * は node.js v18.2.0 を指していて、ワタシが今使ってる v14.17.5 のものと既に
 5  * インターフェイスが違ってるらしい。v18.2 のものだと
 6  *     require('node:child_process');
 7  */
 8 const subprocess = require('child_process');
 9 
10 /*
11  * あえて DOS コマンドを呼び出すの巻。
12  */
13 const dosattrib = subprocess.exec("ATTRIB /L", (err, stdout, stderr) =>  {
14     console.log(stdout.toString());
15 });

これはちょっとイケとらんな。external process に渡す引数にね、空白やら引用符を含む場合に面倒を背負いこむのを避けるために、argv そのものを渡すノリが楽だ、ということなんだが。shell=True 相当が楽なことがないとは言わないけど…。

まぁでもこれが出来れば、Python などのほかの言語を自由に組み合わせたアプローチを採れる、てことね。

あ、Python の subprocess.check_output 的な楽さを求めるなら、たぶん spawnSync が近いのかな:

 1 /*
 2  * 本日 2022-05-22 時点での
 3  *   https://nodejs.org/api/child_process.html
 4  * は node.js v18.2.0 を指していて、ワタシが今使ってる v14.17.5 のものと
 5  * 既にインターフェイスが違ってるらしい。v18.2 のものだと
 6  *     require('node:child_process');
 7  */
 8 const subprocess = require('child_process');
 9 
10 /*
11  * あえて DOS コマンドを呼び出すの巻。
12  */
13 const dosattrib = subprocess.spawnSync("ATTRIB", ["/L"]);
14 console.log(dosattrib.stdout.toString());

サブプロセスが時間がかかるので非同期にやらねばならぬ、となればこれはダメだけど、すぐに返ってくるとわかってるものならこれが一番楽そうね。


ちなみに。

『最終的にはこういうのを WSL2 や Docker で動かすようなことを考えたい、というのが今思ってるロードマップ』の念頭にあるのは、最終的には「Docker Compose」で、これに頼ると今例にしたように node.js + Python みたいな任意の組み合わせだとても「誰もがどこででも同じものを再現出来てハッピーろ」みたいな話ね。

昔はこういう http サービスのアプローチには「ほぼ apache 必須、python CGI を使いたければ mod_python 夜露死苦」みたいに、選択肢が限られてたわけだね。CGI 以外の動的 http サービスの最初期のものが PHP、JSP、Microsoft の ASP あたりかと思うが、これらが世に登場するや、世界はほぼそれだけになった。その時代でも、今回ワタシが実例をみせた「node.js だけで http サービスを提供するサーバアプリケーション」のようなものは、たとえば Python だけで、あるいは考えたくもないが C 言語だけで、とか、まぁ出来たは出来たけれど、性能の問題や機能的な網羅性の関係で「製品品質ならば結局は Apache に頼るしかなく」という時代がずっと続いていた、ということなわけだ。でこの場合、「httpサービスタイプのアプリケーション」を公開したい場合に「製品で使う以外であんたの PC で動かしたくば Apache インストールしやがれ or DIE」。そして Windows ユーザが死ぬ。Unix ユーザだって「依存物の衝突にて DIE がち」。(そもそも「製品で使いたくば」の場合に必要なのは、恐怖の「デプロイ」という作業だ。うまくいくうちはこれはほとんどただのアップロード作業だが、バージョンコンフリクトなどの問題が発生したら…、あとはご察し。)

だけれども今は違う」ということ。レンタルサーバの仕様などの制約があればそれに従うしかないのでその場合は従来通り諦めるしかないけれど、おそらくこれからのレンタルサーバなどのサービスでは Docker が当たり前に使えるようになるんだろうと思ってる。この場合に、node.js だけでサービスすることが Apache (+PHP など)に頼るのに劣るのか、のようなことは、細かいことはまだワタシはわからんけど、シンプルなニーズで Apache がないと出来ないこと、というのはワタシは思い付かない。ないような気がするけどどうだろうね?

Docker に頼るアプローチを採ると、リンクしたビデオで紹介されてるように、レプリケーションのような WEB サービスにはいずれ不可欠になるようなこともかなり簡単に構築出来るし、「そうしたものに必要な依存物」の問題は、それこそがまさに Docker が解決する問題だ、そいつはステキだ、と。

まぁ今はまだ「へぇ、そんなことが出来るのか、ステキだ」とビデオをみて妄想してるだけの段階。今はその前段階の「なにで作ろうが Docker に頼るつもりなら依存物問題は悩みの種にはならんのであるから、だったら node.js で遊んでおくか」の段階。で、「Docker ならば、皆にお披露目するのがすんげー楽であろう」てことね。おそらく DockerHub にホストするのでなくとも Dockerfile と作ったアプリケーションをアーカイブにまとめて配布、みたいな公開の仕方が出来て、その配布物は「Docker を介するので誰の環境でも問題なく動作する」であろうと。



Related Posts