第六章 Zuul網關路由及源碼分析筆記

前面所有的微服務都是通過Eureka找到的,但是在很多開發中爲了規範微服務的使用,提供有一個處理控制器Zuul。Zuul其實是一個API網關,類似於設計模式裏面的Facade門面模式,他的存在就像是整個微服務的門面,所有的外部客戶端訪問都需要經過它來進行調度與過濾。

代碼Git地址:https://gitee.com/hankin_chj/springcloud-micro-service.git

一、zuul的基本使用

1、新建模塊springcloud-micro-zuul-gateway

1.1、springcloud-micro-zuul-gateway的pom文件如下

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

1.2、zuul-gateway修改application.yml文件

server:
  port: 9501
eureka:
  client: # 客戶端進行Eureka註冊的配置
    service-url:
      defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
    register-with-eureka: false
spring:
  application:
    name: springcloud-micro-zuul-gateway

1.3、zuul-gateway創建啓動類

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

1.4、運行啓動報錯:

 

發現啓動報錯,其實這是因爲zuul目前對springboot2.1.2及以上版本的支持並不好,這也是zuul最近一直被人詬病的地方,爲了解決這個問題只好降低springboot的版本至2.0.7及以下。

1.5、降低系統的springboot版本爲2.0.7.RELEASE

springcloud-micro-service修改父工程pom文件,降低springboot版本爲2.0.7.RELEASE

<!-- 進行SpringCloud依賴包的導入處理 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-dependencies</artifactId>
  <version>Finchley.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>
<!-- SpringCloud離不開SpringBoot,所以必須要配置此依賴包 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <!--<version>2.1.3.RELEASE</version>-->
  <version>2.0.7.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>

注意:

因爲zuul的使用問題降低了springboot的版本,這個使用要注意將數據庫的驅動名稱也做相應的修改:

# 配置MySQL的驅動程序類,注意springboot降低版本以後不能使用com.mysql.cj.jdbc.Driver

driver-class-name: com.mysql.jdbc.Driver

重新啓動ZuulApp:

正常訪問用戶服務:http://localhost:8090/user/get/1

使用zuul代理訪問用戶服務:http://localhost:9501/springcloud-micro-user/user/get/1

 

2、Zuul配置路由

前面以及簡單的使用了zuul,但你會發現訪問地址還必須知道程序的名稱,如果不知道這個名稱是無法訪問的,但如果讓用戶知道了這名稱,那麼使用zuul就是去它的實際意義的,我們可以通過名稱直接調用。既然是使用代理,那麼代理的功能就是不能讓用戶看到真實的操作,屏蔽真實的調用地址,這個時候就需要自己增加zuul的路由規則配置了。

2.1、zuul-gateway修改application.yml配置文件,增加路由配置

# 使用zuul代理
zuul:
  routes:
    springcloud-micro-user: /user-proxy/**

這個時候就可以通過/users-proxy 來訪問springcloud-micro-user服務

訪問地址:http://localhost:9501/user-proxy/user/get/1,可以正常返回結果。

2.2、忽略掉用戶服務的名稱

但是還會發現,雖然現在以及開啓了路由訪問的支持,但依然通過應用程序的名稱還是能訪問

訪問地址:http://localhost:9501/microcloud-provider-users/users/get/1

zuul-gateway修改application.yml文件,忽略掉用戶服務的名稱:

# 使用zuul代理
zuul:
  routes:
    springcloud-micro-user: /user-proxy/**
  ignored-services: springcloud-micro-user

做完後,就可以進行代理的安全使用,但真實情況下,一般會有很多微服務,如果完全按照上面的配置方式會非常的麻煩,所有最加到的做法是可以採用一個通配符“*”的模式來統一完成。

2.3、zuul-gateway修改application.yml文件

# 使用zuul代理
zuul:
  routes:
    springcloud-micro-user: /user-proxy/**
  #ignored-services: springcloud-micro-user
  ignored-services: "*"

重新啓動訪問:http://localhost:9501/user-proxy/user/get/1,正常返回數據。

訪問:http://localhost:9501/springcloud-micro-user/user/get/1,則無法訪問。

2.4、zuul中另外一種配置方式

除開上面這一種訪問模式以外,在zuul中還有另外一種配置方式,修改application.yml文件:

zuul: # zuul的另外一隻配置方式
  routes:
    user.path: /user-proxy/**
    user.serviceId: springcloud-micro-user
  ignored-services:
    "*"

其中在配置文件中出現的user其實是一個邏輯名稱,這個名稱主要作用是將path與serviceId綁定在一起。

2.5、zuul-gateway脫離eureka進行訪問

zuul-gateway模塊如果說不想通過eureka進行訪問,對於zuul來說也是可以實現的,但是在真實的開發環境中,基本不會使用,yml配置如下所示:

# zuul-gateway脫離eureka進行訪問
zuul:
  routes:
    user:
      path: /user-proxy/**
      serviceId: springcloud-micro-user
    user2:
      path: /user2-proxy/**
      url: http://localhost:8090/
  ignored-services:
    "*"

重新啓動訪問:http://localhost:9501/user2-proxy/user/get/1,依然可以正常返回數據。

2.6、zuul-gateway模塊設置公共前綴prefix

zuul:
  routes:
    user:
      path: /user-proxy/**
      serviceId: springcloud-micro-user
    user2:
      path: /user2-proxy/**
      url: http://localhost:8090/
  ignored-services:
    "*"
  prefix: /chj-api

注意:一旦設置了公共前綴,所以的訪問路徑都要在前面加上前綴

分別通過兩個路由來訪問測試:

http://localhost:9501/chj-api/user-proxy/user/get/1

http://localhost:9501/chj-api/user2-proxy/user/get/1

都可以正常番薯數據。

3、zuul過濾訪問

其實zuul的功能本質上就是一個代理操作,類似於nginx,但是在真實的使用中,所有的微服務一點都有增加的認證信息,那麼就必須在其訪問之前追加認證的頭部操作,這樣的功能需要通過zuul的過去操作完成。

3.1、zuul增加產品微服務

zuul-gateway修改application.yml配置,增加產品微服務配置:

zuul:
  routes:
    user:
      path: /user-proxy/**
      serviceId: springcloud-micro-user
    user2:
      path: /user2-proxy/**
      url: http://localhost:8090/
    product:
      path: /product-proxy/**
      serviceId: springcloud-micro-product
  ignored-services:
    "*"
  prefix: /chj-api

重啓訪問:http://localhost:9501/chj-api/product-proxy/prodcut/get/1

這樣直接訪問是訪問不到的,需要添加安全模塊,查看控制檯報錯信息如下:

com.netflix.zuul.exception.ZuulException: Forwarding error at

org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.handleException(RibbonRoutingFilter.java:189) ~[spring-cloud-netflix-zuul-2.0.0.RELEASE.jar:2.0.0.RELEASE]

at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.forward(RibbonRoutingFilter.java:164) ~[spring-cloud-netflix-zuul-2.0.0.RELEASE.jar:2.0.0.RELEASE]

at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.run(RibbonRoutingFilter.java:112) ~[spring-cloud-netflix-zuul-2.0.0.RELEASE.jar:2.0.0.RELEASE]

at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117) ~[zuul-core-1.3.1.jar:1.3.1]

at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) ~[zuul-core-1.3.1.jar:1.3.1]

at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.1.jar:1.3.1]

3.2、zuul-gateway模塊追加過濾處理

增加過濾器主要是爲了攔截訪問路勁添加用戶安全校驗信息:

/**
 * zuul過利器配置(主要針對用戶安全校驗)
 */
public class AuthorizedRequestFilter  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 {
        RequestContext currentContext = RequestContext.getCurrentContext() ; // 獲取當前請求的上下文
        String auth = "admin:admin"; // 認證的原始信息
        byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII"))); // 進行一個加密的處理
        String authHeader = "Basic " + new String(encodedAuth);
        currentContext.addZuulRequestHeader("Authorization", authHeader);
        return null;
    }
}

其中filterType爲過濾的類型,在進行Zuul過濾的時候可以設置其過濾執行的位置,那麼此時有如下幾種類型:

public static final String ERROR_TYPE = "error";
public static final String POST_TYPE = "post";
public static final String PRE_TYPE = "pre";
public static final String ROUTE_TYPE = "route";

·pre:在請求發出之前執行過濾,如果要進行訪問,肯定在請求前設置頭信息;

·route:在進行路由請求的時候被調用;

·post:在路由之後發送請求信息的時候被調用;

·error:出現錯誤之後進行調用。

3.3、zuul-gateway模塊建立一個配置程序類

@Configuration
public class ZuulConfig {
    @Bean
    public AuthorizedRequestFilter getAuthorizedRequestFilter() {
        return new AuthorizedRequestFilter() ;
    }
}

這個時候再次訪問:

http://localhost:9501/chj-api/product-proxy/prodcut/get/1

http://localhost:9501/chj-api/user-proxy/user/get/1

http://localhost:9501/chj-api/user2-proxy/user/get/1

這兩個服務都能正常訪問了。

4、Zuul安全訪問

作爲所有接口的統一門面,zuul也是可以進行加密訪問的

4.1、增加安全訪問模塊

zuul-gateway修改pom文件,增加spring-boot-starter-security依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

4.2、增加用戶配置

zuul-gateway修改application.yml配置文件,增加用戶配置信息:

spring:
  application:
    name: springcloud-micro-zuul-gateway
  security:
    user:
      name: admin
      password: admin

4.3、啓動服務測試

再訪問http://localhost:9501/chj-api/user-proxy/user/get/1

這個時候就需要輸入用戶名密碼了。

二、Feign訪問Zuul

前面學習feign的時候確實已經知道,他其實是去eureka中獲取服務地址的,如果想使用feign來訪問zuul,首先就應該讓zuul註冊到eureka中。

1、zuul-gateway增加eureka相關配置

zuul-gateway模塊修改application.yml文件,添加如下配置信息:

eureka:
  client: # 客戶端進行Eureka註冊的配置
    service-url:
      defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
  instance:
    instance-id: microcloud-zuul-gateway
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 2 # 設置心跳的時間間隔(默認是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果現在超過了5秒的間隔(默認是90秒)

2、zuul代理系統所有接口服務訪問

springcloud-micro現在所有的服務都要通過zuul的代理進行訪問,需要在系統公共接口模塊增加接口。

2.1、service-feign公共接口模塊新增接口IZUUlClientService:

@FeignClient(name = "SPRINGCLOUD-MICRO-ZUUL-GATEWAY",configuration = FeignClientConfig.class,
        fallbackFactory = IZUUlClientServiceallbackFactory.class)
public interface IZUUlClientService {
    // ***********************產品服務模塊接口***********************
    @RequestMapping("/chj-api/product-proxy/prodcut/get/{id}")
    public Product getProduct(@PathVariable("id")long id);
    @RequestMapping("/chj-api/product-proxy/prodcut/list")
    public List<Product> listProduct() ;
    @RequestMapping("/chj-api/product-proxy/prodcut/add")
    public boolean addPorduct(Product product) ;
    // ***********************產品服務模塊接口***********************
    @RequestMapping("/chj-api/user-proxy/user/get/{name}")
    public User getUser(@PathVariable("name")String name);
}

2.2、增加容錯處理實現

新增IZUUlClientServiceallbackFactory,在Zuul由於出現網絡問題失去聯繫後進行容錯處理

/**
 * 在Zuul由於出現網絡問題失去聯繫後進行容錯處理
 */

@Component
public class IZUUlClientServiceallbackFactory implements FallbackFactory<IZUUlClientService> {
    @Override
    public IZUUlClientService create(Throwable throwable) {
        return new IZUUlClientService() {
            @Override
            public Product getProduct(long id) {
                Product product = new Product();
                product.setProductId(999999L);
                product.setProductName("feign-zuulName");
                product.setProductDesc("feign-zuulDesc");
                return  product;
            }
            @Override
            public List<Product> listProduct() {
                return null;
            }
            @Override
            public boolean addPorduct(Product product) {
                return false;
            }
            @Override
            public User getUser(String name) {
                User user = new User();
                user.setSex("F");
                user.setAge(30);
                user.setName("zuul-fllback:"+name);
                return user;
            }
        };
    }
}

2.3、服務消費者添加zuul代理接口訪問方法

consumer-hystrix模塊修改ConsumerProductController,增加方法getProductAndUser,訪問接口:

@RestController
@RequestMapping("/consumer")
public class ConsumerProductController {
    @Resource
    private IProductClientService iProductClientService;
    @Resource
    private IZUUlClientService izuUlClientService;
    @RequestMapping("/product/get")
    public Object getProduct(long id) {
        return  iProductClientService.getProduct(id);
    }
    @RequestMapping("/product/list")
    public  Object listProduct() {
        return iProductClientService.listProduct();
    }
    @RequestMapping("/product/add")
    public Object addPorduct(Product product) {
        return  iProductClientService.addPorduct(product);
    }
    // 通過zuul全局網關調用用戶和產品服務
    @RequestMapping("/product/getProductAndUser")
    public Object getProductAndUser(long id) {
        Map<String,Object> result = new HashMap();
        result.put("product",izuUlClientService.getProduct(id));
        result.put("user",izuUlClientService.getUser(id+""));
        return  result;
    }
}

2.4、啓動服務測試

依次啓動eureka,user服務,product服務,zuul服務與consumer-feign-hystrix服務。

1)在地址欄輸入:http://localhost/consumer/product/getProductAndUser?id=1

查詢結果如下:

{"product":{"productId":null,"productName":"java編程","productDesc":"springcloud"},

"user":{"name":"1","age":31,"sex":"boy"}}

2)關閉zuul服務繼續訪問:

http://localhost/consumer/product/getProductAndUser?id=1,返回結果:

{"product":{"productId":999999,"productName":"feign-zuulName","productDesc":"feign-zuulDesc"},

"user":{"name":"zuul-fllback:1","age":30,"sex":"F"}}

返回結果都是rollback的數據,說明服務降級已經開啓。

3、Zuul熔斷

zuul是一個代理服務,但如果被代理的服務突然斷了,這個時候zuul上面會有出錯信息,例如,停止product服務,訪問http://localhost:9501/enjoy-api/product-proxy/prodcut/get/1會報錯。

現在服務的調用方已經做了處理,不會出現這樣的錯誤信息,但一般來說,對於zuul本身代理方,也應該進行zuul的降級處理。

3.1、修改uul-gateway模塊建立fallback回退處理類

注意HttpHeaders包的引入import org.springframework.http.HttpHeaders;

@Component
public class ProviderFallback implements FallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.set("Content-Type", "text/html; charset=UTF-8");
                return headers;
            }
            @Override
            public InputStream getBody() throws IOException {
                // 響應體
                return new ByteArrayInputStream("產品微服務不可用,請稍後再試。".getBytes());
            }
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.BAD_REQUEST;
            }
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.BAD_REQUEST.value();
            }
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.BAD_REQUEST.getReasonPhrase();
            }
            @Override
            public void close() {
            }
        };
    }
}

3.2、重新啓動服務

訪問http://localhost/consumer/product/getProductAndUser?id=1,返回結果如下:

{"product":{"productId":null,"productName":"java編程","productDesc":"springcloud"},

"user":{"name":"1","age":31,"sex":"boy"}}

關閉product服務返回結果如下:

{"product":{"productId":999999,"productName":"feign-zuulName","productDesc":"feign-zuulDesc"},

"user":{"name":"1","age":31,"sex":"boy"}}

備註:

getRoute方法可以返回服務的ID,比如springcloud-micro-product,如果需要匹配全部適應 “*”。

二、zuul原理解析

第一步:引入依賴,在啓動類中添加@EnableZuulProxy,聲明這是一個Zuul代理。

第二步:註冊到Eureka Server啓動服務,訪問這個端口,url中帶上要請求的服務名。

1、啓動類中添加@EnableZuulProxy

1.引入依賴,在啓動類中添加@EnableZuulProxy,聲明這是一個Zuul代理。

2.註冊到Eureka Server啓動服務,訪問這個端口,url中帶上要請求的服務名。

1.1、zuul默認是支持開啓斷路器(hystrix)

@EnableZuulProxy註解默認引入了@EnableCircuitBreaker,所以zuul默認是支持開啓斷路器(hystrix)

@EnableCircuitBreaker //開啓斷路器(hystrix)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({ZuulProxyMarkerConfiguration.class})
public @interface EnableZuulProxy {
}

1.2、查看ZuulProxyMarkerConfiguration源碼:

源碼位置:org.springframework.cloud.netflix.zuul.ZuulProxyMarkerConfiguration

@Configuration //讓ZuulProxyMarkerConfiguration 配置生效
public class ZuulProxyMarkerConfiguration {
    public ZuulProxyMarkerConfiguration() {
    }

//創建Marker 實例,讓ZuulProxyMarkerConfiguration 配置生效
    @Bean
    public ZuulProxyMarkerConfiguration.Marker zuulProxyMarkerBean() {
        return new ZuulProxyMarkerConfiguration.Marker();
    }
    class Marker {
        Marker() {
        }
    }
}

2、zuul默認啓動類加載配置分析

註冊到Eureka Server啓動服務,訪問這個端口,url中帶上要請求的服務名。

配置類入口:spring-cloud-netflix-zuul-2.0.0.RELEASE.jar!\META-INF\spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

其中ZuulProxyAutoConfiguration的父類是ZuulServerAutoConfiguration

@Configuration
@Import({RestClientRibbonConfiguration.class, OkHttpRibbonConfiguration.class, HttpClientRibbonConfiguration.class, HttpClientConfiguration.class})
@ConditionalOnBean({Marker.class})
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

2.1、分析方法zuulServlet

代碼位置:org.springframework.boot.web.servlet.ServletRegistrationBean

該方法向ServletRegistrationBean中註冊了一個ZuulServlet對象,而ZuulServlet又繼HttpServlet

//註冊一個zuulServlet

@Bean
@ConditionalOnMissingBean(
    name = {"zuulServlet"}
)
public ServletRegistrationBean zuulServlet() {
    ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(),

new String[]{this.zuulProperties.getServletPattern()});
    servlet.addInitParameter("buffer-requests", "false");
    return servlet;
}

2.2、ZuulServlet.service方法分析

Zuulfilter有四種過濾方式:pre(前置)route(路由定位)post(執行後)error(出錯)

執行流程如下圖所示:

 

執行代碼如下面:com.netflix.zuul.http.ZuulServlet.service方法

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    try {
        this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
        RequestContext context = RequestContext.getCurrentContext();
        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();
    }
}

2.3、FilterProcessor#runFilters方法分析:

不管是哪一種過濾器,最後都會落到com.netflix.zuul.FilterProcessor#runFilters方法上面

void postRoute() throws ZuulException {
    this.zuulRunner.postRoute();
}
void route() throws ZuulException {
    this.zuulRunner.route();
}
void preRoute() throws ZuulException {
    this.zuulRunner.preRoute();
}

com.netflix.zuul.ZuulRunner代碼:

public void postRoute() throws ZuulException {
    FilterProcessor.getInstance().postRoute();
}
public void route() throws ZuulException {
    FilterProcessor.getInstance().route();
}
public void preRoute() throws ZuulException {
    FilterProcessor.getInstance().preRoute();
}

最後方法處理都在com.netflix.zuul.FilterProcessor中,以方法postRoute爲例:

public void postRoute() throws ZuulException {
    try {
        this.runFilters("post");
    } catch (ZuulException var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName());
    }
}

FilterProcessor.runFilters()方法實現如下:

public Object runFilters(String sType) throws Throwable {
    if (RequestContext.getCurrentContext().debugRouting()) {
        Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
    }
    boolean bResult = false;

//據filter類型,獲取排過序的filter
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
        for(int i = 0; i < list.size(); ++i) {
            ZuulFilter zuulFilter = (ZuulFilter)list.get(i);

       //處理filter執行
            Object result = this.processZuulFilter(zuulFilter);
            if (result != null && result instanceof Boolean) {
                bResult |= (Boolean)result;
            }
        }
    }
    return bResult;
}

接下來查看FilterProcessor.processZuulFilter方法源碼:

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
    RequestContext ctx = RequestContext.getCurrentContext();
    boolean bDebug = ctx.debugRouting();
    String metricPrefix = "zuul.filter-";
    long execTime = 0L;
    String filterName = "";
    try {
        long ltime = System.currentTimeMillis();
        filterName = filter.getClass().getSimpleName();
        RequestContext copy = null;
        Object o = null;
        Throwable t = null;
        if (bDebug) {
            Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
            copy = ctx.copy();
        }

//真正執行ZuulFilter內存方法
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        execTime = System.currentTimeMillis() - ltime;
        switch(s) {
        case FAILED:
            t = result.getException();
            ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
            break;
        case SUCCESS:
            o = result.getResult();
            ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
            if (bDebug) {
                Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                Debug.compareContextState(filterName, copy);
            }
        }
        if (t != null) {
            throw t;
        } else {
            this.usageNotifier.notify(filter, s);
            return o;
        }
    } catch (Throwable var15) {
        if (bDebug) {
            Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() +

" order:" + filter.filterOrder() + " " + var15.getMessage());
        }
        this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
        if (var15 instanceof ZuulException) {
            throw (ZuulException)var15;
        } else {
            ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType()

+ ":" + filterName);
            ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
            throw ex;
        }
    }
}

2.4、真正執行ZuulFilter的runFilter內存方法

com.netflix.zuul.ZuulFilter#runFilter

public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!this.isFilterDisabled()) {
        if (this.shouldFilter()) {
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {

   //執行,run方法就是具體filter 實現的run方法
                Object res = this.run();
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable var7) {
                t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(var7);
            } finally {
                t.stopAndLog();
            }
        } else {
            zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    return zr;
}

通過查看源碼發現run方法是在IZuulFilter接口中定義的,而前面我們搭建zuul模塊的時候就實現了該接口的run方法:

public interface IZuulFilter {
    boolean shouldFilter();
    Object run() throws ZuulException;
}

2.5、最後回到了我們自定義的AuthorizedRequestFilter上面

/**
 * zuul過利器配置(主要針對用戶安全校驗)
 */
public class AuthorizedRequestFilter  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 {
        RequestContext currentContext = RequestContext.getCurrentContext() ; // 獲取當前請求的上下文
        String auth = "admin:admin"; // 認證的原始信息
        byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII"))); // 進行一個加密的處理
        String authHeader = "Basic " + new String(encodedAuth);
        currentContext.addZuulRequestHeader("Authorization", authHeader);
        return null;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章