Spring Cloud Zuul过滤器介绍及使用示例

目录

相关知识

Zuul过滤器

         Zuul过滤器的使用方式

         Zuul过滤器的类型

         Zuul过滤器的调用顺序(过滤优先级)

         Zuul调用目标服务的时机

         Zuul过滤器简单使用示例(以pre过滤器为例)

         Zuul过滤器之Error过滤器处理zuul调用服务出现的异常示例


相关知识

       在SpringCloud微服务中,Zuul除了最常用的(动态)路由功能外,还有很多其他的功能,如过滤、认证、服务迁移、压力测试等等,今天主要介绍Zuul的过滤器。

        默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉请求头信息中的 一些敏感信息,防止它们被传递到下游的外部服务器。 默认的敏感头信息通过 zuul.sensitiveHeaders参数定义(可通过覆盖的方式来设置),包括Cookie、Set-Cookie、Authorization 三个属性
注:可通过覆盖的方式来设置。如指向过滤掉Cookie、Set-Cookie,不想过滤掉Authorization ,那么只要需要
       在application.properties文件中设置zuul.sensitiveHeaders = Cookie, Set-Cookie即可。


Zuul过滤器

Zuul过滤器的使用方式

         使用方式很简单,继承ZuulFilter类,重写相应的方法即可(可详见本文末给出的使用示例)。

Zuul过滤器的类型

  • pre:路由请求前过滤。

  • post:路由请求后(此时已经走完目标服务程序了)过滤。

  • route:路由请求时过滤。

  • error:当上述三种过滤器抛出异常时,会走error过滤。

注:如果目标服务抛出异常,并不会走error过滤器,error过滤器的“管辖范围”只是pre、post、route这三种过滤器。

Zuul过滤器的调用顺序(过滤优先级)

源码ZuulServlet类(相关截图)

       上图是四种Zuul过滤器的总体过滤顺序,对于同一类型的过滤器,其过滤顺序是由filterOrder方法(继承ZuulFilter类时,需要重写此方法)的返回值决定的,filterOrder返回值越小,在同一类型种,越先过滤,此处源码可见FilterLoader类的以下部分

Zuul调用目标服务的时机

在Zuul的众多route类型的过滤器中,RibbonRoutingFilter过滤的run()方法里,实现了由Zuul路由到具体的服务

Zuul过滤器简单使用示例(以pre过滤器为例)

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.util.RequestBodyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Random;

/**
 * 使用ZuulFilter实现 简单认证
 *
 * @author JustryDeng
 * @date 2019/3/1 1:36
 */
@Component
@Slf4j
@PropertySource(value = {"classpath:/authe_info.properties"}, encoding="utf8")
public class PreFilter extends ZuulFilter {

    @Value("${need-filter-uri}")
    private String[] needFilterURIs;

    /**
     * 过滤类型
     * 有:
     *  【pre】路由请求前被调用过滤、
     *  【post】后置过滤、
     *  【error】错误过滤、
     *  【route】路由请求时被调用
     *
     * @date 2019/3/19 14:49
     */
    @Override
    public String filterType() {
        // 设置为 前置过滤
        return "pre";
    }

    /**
     * 设置过滤优先级
     *
     * 注:当有多个同类型(即;同filterType)的ZuulFilter的子类时,可使用此返回值指定过滤器优先级;值越小越先过滤
     *
     * @date 2019/3/19 14:55
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 设置 哪些请求 需要过滤
     *
     * @date 2019/3/19 14:55
     */
    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String uri = request.getRequestURI();
        // 当为目标URI时,需要过滤
        return Arrays.asList(needFilterURIs).contains(uri);
    }

    /**
     * 实际逻辑逻辑
     *
     * 注:当RequestContext.setSendZuulResponse(false);时表示过滤失败,zuul不对其进行路由
     */
    @Override
    public Object run() {
        // 获取请求上下文
        RequestContext requestContext = RequestContext.getCurrentContext();

        // 获取请求
        HttpServletRequest request = requestContext.getRequest();
        if (doFiltrate(request)) {
            // 验证通过
            return null;
        }
        // 如果验证不通过,那么过滤该请求,不往下级服务去转发请求,到此结束
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
        requestContext.setResponseBody(HttpStatus.FORBIDDEN.getReasonPhrase());
        requestContext.getResponse().setContentType("text/html;charset=UTF-8");
        return null;
    }
    
    /**
     * 进行过滤
     *
     * @param request
     *             请求
     * @return 是否通过
     * @author JustryDeng
     * @date 2019/3/1 2:13
     */
    private boolean doFiltrate (HttpServletRequest request) {
        try {
            /// 获取请求头
            String who = request.getHeader("Authorization");
            log.info(" requestHeader param 【Authorization】 is -> {} !", who);

            /// 向请求头中添加信息
            // requestContext.addZuulRequestHeader("");

            // 获取请求体
            RequestBodyUtil requestBodyUtil = new RequestBodyUtil(request);
            String requestBody = requestBodyUtil.getBody();
            log.info(" got requestBady -> {}", requestBody);

            // TODO 由于是测试代码,这里随机返回 成功、失败
            return new Random().nextBoolean();
        } catch (Exception e) {
            log.error(" zull authe occur error !", e);
            return false;
        }
    }
}

Zuul过滤器之Error过滤器处理zuul调用服务出现的异常示例

场景说明:

     当zuul通过eureka调用一个不可用、不存在、宕机了的服务时,可能就会直接返回类似于这样的不友好的画面:

我们可以通过编写一个异常过滤器来处理这种情况

第一步:先编写一个Controller设置当出现这种情况时,返回的响应。

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * zuul异常处理 现场
 *
 *
 * @author JustryDeng
 * @date 2019/4/19 15:52
 */
@RestController
public class ErrorHandlerController {

    /**
     * zuul调用服务异常时,走此方法
     *
     * 注意:method的方法类别 最好全面一点,如本人这里将可能的请求方式全列出来了
     *
     * @return  zuul其他服务异常时的响应
     * @date 2019/4/19 16:51
     */
    @RequestMapping(value = "/justrydeng/error",
                    method = {RequestMethod.GET, RequestMethod.HEAD,
                              RequestMethod.POST, RequestMethod.PUT,
                              RequestMethod.PATCH, RequestMethod.DELETE,
                              RequestMethod.OPTIONS, RequestMethod.TRACE})
    public String errorHandler() {
        // 设置当发生错误时,返回给前端的数据
        return "zuul 调用服务时出错了!";
    }
}

第二步:编写zuul异常处理过滤器,设置当异常时,将请求转发至第一步编写的方法的uri地址。

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

/**
 * 异常过滤器
 *
 * @author JustryDeng
 * @date 2019/3/1 1:36
 */
@Component
@Slf4j
public class ErrorFilter extends ZuulFilter {

    @Override
    public String filterType() {
        // 设置为 错误过滤
        return "error";
    }

    @Override
    public int filterOrder() {
        // 注意: 此处自定义的优先级 要高于SendErrorFilter的优先级,
        // 由于SendErrorFilter的优先级是0, 由于值越小优先级越高,
        // 所以这里的值要小于0
        return -1;
    }

    @Override
    public boolean shouldFilter() {
       return true;
    }

    /**
     * 重写run()方法
     *
     * 提示:此方法的编写 可 参考 SendErrorFilter的run方法;
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        try {
            Throwable throwable = ctx.getThrowable();
            log.error("记录zuul调用服务时发生的异常信息", throwable);
            // 设置请求转发到自定义的controller方法对应的uri地址
            RequestDispatcher dispatcher = request.getRequestDispatcher("/justrydeng/error");
            if (dispatcher != null) {
                if (!ctx.getResponse().isCommitted()) {
                    // 一定要将【sendErrorFilter.ran】的值设置为true;这样就不会走SendErrorFilter过滤器了
                    // 提示:由于过滤器执行顺序的问题,如果走了SendErrorFilter的话,那么就会覆盖我们设置好了的响应信息
                    ctx.set("sendErrorFilter.ran", true);
                    dispatcher.forward(request, ctx.getResponse());
                }
            }
        } catch (Exception ex) {
            // 若到这一步, 可选择邮件告警等
            log.error("系统异常", ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }

}

测试一下:

我们利用zuul调用一个被关闭了的服务,此时响应就变为了:

由此可见:zuul调用服务出错时的过滤器编写成功!

 

 

^_^ 如有不当之处,欢迎指正

^_^ 参考书籍
             
《Spring Cloud微服务实战》,翟永超 著

^_^ 测试代码托管链接 
               
https://github.com/JustryDeng/CommonRepository

^_^ 本文已经被收录进《程序员成长笔记(第四部)》,笔者JustryDeng

猜你喜欢

转载自blog.csdn.net/justry_deng/article/details/88687736