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接口上

 

 

  

posted @ 2018-05-31 10:24  StoneGeek  阅读(331)  评论(0编辑  收藏  举报