路由网关Gateway+自定义路由过滤
☆本文章的代码地址
★项目完整代码——gitee地址:不断更新
本文基于上一篇文章Demo代码编写
网关路由(个人理解)点赞不收啊啊啊啊啊啊啊
根据其他各位大佬的Blog和官网解释自己总结:
1、如上一篇blog说写的,ribbon+resttemplate方式消费服务或者feign方式消费服务,都需要指定一个动态的
ip:port/映射路径/具体的接口
2、麻烦,有了网关这东西,各种服务哪怕IP和端口不一样,让他请求同一个IP+端口。
3、举例:gatewayIP:port/service/consume/getUser
gatewayIP是网关的总IP。
port是网关的端口。
service不同服务新增的一个context-path路径
consume/getUser 还是原来的请求路径,
综合起来就是网关IP+网管端口+不同的服务名+服务下的请求路径
有可能呢,用户userService服务有3个相同的节点,订单orderServcie服务呢有5个相同的节点。那就是8个服务了,但是只需要改
gatewayIP:port/service
/consume/getUser标红的部位便可,在提取一下到一个常量里,要修改的话不就是只该一个常量就可以了么
个人瞎理解,还请移步百度自行查询各种名词释义,有自己的理解便可
。
在使用网关的时候呢,还能对请求做一些过滤filter的效果。这篇文章练习网关Gataway网关,下一篇再练习Zuul网关
新建一个模块cloud-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 https://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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.crazy</groupId>
<artifactId>cloud-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-gateway</name>
<description>网关gateway-Demo</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
gateway的配置文件
server:
port: 8776 # 设置服务提供者的端口号
eureka:
client:
service-url:
defaultZone: http://localhost:8770/eureka/ #注册到那里的注册中心
spring:
application:
name: Eureka-Gateway # 网关项目模块所提供服务的名称
cloud:
gateway:
discovery:
locator:
enabled: true # 是否适用默认路由(通过gatewayUri:port/服务名/path直接访问服务接口)
lower-case-service-id: true # 是否忽略服务名大小写
routes: # 路由,构建网关的基本模块,由ID,目标URI,一系列断言和过滤器组成,如果断言为true则匹配该路由
# 第一个路由的作用是/service/**的路径全部路由到Eureka-Service服务去,会进行负载均衡。
- id: gateway1 # 路由ID 要求上下文唯一性【可以配置多个路由】
uri: lb://Eureka-Service # 目标服务(lb:貌似是负载均衡的含义loadbalancer)
predicates: # 断言:可以配置多个谓词用来匹配请求的路径规则,匹配到了之后路由到uri的服务/网址去
- Path=/service/** # 访问gateway的路由【谓词,这里还有各种各样的谓词,详细不多练,用的时候去官网看,一大堆,这里用Path】
# 第2个路由的左右,直接访问网关IP+端口的话,扔到baidu去,我没弄其他的服务,模拟有个baidu服务吧。空链接直接跳百度页面去
- id: gateway2
uri: http://www.baidu.com
predicates:
- Path=/
# 第3个路由,自定义路由过滤器:package com.crazy.config.beans;看看每个请求的耗时多少ms
自定义路由过滤
看某个请求的用时
Beans.java
package com.crazy.config.beans;
import com.crazy.config.RequestTimeFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description 自定义过滤器路由3
* @Author Crazy
* @Date 2020/10/13
* @Copyright
*/
@Configuration
public class Beans {
/**
* 看看/service/test/getUser请求的时间用了多少ms
* @param builder
* @return
*/
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
RouteLocator gateway2 = builder.routes()
.route(r -> r.path("/service/test/getUser")
.filters(f -> f.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
.uri("lb://Eureka-Service")
.order(0)
.id("gateway3")
)
.build();
return gateway2;
}
}
RequestTimeFilter.java
需要实现GatewayFilter, Ordered
package com.crazy.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description 看看/service/test/string请求的时间用了多少ms
* @Author Crazy
* @Date 2020/10/13
* @Copyright
*/
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log logger = LogFactory.getLog(RequestTimeFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//定个开始开始时间
exchange.getAttributes().put("beginTime",System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(()->{
Long beginTime = exchange.getAttribute("beginTime");
if (beginTime!=null){
logger.info(">>>>>>请求"+exchange.getRequest().getURI().getRawPath()+"耗时: "+(System.currentTimeMillis() - beginTime) + "ms");
}
})
);
}
@Override
public int getOrder() {
//定义优先级
return 0;
}
}
最后启动类不要忘了加上注解,还有服务提供者增加了一层上下文路径/service
**还好我是不用消费端了(暂时),要么消费端的也得去加/service,提交代码的时候在整整吧
然后我的启动项配置该了一下,不容易混乱,如下:要启动的名字格式,端口+服务名
依次启动我们需要的,一个registry,三个服务,一个网关即可
postman测试如下
路由:gateway1
路由:gateway2
路由:gateway3
自定义路由过滤器:校验token(重定向跳转)
- 根上面的那个过滤器一样,自定义个过滤器实现
GatewayFilter, Ordered
package com.crazy.config.beans.filter;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Crazy
* @Description 检查参数和token重定向uri
* @date 2020/10/14-2:48
* @Copyright HomeComputer
*/
public class CheckTokenFilter implements GatewayFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CheckTokenFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求头Headers里面的token是否为空
String token = exchange.getRequest().getHeaders().getFirst("token");
//获取某一个参数,是否有传
String str = exchange.getRequest().getQueryParams().getFirst("str");
logger.info("传入的token:" + token + ",传入的参数str:" + str);
if (StringUtils.isBlank(token) || StringUtils.isBlank(str)) {
//重定向到某个页面或者某个提醒错误的服务请求地址,使用Http的请求头location字段
String url = "http://www.baidu.com";
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SEE_OTHER);
response.getHeaders().set(HttpHeaders.LOCATION, url);
return response.setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
- 修改Beans的代码,增加一个路由的配置信息,过滤器选择刚建的那个
CheckTokenFilter
/**
* 检查/service/test/baidu请求token和参数。如果空跳转到百度去
*
* @param builder
* @return
*/
@Bean
public RouteLocator routeFilterToken(RouteLocatorBuilder builder) {
RouteLocator gateway4 = builder.routes()
.route(r -> r.path("/service/test/baidu")
.filters(f -> f.filter(new CheckTokenFilter())
.addRequestHeader("X-Response-Default-Foo", "Default-Bar"))
.uri("lb://Eureka-Service")
.order(0)
.id("gateway4")
)
.build();
return gateway4;
}
然后当访问网关IP:端口/service/test/baidu的时候,会被这个路由所匹配到,里面做的过滤器校验如果token携带了,并且参数也传了,就正常执行负载均衡到服务【lb://Eureka-Service】,如果却了一个参数就给他重定向到baidu去。
自定义全局过滤器:全局日志收集
全局过滤器是实现的GolbalFilter接口,不是GatewayFilter
(我是打到控制台,没写到文件去)
LogFilter.java
package com.crazy.config.beans.filter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.alibaba.fastjson.JSON;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Map;
/**
* @Description 全局请求-响应日志打印 实现的 GlobalFilter
* @Author Crazy
* @Date 2020/10/14
* @Copyright
*/
@Component
public class LogFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);
private static final String REQ_HEAD = "\n====================请求信息Begin====================";
private static final String REQ_SPILT = "\n====================请求信息 End====================";
private static final String RESP_HEAD = "\n====================响应信息Begin====================";
private static final String RESP_SPLIT = "\n====================响应信息 End====================";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//计时器
TimeInterval timer = DateUtil.timer();
StringBuilder reqMsg = new StringBuilder();
StringBuilder respMsg = new StringBuilder();
// 获取请求的各项信息
ServerHttpRequest req = exchange.getRequest();
InetSocketAddress address = req.getRemoteAddress();
String method = req.getMethodValue();
URI uri = req.getURI();
HttpHeaders headers = req.getHeaders();
Map queryMap = req.getQueryParams();
String query = JSON.toJSONString(queryMap);
reqMsg.append(REQ_HEAD + "时间:" + DateUtil.now());
reqMsg.append("\n 请求头=").append(headers);
reqMsg.append("\n 参数信息=").append(query);
reqMsg.append("\n 请求方式=").append(method);
reqMsg.append("\n 请求路径=").append(uri.getPath());
reqMsg.append(REQ_SPILT);
logger.info(reqMsg.toString());
//以下为响应
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
respMsg.append(RESP_HEAD);
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@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);
String responseResult = new String(content, Charset.forName("UTF-8"));
respMsg.append("\n 响应状态码=").append(this.getStatusCode());
respMsg.append("\n 响应头信息=").append(this.getHeaders());
respMsg.append("\n 响应结果=").append(responseResult);
respMsg.append(RESP_SPLIT);
respMsg.append("耗时ms:").append(timer.intervalRestart());
logger.info(respMsg.toString());
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -10;
}
}
结束:总结
两种路由网关的区别:zuul和Gateway
Zuul:
阻塞式的 API,不支持长连接
底层是servlet,Zuul处理的是http请求
没有异步支持,流控等均由hystrix支持。
依赖包spring-cloud-starter-netflix-zuul。
Gateway:
底层是servlet,但使用了webflux,多嵌套了一层框架
依赖spring-boot-starter-webflux和/ spring-cloud-starter-gateway
提供异步支持,提供抽象负载均衡,提供抽象流控,并默认实现了RedisRateLimiter
相同点:底层都是servlet,都是web网关,处理http请求
不同点:
1、gateway比zuul多依赖了spring-webflux,变得更强大,还实现了限流和负载均衡等,但是也仅适合于springCloud这一套东西
2、zuul可以扩展到其他的微服务框架中,没有实现限流和负载均衡这些。
3、zuul不支持异步,gateway支持异步,gateway也具有更好的拓展性
4、Zuul的2.x版本基于Netty实现了非阻塞,支持长连接了
写在前面了更详细的总结写在Demo项目里面,在最上面
未完待续——如果看到这里了,(●'◡'●)该去点赞了啊喂
有空写完其他的服务相关的再来补充,啥玩意路由啊,断路器啊,消息总线啊,服务链路啊啥的。