If you host dozens of web services that reside at various subdomains, TCP ports, and paths, then migrating them to live under a single address could simplify how clients access them and make your job of managing access easier. It would mean moving from a hodgepodge of address schemes, such as:
- www.example.com/app-a/
- www.example.com:8080/app-b/
- app-c.example.com
to a single address wherein services are designated by the URL’s path:
- www.example.com/app-a/
- www.example.com/app-b/
- www.example.com/app-c/
The good news is that you don’t need to rearrange your entire network to make this happen. You can deploy the HAProxy load balancer in front of your services and then use path-based routing to direct requests to the correct backend service. Here’s how to do it.
Configure HAProxy for path-based routing
In the end, your HAProxy configuration will look like this:
Let’s dig into what it all means. Consider the frontend section, which receives requests. It contains a use_backend
directive that directs traffic for each application.
The first line states that if the path is /a or if it begins with /a/ then route to the backend named app-a. If the path is /b or if it begins with /b/ then route to the backend named app-b. The path
fetch method compares the whole path, while path_beg
compares the beginning of the path. These lines route requests to a specified backend pool of servers when the given condition is true.
For example, the first line will match requests such as:
- www.example.com/a
- www.example.com/a/my-function
But it will not match:
- www.example.com/aaa
- www.example.com/abbb/my-function
If you would like to match on other prefixes, simply make a list of them:
This syntax can be a bit terse. So if you prefer a more verbose option, you can break out the conditional expression onto a separate line or lines. The syntax below has the same result, but explicitly defines the condition using two acl directives. By having two such directives with the same name, they form a single expression separated by a logical or:
Did you know? You can also use map files to store long lists of path-to-backend mappings.
Next, define the backend sections.
In this example, the http-request replace-path
directives remove the prefixes /a and /b before relaying the request to the server. A request for the URL path /a/my-function would become just /my-function before reaching the server. It’s important that this directive goes into the backend and not the frontend because if it were in the frontend, it would replace the path, stripping off the prefix, before the use_backend
rules could try to match it, resulting in no match. In general, HAProxy applies http-request
rules before use_backend
rules. Therefore, it’s better to route the request first, then apply such transformations.
The http-request replace-path
directive expects a regular expression, such as /a(/)?(.*)
, which states that you want to match any path that begins with /a optionally followed by a forward slash, and then followed by anything else. The anything else part, enclosed in parentheses, is captured, meaning you can refer to it in the replacement value by number. In this instance, the anything else we captured is referenced with \2, since it is the second capture group (second set of parentheses).
Below are a few ways in which HAProxy will route requests and how the backend servers will see them:
Original requested path | Routes to server | Server sees |
/a/ | app-a | / |
/b | app-b | / |
/a?foo=bar | app-a | /?foo=bar |
/b/my-function?foo=bar | app-b | /my-function?foo=bar |
Conclusion
In this blog post, you learned how to configure path-based routing using HAProxy. Several key points to remember: define conditions for which application to route the request to by using the path
and path_beg
fetch methods to match the path, and you can strip off the prefix before the request is relayed to the server by using the http-request replace-path
directive.
Hello!
This is very helpful and clarifies the use of this directive nicely!
Because I am using a Raspberry Pi, however, I am stuck with haproxy 1.8.29, which does not have ‘replace-path’.
Could you help me understand how to achieve the same path-based routing described here using older directives like reqrep?
Thanks!
Hi Evan, you would use a similar regular expression syntax + capture groups, but when using reqrep or reqirep, the regular expression is working on the entire HTTP request line, and so you must change it to get past the scheme (http://) and domain. Check out http://docs.haproxy.org/1.8/configuration.html#reqirep for an example.
Hi!
Is there any way to use HAProxy to route to different LOCAL paths?
For example, lets say I have two directories mounted on one host from different VPN connections:
/usr/local/DIRECTORYA
and
/usr/local/DIRECTORYB
The data in both DIRA and B are identical and synced
I want to access the data from /usr/local/MYDATA where HAProxy would use roundrobin to choose either DIRA or DIRB to pull from…
Is this possible?
You will need a process or processes that serves the contents of those directories, and then HAProxy can round-robin load balance them. You could, for example, run a node.js web server. The processes would be exposed on different TCP ports or on different UNIX sockets.
Unless I am missing something, or the article was changed, I do not see anything in the article on how to handle the third example ‘app-c.example.com’ -> ‘www.example.com/app-c/’.This was the case I was most interested in.
But based on this article, I think what needs to be done is to create an acl like ‘acl app-c-path hdr_dom(host) -i app-c.example.com’ and then use configuration similar to the others.
Thanks
Good catch. I focused only on path-based routing and not subdomain-based routing. The aspiration of this article is to show the value of consolidating on a path-based routing strategy. But, if you prefer to route based on subdomain, check out this article: https://www.haproxy.com/blog/how-to-map-domain-names-to-backend-server-pools-with-haproxy/
Hello!
Is path-based routing possible with an https connection?
Yes, when you use ‘mode http’ and you terminate the SSL connection in HAProxy, you can use path-based routing with HTTPS.
Hello,
I’m looking to rewrite the backend server response with the URI format viewed by the client.
Do you have some tips to do that ?
Hi Romain, could you give some context (or reach out to me via slack.haproxy.org)? I don’t think the client will see a different URI than what they sent, because the path replacement happens only between the HAProxy – server communication (should be invisible to the client).