Nginx возвращать в json-ответе данные полученные POST-запросом

На сервере nginx 1.10.3

Создана тестовая заглушка, которая на любой запрос отвечает 200 и статичный json

cat mockserver.test.conf

server {
    listen 80;
    server_name mockserver.test;
    location / {
                return 200 '{"name": "qwerty", "phone": "+123"}';
    }
}

Как можно настроить nginx, чтобы при отправке POST-запросом данных

POST http://mockserver.test/

{"token": "+444"}

В ответ приходил json

{"name": "qwerty", "phone": "+444"}

То есть в значение ключа phone ответа подставлялось значение ключа token из POST-запроса.

https://nginx.org/en/docs/varindex.html

Пробовал использовать:

return 200 '{"phone": "$request_body"}' - вернуло пустую строку

return 200 '{"phone": "$request"}' - вернуло только POST / HTTP/1.1

Обновил с учетом комментариев.


Ответы (1 шт):

Автор решения: Ivan Shatsky

На той фазе обработки запроса, на которой отрабатывает директива rewrite, тело запроса ещё не прочитано, так что ничего удивительного. А оно действительно содержит только те данные, которые надо вставить в JSON, без какой-либо их обработки? В принципе это можно решить дополнительным проксированием на самого себя, и передавать содержимое тела запроса через кастомный хедер, например, proxy_set_header X-Request-Body $request_body;, тогда во втором серверном блоке оно будет доступно через переменную $http_x_request_body:

server {
    listen 80;
    server_name mockserver.test;
    location / {
        proxy_set_header Host local.mockserver.test;
        proxy_set_header X-Request-Body $request_body;
        proxy_pass http://127.0.0.1;
    }
}

server {
    listen 127.0.0.1:80;
    server_name local.mockserver.test;
    return 200 '{"phone": "$http_x_request_body"}';
}

А ещё при использовании подобной техники (дополнительного слоя проксирования) для несколько большей эффективности можно слушать отдельный сокет:

server {
    listen 80;
    server_name mockserver.test;
    location / {
        proxy_set_header X-Request-Body $request_body;
        proxy_pass http://unix:/tmp/nginx.sock;
    }
}

server {
    listen unix:/tmp/nginx.sock;
    return 200 '{"phone": "$http_x_request_body"}';
}

Дополнение 1

Прошу прощения, как-то я на ночь глядя и не заметил, что в вопросе приведен пример входящего JSON. Наверное меня сбила с толку попытка вставить в исходящий JSON переменную $request_body целиком. Вообще для таких случаев хорошо подходит модуль njs (пример разбора входящего JSON с использованием модуля njs можно посмотреть здесь). Но для простых случаев можно попытаться обойтись блоком map с регуляркой (взята отсюда):

map $request_body $phone {
    '~"phone":\s*"((?:(?=\\\\)\\\\(?:["\\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\\\0-\x1F\x7F])*)"'  $1;
}

server {
    listen 80;
    server_name mockserver.test;
    location / {
        proxy_set_header X-Phone $phone;
        proxy_pass http://unix:/tmp/nginx.sock;
    }
}

server {
    listen unix:/tmp/nginx.sock;
    return 200 '{"phone": "$http_x_phone"}';
}

Использованное регулярное выражение предназначено для получения строковых значений JSON по заданному имени ключа (в данном случае phone) и в более привычном синтаксисе PCRE может быть записано так:

/"phone":\s*"((?:(?=\\)\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\0-\x1F\x7F])*)"/

Дополнение 2

По неизвестной мне причине nginx версии 1.10.3, которым почему-то (в 2025 году!) пользуется автор вопроса, ругается на использование нумерованной группы в регулярном выражении из блока map:

nginx: [emerg] unknown "1" variable

(хотя в современных версиях nginx эта конфигурация вполне работоспособна). Зато тот же самый nginx 1.10.3 прекрасно понимает то же самое регулярное выражение с использованием именованной группы:

map $request_body $phone {
    '~"phone":\s*"(?<value>(?:(?=\\\\)\\\\(?:["\\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\\\0-\x1F\x7F])*)"'  $value;
}

Разбираться в том, какие именно версии nginx затрагивает этот баг, и когда именно он был исправлен, у меня никакого желания нет :) Главное, что workaround, приведенный выше, вполне работоспособен.

Дополнение 3

Конфигурация nginx в соответствии с уточнёнными условиями вопроса:

map $request_body $phone {
    '~"token":\s*"(?<value>(?:(?=\\\\)\\\\(?:["\\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\\\\0-\x1F\x7F])*)"'  $value;
}

server {
    listen 80;
    server_name mockserver.test;
    location / {
        proxy_set_header X-Phone $phone;
        proxy_pass http://unix:/tmp/nginx.sock;
    }
}

server {
    listen unix:/tmp/nginx.sock;
    return 200 '{"name": "qwerty", "phone": "$http_x_phone"}';
}

А также! Для того, чтобы вышестоящий сервис корректно обработал отданный нами JSON, к последнему блоку server { ... } может понадобиться добавить директиву

default_type application/json;
→ Ссылка