乐趣区

Spring-Cloud-Zuul-实践三-过滤器

在设计系统阶段,一定记住 Zuul 的核心思想:Zuul 是做服务的路由,而不是页面的转发。这是 zuul 与 Nginx 根本上的不同。

Zuul 没有提供良好的跳转或转发功能,这和它的使用场景是有关的。它的核心功能包括验证服务权限,验证成功或失败,成功或失败后放行还是拦截等等。若拦截,只需返回响应码即可,至于成功或失败的页面显示,则应该由前端负责跳转或转发。
请一定遵守标准化的设计,不要让 Zuul 参与页面跳转(当然,Zuul 也可以实现页面跳转,本文后半部分会涉及到,但不推荐由 Zuul 实现跳转)

Zuul 的验证功能基本在它的过滤器中实现。

过滤器类型与请求的生命周期

每一个请求发送到 Zuul,都有其对应的不同阶段,可以称其为“生命周期”,比如初始化阶段,处理请求阶段,结束阶段,出 error 阶段等等。

在谈下一话题前,请一定分清楚“转发 ”和“ 路由 ”的区别。
如果说定义,路由是一种策略,转发是一条路径。
说浅显一点,路由是网状结构下的各种路径解的集合,而转发是点对点的一条通信路径。
再浅显一些,就像学生年代你上课时给女神小红递纸条,路由是你将纸条给你同桌(代理),你同桌会选择一条消息发送的路径,比如将纸条传递给 A,再到 B,再到 C,最后到小红。由同桌发到小红的整个过程就叫路由。你只需要将纸条给同桌(代理),剩下的不需要你参与,你的同桌会选择最优路径,无论是通过 ABC,还是 BAC,当纸条传递成功,你的代理会反馈给你结果(同桌会告诉你纸条传递成功,小红看都没看就扔进垃圾桶),你不需要知道中间隔了多少人。这就是路由。
而转发,就是你直接把纸条揉成团,扔给小红,小红收到后含羞一笑,你看到后心花怒放。这就是转发。
最浅显的概括就是:路由是多跳的转发,转发的一跳的路由。这一点从英文 routing 和 forwading 的区别就可以明白。
如果还无法理解,请重新读一遍计算机网络。


对于微服务项目来说,Zuul 实现的就是路由功能,往往也被会称做路由转发、网关等等,但意思都是一样的。
本文在谈 Zuul 的拦截器,
每一个 request 在发送到 Zuul 后,都有几个不同的阶段(生命周期),Zuul 的几个拦截器对应 request 的不同阶段,所以我们先谈一下 request 的生命周期:

  1. 请求刚刚被 Zuul 接收到,Zuul 需要解析请求的地址,以及其请求参数等等。
  2. 请求即将被路由。Zuul 已经确定此请求该发送至哪一个 service,并已准备好将其发送过去。
  3. 请求被路由至目标服务
  4. 请求在以上三个阶段中的某一个出错。

以上四个阶段是 request 的生命周期,Zuul 的四种拦截器分别对应以上的四个 request 生命周期:PRE, ROUTING, POST, ERROR.

所以,如果想要对 request 的某个生命周期进行操作(过滤),只需要按需实现这四种拦截器即可。

那么,如何实现这四种拦截器呢 ?依然超级简单,答案只有一个:继承ZuulFilter 类并覆写父类方法即可。
区分拦截器的类型在方法 filterType() 中。
闲言少叙,上代码。
以下是一个最基本的 PRE 拦截器。

实现拦截器

定义
新建一个类,继承 ZuulFilter,并覆写其方法。

public class PreRequestFilter extends ZuulFilter {private static final Logger logger = LoggerFactory.getLogger(RouteRequestFilter.class);

    // 表示此拦截器类型,pre、route、post、error
    @Override
    public String filterType() {return "pre";}
    
    // 执行顺序,若有多个同种拦截器,比如有多个 pre 拦截器,他们的执行顺序是什么
    @Override
    public int filterOrder() {return 0;}

    // 是否应用这个拦截器,true 是应用,false 表示这个拦截器是失效的
    @Override
    public boolean shouldFilter() {return false;}

    // 此方法为核心方法,表示拦截器中执行的逻辑
    @Override
    public Object run() throws ZuulException {System.out.println("pre");
        return null;
    }
}

注册为 Bean
在主类中将此拦截器注册为 Bean

@EnableZuulProxy
@SpringBootApplication
public class ZuulApp {public static void main(String[] args) {SpringApplication.run(ZuulApp.class, args);
    }

    @Bean
    public PreRequestLogFilter preRequestLogFilter(){return new PreRequestLogFilter();
    }

此时,一个最基本的拦截器就创建成功了!

拦截请求

在 run 方法中,添加以下内容:

@Override
    public Object run(){
        // 获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        // 获取 Request
        HttpServletRequest request = ctx.getRequest();
        // 获取请求参数 accessToken
        String accessToken = request.getParameter("accessToken");
        // 使用 String 工具类
        if (StringUtils.isBlank(accessToken)) {logger.error("no token");
            logger.warn("accessToken is empty");
            ctx.setSendZuulResponse(false);  // 进行拦截
            ctx.setResponseStatusCode(401);
            try {ctx.getResponse().getWriter().write("accessToken is empty");
            } catch (IOException e) {e.printStackTrace();
            }

            return null;
        }
        return null;}

此时,若通过 Zuul 访问微服务,若没有 accessToken 参数,会发现请求被拦截,页面上会显示“accessToken is empty”

退出移动版