Как стать автором
Обновить

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

Время на прочтение2 мин
Количество просмотров13K

Самое страшное зло в 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 здесь совершенно не причем. Всему "виной" лишь порядок фаз обработки запроса.

Теги:
Хабы:
+12
Комментарии25

Публикации

Истории

Работа

Ближайшие события