Nginx https rewrite convierte POST en GET

17

Mi servidor proxy se ejecuta en ip A y así es como las personas acceden a mi servicio web. La configuración de nginx redirigirá a una máquina virtual en la ip B.

Para el servidor proxy en IP A, tengo esto en mis sitios disponibles

server {
        listen 443;
        ssl on;
        ssl_certificate nginx.pem;
        ssl_certificate_key nginx.key;

        client_max_body_size 200M;
        server_name localhost 127.0.0.1;
        server_name_in_redirect off;

        location / {
                proxy_pass http://10.10.0.59:80;
                proxy_redirect http://10.10.0.59:80/ /;

                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

}

server {
        listen 80;
        rewrite     ^(.*)   https://$http_host$1 permanent;
        server_name localhost 127.0.0.1;
        server_name_in_redirect off;
        location / {
                proxy_pass http://10.10.0.59:80;
                proxy_redirect http://10.10.0.59:80/ /;
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

El proxy_redirectfue tomado de cómo hago que nginx reenvíe solicitudes HTTP POST a través de reescribir

Todo lo que llegue a la IP pública llegará a 443 debido a la reescritura. Internamente, estamos reenviando a 80 en la máquina virtual.

Pero cuando ejecuto un script de Python como el siguiente para probar nuestra configuración

import requests

data = {'username': '....', 'password': '.....'}
url = 'http://IP_A/api/service/signup'

res  = requests.post(url, data=data, verify=False)
print res
print res.json
print res.status_code
print res.headers

Estoy obteniendo un 405 Method Not Allowed. En nginx encontramos que cuando llegó al servidor interno, el nginx interno estaba recibiendo una GETsolicitud, aunque en el encabezado original hicimos un POST(esto se mostró en el script de Python).

Entonces parece que reescribir tiene un problema. ¿Algúna idea de cómo arreglar esto? Cuando comenté la reescritura, seguro que llega a 80, y pasó. Dado que rewrite pudo hablar con nuestro servidor interno, entonces reescribir en sí no tiene ningún problema. Es solo la reescritura reducida POSTa GET.

¡Gracias!

(Esto también se preguntará en el foro de Nginx porque es un bloqueador crítico ...)

CppLearner
fuente

Respuestas:

8

No es Nginx, es tu navegador.

Nota de RFC2616:

RFC 1945 y RFC 2068 especifican que el cliente no puede cambiar el método en la solicitud redirigida. Sin embargo, la mayoría de las implementaciones de agentes de usuario existentes tratan 302 como si fuera una respuesta 303, realizando un GET en la ubicación [..]

Esto es cierto para todos los navegadores populares y no hay nada que pueda hacer al respecto.

c2h5oh
fuente
@ c2h50h Entiendo que la especificación HTTP declaró algo similar como eso. ¿Pero qué puedo hacer en Nginx? Me refiero a esto es la configuración por trivial donde la gente hacia adelante 443 a un puerto interno 80, pero todavía pueden hacer PUT, POST, DELETE, GET. En mi configuración anterior no tenía este proxy adicional en el frente sirviendo a la multitud. Tenía la misma configuración en el mismo servidor interno (nuestro servidor de prueba). Eso funciona bien
CppLearner
Nada. Es 100% del lado del cliente. Si un servidor web, cualquier servidor web, devuelve una redirección 301 o 302, el navegador, en el lado del cliente, reemplazará cualquier tipo de solicitud GET. Ninguna configuración del lado del servidor o los encabezados http devueltos no cambiarán eso. Es así por razones históricas (los primeros navegadores se comportaron de esa manera debido a malentendidos y se convirtió en un estándar de facto).
c2h5oh
Bueno, para uno esto no es realmente un navegador. Bueno, puedes decir que es navegador porque usa el protocolo HTTP ... bueno ... está bien. Pero, de nuevo, parece que esto solo sucede si estaba haciendo sobre esta configuración de dos capas. Si tuviera que poner la misma configuración directamente en la máquina interna y ejecutara la prueba allí, no se quejaría. Pero, de nuevo, ¿cómo lo hace la gente en su producción? Supongo que algunas personas están haciendo algo similar, revierta 443 a alguna VM que puede estar ejecutando solo 80. Si hay una mejor práctica, me gustaría aprenderla y escucharla.
CppLearner
1
Por navegador me refería al cliente HTTP y con todos los clientes populares POSTse convertirá GETsi se redirige 301 o 302. POST seguirá siendo POST en la redirección de proxy, pero no en la reescritura.
c2h5oh
1
RFC2616 nuevamente: por lo If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.tanto, la mayoría de los navegadores mostrarán un mensaje de advertencia, ya que para otros clientes HTTP ni siquiera puedo adivinar cuál será su comportamiento.
c2h5oh
1

Descubrí que POST /api/brandse estaba convirtiendo GET /api/brandporque la aplicación web que estaba usando ( flask-restful) estaba haciendo una solicitud "no válida". Si usé POST /api/brand/(observe el final /), fue exitoso.

gaozhidf
fuente
Estaba usando Postman para probar el inicio de sesión django rest-auth y estaba viendo el mismo problema descrito. La clave fue que había descuidado el '/' final en la solicitud POST.
Steve L