19

My proxy server runs on ip A and this is how people access my web service. The nginx configuration will redirect to a virtual machine on ip B.

For the proxy server on IP A, I have this in my sites-available

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;
        }
}

The proxy_redirect was taken from how do I get nginx to forward HTTP POST requests via rewrite?

Everything that hits the public IP will hit 443 because of the rewrite. Internally, we are forwarding to 80 on the virtual machine.

But when I run a python script such as the one below to test our configuration

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

I am getting a 405 Method Not Allowed. In nginx we found that when it hit the internal server, the internal nginx was getting a GET request, even though in the original header we did a POST (this was shown in the Python script).

So it seems like rewrite has problem. Any idea how to fix this? When I commented out the rewrite, it hits 80 for sure, and it went through. Since rewrite was able to talk to our internal server, so rewrite itself has no issue. It's just the rewrite dropped POST to GET.

Thank you!

(This will also be asked on Nginx forum because this is a critical blocker...)

10

It's not Nginx, it's your browser.

Note from RFC2616:

RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. However, most existing user agent implementations treat 302 as if it were a 303 response, performing a GET on the Location [..]

This is true for all popular browsers and there is nothing you can do about it.

11
  • @c2h50h I understand that the HTTP spec stated something similar like that. But what can I do in Nginx? I mean this is a trivial setup where people forward 443 to an internal 80 port, but they can still do PUT, POST, DELETE, GET. In my previous setup I didn't have this extra proxy at the front serving the crowd. I had the same configuration on the same internal server (our test server). That works fine. – CppLearner Oct 2 '12 at 20:51
  • Nothing. It's 100% client side. If a webserver, any webserver, returns a 301 or 302 redirect then browser, on client side, will replace any type of request to GET. No server-side configuration or any http headers returned will not change that. It's like that for historical reasons (early browsers behaved that way due to misunderstanding and it became a de facto standard). – c2h5oh Oct 2 '12 at 20:54
  • Well for one this is not really a browser. Well you can say it's browser because it uses HTTP protocol..okay.. thats fine. But then again, it looks like this only happens if I were doing over this two-layer configuration. If I were to put the same configuration directly on the internal machine, and running the test there, it will not complain. But then again, how do people do it in their production? I would assume some people are doing similar thing, reverse 443 to some VM that may be running 80 only. If there is a better practice, I'd like to learn it and hear about it. – CppLearner Oct 2 '12 at 20:58
  • 1
    By browser I meant HTTP client and with all popular clients POST will become GET if it's 301 or 302 redirected. POST will remain POST on proxy redirect, but not on rewrite. – c2h5oh Oct 2 '12 at 21:22
  • 1
    RFC2616 again: 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. So most browsers will pop up a warning message, as for other HTTP clients I can't even guess what their behavior will be. – c2h5oh Oct 2 '12 at 22:02
2

I found POST /api/brand was being turned into GET /api/brand because the web application I was using (flask-restful) was making an "invalid" request. If I used POST /api/brand/ (notice the trailing /), it was successful.

1
  • I was using Postman to test django rest-auth login and was seeing the same issue described. The key was that I had neglected the trailing '/' in the POST request. – Steve L Mar 13 '19 at 20:43
2

TL;DR If you want to completely redirect to a new resource and method and body of the requests should not be changed, use 308 instead of 301 or 302.

301 is permanent redirect, but 302 is temporary so search engines don't change urls associated with that website when 302 is used. 301 and 302 indicate method and body should not be altered, but not all user agents align with that. Read this explanation from Mozilla:

The HyperText Transfer Protocol (HTTP) 302 Found redirect status response code indicates that the resource requested has been temporarily moved to the URL given by the Location header. A browser redirects to this page but search engines don't update their links to the resource (in 'SEO-speak', it is said that the 'link-juice' is not sent to the new URL). Even if the specification requires the method (and the body) not to be altered when the redirection is performed, not all user-agents conform here - you can still find this type of bugged software out there. It is therefore recommended to set the 302 code only as a response for GET or HEAD methods and to use 307 Temporary Redirect instead, as the method change is explicitly prohibited in that case. In the cases where you want the method used to be changed to GET, use 303 See Other instead. This is useful when you want to give a response to a PUT method that is not the uploaded resource but a confirmation message such as: 'you successfully uploaded XYZ'.

308 and 307 both redirect to a new resource but they guarantee body and method of request won't be altered. the difference is that 308 is permanent and 307 is temporary, so 308 will signal search engines to change urls. See this:

The only difference between 307 and 302 is that 307 guarantees that the method and the body will not be changed when the redirected request is made. With 302, some old clients were incorrectly changing the method to GET: the behavior with non-GET methods and 302 is then unpredictable on the Web, whereas the behavior with 307 is predictable. For GET requests, their behavior is identical.

-1

My problem with this was that I was requesting an HTTP request, which was getting 301 redirected to HTTPS. Using HTTPS for the requests solves this issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.