njsを使ってnginxで数独をする
njs
この記事はkb Advent Calendar 2020 12日目の記事です。 https://adventar.org/calendars/5280
軽く動的なサーバサイドのコードを動かしたいけど、サーバーを立ち上げるのにnodejsをインストールしてデーモン化とか面倒… とはいえphp/php-fpmで書くのもな…
そういうときにもしかすると便利かもしれないのがnjsです。
njsはnginx上で ECMAScript5.1
相当のJavascriptを動かすことが出来るサブセットで、 modules/ngx_http_js_module.so
をload_moduleすることで使用する事ができます。
使い方は簡単、
- 動かしたいjavascriptのコードを用意し、
- nginx.conf に
load_module modules/ngx_http_js_module.so;
を追加 - httpモジュール内で
js_include [jsファイルパス];
で呼び出す - location モジュール内で
js_content [includeしたjsファイル内のfuction];
で該当のfunctionを呼び出す。
以上です。
Hello World
使い方を見ていきます。
├── Dockerfile ├── nginx.conf └── src └── hello_world.js
上記の構成で hello_world.js
を作成します。
hello_world.js
function hello(r) { r.return(200, "Hello world!"); } function params(r) { var h = r.headersIn["test"]; r.headersOut['test'] = "[header]Hello " + h; r.status = 200; r.contentType = 'text/plain'; r.sendHeader(); var b = r.args["test"]; var msg = '[body]Hello '+ b; r.send(msg); r.finish(); }
helloは Hello world!
を出力し、 params
はリクエストパラメータを読み取り、Helloのあとにつけてレスポンスを行うfunctionです。
nginx.conf
worker_processes 1; load_module modules/ngx_http_js_module.so; events { worker_connections 1024; } http { js_include src/hello_world.js; server { server_name _; location = /favicon.ico { access_log off; log_not_found off; } location /hello { js_content hello; } location /params { js_content params; } } }
nginx.conf
で先程作成したjsをimportし、実行したいlocation内でjs_content
とfunction名を指定します。
今回はDocker内で実行させるため、以下の様なDockerfileを書きます。
Dockerfile
FROM nginx:latest COPY nginx.conf /etc/nginx/nginx.conf COPY src /etc/nginx/src EXPOSE 80
あとはbuild/runしてnginxを起動します。
$ docker build . -t docker-njs && docker run -p 80:80 -it docker-njs
curlでアクセスしてみると動作してることがわかります。
$ curl localhost/hello Hello world! $ curl -v -H test:fuga "localhost/params?test=hoge" * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 80 (#0) > GET /params?test=hoge HTTP/1.1 > Host: localhost > User-Agent: curl/7.64.1 > Accept: */* > test:fuga > < HTTP/1.1 200 OK < Server: nginx/1.19.5 < Date: Tue, 15 Dec 2020 15:39:14 GMT < Content-Type: text/plain < Transfer-Encoding: chunked < Connection: keep-alive < test: [header]Hello fuga < * Connection #0 to host localhost left intact [body]Hello hoge* Closing connection 0
数独を動かす
やはり動的なコンテンツを出力するのであればオウム返しでは物足りないので、何か便利なサービスを動かしたいです。自分は数独がとても苦手なのですが、数独を出題してくれるやつを動かしてみます。
とはいえこの記事は数独のアルゴリズムを解説する記事ではないので、すでにあるものを持ってきて使います。
探すとsudoku.jsなるものがあったので、これをそのまま使ってみます。
sudoku.js
の使い方は簡単で、jsを読み込むだけで
を行えるようです。
ここで、njsはjs_import
を用いることでesmoduleのimport/exportのような機能を使うことが出来るのですが、js_import
とjs_include
を同時に使うことが出来ないため、どちらかに統一する必要があります。
ここで、sudoku.js
があまりexportしやすい作りになっておらず、sudoku.js
の1ファイルのみをjs_import
でimportする方向にします。
curl https://raw.githubusercontent.com/robatron/sudoku.js/master/sudoku.js > src/sudoku.js
sudoku.jsを落としてきて、以下のようなpatchを書きます。
sudoku.js自体はconsole.log
が無いためreturnに変更した部分以外は基本的に変更せず、generate()とsolve()を追加します。
sudoku.js.patch
diff --git a/sudoku/src/sudoku.js b/sudoku/src/sudoku.js index 65039da..46a5f70 100644 --- a/sudoku/src/sudoku.js +++ b/sudoku/src/sudoku.js @@ -670,7 +670,7 @@ } } - console.log(display_string); + return (display_string); }; sudoku.validate_board = function(board){ @@ -804,4 +804,30 @@ initialize(); // Pass whatever the root object is, lsike 'window' in browsers -})(this); \ No newline at end of file +})(this); + +function generate(r) { + var level = "easy"; + switch (r.args["level"]) { + case 'easy': + level = "easy"; + break; + case 'medium': + level = "medium"; + break; + case 'hard': + level = "hard"; + break; + + } + + var q = sudoku.generate(level); + var v = sudoku.print_board(q); + + r.return(200, q + '\n\n' + v ); +} + +function solve(r) { + var q = r.args["q"] + r.return(200, sudoku.print_board(sudoku.solve(q))); +} \ No newline at end of file
上記のpatchを適用してnginx.conf
を記述し、nginxを起動させます。
worker_processes 1; load_module modules/ngx_http_js_module.so; events { worker_connections 1024; } http { js_include src/sudoku.js; server { server_name _; location = /favicon.ico { access_log off; log_not_found off; } location /generate { js_content generate; } location /solve { js_content solve; } } }
$ patch -u src/sudoku.js < sudoku.js.patch $ docker build . -t docker-njs && docker run -p 80:80 -it docker-njs
クエリパラメータにlevelを入れることでレベルが変えられているかを確認してみます。
$ curl "http://localhost/generate?level=easy" 7.193.8.42834.59679..768.323.5.46791.16.9728387.3216.56.8...51.192.53478547189326 7 . 1 9 3 . 8 . 4 2 8 3 4 . 5 9 6 7 9 . . 7 6 8 . 3 2 3 . 5 . 4 6 7 9 1 . 1 6 . 9 7 2 8 3 8 7 . 3 2 1 6 . 5 6 . 8 . . . 5 1 . 1 9 2 . 5 3 4 7 8 5 4 7 1 8 9 3 2 6
$ curl "http://localhost/generate?level=hard" .2..9..5..7...4..9.59......71298.46.936.4....8451...9.5648293713874..9262917..... . 2 . . 9 . . 5 . . 7 . . . 4 . . 9 . 5 9 . . . . . . 7 1 2 9 8 . 4 6 . 9 3 6 . 4 . . . . 8 4 5 1 . . . 9 . 5 6 4 8 2 9 3 7 1 3 8 7 4 . . 9 2 6 2 9 1 7 . . . . .
以上、generateが実行され、数独が生成されています。
ちなみに、ドキュメントにはtypescriptをnjsに変換して使う方法なども載っているので、多少ちゃんと使う方法もあるのではないかなと思われます。
参考: