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

Комментарии 25

Допишите сразу про map вместо if, удобнее будет

По map есть отличная статья, но а данном случае он также не поможет.

Прототип решения по ссылке на форум вполне хорош.
Нам нужно переместить обработку условия в CONTENT-фазу, njs вполне для этого подходит, применительно к примеру из статьи как-то так:

js_import check_body.js;
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;
        
        js_content check_body.body_empty;
    }
}
function body_empty(r) {
  if (r.method == "POST" && (typeof(r.requestBody) == "undefined" || r.requestBody === null)) {
    r.return(418, "Empty\n");
  } else {
    r.return(200, "ret:[" + r.requestBody + "]\n");
  }
}

export default {body_empty};
$ curl -w "HTTP CODE: %{http_code}\n" -d "key1=value1" -X POST "example.com/index.php" \
ret:[key1=value1]
HTTP CODE: 200
$ curl -w "HTTP CODE: %{http_code}\n" -X POST "example.com/index.php"
Empty
HTTP CODE: 418

А использовать ноду точно будет дешевле, чем прямо в fastcgi-скрипте проверить?

Скажем так - я не думаю, что njs станет узким местом.
Но и делать так я точно не стал бы - сопровождать такое никакого желания. Решение только на случай если "можно использовать только nginx".
Как вариант можно использовать https://github.com/calio/form-input-nginx-module:

    location /index.php {
        fastcgi_pass   unix:/var/run/php-fpm.sock;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include fastcgi_params;
        
        set_form_input_multi $data;
        
        if ( $data = '' ) {
          return 418;
        }
    }

Но, опять же, спорное решение и в плане поддержки и в плане производительности.

Это не нода, а встроенный в nginx JS-движок, по скорости не должен уступать lua в оpenresty. Однако, если есть строгая необходимость логгировать $request_body без лишних скриптов, то можно его проксировать на себя, в отдельный сервер/порт на 127.0.0.1, в котором уже можно делать return и прочие запретные вещи.

Спасибо.

Давайте рассмотрим два крайних случая:

  1. Один реквест из тысячи с пустым body.

  2. Один реквест из тысячи с непустым body.

Собственно, мне просто интересно для каждого из двух случаев – что обойдётся дешевле: "if" вынесенный в отдельную njs-функцию (т.е. в обоих случаях 1000 раз выполнится js) или "if" в начале обработки FastCGI-запроса (сразу после Accept или что там)? Или это зависит от того, на чём FastCGI сделан?

Ну и третий вариант, как написал @RekGRpth– использовать echo_read_request_body. Насколько это дорогое удовольствие?

Использование echo_read_request_body - абсолютно бесполезно, точнее просто вредно. Если пакет поднялся до фазы CONTENT, то для того чтобы выполнить if (значит попасть в фазу REWRITE) у него есть только один выход - выполнить проксирование. Но при использовании директивы proxy_pass переменная $request_body получит значение автоматически, то есть echo_read_request_body просто лишено смысла.

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;
    }
}
<?php
if(!count($_REQUEST)) http_response_code(418);
die();

19691

js_import /scripts/nginx/js/check_body.js;
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;
        
        js_content check_body.body_empty;
        #if ( $request_body = '' ) {
        #  return 418;
        #}
    }
}

12263

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;
        
        set_form_input_multi $data;
        
        if ( $data = '' ) {
          return 418;
        }
    }
}

6009

Время в наносекундах от ngx_http_handler:entry до ngx_http_finalize_request:return

Усредненное для 100000 "пустых" POST-запросов.

Спасибо!
Хороший пример насколько модуль быстрее njs.
Сейчас под рукой нет опенрести чтобы луа проверить, но сразу после выхода njs, когда тестировал луа был самую малость быстрее. Но то может на моих сценариях.

Хороший пример насколько модуль быстрее njs.

Скорее пример того насколько быстрее будет завершить обработку в REWRITE-фазе, чем в CONTENT

Маршруты пакета для обоих случаев:

$ cat /tmp/tree.txt | grep phase | awk '{print $3}' | uniq
ngx_http_core_generic_phase
ngx_http_core_rewrite_phase
ngx_http_core_find_config_phase
ngx_http_core_rewrite_phase

$ cat /tmp/tree_njs.txt | grep phase | awk '{print $3}' | uniq
ngx_http_core_generic_phase
ngx_http_core_rewrite_phase
ngx_http_core_find_config_phase
ngx_http_core_rewrite_phase
ngx_http_core_post_rewrite_phase
ngx_http_core_generic_phase
ngx_http_core_access_phase
ngx_http_core_post_access_phase
ngx_http_core_generic_phase
ngx_http_core_content_phase
Спасибо за уточнение.
Надо будет потом потестить все это на реальных данных.
Последнее время забил на все эти защиты на стороне nginx-а, все равно помогают только от самого школьного ддоса.
А если нужна какая-то хитрая обработка запроса именно на стороне nginx-а, то тогда уже известный openresty или в крайнем случае njs
А насчет fastcgi это идеальные данные, в реальности под нагрузкой будет еще медленнее, т.к. очень плохо параллелится, под сверх нагрузками.

Насколько я понял из задачи это была простая защита от спама пустыми POST запросами и тут бы все в php-fpm все уперлось
Кстати, проксирование на себя позволяет делать и другие костыли, для обмана порядка фаз обработки запроса.
Но все-таки это вообще не интуитивно и легко в будущем потом сломать если планируются правки конфига. Поэтому раньше было проще опенрести поставить и юзать lua, а сейчас на мейнлайне njs уже можно использовать

я так обычно делаю, если надо обойти if

location /index.php {
  	if ( $request_body = '' ) {
    		return 418;
  	}
    try_files /nonexistent @proxy;
}
location @proxy {
  	fastcgi_pass ...
} 

главное, чтобы /nonexistent не существовал

Как это поможет установить значение в переменной $request_body?

а вот это уже другой вопрос и на него можно ответить например так echo_read_request_body

echo-nginx-module работает в CONTENT-фазе, там мы можем получить $request_body и без сторонних модулей.

Как всё это поможет проверить переменную $request_body в REWRITE-фазе?

хм... точно, а вот так сработало

location /index.php {
    set_form_input $data;
    if ($request_body = '') { return 418; }
    try_files /nonexistent @proxy;
}
location @proxy {
  	fastcgi_pass ...
} 

Этот пример я уже приводил выше.
Так и должно работать, так как директивы модуля работают в фазе REWRITE:

$ grep -rnF PHASE ../form-input-nginx-module-master/
../form-input-nginx-module-master/src/ngx_http_form_input_module.c:372:    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);

Ну и try_files здесь не нужен, так как работаем в REWRITE_PHASE, то и if отработает нормально.

Очень плохой пример.

1) $request_body здесь установился "случайно", просто потому что модуль form-input-nginx-module получил/установил его при парсинге $data.

2) Лишние телодвижения с try_files. Вы поднялись до PRECONTENT_PHASE и зачем-то пошли на второй круг по всем фазам (заново, начиная от REWRITE_PHASE) вместо того чтобы сразу перейти к CONTENT_PHASE. Этот дополнительный проход не выполняет никаких действий вообще.

Я бы проверял не $request_body, а наличие $http_content_length или $http_transfer_encoding = ‘chunked’. Обе эти переменные уже определены на этапе REWRITE_PHASE.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории