精华内容
下载资源
问答
  • Zuul Filter

    万次阅读 2018-10-30 17:51:43
    文章目录简介Filter 类型Zuul 原生 Filter 简介   Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,它们能够在进行 HTTP 请求或者响应的时候执行相关操作。可以说,没有 Filter 责任链,就没有如今的 ...

    简介

      Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,它们能够在进行 HTTP 请求或者响应的时候执行相关操作。可以说,没有 Filter 责任链,就没有如今的 Zuul,更不可能构成功能丰富的网关。基本上你想要在网关实现的功能都要与 Filter 有关。它是 Zuul 中最为开放与核心的功能。 Zuul Filter 的主要特性有以下几点:

    • Filter 的类型:Filter 的类型决定了此 Filter 在 Filter 链中的执行顺序。可能是路由动作发生前,可能是路由动作发生时,可能是路由动作发生后,也可能是路由过程发生异常时。
    • Filter 的执行顺序:同一种类型的 Filter 可以通过 flterOrder() 方法来设定执行顺序。一般会根据业务的执行顺序需求,来设定自定义 Filter 的执行顺序。
    • Filter 的执行条件:Filter 运行所需要的标准或条件。
    • Filter 的执行效果:符合某个 Filter 执行条件,产生的执行效果。

      Zuul 内部提供了一个动态读取、编译和运行这些 Filter 的机制。Filter 之间不直接通信,在请求线程中会通过 RequestContext 来共享状态,它的内部是用 ThreadLocal 实现的,当然你也可以在 Filter之间使用 ThreadLocal 来收集自己需要的状态或数据。

    Filter 类型

      Zuul 中不同类型 filter 的执行逻辑核心在 com.netflix.zuul.http.ZuulServlet 类中定义,该类相关代码如下:

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
    
            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();
    
            try {
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }
    
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
    

    流程图

      这张经典的官方流程图有些问题,其中 post Filter 抛错之后进入 error Filter,然后再进入 post Filter 是有失偏颇的。实际上 post Filter 抛错分两种情况:

    1. 在 post Filter 抛错之前,pre、route Filter 没有抛错,此时会进入 ZuulException 的逻辑,打印堆栈信息,然后再返回 status = 500 的 ERROR 信息。
    2. 在 post Filter 抛错之前,pre、route Filter 已有抛错,此时不会打印堆栈信息,直接返回status = 500 的 ERROR 信息。

      也就是说,整个责任链流程终点不只是 post Filter,还可能是 error Filter,这里重新整理了一下,如图:

    流程图

      这样就比较直观地描述了 Zuul 关于 Filter 的请求生命周期。Zuul 中一共有四种不同生命周期的 Filter,分别是:

    • pre:在 Zuul 按照规则路由到下级服务之前执行。如果需要对请求进行预处理,比如鉴权、限流等,都应考虑在此类 Filter 实现。
    • route:这类 Filter 是 Zuul 路由动作的执行者,是 Apache Http Client 或 Netflix Ribbon 构建和发送原始 HTTP 请求的地方,目前已支持 Okhttp。
    • post:这类 Filter 是在源服务返回结果或者异常信息发生后执行的,如果需要对返回信息做一些处理,则在此类 Filter 进行处理。
    • error:在整个生命周期内如果发生异常,则会进入 error Filter,可做全局异常处理。

      在实际项目中,往往需要自实现以上类型的 Filter 来对请求链路进行处理,根据业务的需求,选取相应生命周期的 Filter 来达成目的。在 Filter 之间,通过 com.netflix.zuul.context.RequestContext 类来进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括请求路由、错误信息、HttpServletRequest、HttpServletResponse,这使得一些操作是十分可靠的,它还扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息。

    Zuul 原生 Filter

      官方文档提到, Zuul Server 如果使用 @EnableZuulProxy 注解搭配 Spring Boot Actuator,会多两个管控端点(注意要开启对应的端点)。

    1. /routes

    routes

    routes details

    1. /filters

    filters

      在 /filters 接口中会返回很多的 Filter 信息,包括:类路径、执行顺序、是否被禁用、是否静态。可以组合成如下图:

    Filters 链

      说明如下:

    Filter 功能

      以上是使用 @EnableZuulProxy 注解后安装的 Filter,如果使用 @EnableZuulServer 将缺少 PreDecorationFilterRibbonRoutingfilterSimpleHostRoutingFilter

      如果你不想使用原生的这些功能,可以采取替代实现的方式,覆盖掉其原生代码,也可以采取禁用策略,语法如下:

    zuul.<SimpleClassName>.<flterType>.disable=true
    

      比如要禁用 SendErrorFilter,在配置文件中添加 zuul.SendErrorFilter.error.disable=true即可。

    自定义 Filter

    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.exception.ZuulException;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
    
    /**
     * @Author:大漠知秋
     * @Description:测试使用第一个 pre Filter
     * @CreateDate:4:34 PM 2018/10/30
     */
    @Component
    @Slf4j
    public class FirstPreFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            log.info("经过第一个 pre 过滤器");
    
            return null;
    
        }
    
    }
    

    日志

    Filter 小例

    • 常量类
    /**
     * @Author:大漠知秋
     * @Description:会话相关常量
     * @CreateDate:5:10 PM 2018/10/30
     */
    public interface SessionContants {
    
        String LOGIC_IS_SUCCESS = "LOGIC_IS_SUCCESS";
    
        String ERROR_RESPONSE_BODY = "{\"status\": 10600, \"msg\":\"%s\"}";
    
    }
    
    • PreFirstFilter
    /**
     * @Author:大漠知秋
     * @Description:测试使用第一个 pre Filter
     * @CreateDate:4:34 PM 2018/10/30
     */
    @Component
    @Slf4j
    public class PreFirstFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            log.info("经过第一个 pre 过滤器");
    
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            if (StringUtils.isBlank(request.getHeader("a"))) {
                // 未经过逻辑
                // 用来给后面的 Filter 标识,是否继续执行
                ctx.set(SessionContants.LOGIC_IS_SUCCESS, false);
                // 返回信息
                ctx.setResponseBody(String.format(SessionContants.ERROR_RESPONSE_BODY, "a Header头不足"));
                // 对该请求禁止路由,禁止访问下游服务
                ctx.setSendZuulResponse(false);
                return null;
            }
    
            // 用来给后面的 Filter 标识,是否继续执行
            ctx.set(SessionContants.LOGIC_IS_SUCCESS, true);
            return null;
    
        }
    
    }
    
    • PreSecondFilter
    /**
     * @Author:大漠知秋
     * @Description:测试使用第二个 pre Filter
     * @CreateDate:4:34 PM 2018/10/30
     */
    @Component
    @Slf4j
    public class PreSecondFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 2;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return (boolean) ctx.get(SessionContants.LOGIC_IS_SUCCESS);
        }
    
        @Override
        public Object run() throws ZuulException {
    
            log.info("经过第二个 pre 过滤器");
    
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            if (StringUtils.isBlank(request.getParameter("b"))) {
                // 未经过逻辑
                // 用来给后面的 Filter 标识,是否继续执行
                ctx.set(SessionContants.LOGIC_IS_SUCCESS, false);
                // 返回信息
                ctx.setResponseBody(String.format(SessionContants.ERROR_RESPONSE_BODY, "b 参数头不足"));
                // 对该请求禁止路由,禁止访问下游服务
                ctx.setSendZuulResponse(false);
                return null;
            }
    
            // 用来给后面的 Filter 标识,是否继续执行
            ctx.set(SessionContants.LOGIC_IS_SUCCESS, true);
            return null;
    
        }
    
    }
    
    • PostFirstFilter
    /**
     * @Author:大漠知秋
     * @Description:测试使用第一个 post Filter
     * @CreateDate:4:34 PM 2018/10/30
     */
    @Component
    @Slf4j
    public class PostFirstFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            log.info("经过第一个 post 过滤器");
    
            RequestContext ctx = RequestContext.getCurrentContext();
            String responseBody = ctx.getResponseBody();
            if (StringUtils.isNotBlank(responseBody)) {
                // 说明逻辑没有通过
                // 设置编码
                ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                ctx.getResponse().setCharacterEncoding("UTF-8");
                // 更改响应状态
                ctx.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
                // 替换掉响应报文
                ctx.setResponseBody(responseBody);
            }
    
            return null;
    
        }
    
    }
    

    结构图

      测试:

    测试

    源码:https://github.com/SlowSlicing/demo-spring-cloud-finchley/tree/ZuulFilter

    展开全文
  • SpringCloud Zuul网关Filter

    2019-01-08 14:23:38
    个人学习SpringCloud系列 Zuul Filter篇 Github Link: https://github.com/panjianlong13/SpringBoot-SpringCloud/tree/master/spring-cloud-zuul-filter Zuul 核心简介 Zuul 除了转发,动态路由,负载均衡等功能...

    个人学习SpringCloud系列 Zuul Filter篇

    Github Link: https://github.com/panjianlong13/SpringBoot-SpringCloud/tree/master/spring-cloud-zuul-filter


    Zuul 核心简介

    Zuul 除了转发,动态路由,负载均衡等功能外,还可以用来鉴权、流量转发、请求统计。

    Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期

    PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

    ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

    POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

    ERROR:在其他阶段发生错误时执行该过滤器。

    除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

     

    禁用指定的Filter

    可以在application.yml中配置需要禁用的filter

    zuul:
        FormBodyWrapperFilter:
            pre:
    	    disable: true

    Zuul Filter实战

    基于上一篇的Zuul项目进行改造

    ZuulFilter 是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用

     开启过滤器

    @EnableZuulProxy
    @SpringBootApplication
    public class ZuulApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(ZuulApplication.class, args);
    	}
    	
    	@Bean
    	public TokenFilter tokenFilter() {
    		return new TokenFilter();
    	}
    
    	@Bean
    	public PasswordFilter PasswordFilter() {
    		return new PasswordFilter();
    	}
    }

    依次开启Eureka Server,Provider-1,Provider-2,Zuul-Filter

    访问Eureka 注册中心 http://localhost:8761/

    1.无Token测试 http://localhost:8080/

    2.有Token无Password测试 http://localhost:8080/?token=peter

    3.有Token,Password测试

     


    Zuul高可用

    实际使用Zuul的方式如图,不同的客户端使用不同的负载将请求分发到后端的Zuul,Zuul在通过Eureka调用后端服务,最后对外输出。因此为了保证Zuul的高可用性,前端可以同时启动多个Zuul实例进行负载,在Zuul的前端使用Nginx或者F5进行负载转发以达到高可用性。

     

     

    展开全文
  • zuul filter

    2017-05-18 11:48:00
    过滤器是Zuul的核心组件,这篇文章我们来详细讨论Zuul的过滤器。下面话不多说,来看看详细的介绍吧。 过滤器类型与请求生命周期 Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤...

    前言

    过滤器是Zuul的核心组件,这篇文章我们来详细讨论Zuul的过滤器。下面话不多说,来看看详细的介绍吧。

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

    Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

    (1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

    (2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

    (3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

    (4) ERROR:在其他阶段发生错误时执行该过滤器。

    除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

     

    通过Filter,我们可以实现安全控制,比如,只有请求参数中有用户名和密码的客户端才能访问服务端的资源。那么如何来实现Filter了?

    要想实现Filter,需要以下几个步骤:

    1、继承ZuulFilter类,为了验证Filter的特性,我们这里创建3个Filter

    根据用户名来过滤

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package com.chhliu.springcloud.zuul;  
    2.   
    3. import javax.servlet.http.HttpServletRequest;  
    4.   
    5. import com.netflix.zuul.ZuulFilter;  
    6. import com.netflix.zuul.context.RequestContext;  
    7.   
    8. public class AccessUserNameFilter extends ZuulFilter {  
    9.     @Override  
    10.     public Object run() {  
    11.         RequestContext ctx = RequestContext.getCurrentContext();  
    12.         HttpServletRequest request = ctx.getRequest();  
    13.   
    14.         System.out.println(String.format("%s AccessUserNameFilter request to %s", request.getMethod(), request.getRequestURL().toString()));  
    15.   
    16.         String username = request.getParameter("username");// 获取请求的参数  
    17.         if(null != username && username.equals("chhliu")) {// 如果请求的参数不为空,且值为chhliu时,则通过  
    18.             ctx.setSendZuulResponse(true);// 对该请求进行路由  
    19.             ctx.setResponseStatusCode(200);  
    20.             ctx.set("isSuccess", true);// 设值,让下一个Filter看到上一个Filter的状态  
    21.             return null;  
    22.         }else{  
    23.             ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由  
    24.             ctx.setResponseStatusCode(401);// 返回错误码  
    25.             ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回错误内容  
    26.             ctx.set("isSuccess", false);  
    27.             return null;  
    28.         }  
    29.     }  
    30.   
    31.     @Override  
    32.     public boolean shouldFilter() {  
    33.         return true;// 是否执行该过滤器,此处为true,说明需要过滤  
    34.     }  
    35.   
    36.     @Override  
    37.     public int filterOrder() {  
    38.         return 0;// 优先级为0,数字越大,优先级越低  
    39.     }  
    40.   
    41.     @Override  
    42.     public String filterType() {  
    43.         return "pre";// 前置过滤器  
    44.     }  
    45. }  

    通过继承ZuulFilter然后覆写上面的4个方法,就可以实现一个简单的过滤器,下面就相关注意点进行说明

     

    filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

    • pre:可以在请求被路由之前调用
    • route:在路由请求时候被调用
    • post:在route和error过滤器之后被调用
    • error:处理请求时发生错误时被调用

         Zuul的主要请求生命周期包括“pre”,“route”和“post”等阶段。对于每个请求,都会运行具有这些类型的所有过滤器。

     

    filterOrder:通过int值来定义过滤器的执行顺序

    shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效

    run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码

    过滤器间的协调
        过滤器没有直接的方式来访问对方。 它们可以使用RequestContext共享状态,这是一个类似Map的结构,具有一些显式访问器方法用于被认为是Zuul的原语,内部是使用ThreadLocal实现的,有兴趣的同学可以看下源码。

    再建一个过滤器,根据密码来过滤:

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package com.chhliu.springcloud.zuul;  
    2.   
    3. import javax.servlet.http.HttpServletRequest;  
    4.   
    5. import com.netflix.zuul.ZuulFilter;  
    6. import com.netflix.zuul.context.RequestContext;  
    7.   
    8. public class AccessPasswordFilter extends ZuulFilter {  
    9.   
    10.     @Override  
    11.     public Object run() {  
    12.         RequestContext ctx = RequestContext.getCurrentContext();  
    13.         HttpServletRequest request = ctx.getRequest();  
    14.   
    15.         System.out.println(String.format("%s AccessPasswordFilter request to %s", request.getMethod(), request.getRequestURL().toString()));  
    16.   
    17.         String username = request.getParameter("password");  
    18.         if(null != username && username.equals("123456")) {  
    19.             ctx.setSendZuulResponse(true);  
    20.             ctx.setResponseStatusCode(200);  
    21.             ctx.set("isSuccess", true);  
    22.             return null;  
    23.         }else{  
    24.             ctx.setSendZuulResponse(false);  
    25.             ctx.setResponseStatusCode(401);  
    26.             ctx.setResponseBody("{\"result\":\"password is not correct!\"}");  
    27.             ctx.set("isSuccess", false);  
    28.             return null;  
    29.         }  
    30.     }  
    31.   
    32.     @Override  
    33.     public boolean shouldFilter() {  
    34.         RequestContext ctx = RequestContext.getCurrentContext();  
    35.         return (boolean) ctx.get("isSuccess");// 如果前一个过滤器的结果为true,则说明上一个过滤器成功了,需要进入当前的过滤,如果前一个过滤器的结果为false,则说明上一个过滤器没有成功,则无需进行下面的过滤动作了,直接跳过后面的所有过滤器并返回结果  
    36.     }  
    37.   
    38.     @Override  
    39.     public int filterOrder() {  
    40.         return 1; // 优先级设置为1  
    41.     }  
    42.   
    43.     @Override  
    44.     public String filterType() {  
    45.         return "pre";  
    46.     }  
    47. }  

    最后建一个post过滤器

     

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package com.chhliu.springcloud.zuul;  
    2.   
    3. import javax.servlet.http.HttpServletRequest;  
    4.   
    5. import com.netflix.zuul.ZuulFilter;  
    6. import com.netflix.zuul.context.RequestContext;  
    7.   
    8. public class AccessTokenFilter extends ZuulFilter {  
    9.     @Override  
    10.     public Object run() {  
    11.         RequestContext ctx = RequestContext.getCurrentContext();  
    12.         HttpServletRequest request = ctx.getRequest();  
    13.   
    14.         System.out.println(String.format("%s AccessTokenFilter request to %s", request.getMethod(),  
    15.                 request.getRequestURL().toString()));  
    16.           
    17.         ctx.setSendZuulResponse(true);  
    18.         ctx.setResponseStatusCode(200);  
    19.         ctx.setResponseBody("{\"name\":\"chhliu\"}");// 输出最终结果  
    20.         return null;  
    21.     }  
    22.   
    23.     @Override  
    24.     public boolean shouldFilter() {  
    25.         return true;  
    26.     }  
    27.   
    28.     @Override  
    29.     public int filterOrder() {  
    30.         return 0;  
    31.     }  
    32.   
    33.     @Override  
    34.     public String filterType() {  
    35.         return "post";// 在请求被处理之后,会进入该过滤器  
    36.     }  
    37. }  

    2、在主类中,先开启前面的两个过滤器

     

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. @Bean  
    2.     public AccessUserNameFilter accessUserNameFilter() {  
    3.         return new AccessUserNameFilter();  
    4.     }  
    5.       
    6.     @Bean  
    7.     public AccessPasswordFilter accessPasswordFilter(){  
    8.         return new AccessPasswordFilter();  
    9.     }  

    3、输入请求,验证

     

    (1)请求为:http://localhost:8768/h2service/user/1?username=chhliu

    测试结果为:

    {"result":"password is not correct!"}

    控制台打印结果

     

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. GET AccessUserNameFilter request to http://localhost:8768/h2service/user/1  
    2. GET AccessPasswordFilter request to http://localhost:8768/h2service/user/1  

    通过了AccessUserNameFilter过滤器,在验证AccessPasswordFilter过滤器的时候失败了

     

    后台无sql打印,说明请求没有被路由

    (2)请求为:http://localhost:8768/h2service/user/1?password=123456

    测试结果为:

    {"result":"username is not correct!"}

    控制台打印结果:

     

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. GET AccessUserNameFilter request to http://localhost:8768/h2service/user/1  

    说明到了AccessUserNameFilter过滤器,但是没有到AccessPasswordFilter过滤器,因为AccessUserNameFilter过滤器的优先级高一些,会先执行,在执行的时候,发现过滤条件不符合,于是跳过了后面所有的过滤器,并返回结果

     

    后台无sql打印,说明请求没有被路由

    (3)请求为:http://localhost:8768/h2service/user/1?password=123456&username=chhliu

    测试结果为:

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. {  
    2.   
    3.     "id": 1,  
    4.     "username": "user1",  
    5.     "name": "张三",  
    6.     "age": 20,  
    7.     "balance": 100.00  
    8.   
    9. }  

    控制台打印的结果:

     

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. GET AccessUserNameFilter request to http://localhost:8768/h2service/user/1  
    2. GET AccessPasswordFilter request to http://localhost:8768/h2service/user/1  

    说明是先执行了AccessUserNameFilter然后才执行AccessPasswordFilter这也和我们前面说的order的值越小,优先级越高是吻合的。

     

    同时被请求的服务有sql输出:

     

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.balance as balance3_0_0_, user0_.name as name4_0_0_, user0_.username as username5_0_0_ from user user0_ where user0_.id=?  

    说明请求被路由了。

     

    4、开启post过滤器,再跑一次

    测试结果:发现post过滤器是最后执行的,尽管它的优先级为0

    关于zuul的Filter的生命周期,见下图

    注:上图有个小错误,routing应该是route

    5、拓展

    zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter

    StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。

    SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机。

    转载于:https://www.cnblogs.com/wangjing666/p/6872591.html

    展开全文
  • Zuul Filter Zuul Filter 的特点 Filter 类型:Filter 类型决定了当前的 Filter 在整个 Filter 链中的执行顺序。 Filter 执行顺序:同一种类型的 Filter 通过 filterOrder() 来设置执行顺序 F...

    Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,能够在进行 HTTP 请求或响应的时候执行相关操作。

    Zuul Filter

    Zuul Filter 的特点

    • Filter 类型:Filter 类型决定了当前的 Filter 在整个 Filter 链中的执行顺序。
    • Filter 执行顺序:同一种类型的 Filter 通过 filterOrder() 来设置执行顺序
    • Filter 执行条件:Filter 执行所需的标准、条件
    • Filter 执行效果:符合某个条件,产生的执行结果

    Zuul 内部提供了一个动态读取、编译、运行这些 Filter 的机制。Filter 之间不直接通信,在请求线程中会通过 RequestContext 共享状态,内部使用 ThreadLocal 实现,也可以在 Filter 之间使用 ThreadLocal 收集自己需要的状态、数据

    Zuul Filter 的执行逻辑源码在 com.netflix.zuul.http.ZuulServlet

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();        // 通过 RequestContext 获取共享状态
            context.setZuulEngineRan();
    
            try {
                this.preRoute();        // 执行请求之前的操作
            } catch (ZuulException var13) {
                this.error(var13);      // 出现错误的操作
                this.postRoute();
                return;
            }
    
            try {
                this.route();       // 路由操作
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }
    
            try {
                this.postRoute();       // 请求操作
            } catch (ZuulException var11) {
                this.error(var11);
            }
        } catch (Throwable var14) {
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
    
    

    Zuul 生命周期

    Zuul 官方文档中,生命周期图。

     

    Zuul Life Cycle

    但官方文档的生命周期图不太准确。

    • 在 postRoute 执行之前,即 postFilter 执行之前,如果没有出现过错误,会调用 error 方法,并调用 this.error(new ZuulException) 打印堆栈信息
    • 在 postRoute 执行之前就已经报错,会调用 error 方法,再调用 postRoute,但是之后会直接 return,不会调用 this.error(new ZuulException) 打印堆栈信息

    由此可以看出,整个 Filter 调用链的重点可能是 postFilter 也可能是 errorFilter

    Zuul Life Cycle

    pre、route 出现错误后,进入 error,再进入 post,再返回
    pre、route 没有出现错误,进入 post,如果出现错误,再进入 error,再返回

    • pre:在 Zuul 按照规则路由到下级服务之前执行。如果需要对请求进行预处理,如:鉴权、限流等,都需要在此 Filter 实现
    • route:Zuul 路由动作的执行者,是 Http Client、Ribbon 构建和发送原始 HTTP 请求的地方
    • post:源服务返回结果或异常信息发生后执行,如果需要对返回值信息做处理,需要实现此类 Filter
    • error:整个生命周期发生异常,都会进入 error Filter,可做全局异常处理。

    Filter 之间,通过 com.netflix.zuul.context.RequestContext 类进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括:请求路由、错误信息、HttpServletRequest、HTTPServletResponse,扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息


    Zuul 原生 Filter

    整合 spring-boot-starter-actuator 后,查看 idea 控制台 endpoints 栏的 mappings,可以看到多了几个 Actuator 端点

    routes 端点

    访问 http://localhost:8989/actuator/routes 可以查看当前 zuul server 映射了几个路径、服务

    {
        "/provider/**": "spring-cloud-provider-service-simple",
        "/spring-cloud-provider-service-simple/**": "spring-cloud-provider-service-simple"
    }
    

    访问 http://localhost:8989/actuator/routes/details 可以查看具体的映射信息

    {
        "/provider/**": {
            "id": "spring-cloud-provider-service-simple",       // serviceId
            "fullPath": "/provider/**",     // 映射 path
            "location": "spring-cloud-provider-service-simple",     // 服务名称,实际上也是 serviceId
            "path": "/**",      // 实际访问路径 
            "prefix": "/provider",      // 访问前缀
            "retryable": false,     // 是否开启重试
            "customSensitiveHeaders": false,        // 是否自定义了敏感 header
            "prefixStripped": true      // 是否去掉前缀(如果为 false,则实际访问时需要加 前缀,且实际请求的访问路径也会加上前缀)
        },
        "/spring-cloud-provider-service-simple/**": {
            "id": "spring-cloud-provider-service-simple",
            "fullPath": "/spring-cloud-provider-service-simple/**",
            "location": "spring-cloud-provider-service-simple",
            "path": "/**",
            "prefix": "/spring-cloud-provider-service-simple",
            "retryable": false,
            "customSensitiveHeaders": false,
            "prefixStripped": true
        }
    }
    

    filters 端点

    访问 http://localhost:8989/actuator/filters ,返回当前 zuul 的所有 filters

    Zuul Filters

    内置 Filters

    名称 类型 顺序 描述
    ServletDetectionFilter pre -3 通过 Spring Dispatcher 检查请求是否通过
    Servlet30WrapperFilter pre -2 适配 HttpServletRequest 为 Servlet30RequestWrapper 对象
    FormBodyWrapperFilter pre -1 解析表单数据,并为下游请求进行重新编码
    DebugFilter pre 1 Debug 路由标识
    PreDecorationFilter pre 5 处理请求上下文供后续使用,设置下游相关头信息
    RibbonRoutingFilter route 10 使用 Ribbon、Hystrix、嵌入式 HTTP 客户端发送请求
    SimpleHostRoutingFilter route 100 使用 Apache Httpclient 发送请求
    SendForwardFilter route 500 使用 Servlet 转发请求
    SendResponseFilter post 1000 将代理请求的响应写入当前响应
    SendErrorFilter error 0 如果 RequestContext.getThrowable() 不为空,则转发到 error.path 哦诶之的路径

    如果使用 @EnableZuulServer 注解,将减少 PreDecorationFilterRibbonRoutingFilterSimpleHostRoutingFilter

    如果要替换到某个原生的 Filter,可以自实现一个和原生 Filter 名称、类型一样的 Filter,并替换。或者禁用掉某个filter,并自实现一个新的。
    禁用语法: zuul.{SimpleClassName}.{filterType}.disable=true,如 zuul.SendErrorFilter.error.disable=true


    多级业务处理

    在 Zuul Filter 链体系中,可以把一组业务逻辑细分,然后封装到一个个紧密结合的 Filter,设置处理顺序,组成一组 Filter 链。

    自定义实现 Filter

    在 Zuul 中实现自定义 Filter,继承 ZuulFilter 类即可,ZuulFilter 是一个抽象类,需要实现以下几个方法

    • String filterType:使用返回值设定 Filter 类型,可以设置为 prerouteposterror
    • int filterOrder:使用返回值设置 Filter 执行次序
    • boolean shouldFilter:使用返回值设定该 Filter 是否执行,可以作为开关来使用
    • Object run:Filter 的核心执行逻辑
    // 自定义 ZuulFilter
    public class FirstPreFilter extends ZuulFilter {
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            System.out.println("自定义 Filter,类型为 pre!");
            return null;
        }
    }
    
    
    // 注入 Spring 容器
    @Bean
    public FirstPreFilter firstPreFilter(){
        return new FirstPreFilter();
    }
    

    此时访问 http://localhost:8989/provider/get-result ,查看控制台:

    Initializing Servlet 'dispatcherServlet'
    Completed initialization in 0 ms
    自定义 Filter,类型为 pre!
    Flipping property: spring-cloud-provider-service-simple.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    Shutdown hook installed for: NFLoadBalancer-PingTimer-spring-cloud-provider-service-simple
    

    业务处理

    使用 SecondFilter 验证是否传入参数 a,ThirdPreFilter 验证是否传入参数 b,在 PostFilter 统一处理返回内容。

    SecondPreFilter

    public class SecondPreFilter extends ZuulFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(SecondPreFilter.class);
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 2;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<");
            // 获取上下文
            RequestContext requestContext = RequestContext.getCurrentContext();
            // 从上下文获取 request
            HttpServletRequest request = requestContext.getRequest();
            // 从 request 获取参数 a
            String a = request.getParameter("a");
            // 如果参数 a 为空
            if (StringUtils.isBlank(a)) {
                LOGGER.info(">>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<");
                // 禁止路由,禁止访问下游服务
                requestContext.setSendZuulResponse(false);
                // 设置 responseBody,供 postFilter 使用
                requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 a 为空!\"}");
                // 用于下游 Filter 判断是否执行
                requestContext.set("logic-is-success", false);
                // Filter 结束
                return null;
            }
            requestContext.set("logic-is-success", true);
            return null;
        }
    }
    

    ThirdPreFilter

    public class ThirdPreFilter extends ZuulFilter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(ThirdPreFilter.class);
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 3;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext context = RequestContext.getCurrentContext();
            // 获取上下文中的 logic-is-success 中的值,用于判断当前 filter 是否执行
            return (boolean) context.get("logic-is-success");
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<");
            // 获取上下文
            RequestContext requestContext = RequestContext.getCurrentContext();
            // 从上下文获取 request
            HttpServletRequest request = requestContext.getRequest();
            // 从 request 获取参数 a
            String a = request.getParameter("b");
            // 如果参数 a 为空
            if (StringUtils.isBlank(a)) {
                LOGGER.info(">>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<");
                // 禁止路由,禁止访问下游服务
                requestContext.setSendZuulResponse(false);
                // 设置 responseBody,供 postFilter 使用
                requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 b 为空!\"}");
                // 用于下游 Filter 判断是否执行
                requestContext.set("logic-is-success", false);
                // Filter 结束
                return null;
            }
            requestContext.set("logic-is-success", true);
            return null;
        }
    }
    

    PostFilter

    
    public class PostFilter extends ZuulFilter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(PostFilter.class);
    
        @Override
        public String filterType() {
            return FilterConstants.POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<");
            RequestContext context = RequestContext.getCurrentContext();
            // 处理返回中文乱码
            context.getResponse().setCharacterEncoding("UTF-8");
            // 获取上下文保存的 responseBody
            String responseBody = context.getResponseBody();
            // 如果 responseBody 不为空,则证明流程中有异常发生
            if (StringUtils.isNotBlank(responseBody)) {
                // 设置返回状态码
                context.setResponseStatusCode(500);
                // 替换响应报文
                context.setResponseBody(responseBody);
            }
            return null;
        }
    }
    

    访问 http://localhost:8989/provider/addhttp://localhost:8989/provider/add?a=1http://localhost:8989/provider/add?a=1&b=1 ,查看控制台

    控制台:

    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.005  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.500  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    

    返回值:

    {"status": 500, "message": "参数 a 为空!"}
    
    {"status": 500, "message": "参数 b 为空!"}
    
    result is : a + b = 2
    

    由此验证自定义 Zuul Filter 成功。



    作者:laiyy0728
    链接:https://www.jianshu.com/p/e8126da2f4fd
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    展开全文
  • 一、什么是动态Filter Zuul提供了一个能够对过滤器进行动态的加载、编译、运行的框架。...下面我们就基于spring-cloud-starter-zuul(SpringCloud版本为Edgware.SR3)进行扩展,实现动态加载Filter的功能 二、Zuul
  • zuul创建filter

    2020-07-03 21:43:54
    package com.filter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration...
  • Zuul

    2021-01-18 18:57:20
    官方文档:https://docs.spring.io/spring-cloud-netflix/docs/2.2.5.RELEASE/reference/html/#router-and-filter-zuul Zuul是netflix开源的一个API Gateway服务器,本质为web servlet应用,但在Spring
  • zuul RequestContext filter原理

    千次阅读 2018-08-02 09:49:05
    zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。其原理就是在zuul把Request route到源web-service的时候,处理一些逻辑,比如Authentication,Load Shedding等 ==HttpRequest -&...
  • Spring Cloud Zuul实现多级自定义Filter
  • zuul

    千次阅读 2018-02-13 14:18:06
    Router and Filter: Zuul路由是微服务架构的不可或缺的一部分。例如:”/” 可能映射到你应用主页,/api/users映射到用户服务,/api/shop映射到购物服务。ZuulZuul是Netflix出品的一个基于JVM路由和服务端的负载...
  • ZuulFilter详解

    2018-12-10 17:11:00
    Zuul详解 官方文档:https://github.com/Netflix/zuul/wiki/How-it-Works Zuul的中心是一系列过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作。 以下是Zuul过滤器的主要特征: 类型:通常在应用过滤...
  • Spring cloud Zuul Filter 使用小经验

    千次阅读 2017-12-15 09:32:03
    今天介绍的FilterZuul中提供的,跟我们之前使用的Servlet Filter不太一样。Zuul中提供Filter的作用有哪些,我觉得分为如下几点: - 网关是暴露在外面的,必须要进行权限控制 - 可以针对服务做控制,在路由的时候...
  • zuul的最大连接数和每个路由的最大连接数默认分别是200和20 Hystrix 的隔离模式是 SEMAPHORE ribbon 的隔离模式可以被改为 thread 如何引入: zuul作为一个代理来调用后台服务,不需要管理跨域和认证等其他...
  • zuulfilter执行顺序

    千次阅读 2018-03-19 16:14:17
    抛开zuul将自定义以及默认filter初始化到FilterRegistry的ConcurrentHashMap中不谈,记录一下zuul的过滤器执行顺序。 1.在ZuulServlet类中的service方法,先给请求创建一个线程局部变量“RequestContext”,其生命...
  • 一、通过Zuul上传文件  参看地址:https://cloud.spring.io/spring-cloud-static/Edgware.SR3/single/spring-cloud.html#_uploading_files_through_zuul  如果您使用@EnableZuulProxy,则可以使用代理路径来上传...
  • 主要介绍了SpringCloud Zuul自定义filter代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Zuul-自定义Filter

    2019-03-26 02:14:40
    自定义过滤器 ...其中filterType()和filterOrder(),以及IZuulFilter的shouldFilter()和Object run()的两个方法。 filterType():定义过滤器的类型,它有4种类型,分别是pre、post、routing和error filt...
  • zuul网关Filter处理流程及异常处理

    万次阅读 2017-09-08 14:25:30
    上一篇介绍了java网关Zuul的简单使用,进行请求路由转发和过滤器的基本操作。这一篇主要看一下它的过滤器Filter的工作流程及异常处理。首先看到Filter的四个方法,FilterType,filterOrder,shouldFilter,run。...
  • 前文回顾: Spring Cloud(一)Eureka Server-单体及集群搭建 Spring Cloud(二) 配置Eureka Client ...FilterZuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING...
  • spring cloud zuul中的过滤器 从FilterConstants入手,看zuul中默认的过滤器 这是摘自上图这个类中的部分代码,静态变量指定了zuul中默认的过滤器和响应的order. ... * Filter Order for {@link DebugFilte...
  • spring cloud网关zuul源码之Filter管理

    千次阅读 2017-04-11 11:12:12
    zuul支持动加载Filter类文件。实现原理是监控存放Filter文件的目录,定期扫描这些目录,如果发现有新Filter源码文件或者Filter源码文件有改动,则对文件进行编译加载。目前zuul支持使用Groovy编写的Filter
  • spring cloud网关zuulfilter注册

    千次阅读 2017-04-11 11:15:35
    原文地址 ...zuul开启 首先由@EnableZuulProxy 注解是开启zuul的注解。 @EnableCircuitBreaker @EnableDiscoveryClient @Target({ElementType.TYPE}) @Retention

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 939
精华内容 375
关键字:

filterzuul