HAProxy community

Can I match path_beg patterns from a map?

TLDR
Is there a way to read a string value from a map and have path_beg treat it like a proper pattern to match against?

The longer version
We manage thousands of domains through a single haproxy config, and we have a limited number of backends (around 6) servicing various portions of each domain’s needs.

Based on customer options, different domains need a different combination of backends for particular routes (the same route might have several backends capable of answering, but it’s specific to each particular domain). This is where it gets messy to hardcode the logic in haproxy, and I’m trying to use maps to keep things very configurable (and we can use the Unix sockets to update the maps).

Currently I’m trying to use a map of domain names as keys with path_beg style patterns as values to choose between several backends. However it doesn’t appear path_beg can accept a string pulled from a map for matching – can anyone confirm, or is there another way to accomplish a similar result?

Using the examples below and curl to test I can set some response headers to verify that haproxy is looking up and receiving the correct values from the map. However, it never matches via path_beg to the strings it pulls from the map. If I hardcode the same strings (routes) path_beg works as expected.

Thanks for taking a look!

haproxy.cfg

#---------------------------------------------------------------------
frontend http-in
    bind *:80
    http-request set-var(txn.domain) req.hdr(Host),field(1,:),lower
    use_backend be-aa if { path_beg -i var(txn.domain),map(/usr/src/app/be-aa.routes.map.txt) }
    use_backend be-bb if { path_beg -i var(txn.domain),map(/usr/src/app/be-bb.routes.map.txt) }

be-aa.routes.map.txt

localhost  /contact

be-bb.routes.map.txt

localhost /api

you can’t compare against a dynamic value. It has to be known at startup time.
what you can do is use an acl file containing the concatenation of host+path (which haproxy calls base).

and then you can use
use_backend be-aa if { base_beg -f /path/to/acl/file }
or
use_backend be-aa if { base_beg,map(/path/to/acl/file) -m found }

or you can use a map file which has base as key and backend name as value, and use

use_backend %[base_beg,map(/path/to/map/file)] if { base_beg,map(/path/to/map/file) -m found }

What about:

frontend http-in bind *80
http-request set-var(txn.domain) req.hdr(Host),field(1,:),lower,map(/usr/src/app/be-aa.routes.map.txt)
use_backend be-aa if { path_beg,strcmp(txn.domain) -eq 0 }  # -eq 0 means true
use_backend be-bb if { path_beg,strcmp(txn.domain) -eq 0 }

@jerome - this is great. Local testing shows this might be the solution we’ve been looking for. I tweaked it slightly to strip the port number (local testing tacks a port onto base) and the www. prefix so our map could reduce the key duplication, but overall I think this will do the trick.

 http-request set-var(txn.lookup) base,lower,regsub(^www\.,,i),regsub(:[0-9]*,,i)
 use_backend %[var(txn.lookup),map_beg(/domain.routes.map.txt)] if { var(txn.lookup),map_beg(/domain.routes.map.txt) -m found }

Now I’m curious how efficient the maps are in-memory if they’ve got ~130k entries…

Thanks again!

Hey @Baptiste
That doesn’t seem to work. The -eq needs to be eq with no leading hyphen, and I think it finds the map value correctly, but the path_beg doesn’t activate on “correct” paths.

Another issue solved by @jerome’s approach is that the backend is stored in the map, which means one map can serve all backends, which is really cool.

Thanks for the response regardless - it’s helpful to see the various ways haproxy commands can be mixed together.