Proxy_pass based on (possibly changed) $host


#1

Hi,

I am porting a lighttpd module that I wrote to nginx. I am new to
nginx. I would like to know if it is possible to choose the
proxy_pass backend from the Host header within a server. In lighttpd,
this is possible by:

$SERVER[“socket”] == “:80” {
$HTTP[“host”] == “bar.foo.com” {
proxy-core.backends = ( “10.0.0.1:80” )
}
$HTTP[“host”] == “baz.foo.com” {
proxy-core.backends = ( “10.0.0.2:80” )
}
}

The module that I am porting to nginx will sometimes update the Host
header such that a different proxy backend should be chosen. For
example, a request from a particular IP address to http://bar.foo.com/
might be updated such that its Host header changes from bar.foo.com to
baz.foo.com, and thus it is served by the 10.0.0.2:80 backend. Is
there a way to specify a relationship between $host and proxy_pass in
nginx.conf within the same server { … }?

-dave


#2

On Wed, Jan 28, 2009 at 02:52:24AM -0800, Dave Bailey wrote:

}
$HTTP[“host”] == “baz.foo.com” {
proxy-core.backends = ( “10.0.0.2:80” )
}
}

However, the better:

server {
    listen   80;
    server_name   bar.foo.com;

    location  / {
        proxy_pass  http://10.0.0.1:80;
    }
}

server {
    listen   80;
    server_name   baz.foo.com;

    location  / {
        proxy_pass  http://10.0.0.2:80;
    }
}

The module that I am porting to nginx will sometimes update the Host
header such that a different proxy backend should be chosen. For
example, a request from a particular IP address to http://bar.foo.com/
might be updated such that its Host header changes from bar.foo.com to
baz.foo.com, and thus it is served by the 10.0.0.2:80 backend. Is
there a way to specify a relationship between $host and proxy_pass in
nginx.conf within the same server { … }?

map $remote_addr  $back1 {
    default       10.0.0.1:80;
    192.168.1.1   10.0.0.2:80;
    192.168.1.5   10.0.0.2:80;
}

server {
    listen   80;
    server_name   bar.foo.com;

    location  / {
        proxy_pass  http://$back1$request_uri;
    }
}

map $remote_addr   $back2 {
    default        10.0.0.2:80;
    192.168.10.1   10.0.0.1:80;
    192.168.10.5   10.0.0.1:80;
}

server {
    listen   80;
    server_name   baz.foo.com;

    location  / {
        proxy_pass  http://$back2$request_uri;
    }
}

#3

Hello,

On Jan 28, 2009, at 12:02 , Igor S. wrote:

$SERVER[“socket”] == “:80” {
server {
server_name baz.foo.com;
bar.foo.com/
}
map $remote_addr $back2 {
proxy_pass http://$back2$request_uri;
}
}

This is very similar to how i have things set up; but this map is
rather static. I “solved” this by using memcached as “map”, a simple
FastCGI application written in C doing the lookup which returns the
backend to use. My question is if this map thing somehow could be
expanded to something of a more dynamic nature?


#4

Hi Igor,

(response at bottom)

On Wed, Jan 28, 2009 at 7:01 AM, Igor S. removed_email_address@domain.invalid wrote:

  location  / {

server {
listen 80;
server_name baz.foo.com;

  location  / {
      proxy_pass  http://$back2$request_uri;
  }

}

I see, so there is no restriction on the number of server { … }
blocks that bind to a given port - so the nginx server { … } is a
name-based virtual host, like with Apache.

So, currently you may only use X-Accel-Redirect:
location = /xar {
internal;

    # you have to store $upstream_http_x_proxy somewhere as it
    # will be discarded just proxy module will start to work
    set   $xproxy  $upstream_http_x_proxy;

    proxy_pass   $xproxy;
}

Hmm, interesting. Suppose, then, that my module stores a hash table
that relates the incoming {Host, uri} to the upstream {Host, uri} for
a large number of {Host, uri} keys. In addition to this mapping, I
would have some “default” location-based mappings in nginx.conf, such
as:

server {
listen 80;
server_name bar.foo.com;
location / {
proxy_pass http://10.0.0.1:80;
proxy_set_header Host $http_host;
}
}

server {
listen 80;
server_name baz.foo.com;
location / {
proxy_pass http://10.0.0.2:80;
proxy_set_header Host $http_host;
}
location /bar/path/one {
proxy_pass http://10.0.0.1:80;
proxy_set_header Host “bar.foo.com”;
}

}

Suppose that I receive the request:

GET /bar/path/one/of/many/thousands HTTP/1.1
Host: baz.foo.com

In my module, I would look this up in a hash table using the {Host,
uri} as the key. I might find that the request should be handled
upstream by 10.0.0.1:80 (i.e. the upstream server that contains
bar.foo.com’s content) at some completely different URI. So my goal
is to rewrite the URI and possibly the Host header as a result of what
I find in the hash lookup, and have the proxy module connect to the
correct upstream backend as a result (and retrieve the rewritten URI).

In your config sample, you have:

set $xproxy $upstream_http_x_proxy;
proxy_pass $xproxy;

Within the module’s C source code that I write, could I do this
through the ngx_ API, as well as something similar to update the
upstream URI? There will be too many entries in the hash table for me
to represent it in nginx.conf.

-dave


#5

On Wed, Jan 28, 2009 at 08:13:05AM -0800, Dave Bailey wrote:

  default        10.0.0.2:80;
  }

}

I see, so there is no restriction on the number of server { … }
blocks that bind to a given port - so the nginx server { … } is a
name-based virtual host, like with Apache.

[ 1st part of response. ]

Yes, and actually server {} is mix of IP-, name-, and port-based virtual
hosts:

http {

    server {
        listen  192.168.10.1;
        listen  192.168.10.1:8000;

        server_name   one.example.com  www.one.example.com;

        ...
    }

    server {
        listen  192.168.10.1;
        listen  192.168.10.2:8000;
        listen  9000;

        server_name   two.example.com  www.two.example.com
                      three.example.com  *.three.example.com;

        ...
    }

    server {
        listen  9000  default;

        server_name   four.example.com  www.four.example.com;

        ...
    }

}

The request is routed according

  1. IP:port
  2. exact server_name
  3. *.name
  4. name.*
  5. regex server_name

#6

On Wed, Jan 28, 2009 at 03:49:23PM +0100, Johan Bergstr?m wrote:

proxy_pass backend from the Host header within a server. In
}
}

map $remote_addr $back1 {
proxy_pass http://$back1$request_uri;
listen 80;
backend to use. My question is if this map thing somehow could be
expanded to something of a more dynamic nature?

You may write a variable handler in C/ngx_http_perl_module for $back1
and $back2, but the problem is now variable handlers are blockeing ones.
There is no way to postpone proxy_pass processing while variable
resolution.

So, currently you may only use X-Accel-Redirect:

 location / {
     fastcgi_pass  ...;

     # it returns
     # ...
     # X-Accel-Redirect: /xar"
     # X-Proxy: http://10.0.0.1:80/some/uri?args
 }

 location = /xar {
     internal;

     # you have to store $upstream_http_x_proxy somewhere as it
     # will be discarded just proxy module will start to work
     set   $xproxy  $upstream_http_x_proxy;

     proxy_pass   $xproxy;
 }

#7

On Wed, Jan 28, 2009 at 08:13:05AM -0800, Dave Bailey wrote:

So, currently you may only use X-Accel-Redirect:
location = /xar {
that relates the incoming {Host, uri} to the upstream {Host, uri} for
}
proxy_pass http://10.0.0.1:80;
In my module, I would look this up in a hash table using the {Host,
proxy_pass $xproxy;

Within the module’s C source code that I write, could I do this
through the ngx_ API, as well as something similar to update the
upstream URI? There will be too many entries in the hash table for me
to represent it in nginx.conf.

You may create $xproxy_url and $xproxy_host variable handlers in C
module
and use them as

 proxy_pass        $xproxy_url;
 proxy_set_header  Host $xproxy_host;

#8

Hi Igor,

On Wed, Jan 28, 2009 at 9:00 AM, Igor S. removed_email_address@domain.invalid wrote:

You may create $xproxy_url and $xproxy_host variable handlers in C module
and use them as

proxy_pass        $xproxy_url;
proxy_set_header  Host $xproxy_host;

Thank you, that is perfect!

-dave