springcloud gateway网关获取post请求参数,网关路由,网关限流,熔断降级等实际操作
上一篇使用了springcloud 一代网关 zullfilter 的拦截和路由,这一篇将使用springcloud新一代网关 gateway 来进行参数拦截,路由,限流,熔断等操作。
首先我们看下pom.xml ,不同版本,可能会有不同的坑,操作基于一下版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hqk</groupId>
<artifactId>gtwayservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gtwayservice</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>
<dependencies>
<!-- 熔断、降级 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--reids 限流-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
首先我们进行网关拦截,验证token session 等操作,如下图代码,
package com.hqk.gtwayservice.filter;
import io.netty.buffer.ByteBufAllocator;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@Component
public class TokenFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String method = serverHttpRequest.getMethodValue();
if ("POST".equals(method)) {
//从请求里获取Post请求体
String bodyStr = resolveBodyFromRequest(serverHttpRequest);
System.out.println("bodyStr:"+bodyStr);
//TODO 这里对你获取的参数进行操作,比如 token session 的验证
//下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
URI uri = serverHttpRequest.getURI();
ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
request = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
//封装request,传给下一级
return chain.filter(exchange.mutate().request(request).build());
} else if ("GET".equals(method)) {
Map requestQueryParams = serverHttpRequest.getQueryParams();
//TODO 得到Get请求的请求参数后,做你想做的事
return chain.filter(exchange);
}
return chain.filter(exchange);
}
/**
* 从Flux<DataBuffer>中获取字符串的方法
*/
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
private DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
@Override
public int getOrder() {
return 0;
}
}
然后我们postman进行测试,如下图所示
这里我们完成第一步的参数拦截
接下来我们将对请求进行路由转发,这里没有注册到注册中心,直接转发服务,直接看yml配置
server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://127.0.0.1:8801 # 已负载均衡方式转发
predicates:
- Path=/**/** #只漏油转发
这里只改变IP进行转发,不会改变请求地址,如需改变请求地址,则改变 Path 路径,可参考
如果项目存在高并发场景,我们可以对请求地址 或 IP 等进行限流,限流配合redis进行使用,首先假如redis配置
redis:
host: localhost
port: 6379
database: 0
然后新建一个类
package com.hqk.gtwayservice.resolver;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetAddress;
@Component
public class IpResolver implements KeyResolver{
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String ip=exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
// exchange.getRequest().getURI().toString() //获取请求地址
//可以对 ip 地址 等进行过滤
System.out.println("ip:"+ip);
return Mono.just(ip);
}
}
然后在yml中配置,这样就可以根据 ip 进行限流
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://47.102.192.50:8081 # http://47.102.192.50:8081 # lb://consumer-service lb 代表从注册中心获取服务,且已负载均衡方式转发
predicates:
- Path=/**/** #只漏油转发
filters:
- name: RequestRateLimiter #名称必须是RequestRateLimiter
args:
key-resolver: "#{@ipResolver}" #使用SpEL按名称引用bean
redis-rate-limiter.replenishRate: 2 #允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 3 #令牌桶的容量,允许在一秒钟内完成的最大请求数
- filter 名称必须是 RequestRateLimiter
- redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求
- redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
- key-resolver:使用 SpEL 按名称引用 bean
在流量过大时进行服务降级,避免请求长时间的等待,这个是在网关层进行熔断降级,不是在服务层进行熔断降级,服务层也可进行熔断降级,接下来会写,我们先看网关层的熔断降级,先写一个降级的类,返回消息
package com.hqk.gtwayservice.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class DefaultHystrixController {
@RequestMapping("/defaultfallback")
public Map<String,String> defaultfallback(){
//System.out.println("降级操作...");
Map<String,String> map = new HashMap<>();
map.put("resultCode","false");
map.put("resultMessage","服务异常");
map.put("resultObj","这里测试网关服务熔断");
return map;
}
}
看下整个yml的配置
server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://47.102.192.50:8081 # http://47.102.192.50:8081 # lb://consumer-service lb 代表从注册中心获取服务,且已负载均衡方式转发
predicates:
- Path=/**/** #只漏油转发
filters:
- name: RequestRateLimiter #名称必须是RequestRateLimiter
args:
key-resolver: "#{@ipResolver}" #使用SpEL按名称引用bean
redis-rate-limiter.replenishRate: 2 #允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 3 #令牌桶的容量,允许在一秒钟内完成的最大请求数
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/defaultfallback #这里是服务降级,调用的方法
redis:
host: localhost
port: 6379
database: 0 #限流redis 端口地址 这里没有设置密码
hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 3000 #超时时间 3秒