Lesson 2

What the backend receives

Picking a route is only half the job. The other half is the path the gateway assembles for the backend, and the three gateways do that on two different principles. Concretely: asked to “strip the prefix”, Envoy and OpenResty send //x/y while Kong sends /x/y.

The question

A client sends /c/x/y. The gateway has matched a route on the segment /c. Whoever wrote the configuration wants the backend to see /x/y — the prefix gone. Each gateway has its own way of saying that:

gatewayhow it is written
Envoyroute: { prefix_rewrite: "/" }
Kongstrip_path: true
OpenRestyproxy_pass http://backend:8080/;

The backend is an nginx that echoes the path it receives, so the answer is read straight off the response.

Nineteen combinations

Every row below is a real curl, and the request is always /c/x/y:

intentconfigurationEnvoyKongOpenResty
change nothing — / strip_path: false / proxy_pass with no URI /c/x/y /c/x/y /c/x/y
strip the prefix "/" / strip_path: true / proxy_pass …:8080/ //x/y /x/y //x/y
replace with /b "/b" / service url …/b + strip_path / proxy_pass …:8080/b /b/x/y /b/x/y /b/x/y
target ends in a slash "/b/" / service url …/b/ + strip_path / proxy_pass …:8080/b/ /b//x/y /b/x/y /b//x/y
slash on both sides pattern "/c/" with target "/b/" /b/x/y /b/x/y /b/x/y
keep the prefix, prepend /b Kong: service url …/b + strip_path: false — /b/c/x/y —
use a regex instead Envoy: regex_rewrite ^/c/(.*)$ → /b/$1 /b/x/y — —

Kong got three further curls to see whether a slash could throw it off: route /c/ with service …/b/, with …/b, and with an address carrying no path at all. All three came back clean — /b/x/y, /b/x/y and /x/y. Kong is insensitive to a trailing slash on either side.

Where the double slash comes from

The table collapses into one sentence. Envoy and nginx do a plain textual replacement of exactly the segment that matched: take the path, cut off as many characters as the pattern is long, and paste the target in front. The pattern /c does not include the next slash, so the remainder is /x/y — still carrying a leading slash. Paste / in front of that and you get //x/y.

Kong cuts along path segments and rejoins with exactly one slash, dropping the trailing slash of the service path as it goes. That is why all four combinations of route /c or /c/ with service …/b or …/b/ give the same /b/x/y.

A double slash is not a cosmetic issue Plenty of backends treat //x/y as quite unlike /x/y: a framework's router sees an empty leading segment and matches no route; an object store sees a different key; and a cache sees two addresses and stores two copies. It tends to surface late, because the landing page /c still works and only /c/x/y breaks.

The fix

For Envoy and OpenResty, put the trailing slash on both sides: pattern /c/ with target /b/. The remainder is then x/y with no leading slash, pasted onto /b/ for a clean /b/x/y — measured, the fifth row of the table.

The cost is that the pattern /c/ no longer matches /c itself, so you usually need a second route for the bare path or a redirect. Envoy offers another way out with regex_rewrite, where you spell out exactly what to keep:

route:
  cluster: backend
  regex_rewrite:
    pattern: { regex: '^/c/(.*)$' }
    substitution: '/b/$1'

That combination gives /b/x/y — as intended, and independent of the pattern's trailing slash.

An Envoy trap hit while building the rig

The first Envoy configuration for this rig was missing the http_filters block with the router filter. The result: Envoy started cleanly with no error line, port 8000 accepted connections, the request was sent in full — and no response ever came back. At info level the log still printed starting main dispatch loop and all dependencies initialized as usual.

http_filters:
- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

The symptom is indistinguishable from a dead backend or a broken network, which sends you looking in the wrong place. The tell: curl -v shows the connection established and the request completely sent off before the timeout — an unreachable backend fails earlier than that.

The lab

Set the path, the matched segment and each gateway's rewrite, and see what the backend receives. The algorithms are rewrites of all three and match 19 of 19 curls from the rig.

Configuration

What the backend receives

Takeaways

The previous lesson covers the other half: one route set, three different answers.