简述
相信通过微服务应用开发入门①web端架构演进的阅读,大家已经知道服务网关是干嘛的;
一般来说服务网关会做以下几件事情:
- 路由:路由是API网关很核心的模块功能,此模块实现根据请求,锁定目标微服务并将请求进行转发
- 鉴权:权限身份认证
- 监控:记录请求响应数据,api耗时分析,性能监控。
- 日志:日志记录。
- 限流:实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。
Spring Cloud Gateway
在SpringBoot1.x的版本中,一般来说用Netflix公司的zuul;(对又是netflix、Netflix是SpringCloud的开源组件大户)
但是在SpirngBoot2.x的发布后,zuul2.x的版本闭源,于是Spring团队有开发了gateway这个网关;
关于相关的资讯,以及注册中心、服务网关选型参考之前的博客SpingCloud资讯-断路器、注册中心、网关
一般来说: 内部请求通过feign和ribbon进行访问 (这个在之前的文章微服务应用开发入门③微服务组件eureka、ribbon、feign和hystrix初识有介绍)
外部请求通过gateway做统一的路由
1.当请求到达gateway,首先Gateway Handler Mapping会判断请求是否匹配
2.如果不匹配404
3.如果匹配将其发送Gateway web handler处理。
4.Gateway web handler处理请求时会经过一系列的过滤器链;
类似zuul,在执行所有“pre”过滤器逻辑时,往往进行了鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有“post”过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。
简单应用
下载项目 https://github.com/zhouxiaohei/cloud-start-demo/tree/master/cloud-gateway-demo
Predicates
Gateway Handler Mapping中用到一个概念叫Predicates,用断言来判断请求是否匹配;
Gateway为我们提供的断言有:时间断言、cookie断言、header断言、host断言、方法类型断言、路径断言等
一般我们会用到路径断言或者header断言;当然可以自定义断言
项目配置文件
大家也可以加上header断言 试试,请求头不包含X-Request-Id并且值是123的,会404;值也可以是一个正则表达式
- Header=X-Request-Id, 123
Gateway Filter
和zuul一样分为pre、post、也从范围上分为普通过滤器和global filter 全局过滤器。
普通过滤器
刚才用到了切割前缀过滤器,可以打开StripPrefixGatewayFilterFactory查看它的源码
它不是一个全局过滤器所以需要在每个路由(route)下面去配置;下图打错了,基础==继承
那我们现在可以仿照上面的过滤器,写一个自定义RequestTimeGatewayFilterFactory,来获取请求时间和请求参数
@Slf4j
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
private static final String KEY = "withParams";
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
if (config.isWithParams()) {
sb.append(" params:").append(exchange.getRequest().getQueryParams());
}
log.info(sb.toString());
}
})
);
};
}
public static class Config {
private boolean withParams;
public boolean isWithParams() {
return withParams;
}
public void setWithParams(boolean withParams) {
this.withParams = withParams;
}
}
}
如果要是用它,按照名称的规则来使用 ;这样转发到service-a的请求,就会打印请求时间和请求参数;
注意点
获取请求时间我们需要在gateway的Pre类型的过滤器(在请求真正触发之前),添加开始时间;
而ServerWebExchange 是一个pre的过滤器,将请求时间加上去
在post类型过滤器(请求结束以后)中去统计到底执行了多久,而chain.filter的内部类中的run()方法中相当于"post"过滤器
全局过滤器
项目代码里面有全局过滤器AuthSignatureFilter,全局过滤器需要实现GlobalFilter;
一般在做登录校验的时候会用到全局过滤器,以JWT为例,如果是登录的地址/api/v1/common/login就放行;
如果是其他地址就校验header中是否有token(也有可能是别的名字);
然后去校验合法性和有效性(JWT不支持校验有效性,需要改造)------这部分代码上没去实现哈;
最后决定是否放行,不通过就返回HTTP状态码401(未认证)
OK,简单的服务网关之旅到这里就结束了,有问题没问题都欢迎关注我和我交流~