当项目中服务细分至很多时,那么就需要一个统一的网关来控制请求、响应,在微服务框架中,Springcloud Gateway为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
代码地址:https://gitee.com/webprogram/springcloud_learn
Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流
Gateway服务网关
网关功能:
- 身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
网关技术实现:
- Gateway
- zuul
zuul是基于servlet的实现,属于阻塞式编程,而springcloud Gateway则是基于spring5中提供的webFlux,属于响应式编程的实现,具备更好的性能
搭建网关服务
搭建网关步骤
- 关键新的module,引入Gateway依赖和nacos服务发现依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 路由配置及nacos注册
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos服务地址
gateway:
routes: # 配置路由
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8082 # 路由的目标地址
uri: lb://userservice # 路由的目标地址 lb表示负载均衡://服务名
predicates: # 路由断言即判断请求是否符合路由规则
- Path=/user/** # 路径匹配规则,只要/user开头才合规
网关可配置内容包括:
- 路由id:唯一标识
- uri:路由目的地,支持lb和http两种
- predicates:路由断言,判断请求是符合要求,符合才进行转发
- filters:路由过滤器,处理请求或响应
通过Gateway访问userservice服务,127.0.0.1:10010/user即可访问到用户服务
服务网关结构图
路由断言
- 在配置文件中写的断言规则只是字符串,这些字符串被Predicate factory读取并处理,转变为路由判断的条件
- SpringCloud Gateway提供断言工厂有十几个
例如:Path=/user/**是按照路径匹配,这个规则是由PathRoutePredicateFactory来处理
11中基本的predicate工厂
名称 | 说明 | 示例 |
---|---|---|
After | 某个时间点后的请求 | - After=2021-09-13T17:32:32.345-07:00(America/Denver) |
Before | 某个时间点之前的请求 | - Beforer=2021-09-13T17:32:32.345+07:00(America/Denver) |
Between | 某两个时间点之前的请求 | - between=2021-09-13T17:32:32.345+07:00(America/Denver),2021-09-13T17:32:32.345+07:00(America/Denver) |
Cookie | 请求中必须包含cookie | - Cookie=XXX |
Header | 请求必须包含header | -Header=token,X-Request |
Host | 请求必须是访问某个host | - Host=**,www.baidu.com |
Method | 请求方式 | - Method=GET,POST |
Path | 请求路径规则 | -Path=/test/** |
Query | 请求必须包含指定参数 | -Query=name,XXX |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
详细用法参考:https://www.cnblogs.com/wgslucky/p/11396579.html
官网解释:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
路由过滤器
Gateway Filter是网关中提供的一种过滤器,可以进入网关的请求和微服务返回的响应做处理:
Spring提供了31中不同的路由过滤工厂,例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的请求头 |
AddResponseHeader | 给响应添加响应头 |
RemovResponseHeader | 移除响应的响应头 |
RequestRateLimiter | 限制请求的流量 |
… | … |
官网解释:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
样例:给userservice添加请求头
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos服务地址
gateway:
routes: # 配置路由
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8082 # 路由的目标地址
uri: lb://userservice # 路由的目标地址 lb表示负载均衡://服务名
predicates: # 路由断言即判断请求是否符合路由规则
- Path=/user/** # 路径匹配规则,只要/user开头才合规
filters: # 过滤器
- AddRequestHeader=Message,spring cloud is good # 添加请求头
- id: order-service
uri: http://127.0.0.1:8082
predicates:
- Path=/order/**
default-filters: # 默认过滤器,会对所有路由起作用
- AddRequestHeader=Message,spring cloud is good
在controller读取请求头做测试:
@GetMapping("/{id}")
public UserEntity getUser(@PathVariable String id, @RequestHeader("Message") String message){
// 打印出请求头信息
logger.info("请求头:"+ message);
}
全局过滤器
GlobaFilter的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样,区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己实现代码
定义的方式
实现GlobalFilter接口
/**
* 处理当前请求,
* @param exchange 请求上下文,里面可获取request、response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return Mono<Void> 返回标识当前过滤器业务结束
*/
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
样例:定义全局拦截器,判断请求参数是否合规
- 参数是否有authorization
- authorization是否为admin
满足则放行,否则回写响应401
//@Order(-1)
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationFilter.class);
/**
* @description: 校验的用户
*/
private static final String ADMIN_NAME = "admin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
// 2.获取参数值
String authorization = queryParams.getFirst("authorization");
// 3. 判断是否为admin
if (!ADMIN_NAME.equals(authorization)) {
// 4. 不符合条件,回写响应
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> responseData = Maps.newHashMap();
responseData.put("code", 401);
responseData.put("message", "非法请求");
responseData.put("cause", "user is not admin");
try {
// 将信息转换为 JSON
ObjectMapper objectMapper = new ObjectMapper();
byte[] data = objectMapper.writeValueAsBytes(responseData);
// 输出错误信息到页面
DataBuffer buffer = response.bufferFactory().wrap(data);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
// 5.符合规则,放过
return chain.filter(exchange);
}
/**
* @description: 定义过滤器执行顺序,数值越小,优先级越高
*/
@Override
public int getOrder() {
return -1;
}
}
启动项目,访问路径http://localhost:10010/user/1,报错{“code”:401,“cause”:“user is not admin”,“message”:“非法请求”}
重新访问:http://localhost:10010/user/1?authorization=admin,成功获取结果{“userId”:“002”,“name”:“李四”}
在实际项目中会有多个过滤器,如何控制它们的执行顺序呢?(参考上例代码)
-
@Order(XX) 在过滤器类上添加该注解
XX为具体数值,数值越小优先级越高
-
实现Ordered接口,重写getOrder()
/**
* @description: 定义过滤器执行顺序,数值越小,优先级越高
*/
@Override
public int getOrder() {
return -1;
}
上面两种方式等效,选择其中一个使用即可
过滤器执行顺序
请求进入网关会碰到三类过滤器:
- 当前路由过滤器
- DefaultFilter
- GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
三者不是同类型的过滤器,如何合并?
当前路由过滤器和DefaultFilter实际就是GatewayFilter,而GlobalFilter则会通过GatewayFilterAdapter适配器转换为GatewayFilter,进而将三者合并到一个过滤器链中
总结
- 每个过滤器必须制定一个int型额order,order越小优先级越高
- GlobalFilter通过实现Ordered接口,或者@Order注解来自定义order值
- 路由过滤器和DefaultFilter的order由spring指定,默认从1开始递增
- 当过滤器order值一样时,会按照DefaultFilter > 路由过滤器 > GlobalFilter的顺序执行
参考一下源码查看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator中getFilters方法,先加载defaultFilter,然后加载某个route的filters,最后合并
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
List<GatewayFilter> filters = new ArrayList();
if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
filters.addAll(this.loadGatewayFilters("defaultFilters", new ArrayList(this.gatewayProperties.getDefaultFilters())));
}
if (!routeDefinition.getFilters().isEmpty()) {
filters.addAll(this.loadGatewayFilters(routeDefinition.getId(), new ArrayList(routeDefinition.getFilters())));
}
AnnotationAwareOrderComparator.sort(filters);
return filters;
}
org.springframework.cloud.gateway.handler.FilteringWebHandler中handle方法,加载全局过滤器GlobalFilter,然后和前面的过滤器合并,根据order排序,组成过滤器链
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
List<GatewayFilter> combined = new ArrayList(this.globalFilters);
combined.addAll(gatewayFilters);
AnnotationAwareOrderComparator.sort(combined);
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
}
跨域
域名不一致就是跨域,包括:
- 域名不同
- 端口不同
跨域问题:浏览器禁止请求的发起者与服务者发生跨域的ajax请求,请求会被浏览器拦截
解决方案:CORS
配置如下:
spring:
cloud:
gateway:
globalcors: # 全局跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
cors-configurations:
'[/**]':
allowedOrigins: # 允许那些网站跨域
- "http://localhost:8090"
- "http://www.baidu.com"
allowedMethods: # 允许跨域ajax的请求方式
- "GET"
- "POST"
- "PUT"
allowedHeaders: "*" # 允许跨域的请求头信息
allowedCredentials: true # 是否允许携带Cookie
maxAge: 36000 # 本次跨域检测的有效期
更多配置请参考官方文档