Nginx. Фазы обработки запроса. If is Evil?

Kate

Administrator
Команда форума
Самое страшное зло в Nginx - это if в location. Об этом написано много, в том числе на nginx.com. Процитируем кусочек:
The only 100% safe things which may be done inside if in a location context are:
- return ...;
- rewrite ... last;
Казалось бы, если использовать конструкцию вида
location / {
if ( $condition ) {
return 418;
}
...
}
то ничего страшного не произойдет, однако, при определенном "умении", можно сломать даже то, что должно работать на 100%. Но будет ли виноват в нашей поломке if?
Предыдущие статьи
Предположим, что у нас был веб-сервер принимающий POST-запросы:
server {
root /www/example_com;
listen *:80;
server_name .example.com;

location /index.php {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
$ cat /www/example_com/index.php
<?php
var_dump($_REQUEST);
$ curl \
> -w "HTTP CODE: %{http_code}\n" \
> -d "key1=value1" \
> -X POST \
> "example.com/index.php" \
>
array(1) {
["key1"]=>
string(6) "value1"
}
HTTP CODE: 200

И в какой-то момент мы захотели отфильтровать запросы с пустыми телом (навеяно вопросом на форуме). Первый порыв мысли приводит конфиг к такому виду:
server {
root /www/example_com;
listen *:80;
server_name .example.com;

location /index.php {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

if ( $request_body = '' ) {
return 418;
}
}
}
Однако, мысль эта ошибочна, и в такой конфигурации мы всегда (как с отправленным телом так и без) получим 418-й ответ:
$ curl \
> -w "HTTP CODE: %{http_code}\n" \
> -X POST \
> "example.com/index.php" \
>
HTTP CODE: 418
$ curl \
> -w "HTTP CODE: %{http_code}\n" \
> -d "key1=value1" \
> -X POST \
> "example.com/index.php" \
>
HTTP CODE: 418
Как определены фазы в Nginx
В нашем условии используется переменная $request_body, значение которой
появляется в location’ах, обрабатываемых директивами proxy_pass, fastcgi_pass, uwsgi_pass и scgi_pass, когда тело было прочитано в буфер в памяти.
Следовательно, значение переменной устанавливается в фазе NGX_HTTP_CONTENT_PHASE, в то время как, директива if модуля ngx_http_rewrite_module исполняется в фазе NGX_HTTP_REWRITE_PHASE, то есть, на момент проверки условия, переменной $request_body не будет присвоено никакого значения вне зависимости от того пуст body или нет, поэтому при любых запросах ответом будет "418 I'm a teapot".
Таким образом, хоть мы и поломали гарантированно работающий вариант, if в location здесь совершенно не причем. Всему "виной" лишь порядок фаз обработки запроса.


 
Сверху