15

I'm trying to convert a reverse proxy using an interesting Apache mod_rewrite setup to use Nginx instead (due to external concerns we are moving from Apache to Nginx, and most everything works fine except this part).

My original setup was to read an HTTP cookie (set by some application) and depending on its value, direct the reverse proxy to different backends. It went something like this:

RewriteCond %{HTTP_COOKIE}  proxy-target-A
RewriteRule ^/original-request/ http://backend-a/some-application [P,QSA]

RewriteCond %{HTTP_COOKIE}  proxy-target-B
RewriteRule ^/original-request http://backend-b/another-application [P,QSA]

RewriteRule ^/original-request http://primary-backend/original-application [P,QSA]

I'm trying to achieve the same using Nginx, and my initial configuration was something like this (where "proxy_override" is the name of the cookie):

location /original-request {
    if ($cookie_proxy_override = "proxy-target-A") {
        rewrite . http://backend-a/some-application;
        break;
    }
    if ($cookie_proxy_override = "proxy-target-B") {
        rewrite . http://backend-b/another-application;
        break;
    }
    proxy_pass http://primary-backend/original-application;
}

But it didn't. I've tried to see if Nginx can read my cookie by writing the primary proxy to redirect to something based on ${cookie_proxy_override} and I can see that it reads the content fine, but the ifs seem to always fail.

My next try, according to Rikih's answer was this:

location /original-request {
    if ($http_cookie ~ "proxy-target-A") {
        rewrite . http://backend-a/some-application;
        break;
    }
    if ($http_cookie ~ "proxy-target-B") {
        rewrite . http://backend-b/another-application;
        break;
    }
    proxy_pass http://primary-backend/original-application;
}

And now I can see that the if block gets activated, but instead of proxying the request (like I thought it would do) it returns a 302 redirect to the URL specified - which is not what I'm trying to do: I need the server to transparently relay the request to the backends and pipe the response to the original client.

What am I doing wrong?

Guss
  • 2,520
  • 5
  • 32
  • 55

4 Answers4

20

Similar to this answer. Nginx's idiomatic approach to this kind of problems is via map.

Basically, you define a map in http section

map $cookie_proxy_override $my_upstream {
  default default-server-or-upstream;
  ~^(?P<name>[\w-]+) $name;
}

Then you simply use $my_upstream in location section(s):

location /original-request {
  proxy_pass http://$my_upstream$uri;
}

Nginx evaluates map variables lazily, only once (per request) and when you are using them.

Alexander Azarov
  • 3,510
  • 20
  • 19
  • 3
    Thanks, that's a better approach then mine, most notably because I can use the named cookie variable directly (not sure why I can't in `if`) and I implemented it. There is one issue though - Nginx (at least my version: 1.0.0) does not like numbered captures in `map`, so I had to use `~^(?P[\w-]+) $name;` instead. I've edited your answer accordingly. – Guss Aug 22 '11 at 09:00
6

Eventually my solution boils down to this:

server {
    ...
    set $upstream "default-server-or-upstream";
    if ($http_cookie ~ "proxy_override=([\w-]+)") {
        set $upstream $1;                                   
    }

    location /original-request {
        proxy_pass http://$upstream/original-application
    }
}

The test is done in the server scope for each request (before the actual redirect is resolved) and is just used to set a variable - this is apparently a supported usage of Nginx "rewrite" module. It also tests the entire $http_cookie like @Rikih suggested, but includes the name of the cookie to make sure I don't match random stuff that people might be throwing at me.

Then in the location scope where I want to do the redirect, I use the variable name that either contains the default upstream configuration or was overwritten by the cookie.

Guss
  • 2,520
  • 5
  • 32
  • 55
1

have you try $http_cookie ? http://wiki.nginx.org/HttpRewriteModule

if ($http_cookie ~* "proxy-target-A" ) { foo; }

chocripple
  • 2,039
  • 14
  • 9
  • That indeed worked for the test, though I'm not sure why I can't just test against the specific cookie name. What I didn't like is that `rewrite` does not actually do a proxy rewrite but instead returns a redirect to the client, and I can't use proxy_pass in the `if` block. I have updated the question accordingly. – Guss May 12 '11 at 11:56
0

i have sample that i use to detect request header based on udid and it's working, might be you will get some idea.

   location / {
      proxy_set_header Host $http_host;
  if ($request_uri ~ ^/(.*)udid=xxxxxxxxxxxxxx(.*)$) {
    proxy_pass   http://1.1.1.1$request_uri;
    break;
  }
  if ($request_uri ~ ^/(.*)udid=yyyyyyyyyyyyyy(.*)$) {
    proxy_pass   http://3.3.3.3$request_uri;
    break;
  }
       proxy_pass http://2.2.2.2$request_uri;
    }
chocripple
  • 2,039
  • 14
  • 9
  • Which version of Nginx are you using? I'm using 1.0 and when I use proxy_pass like you specified here, I get this error message: `nginx: [emerg] "proxy_pass" may not have URI part in location given by regular expression, or inside named location, or inside the "if" statement, or inside the "limit_except" block in /etc/nginx/conf.d/proxy.conf:47` – Guss May 13 '11 at 07:26
  • i use nginx-0.8.53-1.el5 – chocripple May 13 '11 at 16:48
  • may be you want to take a look http://forum.nginx.org/read.php?2,13955,15981 – chocripple May 13 '11 at 16:51
  • The solution in the forum is to not change the request URI when proxying to another server, but that is exactly what I need to do - rewrite the request URI to target an application different then what the original URL includes. Also, your example seems to also use the request URI in the `proxy_pass` command, so I'm not sure how it can work for you given the above forum discussion. – Guss May 15 '11 at 07:23