Давайте посмотрим, как работает следующая конфигурация nginx.
worker_processes 1;
error_log logs/error.log notice;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
if ($arg_a) {
rewrite_log on;
rewrite ^(.*)$ /catch$1;
}
location /catch {
internal;
rewrite ^/catch(.*)$ $1 last;
}
location /test {
return 403;
}
}
}
Логика здесь простая: если в URI присутствует аргумент a, то мы хотим увидеть в логе трейс всех реврайтов (rewrite_log on). Сделаем запрос без аргумента a.
$ curl 'http://localhost/test'
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.5.9</center>
</body>
</html>
Ожидаемый ответ 403, ожидаемое отсутствие записей в логе. А теперь добавим в URI аргумент a.
$ curl 'http://localhost/test?a=1'
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.5.9</center>
</body>
</html>
Всё то же самое, в том числе и отсутствие записей в логе. Но последний факт - это уже неожиданность, ведь мы установили флаг rewrite_log внутри серверного if ($arg_a).
В чем же проблема? Давайте взглянем на определение директивы rewrite_log в исходном коде модуля rewrite в файле src/http/modules/ngx_http_rewrite_module.c.
{ ngx_string("rewrite_log"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_rewrite_loc_conf_t, log),
NULL },
Из определения видно, что директива устанавливает флаг log, который определен в конфигурации уровня location. А теперь посмотрим, что делает директива if с конфигурацией уровня location (в том же файле в функции ngx_http_rewrite_if()).
if (pclcf->name.len == 0) {
if_code->loc_conf = NULL;
cf->cmd_type = NGX_HTTP_SIF_CONF;
} else {
if_code->loc_conf = ctx->loc_conf;
cf->cmd_type = NGX_HTTP_LIF_CONF;
}
В случае, если директива if находится на уровне server, контекст уровня location отсутствует! Это объясняет, почему директивы, настраивающие данные конфигурации уровня location, такие как rewrite_log в модуле rewrite, не работают в серверном if. С другой стороны, директивы типа set и rewrite из того же модуля будут прекрасно работать внутри серверного if, поскольку они не связаны с изменением данных внутри конфигурации уровня location, а создают глобальные объекты для дальнейшей интерпретации во время исполнения программы.
Давайте посмотрим, что было бы, если бы флаг log был объявлен внутри конфигурации уровня server. В модуле rewrite нет конфигурации уровня server, мы ее создадим... В общем, я просто приведу патч относительно версии nginx 1.5.9.
--- src/http/modules/ngx_http_rewrite_module.c 2014-02-02 19:52:13.954829708 +0400
+++ src/http/modules/ngx_http_rewrite_module.c.new 2014-02-02 19:15:00.060722697 +0400
@@ -11,15 +11,22 @@
typedef struct {
+ ngx_flag_t log;
+} ngx_http_rewrite_srv_conf_t;
+
+
+typedef struct {
ngx_array_t *codes; /* uintptr_t */
ngx_uint_t stack_size;
- ngx_flag_t log;
ngx_flag_t uninitialized_variable_warn;
} ngx_http_rewrite_loc_conf_t;
+static void *ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf,
void *parent, void *child);
@@ -86,8 +93,8 @@
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
- NGX_HTTP_LOC_CONF_OFFSET,
- offsetof(ngx_http_rewrite_loc_conf_t, log),
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_srv_conf_t, log),
NULL },
{ ngx_string("uninitialized_variable_warn"),
@@ -109,8 +116,8 @@
NULL, /* create main configuration */
NULL, /* init main configuration */
- NULL, /* create server configuration */
- NULL, /* merge server configuration */
+ ngx_http_rewrite_create_srv_conf, /* create server configuration */
+ ngx_http_rewrite_merge_srv_conf, /* merge server configuration */
ngx_http_rewrite_create_loc_conf, /* create location configuration */
ngx_http_rewrite_merge_loc_conf /* merge location configuration */
@@ -142,6 +149,7 @@
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_main_conf_t *cmcf;
ngx_http_rewrite_loc_conf_t *rlcf;
+ ngx_http_rewrite_srv_conf_t *rscf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
@@ -169,10 +177,12 @@
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
+ rscf = ngx_http_get_module_srv_conf(r, ngx_http_rewrite_module);
+
e->ip = rlcf->codes->elts;
e->request = r;
e->quote = 1;
- e->log = rlcf->log;
+ e->log = rscf->log;
e->status = NGX_DECLINED;
while (*(uintptr_t *) e->ip) {
@@ -227,6 +237,34 @@
static void *
+ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_rewrite_srv_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_srv_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->log = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_rewrite_srv_conf_t *prev = parent;
+ ngx_http_rewrite_srv_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->log, prev->log, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_rewrite_loc_conf_t *conf;
@@ -237,7 +275,6 @@
}
conf->stack_size = NGX_CONF_UNSET_UINT;
- conf->log = NGX_CONF_UNSET;
conf->uninitialized_variable_warn = NGX_CONF_UNSET;
return conf;
@@ -252,7 +289,6 @@
uintptr_t *code;
- ngx_conf_merge_value(conf->log, prev->log, 0);
ngx_conf_merge_value(conf->uninitialized_variable_warn,
prev->uninitialized_variable_warn, 1);
ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10);
В новой экспериментальной версии модуля rewrite мы перенесли флаг log из конфигурации уровня location в специально созданную конфигурацию уровня server, а также объявили новые хендлеры для создания и мерджа конфигураций уровня server, в которые перенесли все манипуляции с флагом log из соответствующих хендлеров конфигураций уровня location.
Компилируем nginx, перезапускаем сервер и посылаем те же тестовые запросы. Оба запроса возвращают ожидаемый 403 Forbidden, но теперь в случае, когда в URI запроса присутствует аргумент а, мы получаем следующую запись в логе:
2014/02/02 20:46:05 [notice] 7345#0: *2 "^(.*)$" matches "/test", client: 127.0.0.1, server: localhost, request: "GET /test?a=1 HTTP/1.1", host: "localhost"
2014/02/02 20:46:05 [notice] 7345#0: *2 rewritten data: "/catch/test", args: "a=1", client: 127.0.0.1, server: localhost, request: "GET /test?a=1 HTTP/1.1", host: "localhost"
2014/02/02 20:46:05 [notice] 7345#0: *2 "^/catch(.*)$" matches "/catch/test", client: 127.0.0.1, server: localhost, request: "GET /test?a=1 HTTP/1.1", host: "localhost"
2014/02/02 20:46:05 [notice] 7345#0: *2 rewritten data: "/test", args: "a=1", client: 127.0.0.1, server: localhost, request: "GET /test?a=1 HTTP/1.1", host: "localhost"
Здорово, мы добились того, к чему стремились! Однако потеряли несравненно большее. Теперь мы не сможем гибко настраивать флаг rewrite_log для каждого отдельного локейшна. Сервер просто не запустится, если этот флаг будет упомянут в одном блоке server более одного раза.
Давайте подытожим. Использование директивы, настраивающей данные в контексте location внутри директивы if уровня server бесполезно. Если бы это работало, то было бы несомненно полезно тем, что позволило бы записывать конфигурационный файл nginx в более выразительном и лаконичном стиле. С другой стороны, выразительный стиль все же доступен, если директива настраивает данные уровня server, однако в этом случае совершенно теряется возможность гибко настраивать конфигурацию для отдельных локейшнов сервера.
Я столкнулся с подобной проблемой при разработке кастомного модуля nginx. И мне пришлось пожертвовать гибкостью настройки внутри отдельных локейшнов ради возможности вызывать директиву из серверного if: в моем случае это казалось более обещающим, чем гибкость настройки. Естественно, перед тем как принять решение, я попытался изучить, как работают с серверным if модули, поставляемые вместе с nginx. Результат сначала показался удивительным: кроме модуля rewrite ни один стандартный модуль не работал с серверным if. Чтобы убедиться в этом, достаточно ввести команду
grep -r NGX_HTTP_SIF_CONF src/
внутри директории с исходниками nginx. Однако, если учесть только что описанный недостаток серверного if, то это уже не выглядит столь странно.