前言
在上篇中介紹了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-server1
和
springcloud-zuul-server2
拿來進行使用既可。
測試
完成上述的代碼開發後,我們來進行
springcloud-zuul
的一系列自定義過濾測試。
首先依次啟動
springcloud-zuul-filter-eureka
、
springcloud-zuul-filter-gateway
springcloud-zuul-filter-server1
springcloud-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
服務,然後在浏覽器上輸入:
請求失敗!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系列部落格:
- SpringCloud學習系列之一 ----- 搭建一個高可用的注冊中心(Eureka)
- SpringCloud學習系列之二 ----- 服務消費者(Feign)和負載均衡(Ribbon)
- SpringCloud學習系列之三----- 斷路器(Hystrix)和斷路器監控(Dashboard)
- SpringCloud學習系列之四-----配置中心(Config)使用詳解)
- SpringCloud學習系列之五-----配置中心(Config)和消息總線(Bus)完美使用版)
- SpringCloud學習系列之六 ----- 路由網關Zuul基礎使用教程)
音樂推薦
原創不易,如果感覺不錯,希望留言推薦!您的支援是我寫作的最大動力!
版權聲明:
作者:虛無境
部落格園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm
個人部落格出處:http://www.panchengming.com
如果你對生活感覺到了絕望,請不要氣餒。因為這樣隻會讓你更加絕望!
所謂的希望往往都是在絕望中萌發的,是以,請不要放棄希望!