1. 程式人生 > >springCloud學習05之api網關服務zuul過濾器filter

springCloud學習05之api網關服務zuul過濾器filter

main 決定 添加 dap 環境 ribbon type() 由於 send

前面學習了zuul的反向代理、負載均衡、fallback回退。這張學習寫過濾器filter,做java web開發的對filter都不陌生,那就是客戶端(如瀏覽器)發起請求的時候,都先經過過濾器filter做一些相關的校驗或業務判斷(如登錄、權限等),zuul也同樣提供了過濾器功能。只要繼承ZuulFilter類即可。

通過前文的介紹,我們對於Zuul的第一印象通常是這樣的:它包含了對請求的路由和過濾兩個功能,其中路由功能負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入口的基礎;而過濾器功能則負責對請求的處理過程進行幹預,是實現請求校驗、服務聚合等功能的基礎。

然而實際上,路由功能在真正運行時,它的路由映射和請求轉發都是由幾個不同的過濾器完成的。其中,路由映射主要通過pre類型的過濾器完成,它將請求路徑與配置的路由規則進行匹配,以找到需要轉發的目標地址;而請求轉發的部分則是由route類型的過濾器來完成,對pre類型過濾器獲得的路由地址進行轉發。

所以,過濾器可以說是Zuul實現API網關功能最為核心的部件,每一個進入Zuul的HTTP請求都會經過一系列的過濾器處理鏈得到請求響應並返回給客戶端。

繼承ZuulFilter.java,需要實現一些方法

package com.fei.springcloud.filter;

import com.netflix.zuul.ZuulFilter;

public class TestFilter extends ZuulFilter{

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

@Override
public Object run() {
return null;
}

@Override
public String filterType() {
return null;
}

@Override
public int filterOrder() {
return 0;
}

}

它們各自的含義與功能總結如下:

filterType:該函數需要返回一個字符串來代表過濾器的類型,而這個類型就是在HTTP請求過程中定義的各個階段。在Zuul中默認定義了四種不同生命周期的過濾器類型,具體如下:
pre:可以在請求被路由之前調用。
routing:在路由請求時候被調用。
post:在routing和error過濾器之後被調用。
error:處理請求時發生錯誤時被調用。
filterOrder:通過int值來定義過濾器的執行順序,數值越小優先級越高。
shouldFilter:返回一個boolean類型來判斷該過濾器是否要執行。我們可以通過此方法來指定過濾器的有效範圍。
run:過濾器的具體邏輯。在該函數中,我們可以實現自定義的過濾邏輯,來確定是否要攔截當前的請求,不對其進行後續的路由,或是在請求路由返回結果之後,對處理結果做一些加工等。

從上圖中,我們可以看到,當外部HTTP請求到達API網關服務的時候,首先它會進入第一個階段pre,在這裏它會被pre類型的過濾器進行處理,該類型的過濾器主要目的是在進行請求路由之前做一些前置加工,比如請求的校驗等。

在完成了pre類型的過濾器處理之後,請求進入第二個階段routing,也就是之前說的路由請求轉發階段,請求將會被routing類型過濾器處理,這裏的具體處理內容就是將外部請求轉發到具體服務實例上去的過程,當服務實例將請求結果都返回之後,routing階段完成,請求進入第三個階段post,此時請求將會被post類型的過濾器進行處理,這些過濾器在處理的時候不僅可以獲取到請求信息,還能獲取到服務實例的返回信息,所以在post類型的過濾器中,我們可以對處理結果進行一些加工或轉換等內容。

另外,還有一個特殊的階段error,該階段只有在上述三個階段中發生異常的時候才會觸發,但是它的最後流向還是post類型的過濾器,因為它需要通過post過濾器將最終結果返回給請求客戶端(實際實現上還有一些差別)

Zuul自帶的核心過濾器

在spring-cloud-netflix-core-XXX.jar架包下

如上圖所示,在默認啟用的過濾器中包含了三種不同生命周期的過濾器,這些過濾器都非常重要,可以幫助我們理解Zuul對外部請求處理的過程,以及幫助我們如何在此基礎上擴展過濾器去完成自身系統需要的功能。下面,我們將逐個地對這些過濾器做一些詳細的介紹:

pre過濾器
ServletDetectionFilter:它的執行順序為-3,是最先被執行的過濾器。該過濾器總是會被執行,主要用來檢測當前請求是通過Spring的DispatcherServlet處理運行,還是通過ZuulServlet來處理運行的。

它的檢測結果會以布爾類型保存在當前請求上下文的isDispatcherServletRequest參數中,這樣在後續的過濾器中,我們就可以通過RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判斷它以實現做不同的處理。

一般情況下,發送到API網關的外部請求都會被Spring的DispatcherServlet處理,除了通過/zuul/路徑訪問的請求會繞過DispatcherServlet,被ZuulServlet處理,主要用來應對處理大文件上傳的情況。另外,對於ZuulServlet的訪問路徑/zuul/,我們可以通過zuul.servletPath參數來進行修改。

Servlet30WrapperFilter:它的執行順序為-2,是第二個執行的過濾器。目前的實現會對所有請求生效,主要為了將原始的HttpServletRequest包裝成Servlet30RequestWrapper對象。
FormBodyWrapperFilter:它的執行順序為-1,是第三個執行的過濾器。

該過濾器僅對兩種類請求生效,第一類是Content-Type為application/x-www-form-urlencoded的請求,第二類是Content-Type為multipart/form-data並且是由Spring的DispatcherServlet處理的請求(用到了ServletDetectionFilter的處理結果)。

而該過濾器的主要目的是將符合要求的請求體包裝成FormBodyRequestWrapper對象。
DebugFilter:它的執行順序為1,是第四個執行的過濾器。

該過濾器會根據配置參數zuul.debug.request和請求中的debug參數來決定是否執行過濾器中的操作。而它的具體操作內容則是將當前的請求上下文中的debugRouting和debugRequest參數設置為true。

由於在同一個請求的不同生命周期中,都可以訪問到這兩個值,所以我們在後續的各個過濾器中可以利用這兩值來定義一些debug信息,這樣當線上環境出現問題的時候,可以通過請求參數的方式來激活這些debug信息以幫助分析問題。

另外,對於請求參數中的debug參數,我們也可以通過zuul.debug.parameter來進行自定義。
PreDecorationFilter:它的執行順序為5,是pre階段最後被執行的過濾器。該過濾器會判斷當前請求上下文中是否存在forward.to和serviceId參數,如果都不存在,那麽它就會執行具體過濾器的操作(如果有一個存在的話,說明當前請求已經被處理過了,因為這兩個信息就是根據當前請求的路由信息加載進來的)。

而它的具體操作內容就是為當前請求做一些預處理,比如:進行路由規則的匹配、在請求上下文中設置該請求的基本信息以及將路由匹配結果等一些設置信息等,這些信息將是後續過濾器進行處理的重要依據,我們可以通過RequestContext.getCurrentContext()來訪問這些信息。

另外,我們還可以在該實現中找到一些對HTTP頭請求進行處理的邏輯,其中包含了一些耳熟能詳的頭域,比如:X-Forwarded-Host、X-Forwarded-Port。

另外,對於這些頭域的記錄是通過zuul.addProxyHeaders參數進行控制的,而這個參數默認值為true,所以Zuul在請求跳轉時默認地會為請求增加X-Forwarded-*頭域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-Proto。

我們也可以通過設置zuul.addProxyHeaders=false關閉對這些頭域的添加動作。

route過濾器
RibbonRoutingFilter:它的執行順序為10,是route階段第一個執行的過濾器。該過濾器只對請求上下文中存在serviceId參數的請求進行處理,即只對通過serviceId配置路由規則的請求生效。而該過濾器的執行邏輯就是面向服務路由的核心,它通過使用Ribbon和Hystrix來向服務實例發起請求,並將服務實例的請求結果返回。
SimpleHostRoutingFilter:它的執行順序為100,是route階段第二個執行的過濾器。該過濾器只對請求上下文中存在routeHost參數的請求進行處理,即只對通過url配置路由規則的請求生效。而該過濾器的執行邏輯就是直接向routeHost參數的物理地址發起請求,從源碼中我們可以知道該請求是直接通過httpclient包實現的,而沒有使用Hystrix命令進行包裝,所以這類請求並沒有線程隔離和斷路器的保護。
SendForwardFilter:它的執行順序為500,是route階段第三個執行的過濾器。該過濾器只對請求上下文中存在forward.to參數的請求進行處理,即用來處理路由規則中的forward本地跳轉配置。
post過濾器
SendErrorFilter:它的執行順序為0,是post階段第一個執行的過濾器。該過濾器僅在請求上下文中包含error.status_code參數(由之前執行的過濾器設置的錯誤編碼)並且還沒有被該過濾器處理過的時候執行。而該過濾器的具體邏輯就是利用請求上下文中的錯誤信息來組織成一個forward到API網關/error錯誤端點的請求來產生錯誤響應。
SendResponseFilter:它的執行順序為1000,是post階段最後執行的過濾器。該過濾器會檢查請求上下文中是否包含請求響應相關的頭信息、響應數據流或是響應體,只有在包含它們其中一個的時候就會執行處理邏輯。而該過濾器的處理邏輯就是利用請求上下文的響應信息來組織需要發送回客戶端的響應內容。

代碼實戰,完整代碼在github上

寫2個pre類型的filter,1個post類型的filter.如果在第一個pre過濾器驗證就失敗了,則後面的過濾器不需要執行了。

TestPre01Filter.java

package com.fei.springcloud.filter;

import javax.servlet.http.HttpServletRequest;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

/**
* 第一個pre類型的filter,prefilter01=true才能通過
* @author Jfei
*
*/
public class TestPre01Filter extends ZuulFilter{

/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
return true;
}

/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {

return FilterConstants.PRE_TYPE;
}

/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {

return 1;
}
/**
* 執行業務操作,可執行sql,nosql等業務
*/
@Override
public Object run() {

RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String prefilter01 = request.getParameter("prefilter01");
System.out.println("執行pre01Filter .....prefilter01=" + prefilter01 );

//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(prefilter01) ){
ctx.setSendZuulResponse(true);//會進行路由,也就是會調用api服務提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便於後面的filter獲取使用
}else{
ctx.setSendZuulResponse(false);//不需要進行路由,也就是不會調用api服務提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便於後面的filter獲取使用
//返回內容給客戶端
ctx.setResponseBody("{\"result\":\"pre01Filter auth not correct!\"}");// 返回錯誤內容
}

return null;
}





}
這是第一個自定義的pre類型filter,說以shuldFilter()為true,也就是必須執行
TestPre02Filter.java

package com.fei.springcloud.filter;

import javax.servlet.http.HttpServletRequest;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

/**
* prefilter02 校驗 prefilter02=true才能通過
* @author Jfei
*
*/
public class TestPre02Filter extends ZuulFilter{

/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
//上一個filter設置該值
return RequestContext.getCurrentContext().getBoolean("isOK");
}

/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {

return FilterConstants.PRE_TYPE;
}

/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {

return 2;
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String prefilter02 = request.getParameter("prefilter02");
System.out.println("執行pre02Filter .....prefilter02=" + prefilter02 );

//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(prefilter02) ){
ctx.setSendZuulResponse(true);//會進行路由,也就是會調用api服務提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便於後面的filter獲取使用
}else{
ctx.setSendZuulResponse(false);//不需要進行路由,也就是不會調用api服務提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便於後面的filter獲取使用
//返回內容給客戶端
ctx.setResponseBody("{\"result\":\"pre02Filter auth not correct!\"}");// 返回錯誤內容
}


return null;
}

}

TestPostFilter.java
package com.fei.springcloud.filter;

import javax.servlet.http.HttpServletRequest;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
*
* post類型的filter,post=true才能通過
*
*/
public class TestPostFilter extends ZuulFilter{

/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
//上一個filter設置該值
return RequestContext.getCurrentContext().getBoolean("isOK");
}

/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {

return FilterConstants.ROUTE_TYPE;
}

/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {

return 1;
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String post = request.getParameter("post");
System.out.println("執行postFilter .....post=" + post );

//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(post) ){
ctx.setSendZuulResponse(true);//會進行路由,也就是會調用api服務提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便於後面的filter獲取使用
}else{
ctx.setSendZuulResponse(false);//不需要進行路由,也就是不會調用api服務提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便於後面的filter獲取使用
//返回內容給客戶端
ctx.setResponseBody("{\"result\":\"post auth not correct!\"}");// 返回錯誤內容
}


return null;
}

}

啟動類,對filter進行註冊
package com.fei.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

import com.fei.springcloud.filter.TestPre02Filter;
import com.fei.springcloud.filter.TestPostFilter;
import com.fei.springcloud.filter.TestPre01Filter;

@EnableZuulProxy
@SpringCloudApplication
public class ZuulFilterApplication {

public static void main(String[] args) {

SpringApplication.run(ZuulFilterApplication.class, args);
}

@Bean
public TestPre01Filter testPre01Filter(){
return new TestPre01Filter();
}

@Bean
public TestPre02Filter testPre02Filter(){
return new TestPre02Filter();
}


@Bean
public TestPostFilter testPostFilter(){
return new TestPostFilter();
}

}

啟動eureka-server,eureka-api(前面學習過,也有完整代碼了),然後啟動Zuul-filter,
瀏覽器請求 http://127.0.0.1:8888/user-api/user/find
http://127.0.0.1:8888/user-api/user/find?prefilter01=true

http://127.0.0.1:8888/user-api/user/find?prefilter01=true&prefilter02=true

對比頁面看到的內容和控制臺打印的日誌,會發現如果pre的校驗都通不過的時候,api微服務就沒打印了,說明沒調用api微服務,post filter也沒執行
---------------------
作者:夢_殤
來源:CSDN
原文:https://blog.csdn.net/dream_broken/article/details/77197585
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

springCloud學習05之api網關服務zuul過濾器filter