Lighttpd – URL and Host Matching and Include-File Spanning

configurationlighttpdreverse-proxy

I have an interesting condition resolution problem to resolve and have not had luck with on-line searching and looking through documentation for lighttpd yet. Many of those searches led to similar questions asked here and useful answers (for those questio so let's see how this one runs:

I have lighttpd running on a gateway router (OpenWRT, Or Turris OS if you prefer as it's a Turris Omnia) and it has a number of domains pointing its way which it farms out, as a revers proxy to servers on the LAN side of that gateway.

The general config being, in pro forma, like:

$HTTP["host"] =~ "(a.com|b.com|c.com)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
} else $HTTP["host"] =~ "(d.org|e.org)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
} else $HTTP["host"] =~ "(f.net|g.net)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
}

That has been working a dream for ages.

Now I would like a particular path, common to all these sites to be served from this router directly.

In pro forma again:

$HTTP["url"] =~ "^/topdir/subir/" {
    server.document-root = "/www/sharedstuff"
}

And I can combine this admirably as follows (and it works):

$HTTP["url"] =~ "^/topdir/subir/" {
    server.document-root = "/www/sharedstuff"
} else {
   $HTTP["host"] =~ "(a.com|b.com|c.com)$" {
       proxy.server  = ( "" => ( ( "host" => "..." ) ) )
       ...
   } else $HTTP["host"] =~ "(d.org|e.org)$" {
       proxy.server  = ( "" => ( ( "host" => "..." ) ) )
       ...
   } else $HTTP["host"] =~ "(f.net|g.net)$" {
       proxy.server  = ( "" => ( ( "host" => "..." ) ) )
       ...
   }
}

Sweet.

BUT, here's the problem I'm trying to solve. I'd like ideally to encapsulate the $HTTP["url"] condition in one included file and the $HTTP["host"] condition in another such that I can:

include "/etc/lighttpd/conf.d/shared.conf"      # contains the `$HTTP["url"]` constraint
include "/etc/lighttpd/conf.d/distributed.conf" # contains the `$HTTP["host"]` constraint

I wonder if I'm hoping for too much here. As I can't think or find of a way to do it.

I imagine if shared.conf contained some statement such that there existed a config statement like:

$HTTP["url"] =~ "^/topdir/subir/" {
    server.document-root = "/www/sharedstuff"
    ignore-all-subsequent-host-match-conditions 
}

Another creative, if naive and impossible, idea is if we could rewrite $HTTP["host"] akin to:

$HTTP["host"] = "null.net"

So that subsequent matches like $HTTP["host"] =~ "(a.com|b.com|c.com)$" all fail, and the request stays local.

Here are some options explored thus far:

Server variables

No go as these are evaluated when configuration is loaded not when requests are processed.

https://redmine.lighttpd.net/projects/1/wiki/docs_configuration#Using-variables

Request Headers

setenv.add-request-header looks attractive:

https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModSetEnv

If in shared.conf we set a custom request header perhaps we can test for it with a $REQUEST_HEADER["header"] in distributed.conf:

https://redmine.lighttpd.net/projects/1/wiki/docs_configuration#Conditional-Configuration

But I have not had any success with that. It seems that a conditional like this:

$REQUEST_HEADER["my_header"] == "value_I_set" {
   # Do not act as a reverse proxy 
} else $HTTP["host"] =~ "(a.com|b.com|c.com)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
} else $HTTP["host"] =~ "(d.org|e.org)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
} else $HTTP["host"] =~ "(f.net|g.net)$" {
    proxy.server  = ( "" => ( ( "host" => "..." ) ) )
    ...
}

Simply does not work, and I cannot really divine why. It's hard to see what's going on but if I log output on conditional handling it seems that $REQUEST_HEADER["my_header"] is always blank even for a URL where in shared.conf this matched:

$HTTP["url"] =~ "^/topdir/subir/" {
    setenv.add-request-header = ("my_header" => "value_I_set")
}

It seems the condition doesn't test request headers set by setenv, so much as those delivered.

Best Answer

One possible config solution is to put your shared config after the $HTTP["host"] conditions, and in your case, to overwrite the proxy config

$HTTP["url"] =~ "^/topdir/subir/" {
    server.document-root = "/www/sharedstuff"
    proxy.server = ()
}

Another solution, more flexible and powerful: lighttpd mod_magnet allows you to write arbitrarily complex logic in a few lines of lua. You could have your "shared" config handle certain requests (in your custom lua script) before lighttpd mod_proxy.

BTW, does the following naive solution also work? Shared config:

$HTTP["url"] =~ "^/topdir/subir/" {
    server.document-root = "/www/sharedstuff"
}

in shared.conf included in lighttpd.conf as

include "/etc/lighttpd/conf.d/shared.conf"      # contains the `$HTTP["url"]` constraint
else {
    include "/etc/lighttpd/conf.d/distributed.conf" # contains the `$HTTP["host"]` constraint
}