SpringCloud學習系列之七 ----- Zuul路由閘道器的過濾器和異常處理

虛無境發表於2019-05-19

前言

在上篇中介紹了SpringCloud Zuul路由閘道器的基本使用版本,本篇則介紹基於SpringCloud(基於SpringBoot2.x,.SpringCloud Finchley版)中的路由閘道器的過濾器Filter以及異常處理的教程。

SpringCloud Zuul Filter

介紹

過濾器概述

Zuul的中心是一系列過濾器,能夠在HTTP請求和響應的路由過程中執行一系列操作。

以下是Zuul過濾器的主要特徵:

  • 型別:通常在應用過濾器時在路由流程中定義階段(儘管它可以是任何自定義字串)
  • 執行順序:在型別中應用,定義跨多個過濾器的執行順序
  • 標準:執行過濾器所需的條件
  • 操作:滿足條件時要執行的操作
  • Zuul提供了一個動態讀取,編譯和執行這些過濾器的框架。過濾器不直接相互通訊 - 而是通過RequestContext共享狀態,RequestContext對每個請求都是唯一的。

過濾器目前用Groovy編寫,儘管Zuul支援任何基於JVM的語言。每個Filter的原始碼都寫入Zuul伺服器上的一組指定目錄,這些目錄會定期輪詢更改。更新的過濾器從磁碟讀取,動態編譯到正在執行的伺服器中,並由Zuul為每個後續請求呼叫。

過濾器型別與請求生命週期

Zuul大部分功能都是通過過濾器來實現的。Zuul中定義了四種標準過濾器型別,這些過濾器型別對應於請求的典型生命週期。

  • PRE:這種過濾器在請求被路由之前呼叫。我們可利用這種過濾器實現身份驗證、在叢集中選擇請求的微服務、記錄除錯資訊等。
  • ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建傳送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等。
  • ERROR:在其他階段發生錯誤時執行該過濾器。

官網Wiki 提供的四種過濾器的生命週期圖。

在這裡插入圖片描述

:此段來之Netflix / zuul的官網Wiki,地址:https://github.com/Netflix/zuul/wiki/How-it-Works。

開發準備

開發環境

  • JDK:1.8
  • SpringBoot:2.0.6.RELEASE
  • SpringCloud:Finchley.SR2

注:不一定非要用上述的版本,可以根據情況進行相應的調整。需要注意的是SpringBoot2.x以後,jdk的版本必須是1.8以上!

服務端

由於在上一篇中我們已經完成了Zuul路由閘道器的基本功能實現,所以服務端這塊我們可以直接把之前的專案拿來直接使用,然後更改相應的名稱以及相關程式碼即可。

自定義過濾器

這裡我們來編寫自定義一個Filter實現類,看看該類是如何工作的。
在編寫該類的時候,發現自定義的Filter類需要繼承ZuulFilter這個類,我們檢視該類的原始碼,發現了該類定義了兩個抽象的方法,並且該類實現了IZuulFilter該介面,該介面也定義了兩個方法,我們就來看看這幾個方法到底是幹嘛的吧。

ZuulFilter原始碼:

在這裡插入圖片描述

filterType這個方法表示按型別對過濾器進行分類,分別是pre、post、route和error,在FilterConstants這個常量類中已經進行定義了,其意義在上述的Filter請求的典型生命週期已經進行過說明了。
filterOrder 這個方法表示Filter執行的順序,數值越小優先順序越高。

IZuulFilter

在這裡插入圖片描述

shouldFilter該方法表示是否執行該過濾器,也可以說是該過濾器的一個開關。
run過濾器的具體邏輯。在該函式中,我們可以實現自定義的過濾邏輯,來確定是否要攔截當前的請求,不對其進行後續的路由,或是在請求路由返回結果之後,對處理結果做一些加工等。

看完上述的原始碼之後,這裡我們再來編寫自定的Filter程式碼。
首先繼承ZuulFilter該類,然後實現裡面的方法。
首先是shouldFilter方法,這裡我們就直接返回true;
然後是filterType,這裡我們就設定為前置過濾器,在請求被路由之前呼叫。
繼而是filterOrder,這裡我們就設定0;
最後是run,這是過濾器的核心業務程式碼,這裡我們就簡單一點,獲取請求的url,如果該url攜帶了token,我們就讓他通過,否則直接攔截。
當然,我們需要將該過濾類使用Bean註解使其生效。
那麼程式碼如下:

自定義的Filter程式碼:

@Component
public class MyZuulFilter extends ZuulFilter{

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

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        ctx.addZuulResponseHeader("Content-type", "text/json;charset=UTF-8");
        ctx.getResponse().setCharacterEncoding("UTF-8");
        System.out.println("請求地址:"+request.getRequestURI());
        String token = request.getParameter("token");
        String msg="請求成功!";
        if(token==null) {
           ctx.setSendZuulResponse(false);
           msg="請求失敗!";
           ctx.setResponseBody(msg);
           ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }   
        return msg;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }
    
    @Bean
    public MyZuulFilter zuulFilter() {
        return new MyZuulFilter();
    }
}

自定義異常類處理

Zuul除了可以自定義過濾器之外,也可以對異常結果進行處理,以保持返回值一致。在進行Zuul使用的時候發現了在發生了異常之後,會呼叫SendErrorFilter異常過濾器,對異常經常處理,同時重定向至/error這個路徑中。所以如果我們需要自定義對異常處理的話,繼承SendErrorFilter該類就可以實現了。我們檢視SendErrorFilter原始碼,其實也是繼承ZuulFilter該類並實現裡面的一些方法,做的自定義異常封裝,其實也可以把SendErrorFilter該類當做一個自定義的過濾器。

由於SendErrorFilter是對ZuulFilter類進行了二次封裝,所以我們自定義的Error程式碼只需繼承SendErrorFilter改成,然後實現其中的run方法即可。

自定義的Error程式碼:

@Component
public class MyErrorFilter extends SendErrorFilter{

    @Override
    public Object run() {
        String msg="請求失敗!"; 
        try{
            RequestContext ctx = RequestContext.getCurrentContext();
            ExceptionHolder exception = findZuulException(ctx.getThrowable());
            System.out.println("錯誤資訊:"+exception.getErrorCause());
            msg+="error:"+exception.getErrorCause();
            HttpServletResponse response = ctx.getResponse();
            response.setCharacterEncoding("UTF-8");
            response.getOutputStream().write(msg.getBytes());                
        }catch (Exception ex) {
            ex.printStackTrace();
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return msg;
    }
    
    @Bean
    public MyErrorFilter errorFilter() {
        return new MyErrorFilter();
    }
}

這裡我們還需要禁用SendErrorFilter過濾器,不然是不會使用我們自定的異常過濾器的。

application.properties 新增如下配置:

zuul.SendErrorFilter.error.disable=true

這裡順便說下禁用過濾器的規則。元件實現的過濾器,滿足執行條件時都是會執行的,若我們想禁用某個過濾器時,可以在配置檔案中配置。
規則:

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

說明:

SimpleClassName為類名,filterType過濾器型別

當然,如果覺得上述的異常處理還是不夠優雅的話,可以使用ControllerAdvice註解進行全域性異常處理,該註解的使用示例可以從個人的springboot專案中進行找到,地址:https://github.com/xuwujing/springBoot-study

自定義異常回退處理

在之前的關於springcloud中SpringCloud學習系列之三----- 斷路器(Hystrix)和斷路器監控(Dashboard)這篇文章中講解過服務的降級處理,其實這裡的處理也是類似,也就是某個服務無法進行訪問的時候,進行回退處理。
這裡我們自定義異常回退處理的程式碼相對而已也比較簡單,只需實現FallbackProvider該介面的方法既可。

該類的原始碼如下:
在這裡插入圖片描述

getRoute該方法主要是指定需要回退服務的名稱。
fallbackResponse該方法提供基於執行失敗原因並進行回退響應。

瞭解之後該原始碼之後,我們再來編寫一個自定義異常回退處理的類。

自定義的Fallback程式碼:

    
@Component
public class MyFallback implements FallbackProvider {
    private static  final  String SERVER_NAME="springcloud-zuul-filter-server2";

    @Override
    public String getRoute() {
        return SERVER_NAME;
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {

        //標記不同的異常為不同的http狀態值
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            //可繼續新增自定義異常類
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    //處理
    private ClientHttpResponse response(final HttpStatus status) {
        String msg="該"+SERVER_NAME+"服務暫時不可用!";
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return status;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return status.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return status.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(msg.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }

    @Bean
    public MyFallback eurekaClientFallback() {
        return new MyFallback();
    }   
}

客戶端

客戶端這邊,我們可以把之前springcloud-zuul專案中的springcloud-zuul-server1springcloud-zuul-server2拿來進行使用既可。

測試

完成上述的程式碼開發後,我們來進行springcloud-zuul的一系列自定義過濾測試。
首先依次啟動springcloud-zuul-filter-eurekaspringcloud-zuul-filter-gatewayspringcloud-zuul-filter-server1springcloud-zuul-filter-server2這四個專案。其中9009是服務端springcloud-zuul-filter-gatewayr的埠,9010是第一個客戶端springcloud-zuul-filter-server1的埠,9011是第二個客戶端springcloud-zuul-filter-server2的埠。

這裡順便說下路由閘道器的預設規則:http://ZUUL_HOST:ZUUL_PORT/微服務例項名(serverId)/** ,轉發至serviceId對應的微服務。比如在瀏覽器輸入:http://localhost:9009/springcloud-zuul-filter-server1/hello地址, 它就會跳轉訪問到:http://localhost:9010/hello/這個地址上。使用這個方式進行測試可以幫助我們更好的瞭解本篇文章的實現目的。

自定義過濾器功能測試

完成上述的專案啟動成功之後。

我們首先在瀏覽器上輸入:

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm

介面返回:

請求失敗!

在這裡插入圖片描述

這裡看到直接進行攔截了,並返回了相應的資訊、

加上token之後在進行訪問

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123

介面返回:

pancm,Hello World!

在這裡插入圖片描述

我們按照我們自定的規則進行訪問之後,發現可以直接訪問到我們想要訪問的服務上,因此該次測試也符合我們的預期,達成了自定義過濾器的處理。

自定義異常類處理功能測試

上述的功能測試ok之後,這裡我們停止掉springcloud-zuul-filter-server1服務,然後在瀏覽器上輸入:

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123

介面返回:

請求失敗!error:GENERAL請求失敗!error:GENERAL

在這裡插入圖片描述
注: 這裡實際是呼叫了兩次。

可以看到這次測試也符合我們的預期,達成了自定義異常的處理。

自定義異常回退處理功能測試

這裡我們再來停止掉springcloud-zuul-filter-server2服務,然後在瀏覽器上輸入:

http://localhost:9009/springcloud-zuul-filter-server2/hi?name=pancm&token=123

介面返回:

該springcloud-zuul-filter-server2服務暫時不可用!

在這裡插入圖片描述

可以看到這次測試也符合我們的預期,達成了 自定義異常回退處理的處理。這裡也順便說下,自定義該服務的異常和自定義異常回退處理最好不要在同一個服務同時使用,如果同時使用,會優先進行自定義異常回退處理的處理。

其他

參考:
https://github.com/Netflix/zuul/wiki/How-it-Works
https://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#_router_and_filter_zuul
http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter/
https://blog.lqdev.cn/2018/10/17/SpringCloud/chapter-ten/#參考資料

專案地址

基於SpringBoot2.x、SpringCloud的Finchley版本開發的地址:https://github.com/xuwujing/springcloud-study

如果感覺專案不錯,希望能給個star,謝謝!

springcloud系列部落格:

音樂推薦

原創不易,如果感覺不錯,希望留言推薦!您的支援是我寫作的最大動力!
版權宣告:
作者:虛無境
部落格園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm    
個人部落格出處:http://www.panchengming.com

相關文章