разные locations для разных методов GET/PUT/OPTIONS у одного пути

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Есть у кого-нибудь опыт решения в Nginx как разделить обработку с одним путем по разным методам?

На StackOverflow предлагают решение только для проксирования
NGINX:
map $request_method $upstream_location {
   PUT     example.com:8081;
   POST    example.com:8081;
   PATCH   example.com:8081;
   default example.com:8082;
}
server {
   location / {
      proxy_pass https://$upstream_location;
   }
}
Мне надо в случае GET отдавать картинку с диска, при PUT - отдавать в PHP, а при OPTIONS - просто CORS-заголовки.

Пока писал, появилась мысль попробовать через try_files $request_method @fcgi_location; и объявить location @PUT, location @OPTIONS, location @fcgi_location.

Может, у кого есть решение?
 

fixxxer

К.О.
Партнер клуба
try_files всегда будет делать лишний stat на первый аргумент.

Тут поможет старый (времен до try_files) трюк с error_page, типа того:

Код:
    location /foo/ {
        error_page 471 =200 @foo_get;
        error_page 472 =200 @foo_post;
        error_page 473 =200 @foo_options;

        if ($request_method = "GET") {
            return 471;
        }
        if ($request_method = "POST") {
            return 472;
        }
        if ($request_method = "OPTIONS") {
            return 473;
        }
        return 400;
    }

    location @foo_get {
        try_files $uri =404;
    }

    location @foo_post {
        fastcgi_pass ...;
    }

    location @foo_options {
        add_header ...;
        return 200 "";
    }
if-а тут бояться не стоит, if + return - это нормально (на самом деле, это единственный нормальный способ использования if - когда внутри только директивы модуля rewrite). Сделать вместо if-ов map даже не пытайся - не получится (return не поддерживает переменные в первом аргументе).
 
Последнее редактирование:

AnrDaemon

Продвинутый новичок
Мне надо в случае GET отдавать картинку с диска, при PUT - отдавать в PHP, а при OPTIONS - просто CORS-заголовки.
Можно решить это без серьёзного перепила конфига nginx, если в нём настроен X-SendFile (X-Accel-Redirect).
Но решение на самом nginx будет немного быстрее, конечно.
 

fixxxer

К.О.
Партнер клуба
С XAR ты быстро упрешься в количество воркеров PHP. Тут же явно подавляющее большинство запросов - банальная отдача статики.

Если десятки RPS, то конечно пофиг, а если сотни и тысячи - тогда ой.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Спасибо!

единственный нормальный способ использования if - когда внутри только директивы модуля rewrite
а есть ли проблемы с
NGINX:
location / {
  if ($request_method = 'OPTIONS') {
     add_header 'Access-Control-Allow-Origin' '*' always;
    return 204;
  }
  try_files $uri $uri/ @application;
}
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Можно решить это без серьёзного перепила конфига nginx, если в нём настроен X-SendFile (X-Accel-Redirect).
Но решение на самом nginx будет немного быстрее, конечно.
для меня это вопрос высокоуровневой архитектуры API
могу ли я написать в сваггере
YAML:
  /images/deckMovieCover/{id}:
    parameters:
      - $ref: '#/components/parameters/UUID'
    get:
      tags: [decks,media]
      summary: Fetch a Movie Cover image
      responses:
        $ref: '#/components/schemas/imageResponse' 
    put:
      tags: [decks,media]
или для PUT и GET будет два разных endpoint
 

fixxxer

К.О.
Партнер клуба
а есть ли проблемы с
Если if заканчивается return-ом, то в 99% случаев все будет ок. Самый гарантированный способ отстрелить себе ногу if-ом - это засунуть в него какое-нибудь проксирование (proxy_pass или fastcgi_pass), тут и сегфолт может быть.

Но это уже другая постановка задачи, ты же еще PUT отделить хотел.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Да, я увидел, что это одна задача.

Сейчас OPTION отрабатываются в php и мешают, это костыль.
Проблема в том, что CORS не место в приложении. Это чисто инфрастурктурная штука уровня HTTP, так же, как отдача статики, зависимость от кода приложения явно лишняя.
Когда я вынесу работу с media на отдельный сервер, а видео-трафика тут будет 98%, получится, что приложение будет ограничивать отдачу видео.

Можно освоить написание этого на встроенном JS, кстати.
 
Последнее редактирование:

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Про nginx: error_page 471 =200 в целом работает, но есть одна небольшая проблема.
Когда я возвращаю из приложения код, например, 204, клиент получает 200.
 

fixxxer

К.О.
Партнер клуба
А наверное и не надо =200 для fpm, он ж все равно 200 вернет когда 200.
Это для локейшена со статикой только надо по идее.
 

fixxxer

К.О.
Партнер клуба
Про nginx: error_page 471 =200 в целом работает, но есть одна небольшая проблема.
Когда я возвращаю из приложения код, например, 204, клиент получает 200.
А, еще recursive_error_pages может понадобиться, чтобы, скажем, на 500ю статическую заглушку nginx-ом отдать
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
получается такой конфиг
NGINX:
    location ~ /images/(?<cover>\w+)/(?<id>[0-9a-f-]+) {
        error_page 472 = @application;
        error_page 473 = @cors;

        if ($request_method = "PUT" ) {
            return 472;
        }
        if ($request_method = "OPTIONS") {
            return 473;
        }

        root /uploads;
        try_files /images/$cover/raw/$id.jpg /images/$cover/raw/$id.png;
    }
    location @cors {
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
        return 204;
    }

    location @application {
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME  /code/api/web/v1.php;
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;
        fastcgi_pass  unix:/api-fpm-socket/socket;
        access_log  /dev/stdout api_request_log;
    }
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Да, наверное, как-то так, один из избыточных 204 можно даже убрать.

Ну еще я предпочитаю fastcgi* (вместе с fastcgi_pass) выносить в отдельный include (вида ${serverName}.fcgi) и прямо копипастить туда fastcgi_params - так гибче, если понадобится что-то поменять для конкретного vhost-а (стандартный маппинг временами мешает, и проще переопределить env как надо в явном виде). Но это дело вкуса уже.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Поправил, в location @cors return 204; обязателен, без него не отарбатывает
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Ну еще я предпочитаю fastcgi* (вместе с fastcgi_pass) выносить в отдельный include (вида ${serverName}.fcgi) и прямо копипастить туда fastcgi_params - так гибче, если понадобится что-то поменять для конкретного vhost-а (стандартный маппинг временами мешает, и проще переопределить env как надо в явном виде). Но это дело вкуса уже.
я в докер-стеке, у меня полный /etc/nginx в репе приложения
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
у меня несколько locations для php - тестировщик попросил adminer, мне иногда хочется из docroot скриптик вызвать без приложения
NGINX:
    location ~ \.php$ {
        include       fastcgi_params;
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_pass  unix:/api-fpm-socket/socket;
        access_log  /dev/stdout api_request_log;
    }

    location @application {
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME  /code/api/web/v1.php;
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;
        fastcgi_pass  unix:/api-fpm-socket/socket;
        access_log  /dev/stdout api_request_log;
    }

    location /adminer {
        include       fastcgi_params;
        root /var/www/html;
        set $adminer_upstream "adminer:9000";
        fastcgi_pass $adminer_upstream;
        fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
    }
 
Сверху