HAproxy domain name to backend mapping for path(url) based routing - haproxy

Does HAProxy support domain name to backend mapping for path based routing.
Currently it does support maps for vhost:
frontend xyz
<other_lines>
use_backend backend1 if { hdr(Host) -i myapp.domain1.com }
use_backend backend2 if { hdr(Host) -i myapp.domain2.com }
Can be rewritten using maps as:
frontend xyz
<other_lines>
use_backend %[req.hdr(host),lower,map_dom(/path/to/map,default)]
With the contents of map file as:
#domainname backendname
myapp.domain1.com backend1
myapp.domain2.com backend2
But if the routing is based on paths as shown in the example below:
frontend xyz
acl host_server_myapp hdr(host) -i myapp.domain.com
acl path_path1 path_beg /path1
acl path_path2 path_beg /path2
use_backend backend1 if host_server_myapp path_path1
use_backend backend2 if host_server_myapp path_path2
Is it possible to have mapping for this usecase? Using base instead of hdr(host) might give the entire path but it will not have the flexibility of domains since base is string comparison. Is there an other way to convert this to haproxy maps.

Start with the Layer 7 base fetch --
This returns the concatenation of the first Host header and the path part of
the request, which starts at the first slash and ends before the question
mark.
...then use map_beg() to match the beginning of the string to the map.
use_backend %[base,map_beg(/etc/haproxy/testmap.map,default)]
If the map file /etc/haproxy/testmap.map has a line matching the prefix, the backend in the map file is used. Otherwise, the backend called default will be used (that's the 2nd argument to map_beg() -- the value to be returned if the map doesn't match).
If the resulting backend doesn't actually exist, HAProxy continues processing the request as if this statement weren't configured at all.
So your map file would look something like this:
example.com/foo this-backend # note, also matches /foo/ba
example.com/foo/bar that-backend # note, matches /foo/bar
example.org/foo some-other-backend
To treat a subdomain as equivalent to the parent domain (e.g., treating example.com and www.example.com to be handled equivalently, without map duplication, as discussed in comments) the regsub() converter could be used to modify the value passed to the map:
use_backend %[base,regsub(^www\.,,i),map_beg(/etc/haproxy/testmap.map,default)]

Related

HAProxy - Add Trailing Slash

I am working on a project where I need requests destined to a particular page to be routed to a separate backend.
For example, all requests for https://mycooldomain.com will go to backend "A". But, if navigating to https://mycooldomain.com/secretpage I want it to go to backend "B".
Now, I have this working but running into an issue where I need the trailing slash for this to work correctly.
So, I need a way to say if request is https://[whateverhostnameisused]/secretpage redirect to https://[whateverhostnameisused]/secretpage/.
Here is a sample of my config so far:
frontend f_https
bind *:443 ssl crt cert.pem
reqadd X-Forwarded-Proto:\ https
#define hosts
acl host_a hdr(host) -i a.mycooldomain.com
acl host_b hdr(host) -i b.mycooldomain.com
acl host_c hdr(host) -i c.mycooldomain.com
#custom acls
acl secret path_beg -i /secretpage
#Custom redirects
##define backend
use_backend b_secret if secret
use_backend b_a if host_a
use_backend b_b if host_b
use_backend b_c if host_c
default_backend b_https
backend b_secret
server secret 192.168.15.15:5575 check
It seems that you are looking for something like this:
http-request redirect scheme https drop-query append-slash if { path -m str /secretpage }
This should work if applied to either the frontend or the backend.
http://cbonte.github.io/haproxy-dconv/1.6/configuration.html#4.2-redirect%20scheme
Specifying the scheme is only necessary because the syntax requires one of location | prefix | scheme, and with the other two options, you have to reassemble the URL yourself in the config.
Note also that reqadd is not officially deprecated, but the preferred way to add that request header is like this:
http-request set-header X-Forwarded-Proto https
Note that no : is specified and the space after the header name must not be escaped with \. This accomplishes the same result, but it uses a different code path inside HAProxy, and should be a more efficient operation. You will want to use the the http-request and http-response directives instead of reqxxx and rspxxx where possible, as they are also better suited to more complex manipulations.

Creating backends according to path

I am using HAProxy as a proxy server in front of Consul, a microservice registry. I use HAProxy to route traffic to a specific backend according to what type of node it is (what service it runs, is it active, what version it is, etc etc)
So far, I can use HAProxy to see what the path begins with, and route it to one backend eg.if the path begins with /blog, it'll go to the blog backend.
My question is, when there are multiple criterion to select a backend, how should I create said backends? Can I nest them in a way? Should I just exhaustive list all combinations of criteria and add a backend for that?
A backend is one or more servers (services, endpoints) that can all handle the same set of requests. You don't define backends based on what goes to them, but rather on what can go to them.
Example, if you have a set of servers that can serve several kinds of static assets, you wouldn't make a backend for javascript, another for CSS, another for images. You'd make one.
backend static-assets
mode http
server static-1 203.0.113.100:80
server static-2 203.0.113.200:80
Then route the appropriate requests to that backend.
You can do this in several ways.
Anonymous ACLs, one per pattern:
frontend main
mode http
bind :80
use_backend static-assets if { path_end .js }
use_backend static-assets if { path_end .css }
use_backend static-assets if { path_end .png }
Anonymous ACLs with explicit || (logical OR):
frontend main
mode http
bind :80
use_backend static-assets if { path_end .js } || { path_end .css } || { path_end .png }
Or use named ACLs. Each line in a named ACL is OR -- only one line needs to match for the ACL to match.
frontend main
mode http
bind :80
acl is_static path_end .js
acl is_static path_end .css
acl is_static path_end .png
use_backend static-assets if is_static
Or use any combination of these.
The first use_backend directive to match is the backend the request will go to -- they're processed in defined order. No "nesting" should be necessary.

pass a backend from backend map to haproxy nbsrv

Can someone advise what I have to change in the nbsrv expression in order to make it work:
frontend webfarm
bind 11.22.33.44:80
...
acl MAIN_not_enough_capacity nbsrv([%[req.hdr(host),lower,map(/etc/haproxy/backend.map,bk_default)]]) eq 0
http-request redirect code 301 location http://global-swajm.example.com if MAIN_not_enough_capacity
use_backend %[req.hdr(host),lower,map(/etc/haproxy/backend.map,bk_default)]
The idea is according to the host in the header to get the right backend name from the map file.
If there are no available servers in this backend the request will be redirected to another haproxy (in another DC).
use_backend is working perfectly:
use_backend %[req.hdr(host),lower,map(/etc/haproxy/backend.map,bk_default)]
but I don't know how to modify the expression for nbsrv in order to pass the backend name:
nbsrv([%[req.hdr(host),lower,map(/etc/haproxy/backend.map,bk_default)]])
just chiming in after having struggled with the same
Use
acl MAIN_not_enough_capacity req.hdr(host),lower,map(/etc/haproxy/backend.map,bk_default),nbsrv eq 0
instead

redirect all root domain request to www. subdomain, keeping the URL intact

I want to redirect all traffic to root domain to the www. version while still keeping path/query string intact, how can I do that? I have setup a HAproxy config like follows:
frontend http
bind *:80
bind *:443
acl has_www hdr_beg(host) -i www
http-request redirect code 301 location http://www.%[hdr(host)]%[req.uri] unless has_www
However this one does the following: example.com/abc?page=1 => www.example.com, where I actually wan't it to do: example.com/abc?page=1 => www.example.com/abc?page=1 - what am I missing?
if you are using HAProxy 1.5 and up this should work for you
Single domain :
acl has_www hdr_beg(host) -i www
redirect prefix http://www.example.com code 301 unless has_www
Multiple domains: (dirty but works)
# match non www requests:
acl has_www hdr_beg(host) -i www.
# add a header that says we need to redirect these type of requests (will explain later)
http-request add-header X-Host-Redirect yes unless has_www
# rule to identify newly added headers
acl www_redirect hdr_cnt(X-Host-Redirect) eq 1
# where the magic happens (insert www. in front of all marked requests)
reqirep ^Host:\ (.*)$ Host:\ www.\1 if www_redirect
# now hostname contains 'www.' so we can redirect to the same url
redirect scheme http if www_redirect
The reason why we add a new header is because it seems that in HAProxy 1.5 and up, acls are being evalutated on each reference. so when you try to do this without the new header it does this :
#catch all domains that begin with 'www.'
acl has_www hdr_beg(host) -i www.
#insert www. in front of all non www requests
reqirep ^Host:\ (.*)$ Host:\ www.\1 unless has_www
#now hostname contains 'www.' so when the next rule gets interpreted it does not match then fails
#redirect to new url
redirect code 301 prefix / if has_www
hope this helps. I've tested this with 5 different domains on HAProxy 1.5 and it worked fine and kept the query strings intact on redirect
A multi-domain solution for HAProxy 1.6 which redirects preserving the path and query parameters:
frontend 443
http-request redirect prefix https://www.%[hdr(host)] code 301 unless { hdr_beg(host) -i www. }

Dynamic routing to backend based on context path in HAProxy

I have specific requirement that depending on my context path I have to redirect my traffic to different server/port though HAProxy. I have already achieved the same with "path_beg" in ACL. Below is the configuration.
use_backend a1 if { path_beg /a1 }
use_backend a2 if { path_beg /a2 }
backend a1
balance roundrobin
server 1-www 172.17.0.1:80 check cookie s2
backend a2
balance roundrobin
server 1-www 172.17.0.3:80 check cookie s2
Now the concern is here every context path I also need to enter a frontend settings like use_backend a1 if { path_beg /a1 } which I would like to avoid. What I want when I need to add a new server I will add the backend as it's necessary but for the front end I am looking for something like this.
use_backend regex
Where regular expression will take the context path from the url and will proceed to the corresponding backend.
Note: backend name will be same as context path. Like if the url is http://example.com/dummy then backend name will be "dummy".
Any suggetion on the same.