引入
<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);
// ...
}
}