Spring Cloud(七):服務網關zuul過濾器


上文介紹了Zuul的基本使用與路由功能,本文接着介紹Zuul的核心概念 —— Zuul過濾器(filter)。

Zuul的功能基本通過Zuul過濾器來實現(類比於Struts的攔截器,只是Struts攔截器用到責任鏈模式,Zuul則是通過FilterProcessor來控制執行),在不同的階段,通過不同類型的過濾器來實現相應的功能。

Zuul過濾器

過濾器類型

zuul的過濾器根據對HTTP請求的不同處理階段包括如下四種類型

  • pre :在請求轉發到后端目標服務之前執行,一般用於請求認證、確定路由地址、日志記錄等
  • route :轉發請求,使用Apache HttpClient 或 Ribbon來構造對目標服務的請求
  • post :在目標服務返回結果后對結果進行處理,比如添加響應頭、收集統計性能數據等
  • error :在請求處理的整個流程中如果出現錯誤,則會觸發error過濾器執行,對錯誤進行處理

客戶端請求經過zuul過濾器處理的流程如下圖

zuul-filter

zuul使用RequestContext來在過濾器之間傳遞數據,數據存於每個request的ThreadLocal,包含請求路由到哪里,錯誤,HttpServletRequest,HttpServletResponse 等這些數據都存儲於RequestContext中。RequestContext 擴展了ConcurrentHashMap,所以我們可以根據需要將信息存於context中進行傳遞。

@EnableZuulProxy vs @EnableZuulServer

zuul提供了兩個注解 @EnableZuulProxy, @EnableZuulServer,來啟用不同的過濾器集合。@EnableZuulProxy 啟用的過濾器 是@EnableZuulServer 的超集, 它包含了@EnableZuulServer 的所有過濾器,proxy主要多了一些提供路由功能的過濾器(可見@EnableZuulServer 不提供路由功能,作為server模式而不是代理模式運行)

@EnableZuulServer 注解啟用的過濾器包括

filter類型 實現類 filter順序值 功能說明
pre ServletDetectionFilter -3 檢測請求是否通過Spring Dispatcher,並在RequestContext 中添加一個key為isDispatcherServletRequest, 值為true(不通過則為false)的屬性
pre FormBodyWrapperFilter -1 解析Form data,為請求的下游進行重新編碼
pre DebugFilter 1 如果請求參數設置了debug,則會將RequestContext.setDebugRouting() ,RequestContext.setDebugRequest() 設置為ture
route SendForwardFilter 500 使用RequestDispatch servlet來轉發請求,轉發地址存於RequestContext中key為FilterConstants.FORWARD_TO_KEY的屬性中,對於轉發到當前應用的接口比較有用
post SendResponseFilter 1000 將代理請求的響應內容寫到當前的響應中
error SendErrorFilter 0 如果RequestContext.getThrowable() 不為空,則會轉發到/error,可以通過error.path來改變默認的轉發路徑/error

@EnableZuulProxy 除了上面的過濾器,還包含如下過濾器

filter類型 實現類 filter順序值 功能說明
pre PreDecorationFilter 5 確定路由到哪里,如何路由,依賴提供的RouteLocator,同時也為下游請求設置多個與proxy相關的header
route RibbonRoutingFilter 10 使用ribbon,hystrix,以及內嵌的http client來發送請求,可在RequestContext中通過FilterConstants.SERVICE_ID_KEY 來找到路由Service的ID
route SimpleHostRoutingFilter 100 使用Apache httpClient來發送請求到一個預先確定的url,可通過RequestContext.getRouteHost()來獲取urls

由上可見@EnableZuulServer 注解並不包含往后端服務負載均衡地路由請求的代理功能,@EnableZuulProxy的PreDecorationFilter,RibbonRoutingFilter過濾器才能擔當此任。PreDecorationFilter通過提供的DiscoveryClientRouteLocator 從 DiscoveryClient(如Eureka)與屬性文件中加載路由定義, 為每個serviceId創建一個route,新服務添加進來,路由也會動態刷新。路由確定了,在RibbonRoutingFilter 中通過ribbon與hystrix結合來向后端目標服務發起請求,並進行負載均衡。過濾器的順序值表示在同類型過濾器中的執行順序,值越小越先執行。

自定義Zuul過濾器

自定義的zuul過濾器與框架自帶過濾器類似,包括四部分

  1. 過濾器類型,包括pre, route, post
  2. 過濾器順序,定義在同類型過濾器中的執行順序,數值越小越先執行
  3. 是否執行過濾,通過一些條件判斷來確定是否執行該過濾器
  4. 過濾器執行體,定義具體執行的操作

比如我們需要在Http請求頭中設置一個值,供請求鏈路的下游環節訪問,則可以自定義一個過濾器如下,

@Component
public class ReqIdPreFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; //在PreDecorationFilter過濾器之前執行
    }

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

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader("reqId", UUID.randomUUID().toString());
        return null;
    }
}

在請求的后續環節,比如后端服務的filter或接口中,則可直接從HttpServletRequest 獲取該header值,如

@GetMapping("hello/reqId")
public String getReqId(HttpServletRequest request) {
    return "hello-service返回:" + request.getHeader("reqId");
}

Zuul的錯誤處理

在zuul過濾器的生命周期中,如果任何一個環節拋出異常,則error過濾器會被執行,SendErrorFilter只有當RequestContext.getThrowable()不為null時才會運行,會設置javax.servlet.error.* 屬性到request中,然后將請求轉發到spring boot的error page, 默認為BasicErrorController實現的/error接口。 有時候我們需要將返回響應格式進行統一,而默認的/error接口實現可能不滿足要求,則可以自定義/error接口。需要實現ErrorController 接口以使默認的BasicErrorController 失效。

@RestController
public class ZuulErrorController implements ErrorController {

    @RequestMapping("/error")
    public Map<String, String> error(HttpServletRequest request){
        Map<String, String> result = Maps.newHashMap();
        result.put("code", request.getAttribute("javax.servlet.error.status_code").toString());
        result.put("message", request.getAttribute("javax.servlet.error.message").toString());
        result.put("exception", request.getAttribute("javax.servlet.error.exception").toString());
        return result;
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

Zuul的服務降級

當調用服務出現超時或異常時,在zuul側可提供回調進行服務降級,返回默認響應結果,如

@Component
public class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return null; //指定這個回調針對的route Id,如果對所有route,則返回* 或null
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        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 {
                Map<String, String> result = Maps.newLinkedHashMap();
                result.put("code", "" + status.value());
                String msg = HttpStatus.GATEWAY_TIMEOUT == getStatusCode() ? "請求服務超時" : "服務器內部錯誤";
                result.put("message", msg);
                return new ByteArrayInputStream(new ObjectMapper().writeValueAsString(result).getBytes());
            }
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

則當服務請求失敗時,統一返回如下格式的響應

{
    "code": "500",
    "message": "服務器內部錯誤"
}

總結

本文主要對Zuul過濾器相關內容及自定義使用進行了介紹,同時對過濾器運行過程中異常的處理及服務調用失敗的降級回調進行了簡單說明。出於篇幅,開發過程中更具體的細節我們后續再繼續探討。
認真生活,快樂分享
歡迎關注微信公眾號:空山新雨的技術空間
獲取Spring Boot,Spring Cloud,Docker等系列技術文章
公眾號二維碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM