Spring Cloud Gateway是基于Spring生态系统之上构建的API网关,包括:Spring 5.xSpring Boot 2.xProject ReactorSpring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注点,例如:安全性监视/指标限流等。

1、什么是服务网关

API Gateway(APIGW / API 网关),顾名思义,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供定制的API。 近几年来移动应用与企业间互联需求的兴起。从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。 这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件。

2、为什么要使用网关

微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想 要请求对应的服务,都需要知道机器的具体 IP 或者域名 URL,当微服务实例众多时,这是非常难以记忆的,对 于客户端来说也太复杂难以维护。此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识 解析判断出具体的微服务地址,再把请求转发到微服务实例。这其中的记忆功能就全部交由网关来操作了。

3、网关核心概念

  • 路由(Route):路由是网关最基础的部分,路由信息由 ID、目标 URI、一组断言和一组过滤器组成。如果断言 路由为真,则说明请求的 URI 和配置匹配。

  • 断言(Predicate):Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring 5.0 框架中 的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自于 Http Request 中的任 何信息,比如请求头和参数等。

  • 过滤器(Filter):一个标准的 Spring Web Filter。Spring Cloud Gateway 中的 Filter 分为两种类型,分别是 Gateway Filter 和 Global Filter。过滤器将会对请求和响应进行处理。

4、功能特征

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

  • 动态路由

  • Predicates 和 Filters 作用于特定路由

  • 集成 Hystrix 断路器

  • 集成 Spring Cloud DiscoveryClient

  • 易于编写的 Predicates 和 Filters

  • 限流

  • 路径重写

5、优势

  • 一方面,因为Zuul已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有;用起来也非常的简单便捷。

  • 另一方面,Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。

6、快速入门

  • 创建SpringBoot工程gateway,引入网关依赖

  • 编写启动类

  • 编写基础配置和路由规则

  • 启动网关服务进行测试

  • 1、创建 gateway服务模块,引入依赖

<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 2、编写启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
/**
 * @Description
 * @Author Admin
 * @Date 2021/10/25
 */
@SpringBootApplication
public class GateWayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class, args);
    }
}
  • 3、编写基础配置和路由规则

server:
  port: 10010
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes:
        - id: user-service # 路由标识,必须唯一
         # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言
           - Path=/user/** # 路径断言,判断路径是否以 /user 开头,如果是则符合规则
        - id: order-service # 路由标识,必须唯一
          uri: lb://orderservice # 路由的目标地址
          predicates: # 路由断言
           - Path=/order/** # 路径断言,判断路径是否以 /order 开头,如果是则符合规则

我们将符合Path 规则的一切请求,都代理到 uri参数指定的地址。

本例中,我们将 /user/**开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

  • 4、重启测试

重启网关,访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到uri:http://userservice/user/1,得到了结果:

{
    "id":1,
    "username":"张三"
}

流程图

路由配置包括:

  • 路由id:路由的唯一标示

  • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

  • 路由断言(predicates):判断路由的规则,

  • 路由过滤器(filters):对请求或响应做处

断言工厂(predicates)

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件 例如 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类处理,Path=/user/是按照路径匹配

像这样的断言工厂在SpringCloudGateway还有十几个:

  • After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]

  • Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]

  • Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]

  • Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p

  • Header 请求必须包含某些header - Header=X-Request-Id, \d+

  • Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org

  • Method 请求方式必须是指定方式 - Method=GET,POST

  • Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**

  • Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name

  • RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24

  • Weight 权重处理

过滤器工厂(GatewayFilter)

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

路由过滤器的种类 Spring提供了31种不同的路由过滤器工厂。例如:

  • AddRequestHeader 给当前请求添加一个请求头

  • RemoveRequestHeader 移除请求中的一个请求头

  • AddResponseHeader 给响应结果中添加一个响应头

  • RemoveResponseHeader 从响应结果中移除有一个响应头

  • RequestRateLimiter 限制请求的流量 4.2 请求头过滤器 下面我们以AddRequestHeader 为例来讲解。

需求:给所有进入userservice的请求添加一个请求头:Truth=codertl is freaking awesome!

只需要修改gateway服务的application.yml文件,添加路由过滤即可:

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
         - Path=/user/** 
        filters: # 过滤器
        # default-filters: # 默认过滤项
         - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
         
当前过滤器写在userservice路由下,因此仅仅对访问userservice的请求有效。
​
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

全局过滤器 (GlobalFilter)

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

定义方式是实现GlobalFilter接口。

public interface GlobalFilter {
    /**
     *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     *
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器 
     * @return {@code Mono<Void>} 返回标示当前过滤器业务结束
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

在filter中编写自定义逻辑,可以实现下列功能:

  • 登录状态判断

  • 权限校验

  • 请求限流等

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

参数中是否有authorization, authorization参数值是否为admin 如果同时满足则放行,否则拦截

实现: 在gateway中定义一个过滤器:

package com.codertl.gateway;
​
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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/**
 * @Description  登录认证的过滤器
 * @Author Admin
 * @Date 2021/10/25
 */
//@Order(-1)  // 执行顺序 或者实现 Ordered 接口
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        // 2. 获取参数中的,authorization 参数
        String auth = queryParams.getFirst("authorization");
        // 3. 判断值 是否等于 admin
        if ("admin".equalsIgnoreCase(auth)) {
            // 4. 相等,放行
            return chain.filter(exchange);
        }
        // 5. 不相等,拦截
        // 5.1 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 5.2 拦截请求
        return exchange.getResponse().setComplete();
    }
​
    @Override
    public int getOrder() {
        return -1;
    }
}

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

  • order值越小,优先级越高

  • 当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器

跨域问题

什么是跨域问题

跨域:域名不一致就是跨域,主要包括:

域名不同: www.taobao.comwww.taobao.orgwww.jd.com 和 miaosha.jd.com

域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS。不知道的小伙伴可以查看 https://www.ruanyifeng.com/blog/2016/04/cors.html

解决跨域问题

在gateway服务的application.yml文件中,添加下面的配置:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期