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

前言

在上篇中介紹了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

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