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:
| gateway | how it is written |
|---|---|
| Envoy | route: { prefix_rewrite: "/" } |
| Kong | strip_path: true |
| OpenResty | proxy_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:
| intent | configuration | Envoy | Kong | OpenResty |
|---|---|---|---|---|
| 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.
//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
- Envoy's
prefix_rewriteand nginx'sproxy_passwith a URI are plain textual replacements of the matched segment; the slashes are yours to get right. - Kong's
strip_pathcuts along path segments and rejoins with exactly one slash, so it never produces a double slash in any combination. - Same intent, different result: stripping a prefix gives
//x/yon Envoy and OpenResty,/x/yon Kong. - The fix for Envoy and nginx is a trailing slash on both sides, or
regex_rewrite. - An Envoy config without the
routerfilter starts cleanly and then hangs every request, reporting nothing.
The previous lesson covers the other half: one route set, three different answers.