目录
2.1.搭建Gateway9527项目,并注册到eureka
4.2spring cloud gateway内置的过滤器工厂
前言
Gateway是建立在Spring强大的生态系统之上的网关服务,基于Spring 5、Spring Boot 2.0等技术,提供了一种简单有效的方式来对Api进行路由管理,同时还提供了一些强大的过滤器功能,如熔断,限流,重试等。Gateway基于WebFlux框架进行实现,底层则采用高性能的RPC框架Netty进行通信,相比于基于Servlet阻塞模型的zuul,它的性能更强大。
一、为什么需要网关?
我们都知道,微服务架构中往往由很多个微服务组成,面对那么多的微服务,客户端往往不知道该怎么去调用这些微服务,如果没有网关的存在,则客户端需要记录每个微服务的ip地址及端口,然后分别去调用。显然,这种方式在面对庞大的微服务集群显然是不适用的,所以就需要一个“司机”,只需告诉他我们想去的地方,便能带我们到预想的目的地,网关在微服务架构中便是充当这样的角色。
来看一下微服务的架构图
可以看到,用户在使用我们系统的时候,可能会同时访问到多个微服务,如果不使用网关,这个模式会产生许多问题:
- 认证复杂,接口都是登陆了才能访问,用户在打开购物车的时候已经登陆过了,点开订单页面又提示需要登陆,这将十分影响用户体验。
- 重构难度大,随着项目迭代,可能需要重新划分该微服务
- 其他微服务,如果采用了对浏览器不友好的协议,如RPC协议,客户端就很难请求
- 安全问题,暴露微服务的ip/域名
而网关正是为了解决这些痛点而生
怎么解决
- 在网关上统一进行登陆认证,然后验证后的用户信息转发至各个微服务
- 对外暴漏的永远是一个网关的「域名」,客户端重构成本大大降低
- 如果其他微服务采用的是对浏览器不友好的协议,可以在网关上转换协议(http、websocket)
二、快速入门
2.1核心概念
为了更好的使用网关,首先来了解一下网关的三大核心概念
- route(路由)——可以简单理解成一条转发规则。包括:id,目标url,predicate集合以及filter集合
- predicate(断言),函数式编程的api,实现路由的匹配条件(控制这个请求能否要走这个路由的条件)
- filter(过滤器),主要用于修改请求以及响应信息
2.2工作流程
当客户端发起请求到gateway,会通过一些匹配条件,定位到真正要访问的微服务节点。并且在转发请求的过程前后,进行一些细粒度的控制(类似Spring的AOP思想)。
其中断言就是我们的匹配条件,只有符合断言条件,才能进行路由的转发,而filter的作用就更加强大,可以让我们在请求前可以做一些参数校验,权限校验,流量监控,日志出书,协议转换等等,在请求后的过滤器中还可以做响应头,响应内容的修改。
不仅如此,Gateway提供了一套十分优雅的方式来给我们扩展,可以说gateway是网关技术选型的不二之选。
二、快速开始
项目源码地址
2.1.搭建Gateway9527项目,并注册到eureka
在eureka界面看到我们搭建的网关微服务,说明项目环境已搭建好
搭建另外一个微服务,cloud-provider-payment8001
怎么做到通过gateway将请求转发到cloud-provider-payment8001呢?
这里采用配置yml的方式来进行配置(后面会介绍通过代码「bean」的方式来进行配置。由于是配置项,官方也推荐使用yml)
打开Gateway9527的配置文件,增加以下选项
server:
port: 9527
spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
routes:
- id: pay_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/get/** # 断言,路径相匹配的进行路由
2.2启动测试
增加网关前,访问 http://localhost:8001/pay/get/1
增加网关后,访问 http://localhost:9527/pay/get/1 也能成功,说明我们的配置已经生效
此外,如果我们的某个微服务是集群部署的,gateway还支持负载均衡的方式来调用,只需要在配置项修改uri为lb开头即可
spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: pay_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #负载均衡后提供服务的路由地址
predicates:
- Path=/pay/get/** # 断言,路径相匹配的进行路由
2.2.1通过bean注入的方式来配置
@Configuration
public class GateWayConfig {
/**
* 配置一个id为path_route_oldou的路由规则
* 当访问【localhost:9527/guonei】时,网关会自动将请求转发到【http://news.baidu.com/guonei】
* @param routeLocatorBuilder /
* @return /
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//这里的配置,实现的效果为:当访问【localhost:9527/guonei】时,网关会将请求转发到【http://news.baidu.com/guonei】
routes.route("path_route_oldou",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
三、断言(Predicate)的使用
断言可以针对http请求中的各种属性,来进行匹配校验,只有匹配断言条件,gateway才会将请求转发到相关的微服务。
3.1常见的路由断言工厂
- 时间相关
- AfterRoutePredicateFactory
- BeforeRoutePredicateFactory
- BetweenRoutePredicateFactory
- Cookie相关
- CookieRoutePredicateFactory
- Header相关
- HeaderRoutePredicateFactory
- HostRoutePredicateFactory
- 请求相关
- MethodRoutePredicateFactory
- PathRequestPredicateFactory
- QueryRoutePredicateFactory
- RemoteAddrRoutePredicateFactory
After
示例
spring:
cloud:
gateway:
routes:
- id: after_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间After配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 < now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- After=2030-01-20T17:42:47.789-07:00[America/Denver]
TIPS
- 技巧:时间可使用
System.out.println(ZonedDateTime.now());
打印,然后即可看到时区。例如:2019-08-10T16:50:42.579+08:00[Asia/Shanghai]
- 时间格式的相关逻辑:
- 默认时间格式:org.springframework.format.support.DefaultFormattingConversionService#addDefaultFormatters
- 时间格式注册:org.springframework.format.datetime.standard.DateTimeFormatterRegistrar#registerFormatters
Before
示例
spring:
cloud:
gateway:
routes:
- id: before_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Before配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 > now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Before=2018-01-20T17:42:47.789-07:00[America/Denver]
Between
示例
spring:
cloud:
gateway:
routes:
- id: between_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Between配置的时间时,才会转发到用户微服务
# 因此,访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2027-01-21T17:42:47.789-07:00[America/Denver]
Cookie
示例
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: lb://user-center
predicates:
# 当且仅当带有名为somecookie,并且值符合正则ch.p的Cookie时,才会转发到用户微服务
# 如Cookie满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Cookie=somecookie, ch.p
Header
示例
spring:
cloud:
gateway:
routes:
- id: header_route
uri: lb://user-center
predicates:
# 当且仅当带有名为X-Request-Id,并且值符合正则\d+的Header时,才会转发到用户微服务
# 如Header满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Header=X-Request-Id, \d+
Host
示例
spring:
cloud:
gateway:
routes:
- id: host_route
uri: lb://user-center
predicates:
# 当且仅当名为Host的Header符合**.somehost.org或**.anotherhost.org时,才会转发用户微服务
# 如Host满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Host=**.somehost.org,**.anotherhost.org
Method
示例
spring:
cloud:
gateway:
routes:
- id: method_route
uri: lb://user-center
predicates:
# 当且仅当HTTP请求方法是GET时,才会转发用户微服务
# 如请求方法满足条件,访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Method=GET
Path
示例
spring:
cloud:
gateway:
routes:
- id: path_route
uri: lb://user-center
predicates:
# 当且仅当访问路径是/users/*或者/some-path/**,才会转发用户微服务
# segment是一个特殊的占位符,单层路径匹配
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Path=/users/{segment},/some-path/**
Query
示例
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://user-center
predicates:
# 当且仅当请求带有baz的参数,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1?baz=xx -> user-center的/users/1
- Query=baz
RemoteAddr
示例
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: lb://user-center
predicates:
# 当且仅当请求IP是192.168.1.1/24网段,例如192.168.1.10,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1 -> user-center的/users/1
- RemoteAddr=192.168.1.1/24
四、过滤器(filter)的使用
过滤器指的是Spring框架中,Gateway filter的实例,通过使用过滤器,可以实现在路由请求前(pre)和请求后(post)对请求进行修改。一般广泛应用在全局日志记录、参数校验、统一的网关鉴权等等。
4.1过滤器的生命周期
- pre:gateway转发请求之前
- post:gateway转发请求之后
4.2spring cloud gateway内置的过滤器工厂
包括:
- AddRequestHeader GatewayFilter Factory
- AddRequestParameter GatewayFilter Factory
- AddResponseHeader GatewayFilter Factory
- DedupeResponseHeader GatewayFilter Factory
- Hystrix GatewayFilter Factory
- FallbackHeaders GatewayFilter Factory
- PrefixPath GatewayFilter Factory
- PreserveHostHeader GatewayFilter Factory
- RequestRateLimiter GatewayFilter Factory
- RedirectTo GatewayFilter Factory
- RemoveHopByHopHeadersFilter GatewayFilter Factory
- RemoveRequestHeader GatewayFilter Factory
- RemoveResponseHeader GatewayFilter Factory
- RewritePath GatewayFilter Factory
- RewriteResponseHeader GatewayFilter Factory
- SaveSession GatewayFilter Factory
- SecureHeaders GatewayFilter Factory
- SetPath GatewayFilter Factory
- SetResponseHeader GatewayFilter Factory
- SetStatus GatewayFilter Factory
- StripPrefix GatewayFilter Factory
- Retry GatewayFilter Factory
- RequestSize GatewayFilter Factory
- Modify Request Body GatewayFilter Factory
- Modify Response Body GatewayFilter Factory
- Default Filters
得益于Spring家族强大的生态,Gateway已经整合到Spring Boot Config,用法与Predicatel类似,可以做到配置即生效,具体Demo参考官方文档GatewayFilter Factories篇
4.3自定义过滤器
方式一
继承AbstractNameValueGatewayFilterFactory,重写apply方法
核心Api如下
- exchange.getRequest().mutate().xxx 修改request
- exchange.mutate().xxx 修改exchange
- chain.filter(exchange) 传递给下一个过滤器处理
- exchange.getResponse() 拿到响应
@Slf4j
@Component
public class PreLogGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return ((exchange, chain) -> {
log.info("请求进来了...{},{}", config.getName(), config.getValue());
ServerHttpRequest modifiedRequest = exchange.getRequest()
.mutate()
.build();
ServerWebExchange modifiedExchange = exchange.mutate()
.request(modifiedRequest)
.build();
return chain.filter(modifiedExchange);
});
}
}
方式二
实现GlobalFilter,Orderd接口
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname==null){
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 合法用户就放行
return chain.filter(exchange);
}
//过滤器排序顺序,数值越小越靠前
@Override
public int getOrder() {
return 0;
}
}