Spring Cloud Gateway
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Spring Cloud Gateway is the reactive counterpart of YARP: predicates select a route, filters transform the exchange, and a target URI decides where it goes.
A narrow edge route
Section titled “A narrow edge route”The course gateway exposes /api/scales/**, removes the public /api prefix and adds one traceable header:
@BeanRouteLocator courseRoutes(RouteLocatorBuilder routes, @Value("${course.scales-uri}") String scalesUri) { return routes.routes() .route("scales", route -> route.path("/api/scales/**") .filters(filters -> filters.stripPrefix(1) .addRequestHeader("X-Course", "spring-cloud-gateway")) .uri(scalesUri)) .build();}The route owns transport policy, not music-domain decisions. Authentication, rate limits and correlation are reasonable edge concerns; chord construction is not. Every filter also changes failure semantics, so keep the chain short and observable.
A real test with no external service
Section titled “A real test with no external service”The integration test binds both processes to OS-assigned ports. A tiny Reactor Netty upstream echoes its path and header; the gateway starts through @SpringBootTest and receives the upstream URI through @DynamicPropertySource.
upstream = HttpServer.create().port(0) .route(routes -> routes.get("/scales/C", (request, response) -> response .sendString(Mono.just(request.uri() + "|" + request.requestHeaders().get("X-Course"))))) .bindNow();The assertion is deterministic:
GET /api/scales/C -> /scales/C|spring-cloud-gatewayGET /other -> 404Run it with:
mvn -B -pl l06-gateway -am testThis covers predicate matching, filter order, forwarding and an unmatched path. It does not prove production latency, TLS termination or proxy-header trust; those require deployment-specific tests.
Exercises
Section titled “Exercises”- Add a route for
/api/progressions/**that preserves the path after removing/api.
Solution
Add a second route with a distinct ID, a path("/api/progressions/**") predicate and stripPrefix(1). Test the upstream URI rather than inspecting the route object.
- Add a response header containing the matched route ID.
Solution
Use addResponseHeader("X-Route", "scales") and assert it with WebTestClient.expectHeader(). Do not expose internal host names.
- Why should a retry filter not cover every request automatically?
Solution
Retries can duplicate non-idempotent effects and multiply load during an outage. Restrict them by method and failure class, bound attempts, and measure retry amplification. Lesson 9 adds resilience deliberately.