Build a Service Mesh (Envoy / Istio style) (13 scenes)
Scene 03 · L4 vs L7 — bytes or requests
An L4 proxy forwards opaque TCP bytes; an L7 proxy parses HTTP and can act on path, method, and headers. The mesh is L7 for everything that follows.
Previously
A sidecar that owns 'policy' has to know what a request IS before it can apply any policy to it — that is the L4/L7 split.
Scene 03
L4 vs L7 — bytes or requests
Diagram
A single sidecar proxy in the middle with a 2-position slider. On the left ('L4'), a proxy that just forwards bytes without opening them is operating at the transport layer — that's an **L4 proxy**: it sees source/destination IP and port but cannot read what's inside the bytes. On the right ('L7'), a proxy that opens the envelope and reads method/path/headers is operating at the application layer — that's an **L7 proxy**: it terminates the HTTP stream and parses method, path, and headers, so it can apply rules to those fields. Below the proxy, a capability table shows which routing tasks each mode supports.
L4 proxy: sees IPs, not bytes' meaning →
L7 sees path + headers · L4 sees only IP/port
The same request enters the same sidecar twice. First as L4: only the IP/port labels light up; the payload is a featureless byte bar. Flip the slider and the proxy's interior changes — the parsed HTTP envelope (method, path, headers) becomes visible.
Implementation
L4Proxy.serve()
transport-layer pipe — bytes pass through opaquely
1def serve():2 conn = accept(':8080')3 upstream = dial('backend:8080')4 # splice both directions; never look inside5 pipe(conn, upstream)6 pipe(upstream, conn)7 # we don't even know if it's HTTP
L7Proxy.serve()
application-layer parse — bytes become an HTTP request
1def serve():2 conn = accept(':8080')3 req = http.parse(conn) # method, path, headers4 route = match(routes, req)5 upstream = dial(route.cluster)6 resp = upstream.send(req)7 http.write(conn, resp)
L7Proxy.match(routes, req)
the rules that only exist once HTTP is parsed
1def match(routes, req):2 for r in routes:3 if r.path and not req.path.startswith(r.path):4 continue5 if r.header:6 name, want = r.header7 if req.headers.get(name) != want:8 continue9 return r # first match wins10 return default_route