路由断言
Spring Cloud Gateway可以进行多种方式的路由断言,以路径方式为例,在application.properties配置文件中增加以下配置:
spring.cloud.gateway.routes[0].id=route_test1
spring.cloud.gateway.routes[0].uri=lb://springCloud-test
spring.cloud.gateway.routes[0].predicates[0]=Path=/test/**
其中:
- id:该过滤器ID
- uri:path匹配的请求自动转发到此地址
- path:路径匹配内容
实现效果为
GlobalFilter全局过滤
在SpringCloud生态系中,可以通过GlobalFilter在Spring Cloud Gateway对所有http请求进行全局过滤,实现包括请求鉴权等功能在内的业务需求。
Spring Cloud Gateway使用的是webflux模式,如何从请求参数中提取到相关数据,特别是针对POST请求如何提取数据,根据springboot和springcloud版本的不同有不同的处理方式,本文采用的版本为:
<spring-boot.version>2.1.2.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
此版本下经过参考过多个教程,最终确定采用的是Springcloud gateway获取post请求内容这篇文章的处理方法,以请求鉴权为例,代码为:
@Component
public class AuthTestFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String method = serverHttpRequest.getMethodValue();
System.out.println(method);
String contentType = serverHttpRequest.getHeaders().getFirst("Content-Type");
if (StringUtil.isNullOrEmpty(contentType)){
contentType = "DEFAULT";
}
if(method.equals("POST")||contentType.startsWith("multipart/form-data")){
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
try {
String bodyString = new String(bytes, "utf-8");
System.out.println(formatStr(bodyString));
JSONObject json = JSON.parseObject(bodyString);
String token = json.getString("token");
if(authCheck(token)){
exchange.getAttributes().put("POST_BODY",bodyString);
}else {
ServerHttpResponse response = exchange.getResponse();
JSONObject responseBody = new JSONObject();
responseBody.put("msg","鉴权失败");
byte[] bits = responseBody.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer2 = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer2));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}else if(method.equals("GET")){
String token = serverHttpRequest.getQueryParams().getFirst("token");
System.out.println(token);
if(authCheck(token)){
return chain.filter(exchange);
}else {
ServerHttpResponse response = exchange.getResponse();
JSONObject responseBody = new JSONObject();
responseBody.put("msg","鉴权失败");
byte[] bits = responseBody.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer2 = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer2));
}
}
return null;
}
@Override
public int getOrder() {
return 0;
}
private String formatStr(String str){
if (str != null && str.length() > 0) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
return m.replaceAll("");
}
return str;
}
public boolean authCheck(String token){
if(token.equals("qwe")){
return true;
}else {
return false;
}
}
}
测试结果为:
GET:
POST:
限流
Spring Cloud Gateway可以通过Redis实现限流,限流的基本原理与实现方式可以参考SpringCloudGateWay系列四:限流
首先在pom.xml中增加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
并且需要安装配置Redis
之后设计限流类型,这里以请求地址为限流依据,指定具体的限流key:
public class ApiKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getPath().value());
}
}
@Component
public class RequestLimitCustomTest {
@Bean(name = "apiKeyResolver")
public ApiKeyResolver apiKeyResolver(){
return new ApiKeyResolver();
}
}
之后需要进行redis配置
spring.redis.host=10.18.226.209
spring.redis.port=6379
spring.redis.database=0
特定路径限流
在application.properties配置文件中增加以下配置
spring.cloud.gateway.routes[0].id=route_test1
spring.cloud.gateway.routes[0].uri=lb://springCloud-test
spring.cloud.gateway.routes[0].predicates[0]=Path=/test/**
spring.cloud.gateway.routes[0].filters[0].name=RequestRateLimiter2
#自定义的bean
spring.cloud.gateway.routes[0].filters[0].args[key-resolver]=#{@apiKeyResolver}
#每秒允许访问次数
spring.cloud.gateway.routes[0].filters[0].args[redis-rate-limiter.replenishRate]=3
#令牌桶容量
spring.cloud.gateway.routes[0].filters[0].args[redis-rate-limiter.burstCapacity]=10
凡是符合过滤要求的请求会受到限流
全局限流
在application.properties配置文件中增加以下配置
spring.cloud.gateway.default-filters[0].name=RequestRateLimiter
spring.cloud.gateway.default-filters[0].args[key-resolver]=#{@apiKeyResolver}
#每秒允许访问次数
spring.cloud.gateway.default-filters[0].args[redis-rate-limiter.replenishRate]=3
#令牌桶容量
spring.cloud.gateway.default-filters[0].args[redis-rate-limiter.burstCapacity]=10
默认对所有请求进行过滤,测试效果为:
可以看到一秒内请求数超过限制,之后的请求即返回失败
熔断
Spring Cloud Gateway可以通过Hystrix实现熔断
首先增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
之后自定义熔断返回结果
@RestController
public class fallbackController {
@RequestMapping("/hystrixfallback")
public Map<String,String> defaultfallback(){
System.out.println("熔断");
Map<String,String> map = new HashMap<>();
map.put("resultCode","1");
map.put("resultMessage","服务异常");
map.put("resultObj","null");
return map;
}
}
在配置文件中配置hystrix
hystrix.metrics.enabled=true
#超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=30000
#转移线程
hystrix.shareSecurityContext=true
此时关闭微服务,测试熔断机制: