SpringCloud Gateway网关组件,你真的懂了吗?
共 8793字,需浏览 18分钟
·
2021-09-13 03:26
点击关注公众号,Java干货及时送达
Java技术迷 | 出品
对于传统的单体应用,我们似乎没有遇到过某种问题,它在如今盛行的微服务架构中非常常见,它就是接口访问。在单体应用中,我们访问的都仅仅是这一个应用的内容,而微服务则不同,在微服务架构中,一个应用被拆分成了很多的微服务:这给前端访问产生了一些麻烦,一般来说,前端都会抽取出一个公共的访问地址,但这些微服务都分布在不同的机器上,导致访问地址都是不一样的,基于此,网关的出现能够轻松解决这一问题。所有想要访问接口的客户端、用户等都先将请求发至网关,再由网关来解决将该请求交给哪个服务进行处理。
SpringCloud为我们提供了网关的实现——Gateway,通过Gateway,我们能够实现身份认证和权限校验;服务路由和负载均衡;请求限流等功能。
网关初体验
SpringCloud Gateway是作为一个独立的微服务工作的,所以我们需要创建一个SpringBoot应用,并引入Nacos和Gateway的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Gateway也是需要注册到Nacos中的,因为只有注册到Nacos中网关才能够发现有哪些服务是健康的,有哪些服务可以正常使用。在网关服务中,我们无需编写任何代码,只需要在application.yml中填写相关配置即可:
server:
port: 10000
spring:
application:
name: service-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: # 网关的路由配置
- id: service-user-route
uri: lb://service-user
predicates:
- Path:=/user/**
网关的路由配置总共需要配置三项,其中id是该路由的唯一标识,uri指定的是需要路由到的服务地址,而predicates表示断言,如果满足断言的要求,则网关便会将请求交给uri指定的服务, lb://
表示将请求负载均衡到 service-user
服务。断言的形式有很多,比如这里使用的Path,它是用来判断请求路径的,当请求路径以user开头,该请求就会被这一网关配置处理。
接下来在service-user服务中编写一个方法:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/find/{userId}")
public User find(@PathVariable("userId") Long userId){
User user = userService.findById(userId);
return user;
}
}
将这两个应用分别启动,然后访问 http://localhost:10000/user/find/1,注意一定是访问网关服务,所以端口是10000,然后访问路径为 /user/find/1
,由于路径以user开头,所以该请求就会被网关交给service-user服务进行处理。
路由配置
在刚刚的例子中,我们使用到了一个路径的路由断言,只需要在-Path中配置/user/**,那么以user开头的请求就会被网关处理,这是如何实现的呢?事实上,Gateway中有很多的路由断言工厂,当我们在配置文件中对断言进行配置后,这些配置就会被路由断言工厂进行解析并处理,而-Path配置就是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory来处理的。SpringCloud Gateway中一共提供了11种基本的路由断言工厂,分别如下:
1.BeforeRoutePredicateFactory:判断是否为某个时间点之前的请求2.AfterRoutePredicateFactory:判断是否为某个时间点之后的请求3.BetweenRoutePredicateFactory:判断是否为某两个时间点之间的请求4.CookieRoutePredicateFactory:判断是否包含某些cookie5.HeaderRoutePredicateFactory:判断是否包含某些header6.HostRoutePredicateFactory:判断请求是否是访问某个host7.MethodRoutePredicateFactory:判断请求方式是否是指定的方式8.PathRoutePredicateFactory:判断请求路径是否满足规则9.QueryRoutePredicateFactory:判断请求参数是否包含指定的参数10.RemoteAddrRoutePredicateFactory:判断请求ip是否在指定的范围内11.WeightRoutePredicateFactory:权重处理
其中BeforeRoutePredicateFactory,配置如下:
spring:
cloud:
gateway:
routes:
- id: before_route
uri: lb://service-user
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
它表示请求的时间在2017年1月20日17点42分之前的请求就满足该路由配置,网关就会将请求交给service-user。
AfterRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: lb://service-user
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
它表示请求的时间在2017年1月20日17点42分之后的请求就满足该路由配置。
BetweenRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: between_route
uri: lb://service-user
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
它表示请求的时间在2017年1月20日17点42分与2017年1月21日17点42分之间的请求就满足该路由配置。
CookieRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: lb://service-user
predicates:
- Cookie=chocolate, ch.p
它表示请求中必须含有一个名字为chocolate的cookie,其值为满足ch.p正则表达式的内容,当然也可以直接配置一个cookie的键值,键与值之间用逗号分隔: - Cookie=name,zhangsan
。
HeaderRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: header_route
uri: lb://service-user
predicates:
- Header=X-Request-Id, \d+
它表示请求中必须含有一个名为 X-Request-Id 的请求头,其值为满足\d+正则表达式的内容,也可以直接配置一个键值: - Header=Accept-Language,zh-CN
。
HostRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: lb://service-user
predicates:
- Host=**.somehost.org,**.anotherhost.org
它表示请求的Host必须具有**.somehost.org,**.anotherhost.org内容。
MethodRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: method_route
uri: lb://service-user
predicates:
- Method=GET,POST
它表示请求的方式必须为GET或Post。
PathRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: path_route
uri: lb://service-user
predicates:
- Path=/red/{segment},/blue/{segment}
这个相信大家很熟悉了,就是用来匹配请求路径的,其中segment是一个占位符,表示单层路径匹配,比如: /red/1
、 /red/blue
、 /blue/1
、 /blue/red
都是满足要求的。
QueryRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://service-user
predicates:
- Query=name
它表示请求中必须携带名为name的参数,比如: http://localhost:10000/find?name=zhangsan
。
RemoteAddrRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: lb://service-user
predicates:
- RemoteAddr=192.168.1.1/24
它表示请求IP必须在192.168.1.1/24网段内,如果你学过计算机网络,应该能够明白,不了解的也不要紧,我就简单介绍一下。 192.168.1.1/24
采用的是斜线记法,斜杠后面的数字表示的是网络前缀,即:该IP地址的前24位为网络号,后8位为主机号,将IP转换为二进制,如下:
1100 0000 1010 1000 0000 0001 0000 0001
也就是说,当主机号全为1时,该IP为最大地址,即:192.168.1.255,所以192.168.1.1/24代表的IP段是192.168.1.1~192.168.1.255。由此可知,只要请求IP在该IP段范围内则是符合要求的。
WeightRoutePredicateFactory配置如下:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: lb://service-user
predicates:
- Path=/user/**
- Weight=group1, 8
- id: weight_low
uri: lb://service-user2
predicates:
- Path=/user/**
- Weight=group1, 2
这里配置了两个路由规则,当请求路径以/user开头时这两个规则都符合条件,但因为配置了权重分组,这两个规则的分组均为group1,所以Gateway会按照权重比进行权衡,将80%的该请求交给service-user处理,将20%的该请求交给service-user2处理。
过滤器配置
在路由的配置中,我们还可以配置一项filter,它是Gateway提供的过滤器,可以对进入网关的请求和返回的响应进行相应的处理。与路由断言类似,Gateway同样提供了过滤器工厂,而且有31种之多:这里仅仅是截取了官网上列举的部分过滤器工厂,比如第一个AddRequestHeader GatewayFilterFactory,从名字就能够看出来,这是用来添加请求头信息的,具体配置方式如下:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: lb://service-user
predicates:
- Path=/user/**
filters:
- AddRequestHeader=X-Request-red, blue
此时若是请求路径满足以/user开头,那么该请求就会被过滤器添加上一个请求头信息,内容为 X-Request-red:blue
;通过配置默认过滤器,可以使过滤器对所有的路由配置生效:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: lb://service-user
predicates:
- Path=/user/**
default-filters:
- AddRequestHeader=X-Request-red, blue
为了更加灵活地适应各种场景,Gateway还提供了一种特殊的过滤器——GlobalFiler(全局过滤器),它的作用与default-filter类似,区别在于GlobalFilter需要我们自己去实现,要做的就是实现GlobalFilter接口:
@Order(0)
@Component
public class MyGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
String tag = params.getFirst("tag");
if ("admin".equals(tag)) {
// 放行
chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
// 设置状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 拦截
return response.setComplete();
}
}
该过滤器可以实现对身份的校验,只有管理员身份的请求才被放行,其它请求就会被拦截。
处理跨域
跨域是每一个前后端分离项目都需要面临的问题,但有了网关,我们就可以将处理跨域的流程写在网关里,无需在每一个微服务中都进行配置了。
需要注意跨域问题是指浏览器禁止请求的发起者与服务端发生跨域的ajax请求。
配置方式如下:
spring:
cloud:
gateway:
globalcors: # 全局跨域处理
add-to-simple-url-handler-mapping: true # 解决options询问请求被拦截的问题
cors-configurations:
'[/**]':
allowedOrigins: # 配置允许哪些网站跨域
- "http://localhost:8000"
- "http://localhost:9000"
allowedMethods: # 配置允许哪些请求方式跨域
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 允许请求携带Cookie
maxAge: 360000 # 跨域检测的有效时间
浏览器在向服务器发起请求之前,会先发送一个option请求进行询问,查看是否满足要求,为了防止这个询问请求被拦截,所以需要配置 add-to-simple-url-handler-mapping: true
; [/**]
表示对所有的请求进行处理; maxAge: 360000
用于配置跨域检测的有效时间,当浏览器发送了一次option请求进行询问并且成功后,在这段有效时间内,服务器将不再要求对浏览器发送过来的请求进行检测,由此提高了性能。
本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载。