SpringCloud Gateway网关解析

831 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

对于传统的单体应用,我们似乎没有遇到过某种问题,它在如今盛行的微服务架构中非常常见,它就是接口访问。在单体应用中,我们访问的都仅仅是这一个应用的内容,而微服务则不同,在微服务架构中,一个应用被拆分成了很多的微服务:

这给前端访问产生了一些麻烦,一般来说,前端都会抽取出一个公共的访问地址,但这些微服务都分布在不同的机器上,导致访问地址都是不一样的,基于此,网关的出现能够轻松解决这一问题。

所有想要访问接口的客户端、用户等都先将请求发至网关,再由网关来解决将该请求交给哪个服务进行处理。

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:判断是否包含某些cookie
  5. HeaderRoutePredicateFactory:判断是否包含某些header
  6. HostRoutePredicateFactory:判断请求是否是访问某个host
  7. 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请求进行询问并且成功后,在这段有效时间内,服务器将不再要求对浏览器发送过来的请求进行检测,由此提高了性能。