1.概述简介
因为zuul在升级换代的时候,核心人员跳槽,技术选型出现分歧,导致现在zuul无人维护,而zuul2正在研发当中,所以spring自己验证了一个路由网关技术,也就是Gateway
解决痛点:前台页面发送过来的请求都会先给到网关,网关可以从注册中心中实时的感知一个服务的上线还是下线,总是能路由到正确的位置。每一个请求过来后要鉴权,限流...Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器的功能,例如:熔断、限流、重试等。
SpringCloudGateway是Spring Cloud的一个全新的项目,旨在为微服务架构提供一种简单有效的统一的API路由管理方式。SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2.0以上的版本中,没有对应的新版本的Zuul2.0以上最新高性能版本进行集成,仍然还是使用Zuul1.x非Reactor模式的老版本,而为了提高网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。基于高并发和异步非阻塞式通信就非常的有优势。
SpringCloudGateway目标是提供统一的路由方式基于Filter链的方式提供了网关的基本功能,例如:安全
,监控/指标
,限流
。
2.三大核心概念
路由(Route):
路由是构建网关的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由。断言(Predicate):
开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求头与断言相匹配则进行路由。过滤(Filter):
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
3.Gateway工作流程
核心逻辑:路由转发,执行过滤器链
客户端向Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。在经过过滤器可能会发送请求之前和之后执行逻辑(pre和post)。Filter在"pre"中可以进行参数校验、权限校验、流量监控、日志输出、协议转换这些。在"post"类型的过滤器可以做响应内容、响应头修改、日志的输出、流量监控等有着重要的作用。
4.入门配置
①添加依赖·,注意吧web和actuator依赖去除掉
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
②配置yml文件
③最后启动类就可以了,配置网关不需要任何的业务逻辑
④测试,发现可以访问成功了
第一种方式:就是以上通过yml进行配置
第二种方式:硬编码来实现(比较麻烦)
5.通过微服务名实现动态路由
在这里网关会替代Ribbon做负载均衡,网关的好处是只对外提供一个接口,而且不用暴露真正的接口。
如下图,在yml中,将写死的实例地址改为我们自己声明的实例名,这个我们在各个微服务模块的yml中都进行过配置。这样做的原因是,目前我们微服务提供者只有8001和8002,但是以后还可能会有8003,8004.....可能会是随便,任何的端口号。这样做不需要关注端口号,而直接配置实例名就可以了。
调用lb方法返回的是服务提供端的端口号,如下可以发现,动态网关配置成功,而且实现了负载均衡。
6、Predicate的使用
- 1.After Route Predicate
- 2.Before Route Predicate
- 3.Between Route Predicate
- 4.Cookie Route Predicate
- 5.Header Route Predicate
- 6.Host Route Predicate
- 7.Method Route Predicate
- 8.Path Route Predicate
- 9.Query Route Predicate
运用crul进行测试,添加cookie之后,如果不添加cookie或者添加的cookie不对都会报错,一般项目总用到三种测试方式,postman,jmeter,curl
其他的那些基本上也都很简单,都是差不多类似的,其实,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
7、过滤器
生命周期:pre和post,种类:GatewayFilter(30多个)和GlobalFilter(10几个)
7.1.实现自定义过滤器
开发过程中,一般自己配置过滤器比较多,首先创建实现GlobalFilter,Ordered两个接口,然后重写方法,一定要记得在类上加上注解@Component。
这样在地址栏访问就一定要加上参数username了,不加或者加错参数,如下,都会报错。
7.2打印日志示例
@Component
public class LogFilter implements GlobalFilter, Ordered {
static final Logger logger = LogManager.getLogger("request");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
StringBuilder logBuilder = new StringBuilder();
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String method = serverHttpRequest.getMethodValue().toUpperCase();
logBuilder.append(method).append(",").append(serverHttpRequest.getURI());
if("POST".equals(method)) {
String body = exchange.getAttributeOrDefault("cachedRequestBody", "");
if(StringUtils.isNotBlank(body)) {
logBuilder.append(",body=").append(body);
}
}
ServerHttpResponse serverHttpResponse = exchange.getResponse();
DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
@Override
public Mono<Void> writeWith(Publisher<extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<extends DataBuffer> fluxBody = (Flux<extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
String resp = new String(content, Charset.forName("UTF-8"));
logBuilder.append(",resp=").append(resp);
logger.info(logBuilder.toString());
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -20;
}
}
7.3鉴权示例
@Component
public class AuthAndLogFilter implements GlobalFilter, Ordered {
static final Logger logger = LogManager.getLogger("request");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpResponse serverHttpResponse = exchange.getResponse();
StringBuilder logBuilder = new StringBuilder();
Map<String, String> params = parseRequest(exchange, logBuilder);
boolean r = checkSignature(params, serverHttpRequest);
if(!r) {
Map map = new HashMap<>();
map.put("code", 2);
map.put("message", "签名验证失败");
String resp = JSON.toJSONString(map);
logBuilder.append(",resp=").append(resp);
logger.info(logBuilder.toString());
DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(resp.getBytes());
serverHttpResponse.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer));
}
DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
@Override
public Mono<Void> writeWith(Publisher<extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<extends DataBuffer> fluxBody = (Flux<extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
String resp = new String(content, Charset.forName("UTF-8"));
logBuilder.append(",resp=").append(resp);
logger.info(logBuilder.toString());
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
private Map<String, String> parseRequest(ServerWebExchange exchange, StringBuilder logBuilder) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String method = serverHttpRequest.getMethodValue().toUpperCase();
logBuilder.append(method).append(",").append(serverHttpRequest.getURI());
MultiValueMap<String, String> query = serverHttpRequest.getQueryParams();
Map<String, String> params = new HashMap<>();
query.forEach((k, v) -> {
params.put(k, v.get(0));
});
if("POST".equals(method)) {
String body = exchange.getAttributeOrDefault("cachedRequestBody", "");
if(StringUtils.isNotBlank(body)) {
logBuilder.append(",body=").append(body);
String[] kvArray = body.split("&");
for (String kv : kvArray) {
if (kv.indexOf("=") >= 0) {
String k = kv.split("=")[0];
String v = kv.split("=")[1];
if(!params.containsKey(k)) {
try {
params.put(k, URLDecoder.decode(v, "UTF-8"));
} catch (UnsupportedEncodingException e) {
}
}
}
}
}
}
return params;
}
private boolean checkSignature(Map<String, String> params, ServerHttpRequest serverHttpRequest) {
String sign = params.get("sign");
if(StringUtils.isBlank(sign)) {
return false;
}
//检查签名
Map<String, String> sorted = new TreeMap<>();
params.forEach( (k, v) -> {
if(!"sign".equals(k)) {
sorted.put(k, v);
}
});
StringBuilder builder = new StringBuilder();
sorted.forEach((k, v) -> {
builder.append(k).append("=").append(v).append("&");
});
String value = builder.toString();
value = value.substring(0, value.length() - 1);
if(!sign.equalsIgnoreCase(MD5Utils.MD5(value))) {
return false;
}
return true;
}
@Override
public int getOrder() {
return -20;
}
}
gateway底层原理了解