Spring Cloud Gateway MVC (Sevlet版本)

297 阅读5分钟

引入

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

Spring Cloud Gateway Server MVC 是基于Springboot和SpringMVC Function,不能使用响应式的库了。服务器是基于Serlvet容器,比如tomcat或者jetty

术语

  • 路由 网关基本组成单元,由id、目标uri、一组断言、一组过滤器组成。
  • 断言 RequestPredicate接口实现,入参是ServerRequest
  • 过滤器 HandlerFilterFunction接口实现
    • 请求前 HandlerFilterFunction.ofRequestProcessor()
    • 请求后 HandlerFilterFunction.ofResponseProcessor()

工作原理

  • HandlerFunctions 路由函数
  • GatewayRequestPredicates 内置了断言
  • FilterFunctions 内置了过滤器
    • BeforeFilterFunctions 请求前过滤器
    • AfterFilterFunctions 请求后过滤器

Java路由API

定义一个实现RouterFunction 的Bean即可。

  • 可以使用RouterFunctionBuilder来简化
import static org.springframework.web.servlet.function.RouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

class SimpleGateway {
    @Bean
    public RouterFunction<ServerResponse> getRoute() {
        return route().GET("/get", http("https://httpbin.org")).build();
    }
}

GatewayRouterFunctions 可以设置元数据到请求的属性中

import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

class SimpleGateway {
    @Bean
    public RouterFunction<ServerResponse> getRoute() {
        return route("simple_route").GET("/get", http("https://httpbin.org")).build();
    }
}

网关处理器函数

使用 HandlerFunctions中的方法来创建 HandlerFunction。最常用的就是 HandlerFunctions.http方法。如果没有提供uri的话,可以通过属性 MvcUtils.GATEWAY_REQUEST_URL_ATTR 来获取uri,这提供了一种loadbalance的方法。

请求断言

After

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: after_route
          uri: https://example.org
          predicates:
          - After=2017-01-20T17:42:47.789-07:00[America/Denver]

Before

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: before_route
          uri: https://example.org
          predicates:
          - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

Bwtween

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: between_route
          uri: https://example.org
          predicates:
          - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

Cookie

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: cookie_route
          uri: https://example.org
          predicates:
          - Cookie=chocolate, ch.p

Header

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: header_route
          uri: https://example.org
          predicates:
          - Header=X-Request-Id, \d+

Host

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: host_route
          uri: https://example.org
          predicates:
          - Host=**.somehost.org,**.anotherhost.org

Method

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: method_route
          uri: https://example.org
          predicates:
          - Method=GET,POST

Path

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: path_route
          uri: https://example.org
          predicates:
          - Path=/red/{segment},/blue/{segment}

Query

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: query_route
          uri: https://example.org
          predicates:
          - Query=green

Weight

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: weight_high
          uri: https://weighthigh.org
          predicates:
          - Weight=group1, 8
        - id: weight_low
          uri: https://weightlow.org
          predicates:
          - Weight=group1, 2

处理器函数

AddRequestHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: add_request_header_route
          uri: https://example.org
          filters:
          - AddRequestHeader=X-Request-red, blue

AddRequestHeadersIfNotPresent

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: add_request_headers_route
          uri: https://example.org
          filters:
          - AddRequestHeadersIfNotPresent=X-Request-Color-1:blue,X-Request-Color-2:green

AddRequestParameter

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: add_request_parameter_route
          uri: https://example.org
          filters:
          - AddRequestParameter=red, blue

AddResponseHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: add_response_header_route
          uri: https://example.org
          filters:
          - AddResponseHeader=X-Response-Red, Blue

CircuitBreaker


spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: circuitbreaker_route
          uri: https://example.org
          filters:
          - CircuitBreaker=myCircuitBreaker

DedupeResponseHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: dedupe_response_header_route
          uri: https://example.org
          filters:
          - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

FallbackHeaders

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: ingredients
          uri: lb://ingredients
          predicates:
          - Path=//ingredients/**
          filters:
          - name: CircuitBreaker
            args:
              name: fetchIngredients
              fallbackUri: forward:/fallback
        - id: ingredients-fallback
          uri: http://localhost:9994
          predicates:
          - Path=/fallback
          filters:
          - name: FallbackHeaders
            args:
              executionExceptionTypeHeaderName: Test-Header

代码使用

import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.fallbackHeaders;
import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerFallbackToGatewayRoute() {
        return route("ingredients")
                .route(path("/ingredients/**"), http())
                .filter(lb("ingredients"))
                .filter(circuitBreaker("fetchIngredients", URI.create("forward:/fallback")))
                .build()
            .and(route("ingredients-fallback")
                .route(path("/fallback"), http("http://localhost:9994"))
                .before(fallbackHeaders())
                .build());
    }
}

LoadBalancer

代码

import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
		return route("api_route")
				.GET("/api/**", http())
					.filter(lb("apiservice"))
					.build();
    }
}
spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: api_route
          uri: lb://apiservice
          predicates:
          - Path=/api/**

MapRequestHeader


spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: map_request_header_route
          uri: https://example.org
          filters:
          - MapRequestHeader=Blue, X-Request-Red

ModifyRequestBody

import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.modifyRequestBody;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
import org.springframework.http.MediaType;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
		return route("rewrite_request_obj")
				.route(host("*.rewriterequestobj.org"), http("https://example.org"))
					.before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
								(request, s) -> new Hello(s.toUpperCase())))
					.build();
    }

	record Hello(String message) { }
}

PrefixPath

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: prefixpath_route
          uri: https://example.org
          filters:
          - PrefixPath=/mypath

PreserveHostHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: preserve_host_route
          uri: https://example.org
          filters:
          - PreserveHostHeader

RedirectTo

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: redirectto_route
          uri: https://example.org
          filters:
          - RedirectTo=302, https://acme.org

RemoveRequestParameter

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: removerequestparameter_route
          uri: https://example.org
          filters:
          - RemoveRequestParameter=red
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
		return route("removerequestparameter_route")
				.GET("/**", http("https://example.org"))
					.before(removeRequestParameter("red"))
					.build();
    }
}

RequestHeaderSize

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: requestheadersize_route
          uri: https://example.org
          filters:
          - RequestHeaderSize=1000B
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestHeaderSize;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsRequestHeaderSize() {
		return route("requestheadersize_route")
				.GET("/**", http("https://example.org"))
					.before(requestHeaderSize("1000B"))
					.build();
    }
}

RateLimiter

这里使用的是Bucket4j

Current version 8.14.0 documentation

import com.github.benmanes.caffeine.cache.Caffeine;
import io.github.bucket4j.caffeine.CaffeineProxyManager;

@Configuration
class RateLimiterConfiguration {

	@Bean
	public AsyncProxyManager<String> caffeineProxyManager() {
		Caffeine<String, RemoteBucketState> builder = (Caffeine) Caffeine.newBuilder().maximumSize(100);
		return new CaffeineProxyManager<>(builder, Duration.ofMinutes(1)).asAsync();
	}
}

配置

import static org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.rateLimit;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsRateLimited() {
		return route("rate_limited_route")
			.GET("/api/**", http("https://example.org"))
				.filter(rateLimit(c -> c.setCapacity(100)
					.setPeriod(Duration.ofMinutes(1))
					.setKeyResolver(request -> request.servletRequest().getUserPrincipal().getName())))
				.build();
    }
}

RewriteLocationResponseHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: rewritelocationresponseheader_route
          uri: http://example.org
          filters:
          - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,

RewritePath

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: rewritepath_route
          uri: https://example.org
          predicates:
          - Path=/red/**
          filters:
          - RewritePath=/red/?(?<segment>.*), /$\{segment}

RewriteResponseHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: rewriteresponseheader_route
          uri: https://example.org
          filters:
          - RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=***

SetPath

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: setpath_route
          uri: https://example.org
          predicates:
          - Path=/red/{segment}
          filters:
          - SetPath=/{segment}

SetRequestHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: setrequestheader_route
          uri: https://example.org
          filters:
          - SetRequestHeader=X-Request-Red, Blue

SetResponseHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: setresponseheader_route
          uri: https://example.org
          filters:
          - SetResponseHeader=X-Response-Red, Blue

SetStatus

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: setstatusstring_route
          uri: https://example.org
          filters:
          - SetStatus=UNAUTHORIZED
        - id: setstatusint_route
          uri: https://example.org
          filters:
          - SetStatus=401

StripPrefix

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: nameRoot
          uri: https://nameservice
          predicates:
          - Path=/name/**
          filters:
          - StripPrefix=2

Retry

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: nameRoot
          uri: https://nameservice
          predicates:
          - Path=/name/**
          filters:
          - StripPrefix=2

RequestSize

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: request_size_route
          uri: http://localhost:8080
          predicates:
          - Path=/upload
          filters:
          - name: RequestSize
            args:
              maxSize: 5000000

SetRequestHostHeader

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: set_request_host_header_route
          uri: http://localhost:8080
          predicates:
          - Path=/headers
          filters:
          - name: SetRequestHostHeader
            args:
              host: example.org

TokenRelay

spring:
  cloud:
    gateway:
      mvc:
        routes:
        - id: resource
          uri: http://localhost:9000
          predicates:
          - Path=/resource
          filters:
          - TokenRelay=

自定义断言和过滤器

RequestPredicate

import org.springframework.web.reactive.function.server.RequestPredicate;

class SampleRequestPredicates {
    public static RequestPredicate headerExists(String header) {
		return request -> request.headers().asHttpHeaders().containsKey(header);
    }
}

使用

import static SampleRequestPredicates.headerExists;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> headerExistsRoute() {
		return route("header_exists_route")
				.route(headerExists("X-Green"), http("https://example.org"))
					.build();
    }
}

HandlerFilterFunction

import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

class SampleHandlerFilterFunctions {
	public static HandlerFilterFunction<ServerResponse, ServerResponse> instrument(String requestHeader, String responseHeader) {
		return (request, next) -> {
			ServerRequest modified = ServerRequest.from(request).header(requestHeader, generateId());
			ServerResponse response = next.handle(modified);
			response.headers().add(responseHeader, generateId());
			return response;
		};
	}
}

使用

import static SampleHandlerFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.filter(instrument("X-Request-Id", "X-Response-Id"))
					.build();
    }
}

before

import java.util.function.Function;
import org.springframework.web.servlet.function.ServerRequest;

class SampleBeforeFilterFunctions {
	public static Function<ServerRequest, ServerRequest> instrument(String header) {
		return request -> ServerRequest.from(request).header(header, generateId());;
	}
}


import static SampleBeforeFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.before(instrument("X-Request-Id"))
					.build();
    }
}

after

import java.util.function.BiFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

class SampleAfterFilterFunctions {
	public static BiFunction<ServerRequest, ServerResponse, ServerResponse> instrument(String header) {
		return (request, response) -> {
			response.headers().add(header, generateId());
			return response;
		};
	}
}


import static SampleAfterFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.after(instrument("X-Response-Id"))
					.build();
    }
}

注册过滤器和断言

import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier;

@Configuration
class SamplePredicateSupplier implements PredicateSupplier {

	@Override
	public Collection<Method> get() {
		return Arrays.asList(SampleRequestPredicates.class.getMethods());
	}

}

META-INF/spring.factories

org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
  com.example.SamplePredicateSupplier

注册过滤器

import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier;

@Configuration
class SampleFilterSupplier extends SimpleFilterSupplier {

    public SampleFilterSupplier() {
		super(SampleAfterFilterFunctions.class);
	}
}

META-INF/spring.factories

org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
  com.example.SampleFilterSupplier

Sevlet 和 Filter

需要注意顺序

application/x-www-form-urlencoded的请求,容器会设置到请求参数上,而gateway使用FormFilter会重建请求体,所以需要读取请求参数的filter,需要把顺序设置到FormFilter之前,如下

import jakarta.servlet.Filter;
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
import org.springframework.core.Ordered;

class MyFilter implements Filter, Ordered {

    @Override
    public int getOrder() {
        return FormFilter.FORM_FILTER_ORDER - 1;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // ...
        filterChain.doFilter(request, response);
        // ...
    }
}