『node.js での、「http.server@python3」相当』微メモ(2) – using ejs, express

これの続きなのでタイトルを引き継ぐが、中身は既に「http.server@python3 相当」の範疇を超えてる。ejs を使うなら例えば jinja2 相当、express を使うなら例えば django 相当。

まずは「すんげー WEB フレームワーク」には頼らないまま、「すんげー Templating」だけに頼るモード。「すんげー WEB フレームワーク」が独自のテンプレートエンジンを作ってる場合もあるとは思うが、いくつかみた中で ejs 依存のものと Pug 依存のものがあることは見つけた。どれがメジャーで無難なのはまだよくわからんけど、とりあえず「メジャーな気がする Express」で使える ejs か、あるいは置かれてる場所から mozilla 謹製っぽい nunjucks あたりがいいのかしらね? とりあえず ejs から始めてみるか。

とりあえず拡張子に応じて、なんてことをせずに「問答無用で ejs 経由」(データは渡さない一番シンプルなやつ):

filesserv.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 var mime = require('mime-types');
 6 var ejs = require('ejs');
 7 
 8 function handler(req, res) {
 9     var q = url.parse(req.url, true);
10     var filename = "." + q.pathname;
11     var conttype = mime.lookup(q.pathname) || 'text/plain';
12     fs.readFile(filename, function(err, data) {
13         if (err) {
14             res.writeHead(404, {'Content-Type': 'text/html'});
15             return res.end("404 Not Found");
16         }
17         res.writeHead(200, {'Content-Type': conttype});
18         let cont = ejs.render(data.toString());
19         res.write(cont);
20         return res.end();
21     });
22 }
23 
24 http.createServer(handler).listen(listenport);

データは渡さなくてもかなりのことが出来る(サーバサイド node.js で出来ること全て書けるので)が、ひとまずすんげーアホな例:

1 <html>
2   <body>
3     <ul>
4 <% for (let i = 0; i < 5; i++) {%>
5         <li>hoge<%= i %></li>
6 <% } %>
7     </ul>
8   </body>
9 </html>

たとえばこれを zzz.html という名前で filesserv.js と同じフォルダに置いたとして、ポート 8081 でサービスするとして:

(今の場合の)render の第二引数・第三引数に何かを渡せば普通の WEB アプリケーションになっていくが、最低でも「組み込み関数」に相当するものや「ベーステンプレート置き場のパス」などを渡すことになるであろう、みたいな流れはさ、いつもだよね。ざっとドキュメントを眺めた感じだと、Go 言語組み込みので苦労したような妙な制約はなさそう。

ちょっと不自然な例になるのは覚悟で、ひとつ前のネタで予告した通り「node.js だけで作るのではなく、自由に好きなものを使っちゃるぜ」に道筋をつけるために、「render に渡すべきデータを外部の python で作る」という妙な実例にしてみる:

filesserv.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 var mime = require('mime-types');
 6 var ejs = require('ejs');
 7 /* 新しい node.js の場合は require('child_process') */
 8 const subprocess = require('child_process');
 9 
10 function handler(req, res) {
11     var q = url.parse(req.url, true);
12     var filename = "." + q.pathname;
13     var conttype = mime.lookup(q.pathname) || 'text/plain';
14     fs.readFile(filename, function(err, data) {
15         if (err) {
16             res.writeHead(404, {'Content-Type': 'text/html'});
17             return res.end("404 Not Found");
18         }
19         res.writeHead(200, {'Content-Type': conttype});
20         const pyres = subprocess.spawnSync("py", ["hoge.py"]);
21         let ctx = JSON.parse(pyres.stdout.toString());
22         /*console.log(ctx);*/
23         let cont = ejs.render(data.toString(), ctx);
24         res.write(cont);
25         return res.end();
26     });
27 }
28 
29 http.createServer(handler).listen(listenport);
hoge.py
1 # -*- coding: utf-8 -*-
2 # CGI のマナーだのそう言ったことなど知ったことではない、ほんとにただのただの
3 # python スクリプトだよ、標準出力に json を吐き出すだけの。
4 import json
5 
6 print(json.dumps({"user": "foo", "host": "bar"}))
zzz2.html
1 <html>
2   <body>
3     <h1><%= host %></h1>
4     <p>Hello, <%= user %></p>
5   </body>
6 </html>


「py」は Windows にしかないランチャなので、Unix ユーザなどは適宜読み替えてほしいのだが、ここがね、「Docker に頼るなら」に関係してくるんだよね。Docker の linux コンテナを使うなら、こういう「Windows なら」みたいな但し書きがいらなくなるんだわ。あと require(‘child_process’) 部分もね。ステキ。まだ皮算用だけど。


続いて「すんげー WEB フレームワーク」の一つの Express + ejs。元にしたサンプルの都合でインターフェイスはちょっと変えるが、まぁ簡単だわ:

serv.js
 1 /*
 2  * https://github.com/expressjs/express/blob/master/examples/ejs/index.js と
 3  * 比較しながら読めば理解は捗る、ハズ。
 4  */
 5 var listenport = parseInt(process.argv[2], 10) || 8080;
 6 var express = require('express');
 7 var path = require('path');
 8 var app = module.exports = express();
 9 /* 新しい node.js の場合は require('child_process') */
10 const subprocess = require('child_process');
11 
12 app.engine('.html', require('ejs').__express);
13 
14 // 「Optional since express defaults to CWD/views」
15 // まぁ「とっぱじめ」としてカレントディレクトリを使うんでもいいんだけど、
16 // 「お行儀」と見做して真似しとく。
17 app.set('views', path.join(__dirname, 'views'));
18 
19 // examples/ejs/index.js の真似から始めたいので、/any.html みたいなのに
20 // 応答する例ではなくて、エントリポイントは / だけ、というものにしとく。
21 app.get("/", (req, res) => {
22     const pyres = subprocess.spawnSync("py", ["hoge.py"]);
23     let ctx = JSON.parse(pyres.stdout.toString());
24     // ここで「zzz3.html」ではなく「zzz3」と書くための方法は元の
25     // examples/ejs/index.js にある。
26     res.render("zzz2.html", ctx);
27 });
28 
29 /* istanbul ignore next */
30 if (!module.parent) {
31     app.listen(listenport);
32     console.log('Express started on port ' + listenport);
33 }

hoge.py も zzz2.html も同じものだが、zzz2.html は views/ フォルダに移動する。結果は同じである:

同じというか、/ にしか反応しないので同じではないけれど、ルーティングが柔軟に出来ることは既にドキュメントで確認済み。まぁこれならやりたいことはすぐに出来るであろう。



Related Posts