As far as the
NGINX.HeadersIn
and
NGINX.HeadersOut
classes of
the ngx_http_js_module
implemented almost fully now we can talk about this headers_in
and headers_out
structs a little.
NGINXのHTTPヘッダは2つの部分に分かれます: 入力リクエストヘッダ(headers_in 構造) と出力リクエストヘッダ (headers_out 構造)。応答としてエンティティのようなものはありません。全てのデータは同じ1つのリクエスト構造に格納されます。実際の応答データはリクエストデータと headers_out
構造フィールドから構築されます。
NGINX内の全ては高度に最適化されます。No memory overhead caused by strings copying, no memory leaks and alloc/free burden as far as memory is managed with pools, no wasted CPU cycles by comparing those strings again and again, everything is cached in a sane way, complicated things are pre-calculated at configure stage. So are the input (and output) headers and all this optimizations are the root of their complexity and beauty.
HTTPヘッダについて少し話しましょう。私達全員が多くのヘッダを見てきています。そして、HTTPのヘッダがとても柔軟なデータ形式であることを知っています。クライアントは一つの簡単なヘッダ、あるいは独自の各行に同じ名前を持つ多くのヘッダ、あるいは多くの行に分割された1つの大きなヘッダを送信するかも知れません。それは幾分乱雑です。今度はNGINXがその乱雑さを統治する番です。
NGINXは頻繁に使われるよく知られたヘッダに注意します(良く知られたheaders_inのリスト)。NGINXはヘッダをパースし使いやすい場所に格納します(headers_in
内の直接のポインタ)。良く知られたヘッダが1つ以上の値(例えばクッキーあるいはCache-Control)から成るかも知れない場合は、NGINXはそれを配列で処理できるかも知れません。数値を持つと知られているヘッダ(Content-Length, Expires) については、NGINXはテキストをパースし、それを直接 headers_in
構造に格納します。ヘッダの残りの全ては注意深く headers_in
構造の中の1つの簡単なリストに格納されます。つまり何も紛失されません。
とは言っても、値を取得するには少なくとも3つの方法があります。既に知っているように、各入力ヘッダ値は headers_in->headers リスト(typeof ngx_list_t) 内のブルーとフォース照合によって取得されるかも知れません。良く知られたヘッダ値は headers_in
構造の中の簡単なポインターの助けによって見つかるかも知れません。(ヘッダが存在しない場合はNULL
)。そして、良く知られた数字のヘッダに関して、値を取得する簡単な方法でさえあります: headers_in
内の特別なフィールド
(content_length_n が良い例です)。
コンパイル時にどのヘッダを読み込もうとしているかを知っている場合は、これが良いです。しかし、名前が単なる文字列の場合に実行時に名前でヘッダを取得するにはどうすればよいか?このような種類の状況のために、ヘッダーのスマートなハッシュcmcf->headers_in_hash
を持ちます。ヘッダがNGINXに知られている場合、ヘッダ名はこのハッシュ内にキャッシュされ、比較的高速にヘッダの値を見つけることができます。ヘッダがハッシュされていない場合、ヘッダの全体のリストを走り読みし、全てのヘッダ名を比較する必要があります。これは速くありませんが、遅くもありません。残念ながら、名前によってヘッダのデジタル(すでにパースされた)表現のオフセットを取得する方法はないため、必要になる度にテキストの値をパースする必要があります。そして、実行時にランダムなヘッダのデータタイプ表現を知らない限りそれが普通です。
So far, we can get a full list of input headers and run through it to
enumerate, direct access a header with its personal field in headers_in
structure; and we even may get the already parsed value if the header is
of type number, time etc.
以下は幾つかの例です。
static ngx_table_elt_t *
search_headers_in(ngx_http_request_t *r, u_char *name, size_t len) {
ngx_list_part_t *part;
ngx_table_elt_t *h;
ngx_uint_t i;
/*
Get the first part of the list. There is usual only one part.
*/
part = &r->headers_in.headers.part;
h = part->elts;
/*
Headers list array may consist of more than one part,
so loop through all of it
*/
for (i = 0; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
/* The last part, search is done. */
break;
}
part = part->next;
h = part->elts;
i = 0;
}
/*
Just compare the lengths and then the names case insensitively.
*/
if (len != h[i].key.len || ngx_strcasecmp(name, h[i].key.data) != 0) {
/* This header doesn't match. */
continue;
}
/*
Ta-da, we got one!
Note, we'v stop the search at the first matched header
while more then one header may fit.
*/
return &h[i];
}
/*
No headers was found
*/
return NULL;
}
ngx_table_elt_t *
search_hashed_headers_in(ngx_http_request_t *r, u_char *name, size_t len) {
ngx_http_core_main_conf_t *cmcf;
ngx_http_header_t *hh;
u_char *lowcase_key;
ngx_uint_t i, hash;
/*
Header names are case-insensitive, so have been hashed by lowercases key
*/
lowcase_key = ngx_palloc(r->pool, len);
if (lowcase_key == NULL) {
return NULL;
}
/*
Calculate a hash of lowercased header name
*/
hash = 0;
for (i = 0; i < len; i++) {
lowcase_key[i] = ngx_tolower(name[i]);
hash = ngx_hash(hash, lowcase_key[i]);
}
/*
The layout of hashed headers is stored in ngx_http_core_module main config.
All the hashes, its offsets and handlers are pre-calculated
at the configuration time in ngx_http_init_headers_in_hash() at ngx_http.c:432
with data from ngx_http_headers_in at ngx_http_request.c:80.
*/
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/*
Find the current header description (ngx_http_header_t) by its hash
*/
hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, len);
if (hh == NULL) {
/*
There header is unknown or is not hashed yet.
*/
return NULL;
}
if (hh->offset == 0) {
/*
There header is hashed but not cached yet for some reason.
*/
return NULL;
}
/*
The header value was already cached in some field
of the r->headers_in struct (hh->offset tells in which one).
*/
return *((ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset));
}
ngx_table_elt_t *
get_host_from_headers_in(ngx_http_request_t *r) {
/*
Returns NULL if there is no such a header.
*/
return r->headers_in.host;
}
off_t
get_content_length_n_from_headers_in(ngx_http_request_t *r) {
/*
Returns -1 if the Content-Length wasn't set.
*/
return r->headers_in.content_length_n;
}
この例は、あらかじめハッシュされたキーを使った検索に比べて、キャッシュおよび最適化されたヘッダアクセスがどれほど高速かの幻想を与えます。
設定ステージにおいて、NGINXは(上で述べたように)良く知られたHTTPヘッダのハッシュngx_hash_t) を生成します。各ペアの中で、キーはヘッダ名でその値はNGIXNヘッダハンドラー構造(とても洗練された構造です。分かるでしょう)です。In this structure we can see the header name, its handler on a stage of headers parsing (for internal use) and, the most interesting, the offset of the header value in the headers_in struct. リクエスト値が追加された時に、このオフセットがリクエスト構造の中の適切なフィールドを満たすために使われます。パースのステージにおいて、NGINXは小文字のヘッダ名(HTTPヘッダ名は大文字小文字を気にしません)のハッシュを計算し、このハッシュ(メインのconfヘッダがあれば)を使ってヘッダのハンドラーを検索します。ハンドラーが見つかると、NGINXはそれを起動します。そうでなければ単にキー/値ペアをヘッダーの単純なリスト(headers_in.headers
)に追加します。どのように生成されるかを知ると、とても単純です ;)
If you’ve red the post you do know almost everything about``headers_out``. 唯一の違いはheaders_outは実行時に名前で出力ヘッダを見つけるためのハッシュを持ちません。
NGINXが多くの場所でヘッダ値を格納するかも知れない限り、ヘッダの設定には注意しなければなりません。全ての良く知られたヘッダは設定するために特別な方法が必要です。数字のヘッダの場合、3度設定することができます: リスト内での簡単なキー/値ペア、headers_in構造内でのポインター、そして、headers_inの特別なフィールド内での実際の数値の値。Every step reflects the way you get the header value.
例えばheaders_out
内のContent-Lengthを設定してみましょう。
ngx_int_t
set_content_length_n_in_headers_out(ngx_http_request_t *r, ngx_str_t *length, off_t length_n) {
ngx_table_elt_t *h;
h = r->headers_out.content_length;
if (h == NULL) {
/*
The header is not present at all. We have to allocate it...
*/
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
/*
... setup the header key ...
*/
h->key.data = (u_char *) "Content-Length";
h->key.len = sizeof("Content-Length") - 1;
/*
... and then set the headers_out field to tell others
that the header is already set.
*/
r->headers_out.content_length = h;
}
/*
So far we have the header and are able to set its value.
Do not forget to allocate the length.data memory in such
place where the memory will survive till the request ends.
The best place to store the data is the request pool (r->pool),
of course.
*/
h->value = *length;
/*
This trick tells ngx_http_header_module to reflect the header value
in the actual response. Otherwise the header will be ignored and client
will never see it. To date the value must be just non zero.
*/
h->hash = 1;
/*
And do not forget to set up the numeric field.
*/
r->headers_out.content_length_n = length_n;
return NGX_OK;
}
未知のヘッダ(独自のもの)は単にリストheaders_out.headers
) に入れられ、忘れさられるかも知れません:
ngx_int_t
set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) {
ngx_table_elt_t *h;
/*
All we have to do is just to allocate the header...
*/
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
/*
... setup the header key ...
*/
h->key = *key;
/*
... and the value.
*/
h->value = *value;
/*
Mark the header as not deleted.
*/
h->hash = 1;
return NGX_OK;
}
HTTPプロキシモジュールはヘッダが小文字にされたキー値を持つと期待され、そうでなければモジュールはクラッシュするだろうことに注意してください。つまり、もしproxy_passディレクティブを使ってサブリクエストをlocationに発行し、幾つかの独自のヘッダを設定したい場合は、以下のように適切に小文字にされたヘッダ名をセットアップしてください:
header->key = (u_char *) "X-La-La-La";
header->lowcase_key = (u_char *) "x-la-la-la";