簡單介紹了關於
Zuul
的一些簡單使用以及一些路由規則的簡單說明。而對於一個統一網關而言,須要處理各類各種的請求,對不一樣的url進行攔截,或者對調用服務的異常進行二次處理等等。今天,咱們就來了解下這方面的相關知識點。html
開始實踐前,咱們先來了解下Zuul
默認的過濾器(注意,這裏講解的Zuul
都是1.X
版本的)。上一章節,也提到了Zuul
的核心就是一系列過濾器。如今咱們來看看Zuul
的過濾器相關信息。java
Zuul
中定義了四種標準過濾器類型,這些過濾器類型對應於請求的典型生命週期。git
Apache HttpClient
或Netfilx Ribbon
請求微服務。routing
和error
過濾器以後被調用。這種過濾器可用來爲響應添加標準的HTTP Header
、收集統計信息和指標、將響應從微服務發送給客戶端等。如今看下官網wiki提供的四種過濾器的生命週期圖。github
一個請求會先按順序經過全部的前置過濾器,以後在路由過濾器中轉發給後端應用,獲得響應後又會經過全部的後置過濾器,最後響應給客戶端。在整個流程中若是發生了異常則會跳轉到錯誤過濾器中。web
通常來講,若是須要在請求到達後端應用前就進行處理的話,會選擇pre(前置過濾器)
,例如鑑權、請求轉發、增長請求參數等行爲。在請求完成後須要處理的操做放在(post)後置過濾器
中完成,例如統計返回值和調用時間、記錄日誌、增長跨域頭等行爲。路由過濾器通常只須要選擇 Zuul 中內置的便可,錯誤過濾器通常只須要一個,這樣能夠在遇到錯誤邏輯時直接拋出異常中斷流程,並直接統一處理返回結果spring
說下error
過濾器:pre
、routing
的任意一個階段若是拋異常了,則執行error
過濾器,而後再執行post
給出響應。而post
異常了,就直接調用error
了。json
知道了過濾器的定義,咱們看看過濾器是怎麼被定義的。查看類com.netflix.zuul.ZuulFilter
類,可知其個抽象類: 如下爲須要實現的方法,其餘具體的可自行查閱下後端
//過濾器類型 String filterType(); //執行順序 越小越先執行 int filterOrder(); //是否執行 返回false 不執行此過濾器 boolean shouldFilter(); //過濾器執行邏輯 Object run();
具體說明下:api
boolean
類型來判斷該過濾器是否要執行。咱們能夠經過此方法來指定過濾器的有效範圍。因此,瞭解了過濾器抽象類的定義,自定義抽象類就簡單了。跨域
經過IDE
咱們來看下已經實現ZuulFilter
的過濾器類。具體的類在:
看看已經提供的過濾器:
能夠看見,Spring cloud zuul
提供了不少過濾器,基本上就開箱即用了。簡單說明下:
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
pre | 1 | DebugFilter | 標記調試標誌 |
pre | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
error | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
組件實現的過濾器,知足執行條件時都是會執行的,若咱們想禁用某個過濾器時,能夠在配置文件中配置。 規則:zuul.<SimpleClassName>.<filterType>.disable=true
說明:SimpleClassName爲類名,filterType過濾器類型
#禁用DebugFilter過濾器 zuul.DebugFilter.pre.disable=true
爲了區分不混淆,建立一個新的項目進行示例:spring-cloud-zuul-advanced
。 對於通用部分,如pom依賴等都是和項目spring-cloud-zuul
同樣的,不同的會具體指出的。你們可查看《第九章:路由網關(Zuul)的使用》,這裏就不重複貼了。
經過以上幾個小節的說明,咱們經過繼承ZuulFilter
類進行自定義過濾器的編寫。這裏直接校驗請求的參數是否帶有token
,若無此參數時,直接進行請求攔截。
/** * 自定義過濾器-校驗請求參數是否合法:包含token參數 * @author oKong * */ @Slf4j public class AccessZuulFilter extends ZuulFilter{ @Override public boolean shouldFilter() { //此方法能夠根據請求的url進行判斷是否須要攔截 return true; } @Override public Object run() throws ZuulException { //獲取請求的上下文類 注意是:com.netflix.zuul.context包下的 RequestContext ctx = RequestContext.getCurrentContext(); //獲取request對象 HttpServletRequest request = ctx.getRequest(); //避免中文亂碼 ctx.addZuulResponseHeader("Content-type", "text/json;charset=UTF-8"); ctx.getResponse().setCharacterEncoding("UTF-8"); //打印日誌 log.info("請求方式:{},地址:{}", request.getMethod(),request.getRequestURI()); String token = request.getParameter("token"); if(StringUtils.isBlank(token)) { //使其不進行轉發 自定義route類型時,在shouldFilter中也須要進行此參數判斷。 ctx.setSendZuulResponse(false); ctx.setResponseBody("{\"code\":\"999500\",\"msg\":\"非法訪問\"}"); ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());//401 //或者添加一個額外參數也能夠 傳遞參數可使用 // ctx.set("checkAuth",false); } //這返回值沒啥用 return null; } @Override public String filterType() { //前置過濾器 return PRE_TYPE; } @Override public int filterOrder() { //執行順序 0 靠前執行 在spring cloud zuul提供的pre過濾器以後執行,默認的是小於0的。 //除了參數校驗類的過濾器 通常上直接放在 PreDecoration前 //即:PRE_DECORATION_FILTER_ORDER - 1; //常量類都在:org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 下 return 0; } }
同時在啓動類中使用@Bean
標記,使其生效。
@Bean public AccessZuulFilter accessZuulFilter() { return new AccessZuulFilter(); }
注意:Spring cloud
爲咱們提供了常量類:org.springframework.cloud.netflix.zuul.filters.support.FilterConstants
靜態引入對於的常量便可。裏面包含了各過濾器的執行順序值、過濾器類型常量以及一些頭部參數或者變量參數名:請求服務ID
、請求URI
等。這些參數都是頗有用的,好比請求服務ID
,若爲空,則直接使用SimpleHostRoutingFilter
進行請求轉發,不然是RibbonRoutingFilter
進行服務轉發。這些變量都是經過PreDecorationFilter
前置過濾器進行賦值處理的。
啓動應用,訪問:http://127.0.0.1:8889/myapi/hello?name=oKong 能夠看見,請求被攔截了,返回了非法訪問提示。
接着,咱們請求參數帶上token
:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,能夠看見請求被正常轉發了。
從目前的文件中,咱們能夠知曉:目前能夠經過serviceId
、url
進行請求轉發,根據PreDecorationFilter
前置過濾器鑑別不一樣的類型,最後經過ribbon
或者常規的http
訪問目標服務。在訪問目標服務,發生異常是在正常不過的了。從第一小節咱們能夠獲悉,當過濾器發生異常時,會調用error
過濾器進行異常信息處理,默認狀況下就是:SendErrorFilter
。首先,咱們看看,默認狀況下,以上兩種異常是如何進行異常信息展示的。
首先,咱們spring-cloud-eureka-client
服務中止了,以後訪問下:http://127.0.0.1:8889/eureka/hello?name=oKong&token=okong ,能夠看見返回的就是正常boot
默認異常,即:/error
頁面。
接着,訪問下:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,相同的都是跳轉至/error
頁面。
能夠發現,第二種錯誤信息更加直觀也更有用,能夠獲悉是服務不可用形成的。
如今,咱們來看看,SendErrorFilter
類的run
方法。
能夠獲悉,其主要的生效條件是包含異常對象:throwable
,而第二個條件只是爲了不二次執行。爲了瞭解下其調用關係,咱們查看下com.netflix.zuul.http.ZuulServlet
類的service
方法,這個類它定義了Zuul處理外部請求過程時,各個類型過濾器的執行邏輯。
以上截圖了此類的service
方法,能夠看見,每調用一個過濾器類型時,外部都是用try..catch
包裹了,異常發生時都調用了error
方法,如今咱們看看error()
方法。
能夠看見,當一個觸發器發生異常時,統一設置了異常對象throwable
,然後去調用error
類型的過濾器。
針對網關本身的api
接口時,和普通的web
應用是同樣的了。也是跳轉至/error
上,此時可使用@ControllerAdvice
進行統一異常處理。關於統一異常的處理,能夠查看《SpringBoot | 第八章:統一異常、數據校驗處理》,這裏就不闡述了。
經過前一章節,咱們值得能夠經過註冊中心的服務ID進行自動轉發,當遠程服務不可用時,咱們能夠經過Hystrix
進行服務回退處理。官網文檔也說明了,只需實現FallbackProvider
接口類便可。
建立一個服務eureka-client
的異常回退類:myEurekaClientFallback
。
/** * 服務 eureka-client 的異常退回處理類 * @author oKong */ public class MyEurekaClientFallback implements FallbackProvider { @Override public String getRoute() { // TODO Auto-generated method stub return "eureka-client"; } @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) { 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 { //可替換成相應的json串的 看業務規定了 return new ByteArrayInputStream("{\"code\":\"999999\",\"msg\":\"服務暫時不可用\"}".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
同時在啓動類中使用@Bean
標記,使其生效。
@Bean public MyEurekaClientFallback eurekaClientFallback() { return new MyEurekaClientFallback(); }
此時,咱們中止spring-cloud-eureka-client
服務,訪問:http://127.0.0.1:8889/eureka/hello?name=oKong&token=okong ,能夠看見看見已經正確返回錯誤信息了。
另外,須要細化異常的,可對fallbackResponse
的Throwable
進行異常判斷的,以獲取具體的異常信息,如超時、處理異常等等。並且,設置了服務回退,此時對於route
過濾器而言是正常調用,未發生異常,因此也就不會調用error
過濾器了。
當使用Ribbon
進行服務調用時,咱們可使用FallbackProvider
進行調用,而當咱們常規的使用url
進行轉發時,咱們也應該進行異常結果處理,以保持返回值一致。已經知道,發生異常時,會調用SendErrorFilter
異常過濾器,對異常常常處理,同時重定向至/error
中,因此,通常上咱們能夠自定義ErrorController類或者參照SendErrorFilter進行二次開發,對返回值進行個性化處理便可。這裏簡單演示下經過自定義異常過濾器
進行異常處理。
/** * 自定義異常類 過濾器 直接擴展 SendErrorFilter 類 * @author oKong * */ @Slf4j public class CustomErrorFilter extends SendErrorFilter{ @Override public Object run() { //重寫 run方法 try{ RequestContext ctx = RequestContext.getCurrentContext(); //直接複用異常處理類 ExceptionHolder exception = findZuulException(ctx.getThrowable()); log.info("異常信息:{}", exception.getThrowable()); //這裏可對不一樣異常返回不一樣的錯誤碼 HttpServletResponse response = ctx.getResponse(); response.getOutputStream().write(("{\"code\":\"999999\",\"msg\":\"" + exception.getErrorCause() + "\"}").getBytes()); }catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; } }
同時,禁用SendErrorFilter
過濾器。
## 停用默認的異常處理器SendErrorFilter zuul.SendErrorFilter.error.disable=true
在啓動類,使用@Bean
生效自定義過濾器。
@Bean @ConditionalOnProperty(name="zuul.SendErrorFilter.error.disable") public CustomErrorFilter customErrorFilter() { return new CustomErrorFilter(); }
重啓應用,訪問:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,能夠看見已是按自定義返回值返回了。
另外注意的是,前面也有提到,當訪問不存在的路徑或者轉發路徑時,依舊是普通的異常,可經過統一異常進行攔截,返回值拼裝的。
本章節主要介紹了關於
Zuul
過濾器和相關異常處理的相關知識點。可能仍是存在不完整的狀況,你們在碰見相關問題時,可查閱下官方文檔的。Zuul
自己還有一些其餘的高級功能的,本人也用的很少,相關配置也是看了官方文檔時才知道如何配置和使用的。因此,不知道相關配置時,能夠去查閱下相關文檔,好比一些忽略頭部信息、忽略服務等等配置,都未涉及。主要仍是用的很少。。原來咱們都是自建一個restful
服務進行統一網關調用的,當頻繁修改api時此方法就有點麻煩須要屢次變更了。主要看業務需求吧,這東西可大可小的。最簡單固然建立個簡單的web
就好了,而當須要實現一些高級功能,好比灰度發佈,動態引流時可能就須要考慮下使用Zuul
或者gateway
。有時間去看看gateway
,聽說性能好呀。關於網關的暫時就告一段落了,接下來會分享一些服務之間調用異常處理的,敬請期待~
目前互聯網上大佬都有分享
SpringCloud
系列教程,內容可能會相似,望多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有錯誤之處,還望提出,謝謝。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
源碼示例:https://github.com/xie19900123/spring-cloud-learning
原文地址:http://blog.lqdev.cn/2018/10/17/SpringCloud/chapter-ten/