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

前言

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

SpringCloud Zuul Filter

介紹

過濾器概述git

Zuul的中心是一系列過濾器,可以在HTTP請求和響應的路由過程當中執行一系列操做。github

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

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

過濾器目前用Groovy編寫,儘管Zuul支持任何基於JVM的語言。每一個Filter的源代碼都寫入Zuul服務器上的一組指定目錄,這些目錄會按期輪詢更改。更新的過濾器從磁盤讀取,動態編譯到正在運行的服務器中,並由Zuul爲每一個後續請求調用。json

過濾器類型與請求生命週期瀏覽器

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

  • PRE:這種過濾器在請求被路由以前調用。咱們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。
  • ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST:這種過濾器在路由到微服務之後執行。這種過濾器可用來爲響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
  • ERROR:在其餘階段發生錯誤時執行該過濾器。

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

在這裏插入圖片描述

:此段來之Netflix / zuul的官網Wiki,地址:github.com/Netflix/zuu…服務器

開發準備

開發環境app

  • 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項目中進行找到,地址:github.com/xuwujing/sp…

自定義異常回退處理

在以前的關於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服務暫時不可用!

在這裏插入圖片描述

能夠看到此次測試也符合咱們的預期,達成了 自定義異常回退處理的處理。這裏也順便說下,自定義該服務的異常和自定義異常回退處理最好不要在同一個服務同時使用,若是同時使用,會優先進行自定義異常回退處理的處理。

其餘

參考: github.com/Netflix/zuu… cloud.spring.io/spring-clou… www.itmuch.com/spring-clou… blog.lqdev.cn/2018/10/17/…

項目地址

基於SpringBoot2.x、SpringCloud的Finchley版本開發的地址:github.com/xuwujing/sp…

若是感受項目不錯,但願能給個star,謝謝!

springcloud系列博客:

音樂推薦

原創不易,若是感受不錯,但願留言推薦!您的支持是我寫做的最大動力! 版權聲明: 做者:虛無境 博客園出處:www.cnblogs.com/xuwujing CSDN出處:blog.csdn.net/qazwsxpcm     我的博客出處:www.panchengming.com

相關文章
相關標籤/搜索