idea创建zuul网关实现过滤器入门实例


zuul两个重要功能,一个是路由转发,另一个就是过滤器。路由转发上一篇随笔已经写了,这一个写过滤器的实例。

spring cloud zuul 实现的过滤器需要包含4个基本特征:过滤类型、执行顺序、执行条件、具体操作。也就是zuulFilter接口的4个抽象方法。

ZuulFilter源码

/*
 * Copyright 2013 Netflix, Inc.
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */
package com.netflix.zuul;

import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.zuul.monitoring.MonitoringHelper;
import com.netflix.zuul.monitoring.Tracer;
import com.netflix.zuul.monitoring.TracerFactory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.*;

/**
 * Base abstract class for ZuulFilters. The base class defines abstract methods to define:
 * filterType() - to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
 * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
 * We also support a "static" type for static responses see  StaticResponseFilter.
 * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
 * 

* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not * important for a filter. filterOrders do not need to be sequential. *

* ZuulFilters may be disabled using Archius Properties. *

* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false * * @author Mikey Cohen * Date: 10/26/11 * Time: 4:29 PM */ public abstract class ZuulFilter implements IZuulFilter, Comparable { private final AtomicReference filterDisabledRef = new AtomicReference<>(); /** * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering, * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling. * We also support a "static" type for static responses see StaticResponseFilter. * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type) * * @return A String representing that type */ abstract public String filterType(); /** * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not * important for a filter. filterOrders do not need to be sequential. * * @return the int order of a filter */ abstract public int filterOrder(); /** * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false * * @return true by default */ public boolean isStaticFilter() { return true; } /** * The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable * * @return */ public String disablePropertyName() { return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable"; } /** * If true, the filter has been disabled by archaius and will not be run * * @return */ public boolean isFilterDisabled() { filterDisabledRef.compareAndSet(null, DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false)); return filterDisabledRef.get().get(); } /** * runFilter checks !isFilterDisabled() and shouldFilter(). The run() method is invoked if both are true. * * @return the return from ZuulFilterResult */ public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!isFilterDisabled()) { if (shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { Object res = run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable e) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(e); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; } public int compareTo(ZuulFilter filter) { return Integer.compare(this.filterOrder(), filter.filterOrder()); } public static class TestUnit { static Field field = null; static { try { field = ZuulFilter.class.getDeclaredField("filterDisabledRef"); field.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); } } @Mock private ZuulFilter f1; @Mock private ZuulFilter f2; @Before public void before() { MockitoAnnotations.initMocks(this); MonitoringHelper.initMocks(); } @Test public void testSort() { when(f1.filterOrder()).thenReturn(1); when(f2.filterOrder()).thenReturn(10); when(f1.compareTo(any(ZuulFilter.class))).thenCallRealMethod(); when(f2.compareTo(any(ZuulFilter.class))).thenCallRealMethod(); ArrayList list = new ArrayList(); list.add(f2); list.add(f1); Collections.sort(list); assertSame(f1, list.get(0)); } @Test public void testShouldFilter() { class TestZuulFilter extends ZuulFilter { @Override public String filterType() { return null; } @Override public int filterOrder() { return 0; } public boolean shouldFilter() { return false; } public Object run() { return null; } } TestZuulFilter tf1 = spy(new TestZuulFilter()); TestZuulFilter tf2 = spy(new TestZuulFilter()); when(tf1.shouldFilter()).thenReturn(true); when(tf2.shouldFilter()).thenReturn(false); try { tf1.runFilter(); tf2.runFilter(); verify(tf1, times(1)).run(); verify(tf2, times(0)).run(); } catch (Throwable throwable) { throwable.printStackTrace(); } } @Test public void testIsFilterDisabled() { class TestZuulFilter extends ZuulFilter { @Override public String filterType() { return null; } @Override public int filterOrder() { return 0; } public boolean isFilterDisabled() { return false; } public boolean shouldFilter() { return true; } public Object run() { return null; } } TestZuulFilter tf1 = spy(new TestZuulFilter()); TestZuulFilter tf2 = spy(new TestZuulFilter()); when(tf1.isFilterDisabled()).thenReturn(false); when(tf2.isFilterDisabled()).thenReturn(true); try { tf1.runFilter(); tf2.runFilter(); verify(tf1, times(1)).run(); verify(tf2, times(0)).run(); } catch (Throwable throwable) { throwable.printStackTrace(); } } @Test public void testDisabledPropNameOnInit() throws Exception { class TestZuulFilter extends ZuulFilter { final String filterType; public TestZuulFilter(String filterType) { this.filterType = filterType; } @Override public boolean shouldFilter() { return false; } @Override public Object run() { return null; } @Override public String filterType() { return filterType; } @Override public int filterOrder() { return 0; } } TestZuulFilter filter = new TestZuulFilter("pre"); assertFalse(filter.isFilterDisabled()); @SuppressWarnings("unchecked") AtomicReference filterDisabledRef = (AtomicReference) field.get(filter); String filterName = filterDisabledRef.get().getName(); assertEquals("zuul.TestZuulFilter.pre.disable", filterName); } } }

filterType:这个函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段,在zuul中默认定义了4种不同生命周期的过滤器类型。分别是:

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

filterOrder: 通过int 值来定义过滤器的执行顺序,数值越小优先级越高

shouldFilter:返回一个boolean值来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围

run  :过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后对处理结果进行加工。

第一步:自定义一个过滤器

package com.ssc.zuul_demo;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.logging.Logger;

public class AccessFilter extends ZuulFilter {
    Logger logger=Logger.getLogger("AccessFilter");
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }
   /**
   *该过滤器主要是看请求是否存在accessToken参数,如果存在是正常访问,否则返回状态码401
   */
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx= RequestContext.getCurrentContext();
        HttpServletRequest request=ctx.getRequest();
        System.out.println(request.getMethod()+"   "+request.getRequestURL().toString());
        Object accessToken =request.getParameter("accessToken");
        if(accessToken==null){
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
        }
        return null;
    }
}

需要为其创建具体的Bean才能启动该过滤器,否则它并不会直接生效,当然也可以这样改

package com.ssc.zuul_demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@EnableZuulProxy
@SpringBootApplication
@EnableEurekaClient
public class ZuulDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApplication.class, args);
    }
    @Bean
    public AccessFilter accessFilter(){
        return new AccessFilter();
    }

}

启动服务,校验

验证反例,不加 accessToken参数

将代码传到gitee:cloud_demo: springcloud样例 (gitee.com)