zoukankan      html  css  js  c++  java
  • Spring Cloud之Zuul

      API网关是一个更为智能的应用服务器,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、负载均衡、校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。

      正此Spring Cloud中提供了基于Netflix Zuul实现的API网关组件-------Spring Cloud Zuul

    一、搭建请求路由(面向应用)

      创建springboot模块,命名我api-gateway,在pom.xml中引入相关依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-zuul</artifactId>
            </dependency>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Brixton.SR5</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

      在主类ApiGatewayApplication.class中添加注解@EnableZuulProxy来开启Zuul的API网关服务功能

    package com.stonegeek;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    /**
     * Created by StoneGeek on 2018/5/28.
     * 博客地址:http://www.cnblogs.com/sxkgeek
     */
    @EnableZuulProxy
    @SpringBootApplication
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }

      在application.properties中配置Zuul应用的基础信息,和请求路由的配置

    server.port=5555
    
    spring.application.name=api-gateway
    
    #配置属性zuul.routes.api.path中的api部分为路由的名字,可以任意定义,但是一组path与url映射关系的路由名要相同
    zuul.routes.api.path
    =/api-a-url/** zuul.routes.api.url=http://localhost:8080/

      配置完成之后,开启eureka-server和service-hello、api-gateway这三个模块,所有符合/api-a-url/**规则的访问都将被路由转发到http://localhost:8080/地址上,也就是说,当我们访问http://localhost:5555/api-a-url/hello的时候,API网关服务会将请求路由到http://localhost:8080/hello提供的微服务接口上。

      结果展示:

    二、搭建请求路由(面向服务)  

      很显然,上述传统的配置方式并不太好,它需要开发人员花费大量的时间去维护各个路由path与url的关系,而我们本节要讲的是面向服务的路由,在这里Spring Cloud Zuul与Spring Cloud Eureka整合到一起,此时路由的path不是映射具体的url,而是让它映射到某个具体的服务,而具体的url交给Eureka的服务发现机制去自动维护

      此时,要在pom.xml中加入相关依赖文件(否则会出现注册不成功的情况):

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka</artifactId>
            </dependency>

      然后,针对我们之前创建的两个微服务应用,service-hello和ribbon-consumer,在api-gateway的配置文件中分别定义了两个名为api-a和api-b的路由来映射他们,另外,通过指定Eureka Server服务注册中心的位置,作用是将自己注册成服务,同时也使Zuul能够获取service-hello和ribbon-consumer服务的实例清单,以实现path映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。

    server.port=5555
    
    spring.application.name=api-gateway
    eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
    
    #配置属性zuul.routes.api.path中的api部分为路由的名字,可以任意定义,但是一组path与url映射关系的路由名要相同
    zuul.routes.api-a.path=/api-a/**
    zuul.routes.api-a.serviceId=service-hello
    
    zuul.routes.api-b.path=/api-b/**
    zuul.routes.api-b.serviceId=ribbon-consumer

      之后,将我们的服务注册中心模块Eureka-server、服务客户端service-hello和ribbon-consumer、API网关模块api-gateway都启动,从Eureka信息面板上看出存在的服务清单:

      通过上面的搭建工作,我们已经可以通过服务网关来访问service-hello和ribbon-consumer这两个服务了,根据配置的映射关系,分别向网关发起下面请求:

    • http://localhost:5555/api-a/hello:该url符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceid为service-hello,所以最终的/hello请求会被发送到service-hello服务的某个实例上去。
    • http://localhost:5555/api-b/ribbon-consumer:该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceid为ribbon-consumer,所以最终的/ribbon-consumer请求会被发送到ribbon-consumer服务的某个实例上去。

    三、搭建请求过滤

      在实现了请求路由功能之后,我们的微服务应用的接口就可以通过统一的API网关入口被客户端访问到了,但是每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对外开放。做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统有了统一的入口,既然这些校验与具体的业务无关,那我们可以在请求到达 的时候就完成校验和过滤,而不是转发后再过滤而导致更长的请求延迟

      1、首先定义一个AccessToken.class的utils工具类(继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了),

    package com.stonegeek.utils;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.logging.Logger;
    
    /**
     * Created by StoneGeek on 2018/5/31.
     * 博客地址:http://www.cnblogs.com/sxkgeek
     *
     */
    public class AccessFilter extends ZuulFilter {
        private final Logger logger =Logger.getLogger(String.valueOf(AccessFilter.class));
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext ctx=RequestContext.getCurrentContext();
            HttpServletRequest request=ctx.getRequest();
            logger.info("send {] request to {}"+request.getMethod()+request.getRequestURL().toString());
            Object accessToken=request.getParameter("accessToken");
            System.out.println(accessToken);
            if(accessToken == null){
                logger.warning("access token is empty");
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(401);
                return null;
            }
            logger.info("access token ok");
            return null;
        }
    }

      上述实现的4个抽象方法的作用分别是:

    • filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期执行。这里定义为pre,代表会在请求被路由之前执行,routind在路由请求时被调用,post在routing和error过滤器之后被调用,error处理请求发生错误时被调用
    • filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据方法返回的值来依次执行
    • shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际应用中我们可以利用该函数来指定过滤器的有效范围。
    • run:过滤器的具体逻辑。这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponStatusCode(401)设置其返回的错误码,当然也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回的body内容进行编辑等。

      在实现了自定义过滤器之后,它并不会直接生效,我们还需要为其创建具体的Bean才能启动过滤器,在ApiGatewayApplication.class中修改:

    package com.stonegeek;
    
    import com.stonegeek.utils.AccessFilter;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.context.annotation.Bean;
    
    /**
     * Created by StoneGeek on 2018/5/28.
     * 博客地址:http://www.cnblogs.com/sxkgeek
     */
    @EnableZuulProxy
    @EnableDiscoveryClient
    @SpringBootApplication
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
    //        SpringApplication.run(ApiGatewayApplication.class, args);
            //此处为请求过滤配置
            new SpringApplicationBuilder(ApiGatewayApplication.class).web(true).run(args);
    
        }
        @Bean
        public AccessFilter accessFilter(){
            return new AccessFilter();
        }
    }

      然后开始验证过滤器的存在:

    • http://localhost:5555/api-a/hello:返回401错误
    • http://localhost:5555/api-a/hello?accessToken=token:正确路由到service-hello的/hello接口上

      

  • 相关阅读:
    微软的十年之变
    如何在易受攻击的SSD上禁用硬件BitLocker加密
    Tech Summit 2018见闻:我们,MVP
    Tech Summit 2018见闻:IT之家读者捕捉铺路集团董事长玄隐
    Windows 10怎么了?
    循环队列
    模拟键盘事件
    模拟鼠标事件
    进程间通信——— 匿名管道
    进程间通信——— LPC
  • 原文地址:https://www.cnblogs.com/sxkgeek/p/9113320.html
Copyright © 2011-2022 走看看