HTTP Echo モジュール

Name

ngx_echo - Brings “echo”, “sleep”, “time”, “exec” and more shell-style goodies to NGINX config file.

注意

このモジュールはNGIXのソースと一緒に配布されません。 インストレーションの説明を見てください。

状態

このモジュールはプロダクションの準備ができています。

バージョン

この文章は2014年11月21日にリリースされたngx_echo v0.57 を説明します。

概要

location /hello {
    echo "hello, world!";
}
location /hello {
    echo -n "hello, ";
    echo "world!";
}
location /timed_hello {
    echo_reset_timer;
    echo hello world;
    echo "'hello world' takes about $echo_timer_elapsed sec.";
    echo hiya igor;
    echo "'hiya igor' takes about $echo_timer_elapsed sec.";
}
location /echo_with_sleep {
    echo hello;
    echo_flush;  # クライアントが以前の出力をすぐに見えるようにします
    echo_sleep   2.5;  # 秒
    echo world;
}
# in the following example, accessing /echo yields
#   hello
#   world
#   blah
#   hiya
#   igor
location /echo {
    echo_before_body hello;
    echo_before_body world;
    proxy_pass $scheme://127.0.0.1:$server_port$request_uri/more;
    echo_after_body hiya;
    echo_after_body igor;
}
location /echo/more {
    echo blah;
}
# the output of /main might be
#   hello
#   world
#   took 0.000 sec for total.
# and the whole request would take about 2 sec to complete.
location /main {
    echo_reset_timer;

    # subrequests in parallel
    echo_location_async /sub1;
    echo_location_async /sub2;

    echo "took $echo_timer_elapsed sec for total.";
}
location /sub1 {
    echo_sleep 2;
    echo hello;
}
location /sub2 {
    echo_sleep 1;
    echo world;
}
# the output of /main might be
#   hello
#   world
#   took 3.003 sec for total.
# and the whole request would take about 3 sec to complete.
location /main {
    echo_reset_timer;

    # subrequests in series (chained by CPS)
    echo_location /sub1;
    echo_location /sub2;

    echo "took $echo_timer_elapsed sec for total.";
}
location /sub1 {
    echo_sleep 2;
    echo hello;
}
location /sub2 {
    echo_sleep 1;
    echo world;
}
# Accessing /dup gives
#   ------ END ------
location /dup {
    echo_duplicate 3 "--";
    echo_duplicate 1 " END ";
    echo_duplicate 3 "--";
    echo;
}
# /bighello will generate 1000,000,000 hello's.
location /bighello {
    echo_duplicate 1000_000_000 'hello';
}
# echo back the client request
location /echoback {
    echo_duplicate 1 $echo_client_request_headers;
    echo "\r";

    echo_read_request_body;

    echo_request_body;
}
# GET /multi will yields
#   querystring: foo=Foo
#   method: POST
#   body: hi
#   content length: 2
#   ///
#   querystring: bar=Bar
#   method: PUT
#   body: hello
#   content length: 5
#   ///
location /multi {
    echo_subrequest_async POST '/sub' -q 'foo=Foo' -b 'hi';
    echo_subrequest_async PUT '/sub' -q 'bar=Bar' -b 'hello';
}
location /sub {
    echo "querystring: $query_string";
    echo "method: $echo_request_method";
    echo "body: $echo_request_body";
    echo "content length: $http_content_length";
    echo '///';
}
# GET /merge?/foo.js&/bar/blah.js&/yui/baz.js will merge the .js resources together
location /merge {
    default_type 'text/javascript';
    echo_foreach_split '&' $query_string;
        echo "/* JS File $echo_it */";
        echo_location_async $echo_it;
        echo;
    echo_end;
}
# accessing /if?val=abc yields the "hit" output
# while /if?val=bcd yields "miss":
location ^~ /if {
    set $res miss;
    if ($arg_val ~* '^a') {
        set $res hit;
        echo $res;
    }
    echo $res;
}

説明

このモジュールはストリーミング入力および出力、並行/順列サブリクエスト、タイマーおよびスリープ、メタデータアクセスのための多くのNGINX内部APIをラップします。

基本的に異なる種類の見せ掛けのサブリクエストの些細なエミュレートにより他のモジュールのテストとデバッグを手助けする様々なユーティリティを提供します。

以下のような実際のアプリケーションで必要とされる便利さも見つけるでしょう。

  1. メモリから直接静的コンテンツを提供する(NGINX設定ファイルからロード)。
  2. 独自のヘッダおよびフッタを使ってupstream応答をラップする(addition module のようなものですが、コンテンツを直接設定ファイルおよびNGINX変数から読み込みます)。
  3. 様々な“NGINX locations” (例えば、サブリクエスト)を(echo_location とその仲間)を使って一つのメインリクエストにマージします。

This is a special dual-role module that can lazily serve as a content handler or register itself as an output filter only upon demand. デフォルトでは、このモジュールは何もしません。

技術的には、このモジュールはモジュールの書き手にとって便利だろう以下の技術を実証します:

  1. コンテントハンドラーから直接並行のサブリクエストを発行します:
  2. サブリクエストのチェインと一緒に連続するものを渡すことで、コンテントハンドラーから直接チェインされたサブリクエストを発行します。
  3. 全てHTTP 1.1.メソッドをおよび任意の偽装したHTTPリクエストボディさえも使ってサブリクエストを発行します。
  4. 同時のイベントおよびタイマーを使ってコンテントハンドラーから直接NGINXのイベントモデルとやり取りをし、必要であればコンテントハンドラの後ろからやり直します。
  5. Dual-role module that can (lazily) serve as a content handler or an output filter or both.
  6. NGINX 設定ファイルの変数の生成と差し込み。
  7. output_chain, flushおよびその類型を使ったストリーミング出力の制御。
  8. コンテントハンドラからのクライアントリクエストボディの読み込み、終了後にコンテントハンドラへの(非同期の)返却。
  9. NGINX Cモジュールの開発を進めるためのPerlベースの宣言型テストスィートの使用。

コンテントハンドラーディレクティブ

以下のディレクティブの使用はコンテントハンドラとしてこのモジュールを現在のNGINX locationに登録します。standard proxyモジュールのような他のモジュールをコンテントハンドラとして使いたい場合は、このモジュールによって提供されるfilter ディレクティブを使ってください。

全てのコンテントハンドラディレクティブは一つのNGINX locationに混成することができ、それらはちょうどBashスクリプト言語のように順番に実行することになっています。

各コンテントハンドラディレクティブは(もしあれば)その引数の中で変数の差し込みをサポートします。

standard default_type ディレクティブで設定されたMIMEタイプは、以下のようにこのモジュールによって考慮されます:

location /hello {
    default_type text/plain;
    echo hello;
}

そして、クライアント側では:

$ curl -I 'http://localhost/echo'
HTTP/1.1 200 OK
Server: nginx/0.8.20
Date: Sat, 17 Oct 2009 03:40:19 GMT
Content-Type: text/plain
Connection: keep-alive

v0.22 リリースから、rewrite moduleif ディレクティブの中に全てのディレクティブを入れることができるようになりました。例えば:

location ^~ /if {
    set $res miss;
    if ($arg_val ~* '^a') {
        set $res hit;
        echo $res;
    }
    echo $res;
}

echo

構文:echo [options] <string>...
デフォルト:none
コンテキスト:location, location if
Phase:content

空白で結合された引数を改行付きでクライアントに送信します。

データがNGINXの後ろにあるバッファでバッファされるかも知れないことに注意してください。出力データを強制的にすぐにフラッシュするには、echoのすぐ後にecho_flushコマンドを使ってください

echo hello world;
echo_flush;

引数が指定されない場合は、echo はシェルの echo コマンドのように改行のみを出力します。

変数が引数の中に現れるかも知れません。例としては、

echo The current request uri is $request_uri;

$request_uriHttpCoreModuleによって公開された変数です。

このコマンドは一つのlocation設定の中で以下のように複数回使うことができます。

location /echo {
    echo hello;
    echo world;
}

クライアント側の出力はこのように見えます

$ curl 'http://localhost/echo'
hello
world

改行のような特別な文字 (\n) とタブ (\t) はC形式のエスケープシーケンスを使ってエスケープすることができます。しかし、注目に値する例外はダラー記号($)です。NGINX 0.8.20の時点では、この文字をエスケープする安全な方法はまだありません。(A work-around might be to use a $echo_dollor variable that is always evaluated to the constant $ character. この機能はこのモジュールの将来のバージョンで導入されるかも知れません。)

echo v0.28 リリースの時点で、-n オプションを使って出力内の改行文字を抑制することができます。

location /echo {
    echo -n "hello, ";
    echo "world";
}

/echo へのアクセスは以下をもたらします

$ curl 'http://localhost/echo'
hello, world

Leading -n in variable values won’t take effect and will be emitted literally, as in

location /echo {
    set $opt -n;
    echo $opt "hello,";
    echo "world";
}

これは以下の出力をもたらします。

$ curl 'http://localhost/echo'
-n hello,
world

One can output leading -n literals and other options using the special -- option like this

location /echo {
    echo -- -n is an option;
}

which yields

$ curl 'http://localhost/echo'
-n is an option

echo_duplicate

構文:echo_duplicate <count> <string>
デフォルト:none
コンテキスト:location, location if
Phase:content

最初の引数によって指定される回数を使って、2つ目の引数によって指示される文字列のデュプリケートを出力します。

例えば、

location /dup {
    echo_duplicate 3 "abc";
}

出力は"abcabcabc"となるでしょう。

カウント数の中のアンダスコアがPerlのように許可されます。例えば、"hello, world"の1000,000,000インスタンスを出力するには:

location /many_hellos {
    echo_duplicate 1000_000_000 "hello, world";
}

count引数は0がありえますが、負数ではありません。同様に、二つ目の文字列引数は空文字("")がありえます。

echo ディレクティブと異なり、結果に改行が追加されません。So it’s possible to “abuse” this directive as a no-trailing-newline version of echo by using “count” 1, as in

location /echo_art {
   echo_duplicate 2 '---';
   echo_duplicate 1 ' END ';  # we don't want a trailing newline here
   echo_duplicate 2 '---';
   echo;  # we want a trailing newline here...
}

以下を得ます

------ END ------

このディレクティブは最初 v0.11で導入されました。

echo_flush

構文:echo_flush
デフォルト:none
コンテキスト:location, location if
Phase:content

ソケットを使ってNGINXが内部的に持つ出力フィルターにバッファされているデータをすぐにクライアント側に送信させます。

技術的にはそのコマンドは単にflushスロットが1に設定されたngx_buf_t オブジェクトを出力します。つまり、ある奇妙なサードパーティの出力フィルタモジュールがNGINXの(最後の)writeフィルタにたどり着く前にブロックするかも知れません。

このディレクティブは何も引数を取りません。

次の例を考えます:

location /flush {
    echo hello;

    echo_flush;

    echo_sleep 1;
    echo world;
}

そしてクライアントサイドで、/flushにアクセスするためにcurlを使うと、すぐに“hello”を見ますが1秒後に最後の"world"の行を見るでしょう。上の例でecho_flushの呼び出し無しでは、ほとんどの場合NGINXの内部バッファのために1秒過ぎるまで何も出力を見ないでしょう。

このディレクティブはサブリクエストが含まれる場合には出力バッファのフラッシュに失敗するでしょう。次の例を考えます:

location /main {
    echo_location_async /sub;
    echo hello;
    echo_flush;
}
location /sub {
    echo_sleep 1;
}

そうすると、クライアントは/subへのサブリクエストが実際に実行を開始する前にたとえecho_flushが実行されたとしても"hellow"を見ることはないでしょう。echo_location_asyncに送信された /main の出力は延期されしっかりとバッファされるでしょう。

これはサブリクエストが初期化される前に送信された出力へは適用 されません。上で与えられた例の修正バージョンについては:

location /main {
    echo hello;
    echo_flush;
    echo_location_async /sub;
}
location /sub {
    echo_sleep 1;
}

クライアントは/sub がsleepに入る前に"hello"をすぐに見るでしょう。

echo, echo_sleep および echo_location_asyncも見てください。

echo_sleep

構文:echo_sleep <seconds>
デフォルト:none
コンテキスト:location, location if
Phase:content

引数で指定された時間、これは秒です、sleepします。

この操作はサーバサイドではecho_blocking_sleep ディレクティブと違ってブロックしないので、NGINXワーカープロセス全体をブロックしないでしょう。

期間は少数点の後に3つの数値を取るかも知れません。そして0.001より大きくなければなりません。

例としては、

location /echo_after_sleep {
    echo_sleep 1.234;
    echo resumed!;
}

裏でリクエストごとに"sleep" ngx_event_tオブジェクトをセットアップし、NGINXイベントモデルに独自のイベントを使ってタイマーを追加し、イベントのタイムアウトをただ待ちます。"sleep"イベントはリクエストごとのため、このディレクティブは並行のサブリクエストで動作します。

echo_blocking_sleep

構文:echo_blocking_sleep <seconds>
デフォルト:none
コンテキスト:location, location if
Phase:content

これは echo_sleep ディレクティブのブロッキングバージョンです。

詳細はecho_sleepのドキュメントを見てください。

裏で、NGINXコアによって提供されるPOSIX互換システムのusleepにマップされるngx_msleepマクロを呼びます。

実行される間このディレクティブは現在のNGINXワーカープロセスを完全にブロックするため、プロダクション環境では絶対に使わないことに注意してください。

echo_reset_timer

構文:echo_reset_timer
デフォルト:none
コンテキスト:location, location if
Phase:content

タイマーの開始時間をnow、つまりこのコマンドがリクエストで実行された時間、に再設定します。

タイマーの開始時間のデフォルトは現在のリクエストが開始した時間で、一つのlocationでもしかすると複数回このディレクティブによって上書きすることができます。例えば:

location /timed_sleep {
    echo_sleep 0.03;
    echo "$echo_timer_elapsed sec elapsed.";

    echo_reset_timer;

    echo_sleep 0.02;
    echo "$echo_timer_elapsed sec elapsed.";
}

クライアント側の出力は以下のようになるかも知れません

$ curl 'http://localhost/timed_sleep'
0.032 sec elapsed.
0.020 sec elapsed.

取得する実際の数値はシステムの現在の活動状態によって少し変わるかもしれません。

このディレクティブの呼び出しは裏にあるNGINXタイマーを(設定ファイル内のいたるところで指定されたタイマーの解像度に関係なく)現在のシステム時間に強制的に更新させるでしょう。更に、$echo_timer_elapsed変数の参照もタイマーの更新を強制的に引き起こすでしょう。

echo_sleep$echo_timer_elapsedも見てください。

echo_read_request_body

構文:echo_read_request_body
デフォルト:none
コンテキスト:location, location if
Phase:content

(ボディがそれほど大きくなくNGINXによってローカルの一時ファイルに保存することができる場合は)$request_body 変数が常に空以外の値を持つように、明示的にリクエストボディを読み込みます。

現在のリクエストは親によって指定された"artificial"ボディを持つサブリクエストかも知れないため、もとのクライアントのリクエストボディでは無いかも知れないことに注意してください。

このディレクティブは echo_sleepのように、それ自身では何も生成しません。

以下は、もとのHTTPクライアントリクエスト(ヘッダとボディが含まれます)をエコーバックする例です:

location /echoback {
    echo_duplicate 1 $echo_client_request_headers;
    echo "\r";
    echo_read_request_body;
    echo $request_body;
}

私側にはこの/echobackのコンテントはこのように見えます(サーバ上でこのlocationにアクセスするためにPerlのLWPユーティリティを使っていました)。

$ (echo hello; echo world) | lwp-request -m POST 'http://localhost/echoback'
POST /echoback HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: lwp-request/5.818 libwww-perl/5.820
Content-Length: 12
Content-Type: application/x-www-form-urlencoded

hello
world

/echoback がメインのリクエストのため、$request_body は元のクライアントのリクエストボディを保持します。

NGINX 0.7.56より前は、$request_body が NGINX 0.7.58 で初めて導入されたため、このディレクティブの使用は意味がありません。

このディレクティブ自身は v0.14で初めて導入されました。

echo_location_async

構文:echo_location_async <location> [<url_args>]
デフォルト:none
コンテキスト:location, location if
Phase:content

二つ目の引数で指定されたurl引数を使って(一つ目の引数で)指定されたlocationにサブリクエストのGETを発行します。

NGINX 0.8.20 の時点では、location引数はngx_http_subrequestの制限により名前付きのlocationをサポート しません同じような echo_location ディレクティブに関しても同じことが言えます。

とても簡単な例

location /main {
    echo_location_async /sub;
    echo world;
}
location /sub {
    echo hello;
}

/main にアクセスすると以下を得ます

hello
world

並行して複数のlocationを呼び出すことも可能です:

location /main {
    echo_reset_timer;
    echo_location_async /sub1;
    echo_location_async /sub2;
    echo "took $echo_timer_elapsed sec for total.";
}
location /sub1 {
    echo_sleep 2; # sleeps 2 sec
    echo hello;
}
location /sub2 {
    echo_sleep 1; # sleeps 1 sec
    echo world;
}

/mainのアクセスは以下をもたらします

$ time curl 'http://localhost/main'
hello
world
took 0.000 sec for total.

real  0m2.006s
user  0m0.000s
sys   0m0.004s

メインハンドラ /main がサブリクエスト /sub1/sub2を完了するのを待た ない、そして素早く続けるので、"0.000 sec"の時間の結果となるのが見えるでしょう。リクエスト全体では、 /sub1/sub2 は並行して実行(あるいはもっと正確には"同時に")するため、完了するのに全体で約2秒かかります。

上の例で echo_blocking_sleep を代わりに使う場合、"blocking sleep"ブロックはNGINXワーカープロセス全体をブロックするため同じ出力を得ますが全体では3秒掛かります。

locationは任意のクエリ文字列の引数を取ることもできます。例えば、

location /main {
    echo_location_async /sub 'foo=Foo&bar=Bar';
}
location /sub {
    echo $arg_foo $arg_bar;
}

/mainのアクセスは以下をもたらします

$ curl 'http://localhost/main'
Foo Bar

クエリ文字列は直接"?"を付けてlocationの引数につなげることは 許可されていません。例えば、/sub?foo=Foo&bar=Barは無効なlocatoinであり、このディレクティブの最初の引数として渡されてはなりません。

技術的に言うと、このディレクティブはNGINXのコンテントハンドラが1つ以上のサブリクエストを直接発行する例です。私の知る限りでは、fancyindex module も同じようなことをします ;)

@fooのような名前付きのlocationはここではサポートされません

このディレクティブは論理的にはecho_subrequest_asyncのGETバージョンと等価です。例えば:

echo_location_async /foo 'bar=Bar';

これは以下と論理的に等価です

echo_subrequest_async GET /foo -q 'bar=Bar';

しかしこのディレクティブの呼び出しはGETのようなHTTPメソッド名および-qのようなオプションのパースをしなくても良いため、GETが付いたecho_subrequest_asyncよりもわずかに高速です。

標準のstandard SSI モジュールを無効にした場合にこのディレクティブには良く知られた問題があります。詳細は良く知られた問題を見てください。

このディレクティブはこのモジュールの v0.09で最初に導入され、少なくとも NGINX 0.7.46 を必要とします。

echo_location

構文:echo_location <location> [<url_args>]
デフォルト:none
コンテキスト:location, location if
Phase:content

echo_location_asyncディレクティブに似ていますが、 echo_locationは並行ではなく 順番に サブリクエストを発行します。すなわち、このディレクティブに続くコンテントハンドラのディレクティブはこのディレクティブによって発行されたサブリクエストが完了するまで実行されないでしょう。

最終的な応答ボディは、出力の中でタイミング変数が使われた場合のみ、echo_location_asyncが代わりに使われた場合とほとんど同じになります。

次の例を考えます:

location /main {
    echo_reset_timer;
    echo_location /sub1;
    echo_location /sub2;
    echo "took $echo_timer_elapsed sec for total.";
}
location /sub1 {
    echo_sleep 2;
    echo hello;
}
location /sub2 {
    echo_sleep 1;
    echo world;
}

(もしecho_location_async を代わりに使った場合は2秒に対して)上の /main は終了するのに全体で3秒掛かるでしょう。私のマシーン上での動きは以下の結果になります:

$ curl 'http://localhost/main'
hello
world
took 3.003 sec for total.

real  0m3.027s
user  0m0.020s
sys   0m0.004s

このディレクティブは論理的には echo_subrequestのGETバージョンと等価です。例えば:

echo_location /foo 'bar=Bar';

これは以下と論理的に等価です

echo_subrequest GET /foo -q 'bar=Bar';

しかしこのディレクティブの呼び出しはGETのようなHTTPメソッド名および-qのようなオプションのパースをしなくても良いため、GETが付いたecho_subrequestよりもわずかに高速です。

裏ではそれはcontinuation としてngx_http_post_subrequest_t オブジェクトを生成し、それを ngx_http_subrequest 関数呼び出しに渡します。NGINXはこの"continuation"を ngx_http_finalize_request 関数呼び出しの中で再び開くでしょう。親のリクエストのコンテントハンドラの実行を再開し、もしあれば次のディレクティブ(コマンド)の実行を開始します。

@fooのような名前付きのlocationはここではサポートされません

このディレクティブは v0.12で初めて導入されました。

引数の意味についての詳細は echo_location_async を見てください。

echo_subrequest_async

構文:echo_subrequest_async <HTTP_method> <location> [-q <url_args>] [-b <request_body>] [-f <request_body_path>]
デフォルト:none
コンテキスト:location, location if
Phase:content

HTTPメソッドを使って非同期のサブリクエスト、任意のurl引数(あるいはクエリ文字列)、および文字列あるいはボディを含むファイルへのパスとして定義することができる任意のリクエストボディを初期化します。

このディレクティブは一般化された echo_location_async ディレクティブのバージョンととても良く似ています。

以下は使い方の例の小さな実演です:

location /multi {
    # body defined as string
    echo_subrequest_async POST '/sub' -q 'foo=Foo' -b 'hi';
    # 絶対パスで無い場合はnginxのprefixパスに相対的な、ファイルパスとして定義されたボディ
    echo_subrequest_async PUT '/sub' -q 'bar=Bar' -f '/tmp/hello.txt';
}
location /sub {
    echo "querystring: $query_string";
    echo "method: $echo_request_method";
    echo "body: $echo_request_body";
    echo "content length: $http_content_length";
    echo '///';
}

そして、クライアント側では:

$ echo -n hello > /tmp/hello.txt
$ curl 'http://localhost/multi'
querystring: foo=Foo
method: POST
body: hi
content length: 2
///
querystring: bar=Bar
method: PUT
body: hello
content length: 5
///

以下はサブリクエストを処理するために標準的なproxy module を使った面白い例です:

location /main {
    echo_subrequest_async POST /sub -b 'hello, world';
}
location /sub {
    proxy_pass $scheme://127.0.0.1:$server_port/proxied;
}
location /proxied {
    echo "method: $echo_request_method.";

    # ここで明示的にbody、あるいは$echo_request_body を読み込む必要があります
    #   empty("")と同じでしょう
    echo_read_request_body;

    echo "body: $echo_request_body.";
}

そして、クライアント側では以下を見るかも知れません

$ curl 'http://localhost/main'
method: POST.
body: hello, world.

@fooのような名前付きのlocationはここではサポートされません

このディレクティブは幾つかのオプションを取ります:

-q <url_args>        サブリクエストのためのURL引数(あるいはURLクエリ文字列)を指定します。

-f <path>            ファイルの内容がサブリクエストのリクエストボディとして提供される
                     ファイルのパスを指定します。

-b <data>            リクエストボディのデータを指定します。

このディレクティブは v0.15で初めて導入されました。

ボディのためのファイルパスを定義するための :github:-f オプションはv0.35 <openresty/echo-nginx-module/tags>で導入されました。

echo_subrequest および echo_location_async ディレクティブも見てください。

標準のstandard SSI モジュールを無効にした場合にこのディレクティブには良く知られた問題があります。詳細は良く知られた問題を見てください。

echo_subrequest

構文:echo_subrequest <HTTP_method> <location> [-q <url_args>] [-b <request_body>] [-f <request_body_path>]
デフォルト:none
コンテキスト:location, location if
Phase:content

これは echo_subrequest_async ディレクティブの非同期バージョンです。echo_locationのように、それはNGINXワーカプロセスをブロックせず(echo_blocking_sleepはします)、むしろサブリクエストのチェインと一緒に制御を渡すためにcontinuationを使用します。

詳細は echo_subrequest_asyncを見てください。

@fooのような名前付きのlocationはここではサポートされません

このディレクティブは v0.15で初めて導入されました。

echo_foreach_split

構文:echo_foreach_split <delimiter> <string>
デフォルト:none
コンテキスト:location, location if
Phase:content

1つ目の引数で指定されるデリミタを使って、二つ目の引数のstringを分割し、結果の項目を最初から最後まで繰り返します。例えば:

location /loop {
    echo_foreach_split ',' $arg_list;
        echo "item: $echo_it";
    echo_end;
}

/main へのアクセスは以下をもたらします

$ curl 'http://localhost/loop?list=cat,dog,mouse'
item: cat
item: dog
item: mouse

前の例で見たように、このディレクティブはecho_end ディレクティブと常に一緒に無ければなりません。

平行するecho_foreach_split ループは許可されますが、入れ子は現在のところ禁止されています。

delimiter は以下のように 複数の 任意の文字を含むことができます。

# this outputs "cat\ndog\nmouse\n"
echo_foreach_split -- '-a-' 'cat-a-dog-a-mouse';
echo $echo_it;
echo_end;

論理的に言うと、このループ構造は(以前の例を使って)perlでの split関数を組み合わせた、まさにforeachループです。

foreach (split ',', $arg_list) {
    print "item $_\n";
}

複数の.js あるいは .css リソースを丸ごとまとめるのにも便利だと気づくでしょう。例は次のようになります:

location /merge {
    default_type 'text/javascript';

    echo_foreach_split '&' $query_string;
    echo "/* JS File $echo_it */";
    echo_location_async $echo_it;
    echo;
    echo_end;
}

そして、クエリ文字列内で指定される.js リソースをマージするためにアクセス/マージします:

$ curl 'http://localhost/merge?/foo/bar.js&/yui/blah.js&/baz.js'

以前の例での/merge location によって生成されたマージされた応答をキャッシュするために、サードパーティのNGINXキャッシュモジュールを使うこともできます。

このディレクティブは v0.17で初めて導入されました。

echo_end

構文:echo_end
デフォルト:none
コンテキスト:location, location if
Phase:content

このディレクティブはループのボディおよび echo_foreach_splitのような条件付制御構造を終了させるために使われます。

このディレクティブは v0.17で初めて導入されました。

echo_request_body

構文:echo_request_body
デフォルト:none
コンテキスト:location, location if
Phase:content

前に読み込んだリクエストボディの内容を出力します。

裏では、大まかに以下のような実装がされています:

if (r->request_body && r->request_body->bufs) {
    return ngx_http_output_filter(r, r->request_body->bufs);
}

$echo_request_body と $request_body 変数と異なり、このディレクティブはもしリクエストボディの一部分あるいは全ての部分がディスク上の一時ファイルに保存されている場合、全てのリクエストボディを表示するでしょう。

まだリクエストボディが読み込まれていない場合は、"no-op"です。

このディレクティブはv0.18で初めて導入されました。

echo_read_request_bodyも見てください。

echo_exec

構文:echo_exec <location> [<query_string>]
構文:echo_exec <named_location>
デフォルト:none
コンテキスト:location, location if
Phase:content

指定されたlocationへの内部リダイレクトをします。以下のように、通常のlocationのために任意のクエリ文字列を指定することができます

location /foo {
    echo_exec /bar weight=5;
}
location /bar {
    echo $arg_weight;
}

あるいは、以下も等価です

location /foo {
    echo_exec /bar?weight=5;
}
location /bar {
    echo $arg_weight;
}

名前付きのlocationもサポートされます。例は次のようになります:

location /foo {
    echo_exec @bar;
}
location @bar {
    # @barではなく /foo を取得するでしょう
    #  これはnginxの潜在的なバグのためです
    echo $echo_request_uri;
}

しかし、名前付きのlocationのリダイレクトに関して(どのような)クエリ文字列もngx_http_named_location 関数の制限のために常に無視されるでしょう。

echo_exec ディレクティブの前に何かを出力しようとしてはいけません。そうでなければリダイレクトしたい先のlocationの適切な応答を見れないでしょう。何らかの出力をするとリダイレクトが起こる前に元のlocationハンドラにHTTPヘッダを送信させることになるだろうからです。

技術的に言うと、このディレクティブはNGINX内部API関数 ngx_http_internal_redirectngx_http_named_location を公開します。

このディレクティブは v0.21で初めて導入されました。

echo_status

構文:echo_status <status-num>
デフォルト:200
コンテキスト:location, location if
Phase:content

デフォルトの応答ステータスコードを指定します。デフォルトは 200 です。このディレクティブは宣言的で、他のechoのようなディレクティブとの相対的な順番は重要ではありません。

例は以下のようになります。

location = /bad {
    echo_status 404;
    echo "Something is missing...";
}

このような応答を得ます:

HTTP/1.1 404 Not Found
Server: nginx/1.2.1
Date: Sun, 24 Jun 2012 03:58:18 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

Something is missing...

このディレクティブは v0.40 リリースで初めて導入されました。

フィルタ ディレクティブ

以下のディレクティブの使用はこのモジュールのフィルタ登録を行います。デフォルトでは、このモジュールによって何もフィルタは登録されません。

各フィルタのディレクティブは(もし引数があれば)その引数の変数の補間をサポートします。

echo_before_body

構文:echo_before_body [options] [argument]...
デフォルト:none
コンテキスト:location, location if
Phase:出力フィルタ

echo ディレクティブのフィルタバージョンです。裏で動くコンテントハンドラによって生成された元の出力の最初にその出力を追加します。

例としては、

location /echo {
    echo_before_body hello;
    proxy_pass $scheme://127.0.0.1:$server_port$request_uri/more;
}
location /echo/more {
    echo world
}

クライアント側からの/echoアクセスは、以下をもたらします。

hello
world

以前の例では、"主要なコンテンツ"を生成する裏で実行されるコンテントハンドラとして 標準プロキシモジュール を借りています。

このフィルタ ディレクティブの複数のインスタンスも許可されます:

location /echo {
    echo_before_body hello;
    echo_before_body world;
    echo !;
}

クライアント側では、出力は以下のようになります。

$ curl 'http://localhost/echo'
hello
world
!

この例では、裏で動くコンテントハンドラとしてこのモジュールによって提供される コンテント ハンドラ ディレクティブ も使用します。

このディレクティブは、echoディレクティブのような -n および -- オプションもサポートします。

このディレクティブは同じようなディレクティブ echo_after_body と混ぜることができます。

echo_after_body

構文:echo_after_body [argument]...
デフォルト:none
コンテキスト:location, location if
Phase:出力フィルタ

echo_before_body ディレクティブにとても似ていますが、裏で実行されるコンテントハンドラによって生成された元の出力の最後にその出力を追加します

以下は簡単な例です:

location /echo {
    echo_after_body hello;
    proxy_pass http://127.0.0.1:$server_port$request_uri/more;
}
location /echo/more {
    echo world
}

クライアント側からの/echoアクセスは、以下をもたらします。

world
hello

複数のインスタンスが許可されます:

location /echo {
    echo_after_body hello;
    echo_after_body world;
    echo i;
    echo say;
}

/echo location へアクセスする間のクライアント側の出力は以下のようになります。

i
say
hello
world

このディレクティブは、echoディレクティブのような -n および -- オプションもサポートします。

このディレクティブは同じようなディレクティブ echo_before_body と混ぜることができます。

変数

$echo_it

これは、ちょうどPerlでの$_のような、echo_foreach_splitによって使われる"topic variable"です。

$echo_timer_elapsed

この変数は、現在のリクエストの開始(サブリクエストかも知れません)、あるいはecho_reset_timerコマンドの最後の起動から経過した秒数を保持します。

結果の時間は小数点の後に3つの数値を取ります。

この変数の参照は、echo_reset_timerディレクティブと同じように、設定ファイル内のいたるところで設定されているタイマーの解像度に関係なく、裏で動いているNGINXタイマーに現在のシステム時間を更新させるでしょう。

$echo_request_body

リクエストボディが一時ファイルに全く保存されていない場合は、前もって読み込まれた(サブ)リクエストのリクエストボディが評価されます。リクエストボディがとても大きな場合でも表示するには、echo_request_body ディレクティブを使ってください。

$echo_request_method

現在のリクエスト(サブリクエストかも知れません)のHTTPリクエストメソッドを評価します。

裏では、r->method_nameに保存されている文字列データを取るだけです。

それを$echo_client_request_method 変数と比較します。

少なくともNGINX 0.8.20とそれ以前は、http core moduleによって提供される$request_method 変数は実際のところ$echo_client_request_method がすることをしています。

この変数は v0.15で初めて導入されました。

$echo_client_request_method

現在のリクエストがサブリクエストであってもメインのリクエストのHTTPメソッドを常に評価します。

裏では、r->main->method_nameに保存されている文字列データを取るだけです。

それを$echo_request_method 変数と比較します。

この変数は v0.15で初めて導入されました。

$echo_client_request_headers

元のクライアントのリクエストヘッダを評価します。

名前が示すとおり、もし現在サブリクエストが実行されている場合でも、常にメインのリクエスト(あるいはクライアントリクエスト)をとるでしょう。

簡単な例は以下の通りです:

location /echoback {
    echo "headers are:"
    echo $echo_client_request_headers;
}

/echobackのアクセスは以下をもたらします

$ curl 'http://localhost/echoback'
headers are
GET /echoback HTTP/1.1
User-Agent: curl/7.18.2 (i486-pc-linux-gnu) libcurl/7.18.2 OpenSSL/0.9.8g
Host: localhost:1984
Accept: */*

Behind the scene, it recovers r->main->header_in (or the large header buffers, if any) on the C level and does not construct the headers itself by traversing parsed results in the request object.

この変数はv0.15で初めて導入されました。

$echo_cacheable_request_uri

現在の(サブ)リクエストのURI(通常は/から始まる)のパースされた形式を評価します。$echo_request_uri 変数と異なり、キャッシュ可能です。

詳細は$echo_request_uri を見てください。

この変数はv0.17で初めて導入されました。

$echo_request_uri

現在の(サブ)リクエストのURI(通常は/から始まる)のパースされた形式を評価します。$echo_cacheable_request_uri 変数と異なり、キャッシュ可能ではありません

$request_uri は現在のリクエストのURIのパースされていない形式のため、これはHttpCoreModuleによって公開される$request_uri変数とかなり異なります。

この変数はv0.17で初めて導入されました。

$echo_incr

1から始まる現在のカウント数を常に生成するカウンターです。サブリクエストでアクセスされた場合でも、カウンターはメインリクエストに常に関連します。

以下の例を考えてみましょう

location /main {
    echo "main pre: $echo_incr";
    echo_location_async /sub;
    echo_location_async /sub;
    echo "main post: $echo_incr";
}
location /sub {
    echo "sub: $echo_incr";
}

/mainのアクセスは以下をもたらします

main pre: 1
sub: 3
sub: 4
main post: 2

このディレクティブはv0.18で初めて導入されました。

$echo_response_status

現在の(サブ)リクエストのステータスコードを評価します。何もなければnullです。

裏では、r->headers_out->statusの単なるテキスト表現です。

このディレクティブは v0.23 リリースで初めて導入されました。

インストール

(NGINXコアおよび他の多くの良いものと同じく)このモジュールをngx_openresty bundleを使ってインストールすることをお勧めします。ダウロードとシステムにnxg_openrestyをインストールする方法は詳細な説明を見てください。これは準備をするのに最も簡単で最も安全な方法です。

他のやり方として、NGINXソースを使って手動でこのモジュールをインストールすることができます:

例えば、バージョン1.7.7 (NGINX互換性を見てください)のNGINXのソースコードをnginx.orgからダウンロードし、このモジュールを使ってソースをビルドします:

$ wget 'http://nginx.org/download/nginx-1.7.7.tar.gz'
$ tar -xzvf nginx-1.7.7.tar.gz
$ cd nginx-1.7.7/

# Here we assume you would install you nginx under /opt/nginx/.
$ ./configure --prefix=/opt/nginx \
      --add-module=/path/to/echo-nginx-module

$ make -j2
$ make install

echo-nginx-module file listからこのモジュールのリリースtarballの最新バージョンをダウンロードします。

また、このモジュールはngx_openresty bundleの中に含まれていてデフォルトで有効です。

互換性

NGINXの以下のバージョンがこのモジュールで動作するはずです:

  • 1.7.x (最後のテスト: 1.7.7)
  • 1.6.x
  • 1.5.x (最後のテスト: 1.5.12)
  • 1.4.x (最後のテスト: 1.4.4)
  • 1.3.x (最後のテスト: 1.3.7)
  • 1.2.x (最後のテスト: 1.2.9)
  • 1.1.x (最後のテスト: 1.1.5)
  • 1.0.x (最後のテスト: 1.0.11)
  • 0.9.x (最後のテスト: 0.9.4)
  • 0.8.x (最後のテスト: 0.8.54)
  • 0.7.x >= 0.7.21 (最後のテスト: 0.7.68)

特に、

0.6.x および 0.5.x のNGINXの早期バージョンは全く動作しない でしょう。

0.7.21より上のNGINXのいずれかバージョンでこのモジュールが動作しないことに気づいた場合は、echo.reporting-a-bugを考えてみてください。

知られている問題

NGINXの未知のバグ(NGINX 1.7.7にもまだあります)のために、標準の SSI モジュールecho_location_async および echo_subrequest_async が出力チェインをメインの出力に正しくマージされることを必要とします。幸いなことに、SSIモジュールはNGINXの configureプロセスの間にデフォルトで有効にされます。

SSIモジュールを有効にせずにこのディレクティブを呼び出すと、どのサブリクエストの内容も無しに不完全な応答を取得し、以下のように NGINXの error.logにアラートメッセージを得るでしょう:

[alert] 24212#0: *1 the http output chain is empty, client: 127.0.0.1, ...

テストのためにこのモジュールを使うモジュール

以下のモジュールはテストスィートの中でこの echo モジュールを利用します:

  • Memc モジュールはほとんど全てのmemcached TCP プロトコルをサポートします。
  • Headers More モジュールは指定した条件での入力および出力ヘッダの追加、設定、消去をすることができます。
  • echo モジュール自身。

どのような形でも echo を使用する他のモジュールがあればメールしてください。上のリストにそれらを追加するでしょう :)

コミュニティ

英語のメーリングリスト

openresty-en メーリングリストは英語を話す人のためのものです。

中国語のメーリングリスト

openresty メーリングリストは中国語を話す人のためのものです。

バグのレポート

テストとコードの調整に多くの努力がされていますが、このモジュールのどこかにいくつかの深刻なバグが潜んでいるに違いありません。なんらかのおかしな動きがあった場合は、躊躇しないでください

  1. GitHubによって提供される issue tracking interface でチケットを作成する。
  2. あるいは、 OpenResty Communityにバグレポート、質問、あるいはパッチを送信する。

ソースリポジトリ

github上のopenresty/echo-nginx-moduleで利用可能です。

変更

このモジュールの各リリースの変更は、ngx_openresty バンドルのchange logsから取得することができます:

http://openresty.org/#Changes

テストスィート

このモジュールはPerl駆動テストスィートが付いています。test casesdeclarative です。Perl世界の Test::Nginx <http://search.cpan.org/perldoc?Test::Nginx> モジュールに感謝します。

アナタの側でそれを実行するには:

$ PATH=/path/to/your/nginx-with-echo-module:$PATH prove -r t

NGINXサーバ バイナリを変更した場合はテストスィートを実行する前に全てのNGINXプロセスを終了する必要があります。

単一のNGINX サーバ (デフォルトでは localhost:1984) は全てのテストスクリプト(.t ファイル)に渡って使用されるため、proveユーティリティを起動する時に-jNを指定することで並行してテストスィートを実行することは意味がありません。

テストスィートの幾つかの部分はNGINXをビルドする時に標準モジュール proxy, rewrite および SSI を有効にする必要があります。

TODO

  • サブリクエストのecho_after_body ディレクティブを修正します。

  • ディレクティブecho_read_client_request_body および echo_request_headers を追加します。

  • NGINXのログ機能を直接設定ファイルから使うために echo_log を追加し、特定のログレベルが指定できます:

    echo_log debug "I am being called.";
    
  • echo_subrequest_async および echo_subrequest にオプション -h および -t のオプションのサポートを追加。例えば、

    echo_subrequest POST /sub -q 'foo=Foo&bar=Bar' -b 'hello' -t 'text/plan' -h 'X-My-Header: blah blah'
    
  • サブリクエストが親のリクエスト(つまり、当のサブリクエストを呼び出している現在のリクエスト)からキャッシュされた変数を継承しなければならないかを制御するオプションを追加。現在のところ、このモジュールによって発行されたサブリクエストは親のリクエストからキャッシュされた変数を継承しません。

  • r->main->count - 1を表示するために、新しい変数$echo_active_subrequests を追加。

  • echo_file およびecho_cached_file ディレクティブを追加。

  • 既存の$echo_client_request_headers変数と一緒にするために新しい変数$echo_request_headers を追加。

  • 新しいディレクティブecho_foreachを追加

    echo_foreach 'cat' 'dog' 'mouse';
    echo_location_async "/animals/$echo_it";
    echo_end;
    
  • 新しいディレクティブecho_foreach_rangeを追加

    echo_foreach_range '[1..100]' '[a-zA-z0-9]';
    echo_location_async "/item/$echo_it";
    echo_end;
    
  • 新しいディレクティブecho_repeatを追加

    echo_repeat 10 $i {
        echo "Page $i";
        echo_location "/path/to/page/$i";
    }
    

    これは言い換えると

    echo_foreach_range $i [1..10];
    echo "Page $i";
    echo_location "/path/to/page/$i";
    echo_end;
    

    このアイデアを提供してくれて、Marcus Clyne ありがとう。

  • 新しいディレクティブecho_random_min および echo_random_maxによる下限/上限を持つ非負数のランダム整数値を常に返す、新しい変数$echo_randomを追加。例えば:

    echo_random_min  10
    echo_random_max  200
    echo "random number: $echo_random";
    

    このアイデアを提供してくれて、Marcus Clyne ありがとう。

Getting involved

AuthorにパッチをサブミットするかGitHub上の echo.source-repository に少しコミットするように依頼することはとても歓迎します。

Author

Yichun “agentzh” Zhang (章亦春) <agentzh@gmail.com>, CloudFlare Inc.

このwikiページもauthor自身によって整備されており、同様に誰でもこのページを改善することは奨励されています。