Spring Cloud Gateway Deep Dive: Request Flow, Handlers & Filters

This article explains the internal execution process of Spring Cloud Gateway, detailing how DispatcherHandler locates HandlerMapping, creates FilteringWebHandler, selects SimpleHandlerAdapter, builds and runs the filter chain—including core filters like RouteToRequestUrlFilter and NettyRoutingFilter—and transforms incoming URLs to their target destinations.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Cloud Gateway Deep Dive: Request Flow, Handlers & Filters

Gateway request execution flow consists of several steps:

1. Core processing class DispatcherHandler

public class DispatcherHandler implements WebHandler {
    public Mono<Void> handle(ServerWebExchange exchange) {
        // ...
        return Flux.fromIterable(this.handlerMappings)
            // find suitable HandlerMapping
            .concatMap(mapping -> mapping.getHandler(exchange))
            .next()
            .switchIfEmpty(createNotFoundError())
            .flatMap(handler -> invokeHandler(exchange, handler))
            .flatMap(result -> handleResult(exchange, result));
    }
}

2. Find HandlerMapping object

The routing request resolves to RoutePredicateHandlerMapping, which stores the matched route in the exchange attributes and returns Mono.just(webHandler) where the webHandler is FilteringWebHandler.

@Bean
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
    return new FilteringWebHandler(globalFilters);
}

3. Find HandlerAdapter object

The FilteringWebHandler (a WebHandler subclass) is finally handled by SimpleHandlerAdapter:

public class SimpleHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return WebHandler.class.isAssignableFrom(handler.getClass());
    }

    @Override
    public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
        WebHandler webHandler = (WebHandler) handler;
        Mono<Void> mono = webHandler.handle(exchange);
        return mono.then(Mono.empty());
    }
}

4. Execute FilteringWebHandler

public class FilteringWebHandler implements WebHandler {
    public Mono<Void> handle(ServerWebExchange exchange) {
        Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
        List<GatewayFilter> gatewayFilters = route.getFilters();
        List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
        combined.addAll(gatewayFilters);
        AnnotationAwareOrderComparator.sort(combined);
        return new DefaultGatewayFilterChain(combined).filter(exchange);
    }
}

5. Core filters

Key filters include RouteToRequestUrlFilter, which builds the target URL from route configuration, and NettyRoutingFilter, which performs the actual network request using a Netty HttpClient. Example configuration and URL transformation are shown.

spring:
  cloud:
    gateway:
      enabled: true
      httpclient:
        connect-timeout: 10000
        response-timeout: 5000
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true
      default-filters:
        - StripPrefix=1
      routes:
        - id: R001
          uri: http://localhost:8787
          predicates:
            - Path=/api-1/**,/api-2/**
          metadata:
            akf: "dbc"
            connect-timeout: 10000
            response-timeout: 5000

Accessing http://localhost:8088/api-1/demos is transformed to http://localhost:8787/demos, and the final URL is stored in the exchange attributes.

Note: the StripPrefixGatewayFilterFactory#apply filter runs after the above filters.

Additional core filter implementation:

public class NettyRoutingFilter implements GlobalFilter {
    private final HttpClient httpClient;
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
            .request(method).uri(url).send((req, nettyOutbound) ->
                nettyOutbound.send(request.getBody().map(this::getByteBuf)))
            .responseConnection((res, connection) -> {
                // handle response, headers, status, timeouts, etc.
                return Mono.just(res);
            });
        Duration responseTimeout = getResponseTimeout(route);
        if (responseTimeout != null) {
            responseFlux = responseFlux.timeout(responseTimeout,
                Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout)))
                .onErrorMap(TimeoutException.class,
                    th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));
        }
        return responseFlux.then(chain.filter(exchange));
    }
    // getHttpClient implementation omitted for brevity
}

End of flow.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavareactiveSpring Cloud GatewayDispatcherHandlerFilteringWebHandlerNettyRoutingFilter
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.