Path-Based Routing With HAProxy

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:

frontend mysite
bind :80
# route to a backend based on path's prefix
use_backend app-a if { path /a } || { path_beg /a/ }
use_backend app-b if { path /b } || { path_beg /b/ }
backend app-a
# strip the prefix '/a' off of the path
http-request replace-path /a(/)?(.*) /\2
server server1 127.0.0.1:8080 check maxconn 30
backend app-b
# strip the prefix '/b' off of the path
http-request replace-path /b(/)?(.*) /\2
server server1 127.0.0.1:8081 check maxconn 30

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.

use_backend app-a if { path /a } || { path_beg /a/ }
use_backend app-b if { path /b } || { path_beg /b/ }

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:

use_backend app-a if { path /a /c /d } || { path_beg /a/ /c/ /d/ }


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:

acl app-a-path path /a /c /d
acl app-a-path path_beg /a/ /c/ /d/
use_backend app-a if app-a-path
Did you know?

You can also use map files to store long lists of path-to-backend mappings.

Next, define the backend sections.

backend app-a
http-request replace-path /a(/)?(.*) /\2
server server1 127.0.0.1:8080 check maxconn 30
backend app-b
http-request replace-path /b(/)?(.*) /\2
server server1 127.0.0.1:8081 check maxconn 30

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.

Subscribe to our blog. Get the latest release updates, tutorials, and deep-dives from HAProxy experts.