nginScriptで遊んでみた

nginxをJavaScriptで拡張できるnginScriptがローンチされたので軽く触ってみた。

https://www.nginx.com/blog/launching-nginscript-and-looking-ahead/

## nginScriptをビルド

nginScriptは今のところnginx本家のMercurialリポジトリからcloneすることができる。また、nginxモジュールの実装とnginScriptの実装が一緒に含まれているため、まずはnginScriptをビルドする。

```
hg clone http://hg.nginx.org/njs/
cd njs
./configure
make
```

## nginxにnginScriptを組み込んでビルド

nginScriptのビルドが完了したら今度はnginx本体のビルドを行う。`configure`実行時に`--add-module`でさきほどビルドしたnginScript(`njs`)のパスを指定してビルドする。

```
wget http://nginx.org/download/nginx-1.9.5.tar.gz
tar zxvf nginx-1.9.5.tar.gz
cd nginx-1.9.5
./configure --add-module=<path-to-njs>/nginx
make
sudo make install
```

## js_setでJavaScriptの実行結果をnginxの変数に代入

`js_set`はJavaScriptの実行結果をnginxの内部変数に代入する。`set_by_lua`や`mruby_set`と違って`http`コンテキストでしか使えないので注意。（`server`、`location`コンテキストでは利用不可）

```nginx
http {
    js_set $var "                                                                                                                                             
        var result = 'Hello, World\n';                                                                                                                
        result                                                                                                                                                
    ";

    server {
        listen 8080;
        location /js-var {
            return 200 $var;
        }
    }
    ...
```

`curl`で叩く。

```console
$ curl -s http://127.0.0.1:8080/js-var
Hello, World
$
```

## js_runでJavaScriptの実行する

`js_run`は特定の`location`に対してJavaScriptを実行するためのフックをかけることができる。`$r`に内部データを突っ込んでるのがとてもPerlっぽい。

```nginx
server {
    listen 8080;
    location /js-run {
        js_run "
            var res;
            res = $r.response;
            res.status = 200;
            res.sendHeader();
            res.send('Hello, World\n');
            res.finish();
        ";
    }
}
```

`curl`で叩く。

```console
$ curl -s http://127.0.0.1:8080/js-var
Hello, World
$
```

nginxには`rewrite`、`access`、`content`、`log`などなどたくさんの実行フェーズがあって[ngx_lua](https://github.com/openresty/lua-nginx-module)や[ngx_mruby](https://github.com/matsumoto-r/ngx_mruby)だとそれぞれのフェーズにフックをかけてもっといろんなことができる(e.g. アップストリームにプロキシする直前にスクリプト実行してアクセス制御とか)のだが、nginScriptでは今のところフックをかけれるのは`content`フェーズのみだ。

## $rからnginxの内部にアクセス

`$r`はJavaScriptでnginxの内部構造にアクセスするためのエンドポイントだ。今のところアクセスできるものは少ない。詳細は[README](http://hg.nginx.org/njs/file/11d4d66851ed/README)を見て欲しい。

```javascript
server {
    listen 8080;
    location /js-r {
        js_run "
            var res;
            res = $r.response;
            res.status = 200;
            res.sendHeader();
            res.send($r.uri);
            res.send('\n');
            res.send($r.method);
            res.send('\n');
            res.send($r.remoteAddress);
            res.send('\n');
            res.finish();
        ";
    }
}
```

`curl`で叩く。

```console
$ curl -s http://127.0.0.1:8080/js-r
/js-r
GET
127.0.0.1
$
```

## レスポンスヘッダを操作

`$r.response.headers`を利用するとレスポンスヘッダを操作することができる。

```javascript
$r.response.headers.Bokko = 'cubicdaiya';
```

なお、HTTPのヘッダは`-`で区切ることがよくあるが、以下のような書き方は`SyntaxError`になる。

```javascript
$r.response.headers.X-Bokko = 'cubicdaiya';
```

ヘッダ名に`-`を含めたい場合は以下のように書く。

```javascript
$r.response.headers['X-Bokko'] = 'cubicdaiya';
```

ところで[ngx_lua](https://github.com/openresty/lua-nginx-module)だと内部的に`_`を`-`に変換してくれてとてもかしこい。

```lua
-- ngx_lua
-- 内部的に「X-Bokko: cubicdaiya」に変換される
ngx.header.X_Bokko = 'cubicdaiya';
```

あと、nginxのレスポンスヘッダは連結リストで表現されているので操作にかかるコストはO(n)である。
また、`Content-Type`と`Content-Length`は専用のアクセサが用意されている。

```javascript
$r.response.headers.contentType = 'text/plain';
$r.response.headers.contentLength = 15;
```

## まとめ

今のところnginScriptでできるのは、

 * `js_set`でJavaScriptの実行結果をnginxの変数に代入できる
 * `js_run`で特定の`location`内でJavaScriptを実行できる
 * `$r`からnginxの内部構造にちょっとだけアクセスできる
 * レスポンスヘッダの操作ができる

といった感じです。まだ機能少なめでearly development phaseという感じですが、リリースされたばかりですし、今後に期待ですね。