侧边栏壁纸
博主头像
林雷博主等级

斜月沉沉藏海雾,碣石潇湘无限路

  • 累计撰写 132 篇文章
  • 累计创建 47 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

8、API网关服务:Spring Cloud Zuul

林雷
2019-11-13 / 0 评论 / 0 点赞 / 235 阅读 / 8,808 字

一 网关服务

1.1 Spring Cloud Zuul

Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这样的设计非常巧妙的将服务治理体系中维护的实例信息利用起来,使得将维护服务实例的工作交给了服务治理框架自动完成。而对应路由规则的维护,Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射。

其次,对于类似名为校验、登录校验在微服务架构中的冗余问题。理论上来说,这些校验逻辑在本质上与微服务应用自身的业务并没有多大的关系,所以它们完全可以独立成一个单独的服务存在,只是它们被剥离和独立出来后,并不是给各个服务调用,而是在API网关上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。Spring Cloud Zuul提供了一套过滤器机制,它可以很好的支持这样的任务。

1.2 快速入门

在实现各种API网关服务的高级功能之前,我们需要有一些后端服务可以用。这里我简单的使用两个工程:spring-cloud-user和spring-cloud-role两个服务,这两个服务提供访问能力。两个工程的代码如下:
springcloudrole.zip
springclouduser.zip

接下来我们使用网关路由到这两个服务上去

1.2.1 构建网关

  • 创建一个基础的Spring Boot工程,命名为spring-cloud-gateway,并在pom.xml中引入spring-cloud-starter-zuul依赖,具体如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.bianjf</groupId>
    <artifactId>spring-cloud-gateway</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- 引入alibaba-nacos Start -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- 引入alibaba-nacos End -->

        <!-- 引入alibaba-nacos-config Start -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- 引入alibaba-nacos-config End -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot启动插件 Start -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- Spring Boot启动插件 End -->

            <!-- 编译工具 Start -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <!-- 编译工具 End -->

            <!-- Maven Install 跳过Test阶段 Start -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <!-- Maven Install 跳过Test阶段 End -->
        </plugins>
    </build>
</project>

对于spring-cloud-starter-zuul依赖,该模块中不仅包含了Netflix Zuul的核心依赖zuul-core,它还包含了网关服务需要的重要依赖:

  • spring-cloud-starter-hystrix
  • spring-cloud-starter-ribbon
  • spring-boot-starter-actuator

1.2.2 创建应用主类

创建应用主类,在主类上添加@EnableZuulProxy注解开启Zuul的API网关服务能力。

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.bianjf"})
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

1.2.3 配置基础信息

在bootstrap.yml中配置Zuul的基础信息,如端口号,应用名,nacos地址等信息

server:
  port: 8080
spring:
  application:
    #服务名称
    name: spring-cloud-gateway
  cloud:
    nacos:
      discovery:
        #Nacos注册中心地址
        server-addr: 192.168.10.3:8848
        #固定本机的IP地址,防止注册时,本机使用其他网卡的地址与Nacos服务器进行通讯
        ip: 192.168.10.79
      config:
        #Nacos配置中心的地址
        server-addr: 192.168.10.3:8848
        #Nacos配置文件的扩展名
        file-extension: yml
        #指定组
        group: dev
    inetutils:
      #使用本地网卡
      use-only-site-local-interfaces: true

1.2.4 请求路由

为了演示请求路由的功能,我们将上面的两个服务注册到服务中心(我这里使用的是nacos,eureka改变配置文件即可)
image.png
image.png

1.2.4.1 传统路由方式

在Spring Cloud Zuul实现路由功能非常简单,只需要对spring-cloud-gateway服务增加一些关于路由规则的配置,就能实现传统的路由转发功能,比如:

zuul:
  routers:
    user:
      path: /user/**
      url: http://localhost:8081

该配置定义了发往API网关服务的请求中,所有符合/user/**规则的访问都被路由转发到http://localhost:8081/ 地址上,也就是说,我们访问:http://localhost:8080/user/user/info 时,API网关服务会将该请求路由到:http://localhost:8081/user/info 接口上。其中配置zuul.routers.user.path中的user部分为路由的名称,可以任意定义。但是一组path和url映射关系的路由名要相同。

这种在实际中用的不多,实际环境中用的更多的是下面的这种

1.2.4.2 面向服务的路由

配置如下:

zuul:
  #具体的路由规则
  routes:
    #用户服务
    spring-cloud-user: /user/**
    #角色服务
    spring-cloud-role: /role/**

针对spring-cloud-user和spring-cloud-role两个微服务,在配置中使用的路由名称分别是spring-cloud-user和spring-cloud-role来映射,通过指定注册中心,除了自己注册成服务之外,同时也让Zuul能够活着spring-cloud-user和spring-cloud-role服务的实例清单,以实现path映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。

通过上面的搭建工作,我们可以通过服务网关来访问spring-cloud-user和spring-cloud-role两个微服务了。

我的项目如下:
springcloudgateway.zip

1.3 请求过滤

Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截过滤,实现的方法很简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了。

/**
* 登陆过滤器
*
*/
public class LoginFilter extends ZuulFilter {
    @Autowired
    private JRedisDao jedisDao;

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    /**
     * 是否需要被执行
     */
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();//request上下文对象

        HttpServletRequest request = ctx.getRequest();//HttpServletRequest对象

        String url = request.getRequestURI().toString();//请求地址

        if (StringUtil.isNotNull(url) && this.isSkipCheck(url)) {//检查地址是否需要被执行
            return false;
        }
        return true;
    }

    /**
     * 执行具体逻辑
     */
    @Override
    public Object run() {
        JSONObject resultJSON = new JSONObject();//返回数据对象

        RequestContext ctx = RequestContext.getCurrentContext();//request上下文对象
        HttpServletRequest request = ctx.getRequest();//HttpServletRequest对象

        String accessToken = request.getParameter("access_token");//访问令牌

        this.LOGGER.info("send {} request to {}" + request.getMethod() + "----- Request URL: " + request.getRequestURL().toString());

        if (StringUtil.isNull(accessToken)) {//访问令牌为空
            this.LOGGER.info("access_token is empty");
            ctx.setSendZuulResponse(false);//false表示不向后端转发请求
            ctx.setResponseStatusCode(200);
            HttpServletResponse response = ctx.getResponse();//HttpServletResponse对象
            response.setContentType("text/html;charset=utf-8");
            ctx.setResponse(response);

            resultJSON.put("errcode", 2);
            resultJSON.put("errmsg", "访问令牌access_token不能为空");
            ctx.setResponseBody(resultJSON.toJSONString());//返回数据
            return null;
        }

        String loginName = this.jedisDao.get(CommonConstrant.PREFIX_TOKEN + "_" + accessToken);

        if (StringUtil.isNull(loginName)) {
            this.LOGGER.info("redis session is empty");
            ctx.setSendZuulResponse(false);//false表示不向后端转发请求
            ctx.setResponseStatusCode(200);
            HttpServletResponse response = ctx.getResponse();//HttpServletResponse对象
            response.setContentType("text/html;charset=utf-8");
            ctx.setResponse(response);
            resultJSON.put("errcode", 2);
            resultJSON.put("errmsg", "登录Session过期, 请重新登录");
            ctx.setResponseBody(resultJSON.toJSONString());//返回数据
            return null;
        }

        this.LOGGER.info("access_token is ok");
        return null;
    }

    /**
     * 过滤器类型。表示在什么阶段该过滤器生效
     * pre表示在请求路由之前被执行
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器的执行顺序
     */
    @Override
    public int filterOrder() {
        return 2;// 优先级为0,数字越大,优先级越低
    }

    /**
     * 跳过该方法定义的URL地址检查
     * @param url
     * @return
     */
    private boolean isSkipCheck(String url){
        String[] skipArr = {"login", "sendvirificationcode","checkversion","checkverificode","getmeetingdevids"};
        for(String skip : skipArr){
            if(url.toLowerCase().contains(skip)){
                return true;
            }
        }
        return false;
    }
}
  • filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
    • pre:请求被路由之前执行
    • route:请求路由时执行
    • post:请求被路由之后执行
    • error:发生错误时执行
  • filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来一次执行。数字越大,优先级越低
  • shouldFilter:判断该过滤器是否需要被执行。
  • run:过滤器的具体逻辑。

1.3 Cookie与头信息

默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HTTP请求头信息中的一些敏感信息,防止他们被传递到下游的服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookit、Authorization三个属性。
通过设置全局参数为空来覆盖默认值:

zuul:
  #清空默认的敏感的请求头key值,防止Cookie等key被拦截
  sensitive-headers:

另外,Zuul对Ribbon和Feign同样支持。

0

评论区