跪拜 Guibai
← Back to the summary

Spring Cloud Gateway's Route-Predicate-Filter Pipeline, End to End

1. Core Positioning of the Gateway: Unified Traffic Entry for Microservices

In a microservices architecture, the backend is split into multiple independent services such as orders, payments, and users. The frontend cannot maintain a massive number of service IPs and ports. Spring Cloud Gateway was created to serve as the sole access entry point for the entire business cluster.

The official recommendation is to base it on the WebFlux reactive server rather than traditional SpringMVC. Relying on a non-blocking asynchronous model, the gateway has a higher concurrent carrying capacity. Its core responsibilities are as follows:

  1. Unified Entry: The frontend only connects to the gateway address, shielding the deployment details of the backend cluster;
  2. Route Forwarding: Distributes requests to corresponding microservices based on matching rules;
  3. Load Balancing: Works with Spring Cloud LoadBalancer to achieve service cluster load balancing;
  4. Convergence of Common Capabilities: Centrally handles cross-origin, request validation, response encapsulation, identity tokens, and other common logic;
  5. Traffic Control: Can be extended to implement rate limiting, authentication, black/white lists, monitoring, and alerts.

Load Balancing Dependency Explanation

The new version of Spring Cloud has removed the built-in Ribbon. Load balancing capability is now independent as spring-cloud-loadbalancer. You must manually introduce the Maven dependency to use the lb:// load balancing protocol:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

Important Development Rules

When using OpenFeign for remote calls, interfaces annotated with @FeignClient cannot have a class-level @RequestMapping. Path annotations are only allowed on interface methods; otherwise, path concatenation errors and 404 exceptions will occur.

Distinguishing Microservice Call Chains

  1. Default Scenario: Services call each other directly through the registry without going through the gateway, which offers higher performance;
  2. Forced Through Gateway: Modify the value of @FeignClient to the gateway service name and adjust the interface request path accordingly. This is suitable for scenarios requiring unified gateway authentication.

2. Routes: The Basic Rules for Gateway Forwarding

A route is the most fundamental component of the gateway. It consists of four parts: a unique ID, a target service address, matching predicates, and filters. The configuration is stored in application-route.yml.

spring:
  cloud:
    gateway:
      routes:
        - id: order-route # Route unique identifier, must not be duplicated
          uri: lb://service-order # lb stands for load balancing, pointing to the order service in the registry
          predicates: # Route matching predicates
            - Path=/order/**
          order: 0

Route matching priority rule: When the order value is not manually specified in the configuration, matching proceeds from top to bottom. The request is forwarded to the first route whose predicates it matches. When the order field is configured, priority decreases from smallest to largest, with 0 being the highest priority.

3. Route Predicates: Judgement Rules for Precise Request Matching

A Predicate is essentially a set of request matching rules. A request is only forwarded to the corresponding service if it meets all the predicate conditions of the current route. The underlying implementation is uniformly generated by the RoutePredicateFactory factory.

1. Official Built-in Common Predicates

Table

Predicate Type Matching Logic
After / Before / Between Matches based on request time, limiting access periods
Cookie Validates the Cookie carried by the request, supports regex for value matching
Header Validates a specified request header, supports regex
Host Matches the accessing domain name
Method Limits the HTTP request method (GET/POST, etc.)
Path Matches the request path, the most commonly used predicate
Query Validates query parameters carried in the URL
RemoteAddr Restricts the client access IP segment
Weight Implements grayscale load balancing based on weight

2. Custom Predicate Factory in Practice

When built-in predicates cannot meet business needs, you can customize a predicate factory. The naming convention is XXXRoutePredicateFactory, and it must inherit from AbstractRoutePredicateFactory. The VipRoutePredicateFactory in the text implements a custom parameter matching predicate: it verifies that a request carries a specified parameter with an exact matching value, allowing only VIP users to access the corresponding route. Key implementation points:

  1. An internal static Config class carries configuration parameters, using @NotEmpty for parameter validation;
  2. shortcutFieldOrder defines the order of parameters for short YAML configuration;
  3. The apply method writes the matching logic, reading parameters from the request to complete the comparison.

package cn.ecut.gateway.predicate;

import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {

    public VipRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("param","value");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {

            @Override
            public boolean test(ServerWebExchange exchange) {
                //localhost/search?q=haha&user=liu
                ServerHttpRequest request = exchange.getRequest();
                String first= request.getQueryParams().getFirst(config.param);
                return StringUtils.hasText(first)&& first.equals(config.value);
            }
         };
    }
    /**
     * Configurable parameters
     */
    @Validated
    public static class Config {
        @NotEmpty
        private  String param;
        @NotEmpty
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public String getParam() {
            return param;
        }

        public void setParam(String param) {
            this.param = param;
        }

    }
}

4. Filters: Unified Processors for Requests and Responses

Filters are responsible for processing requests and responses before and after forwarding. They are divided into global default filters (executed for all requests) and route-specific custom filters (effective only for the current route). Custom filters must inherit the corresponding filter factory parent class, and the class name must be consistent with the YAML configuration name.

1. Parent Class Distinction

2. In Practice: One-Time Token Filter OnceTokenGatewayFilterFactory

Developed based on AbstractNameValueGatewayFilterFactory, this filter relies on the WebFlux reactive asynchronous API. After request processing is complete, it automatically injects a one-time token into the response header:

  1. Post-processing logic is placed inside chain.filter(exchange).then() to ensure the Header is modified after the response is generated;
  2. Supports two token modes: a UUID random string and a fixed JWT identifier;
  3. YAML configuration format: OnceToken=custom header name, token type.

Complete core code:

package cn.ecut.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;
/**
 * Reactive APIs are all asynchronous
 */
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // Each time it opens, add a one-time token, supporting various formats like uuid, jwt, etc.
                return chain.filter(exchange).then(
                        Mono.fromRunnable(() -> {
                            ServerHttpResponse response = exchange.getResponse();
                            HttpHeaders headers = response.getHeaders();
                            String value = config.getValue();
                            if("uuid".equalsIgnoreCase(config.getValue())){ // case-insensitive
                                value= UUID.randomUUID().toString();
                            }
                            if("jwt".equalsIgnoreCase(config.getValue())){
                                value="jwt";
                            }
                            headers.add(config.getName(), value);
                        })
                );
            }
        };
    }
}

5. Global Cross-Origin CORS Configuration for the Gateway

In a monolithic project, @CrossOrigin can be added to a Controller to solve cross-origin issues. In a microservices architecture, it is recommended to configure global CORS uniformly at the gateway to avoid repetitive development in each service. A basic configuration example:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # Effective for all global paths
            allowedOrigins: "*" # Allow all frontend domains (specify domains in production)
            allowedMethods: "*" # Allow all request methods

Production optimization tip: allowedOrigins: * poses a security risk. You should fill in the fixed frontend domain name; when enabling allowCredentials to carry cookies, wildcard domains are prohibited.

6. Overall Summary and Implementation Approach

Spring Cloud Gateway is the traffic hub of a microservices architecture. The entire system is composed of three core components: Routes, Predicates, and Filters:

  1. Routes: Define forwarding targets and implement traffic distribution;
  2. Predicates: Finely control which requests can pass through the current route;
  3. Filters: Uniformly intercept and modify requests and responses to implement common logic such as cross-origin, token issuance, logging, and authentication.

Relying on a reactive non-blocking architecture, the gateway offers high-performance advantages while providing standardized extension interfaces to support flexible expansion of business capabilities with custom predicates and filters. During development, it is necessary to follow specifications for load balancing dependencies, Feign annotations, and reactive post-processing to avoid common issues like 404 errors, response header failures, and parameter matching exceptions.

In actual projects, you can extend capabilities based on the custom components in this text: combine the token filter to implement unified gateway authentication, use a custom IP predicate to implement access black/white lists, and use a global filter to uniformly print request logs. Converge all traffic processing logic unrelated to business into the gateway, significantly simplifying backend microservice development costs.