I need to write an nginx location directive to proxy requests to subdirectory to another server preserving urlencoding and removing subdirectory prefix.
Here's an artificial example — request like this:
http://1.2.3.4/api/save/http%3A%2F%2Fexample.com
should pass as
http://abcd.com/save/http%3A%2F%2Fexample.com
I tried several different ways. Here're couple of them:
- From this SO question
location /api/ {
rewrite ^/api(/.*) $1 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://abcd.com;
}
But it decodes the string, so http://abcd.com
gets /save/http://example.com
- From another SO question
location /api/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://abcd.com;
}
But it keeps subdirectory, so http://abcd.com
gets /api/save/http%3A%2F%2Fexample.com
.
What's needed is somewhere in the middle. Thank you!
UPD: Here's a ticket in nginx bug tracker
But there is no easy way to fix this nginx behaviour. There are some bugs in nginx trac, you could add yours. trac.nginx.org/nginx/…. So, I think that the simplest way is to have subdomain. – Alexey Ten Feb 24 '15 at 14:49
https://trac.nginx.org/nginx/ticket/727
If you want nginx to do something custom, you can do so using proxy_pass with variables (and the $request_uri variable, which contains original unescaped request URI as sent by a client). In this case it will be your responsibility to do correct URI transformations. Note though that this can easily cause security issues and should be done with care.
Challenge accepted!
location /api/ {
rewrite ^ $request_uri;
rewrite ^/api/(.*) $1 break;
return 400;
proxy_pass http://127.0.0.1:82/$uri;
}
That's it, folks!
Here's for the full proof.
The config file for nginx/1.2.1
:
server {
listen 81;
#first, the solution
location /api/ {
rewrite ^ $request_uri;
rewrite ^/api/(.*) $1 break;
return 400; #if the second rewrite won't match
proxy_pass http://127.0.0.1:82/$uri;
}
#next, a few control groups
location /dec/ {
proxy_pass http://127.0.0.1:82/;
}
location /mec/ {
rewrite ^/mec(/.*) $1 break;
proxy_pass http://127.0.0.1:82;
}
location /nod/ {
proxy_pass http://127.0.0.1:82;
}
}
server {
listen 82;
return 200 $request_uri\n;
}
Here are the results of running the queries for each location:
% echo localhost:81/{api,dec,mec,nod}/save/http%3A%2F%2Fexample.com | xargs -n1 curl
/save/http%3A%2F%2Fexample.com
/save/http:/example.com
/save/http:/example.com
/nod/save/http%3A%2F%2Fexample.com
%
Note that having that extra return 400;
is quite important — otherwise, you risk having a security issue (file access through //api
etc), as Maxim has briefly mentioned in your trac ticket.
P.S. If you think using the rewrite engine as a finite-state automaton is super cool, you might also want check out my http://mdoc.su/ project, or fork it github.
What you have to do is fairly easy as long as we are talking prefix matching with ^~
or no modifier
location /api/ {
# if you don't want to pass /api/ add a trailing slash to the proxy_pass
proxy_pass http://localhost:8080/;
...
}
And everything will be passed along without decoding, you don't have to pass $uri
Also while you use proxy pass you should also set these headers
# pass headers and body along
proxy_pass_request_headers on;
proxy_pass_request_body on;
# set some headers to make sure the reverse proxy is passing along everything necessary
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;