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

от автора

Самое страшное зло в 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
typedef enum {     NGX_HTTP_POST_READ_PHASE = 0,     NGX_HTTP_SERVER_REWRITE_PHASE,     NGX_HTTP_FIND_CONFIG_PHASE,     NGX_HTTP_REWRITE_PHASE,     NGX_HTTP_POST_REWRITE_PHASE,     NGX_HTTP_PREACCESS_PHASE,     NGX_HTTP_ACCESS_PHASE,     NGX_HTTP_POST_ACCESS_PHASE,     NGX_HTTP_PRECONTENT_PHASE,     NGX_HTTP_CONTENT_PHASE,     NGX_HTTP_LOG_PHASE } ngx_http_phases;

В нашем условии используется переменная $request_body, значение которой

появляется в location’ах, обрабатываемых директивами proxy_passfastcgi_passuwsgi_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 здесь совершенно не причем. Всему «виной» лишь порядок фаз обработки запроса.


ссылка на оригинал статьи https://habr.com/ru/articles/570996/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *