天天看點

SpringCloud API網關-Zuul

如果所有的微服務系統都對外提供服務,那麼這些微服務系統都需要實作諸如資料驗證、安全校驗、接口權限等功能,這對于系統的維護非常不利,這也就是api網關存在的原因。在SpringCloud的大家庭中,使用的是zuul元件來搭建api網關。zuul是一個能夠實作動态路由、監控、彈性擴充并且安全的API網關元件。

在之前的一篇部落格《

API網關系統架構 》中,有讨論過一個成熟的網關系統應該具備注入:統一接入、安全防護、流量管控、協定轉換、安全、高可用、高并發、友善擴充、友善運維等能力,是以這裡不過多讨論api網關應該如何設計,本文重點從源碼的角度分析Zuul的實作。

一 請求路由

路由的功能其實比較簡單,代碼層面隻需要能夠實作将使用者的請求路徑和服務提供者相對應即可,是以這一部分沒有附帶源碼,僅簡單的列舉了其主要的路由規則。

1  傳統路由方式

配置規則

zuul.routes.<路由名稱>.path=<路徑映射> 

zuul.routes.<路由名稱>.url=<服務提供者位址>

示例如下:

zuul.routes.api-a-url.path=/test/**

zuul.routes.api-a-url.url=http://localhost:8080/test

此規則表示所有/test/**請求都會使用 

http://localhost:8080/test

對應的微服務上。

2  面向服務的路由方式

配置規則:

zuul.routes.<路由名稱>.path=<路徑映射>

zuul.routes.<路由名稱>.serviceId=<服務名>

zuul.routes.api-a.path=/testa/**

zuul.routes.api-a.serviceId=testa-service

zuul.routes.api-b.path=/testb/**


zuul.routes.api-b.serviceid=testb- service

此規則表示所有/testa/**請求都會使用testa-service的微服務,所有/testb/**請求都會使用testb-service的微服務,當然這需要配合Eureka來使用。

二 過濾器

如果想讓底層的業務系統收到的請求都是經過安全驗證的,傳回結果具有統一格式等,在zuul中可以通過過濾器實作,

1  請求生命周期

1)   zuul 1.0結構

https://github.com/Netflix/zuul/wiki/How-it-Works

,如下所示:

SpringCloud API網關-Zuul

2)   zuul 2.0結構

https://github.com/Netflix/zuul/wiki/How-It-Works-2.0
SpringCloud API網關-Zuul

Inbound Filters是前置過濾器,Outbound Fileter是後置過濾器

2  過濾器

可以繼承ZuulFilter建立過濾器,示例如下:

public class TestFilter extends ZuulFilter  {

    @Override

    public String filterType() {

        //pre、post、error

        return "pre";

    }

    public int filterOrder() {

        return 0;

    public boolean shouldFilter() {

        // 需要使用此過濾器攔截,則需要傳回true

        return true;

    public Object run() {

        // 過濾的邏輯

}

filterType: 過濾器的類型,取值包括:

pre:可以在請求被路由之前調用。

routing:在路由請求時被調用。

post: 在routing和error過濾器之後被調用。

error: 處理請求時發生錯誤時被調用。

filterOrder:過濾器的執行順序。當請求在一個階段中存在多個過濾器時, 需要根據該方法傳回的值來依次執行,數字越小越先執行。

shouldFilter:判斷該過濾器是否需要被執行,可以利用該函數來指定過濾器的有效範圍。

run:過濾器的具體邏輯

3  Zuul提供的過濾器

1)   處理過程

預設情況下,zuul提供的filter會按照以下順序執行。

SpringCloud API網關-Zuul

2)   Pre過濾器

a)    ServletDetectionFilter

用來檢測目前請求是通過Spring的DispatcherServlet處理運作的,還是通過Zuu1Servlet來處理運作的。可以通過一下代碼擷取檢測結果:

RequestContext ctx = RequestContext.getCurrentContext();

boolean isDetectionRequest = ctx.get(RequestUtils.IS_DISPATCHERSERVLETREQUEST);

一般情況下,發送到API網關的外部請求都會被Spring的DispatcherServlet處理,除了通過/zuul/路徑通路的請求外,因為此種請求會被Zuu1Servlet處理,主要用來應對處理大檔案上傳的情況。可以通過zuul.servletPath參數來進行修改。

b)    Servlet30WrapperFilter

主要為了将HttpServletRequest包裝成Servlet30RequestWrapper對象。

c)    FormBodyWrapperFilter

該過濾器僅對contentType為application/x-www-form-urlencoded或者multipart/form-data的請求有效。作用是将HttpServletRequest包裝成FormBodyRequestWrapper對象。

d)    DebugFilter

根據配置參數zuul.debug.requet和請求中的debug參數來決定是否執行過濾器中的操作,其代碼如下:

@Override

public boolean shouldFilter() {

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

   if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {

      return true;

   }

   return ROUTING_DEBUG.get();

public Object run() {

   RequestContext ctx = RequestContext.getCurrentContext();

   ctx.setDebugRouting(true);

   ctx.setDebugRequest(true);

   return null;

e)    PreDecorationFilter

該過濾器會判斷目前請求上下文中是否存在forward.to和serviceId參數 ,如 果都不存在,那麼會執行過濾器的操作。

此過濾器的内容是為目前請求做一些預處理,比如,進行路由規則的比對、 在請求上下文中設定該請求的基本資訊以及将路由比對結果等一些設定資訊等,這些資訊将是後續過濾器進行處理的重要依據,我們可以通過RequestContext.getCurrentContext ()來 通路這些資訊。 

3)   Routing過濾器

a)    RibbonRoutingFilter

該過濾器隻對請求上下文中存在serviceid參數的請求進行處理,即隻對通 過service工d配置路由規則的請求生效。而該過濾器的執行邏輯就是面向服務路 由的核心,它通過使用Ribbon和Hystrix來向服務執行個體發起請求,并将服務執行個體的請求結果傳回。

b)    SimpleHostRoutingFilter

該過濾器隻對通過url配置路由規則的請求生效。而該過濾器的執行邏輯就是直接向routeHost參數的實體位址發起請求。

   RequestContext context = RequestContext.getCurrentContext();

   HttpServletRequest request = context.getRequest();

   MultiValueMap<String, String> headers = this.helper

        .buildZuulRequestHeaders(request);

   MultiValueMap<String, String> params = this.helper

        .buildZuulRequestQueryParams(request);

   String verb = getVerb(request);

   InputStream requestEntity = getRequestBody(request);

   if (request.getContentLength() < 0) {

      context.setChunkedRequestBody();

   String uri = this.helper.buildZuulRequestURI(request);

   this.helper.addIgnoredHeaders();

   try {

      HttpResponse response = forward(this.httpClient, verb, uri, request, headers,

            params, requestEntity);

      setResponse(response);

   catch (Exception ex) {

      context.set(ERROR_STATUS_CODE,

           HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

     context.set("error.exception", ex);

c)    SendForwardFilter

僅僅對forward請求有效,該過濾器隻對請求上下文中存在forward.to參數的請求進行處理。

   return ctx.containsKey("forward.to")

         && !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);

      RequestContext ctx = RequestContext.getCurrentContext();

      String path = (String) ctx.get("forward.to");

      RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);

      if (dispatcher != null) {

        ctx.set(SEND_FORWARD_FILTER_RAN, true);

         if (!ctx.getResponse().isCommitted()) {

           dispatcher.forward(ctx.getRequest(), ctx.getResponse());

           ctx.getResponse().flushBuffer();

         }

      }

     ReflectionUtils.rethrowRuntimeException(ex);

4)   post過濾器 

a)    SendErrorFilter

該過濾器僅在請求上下文中 包含error.status_code參數并且還沒有被該過濾器處理過的時候執行。該過濾器邏輯就是forward到API網關/error來影響錯誤資訊。

   // only forward to errorPath if it hasn't been forwarded to already

   return ctx.containsKey("error.status_code")

         && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);

      HttpServletRequest request = ctx.getRequest();

      int statusCode = (Integer) ctx.get("error.status_code");

     request.setAttribute("javax.servlet.error.status_code", statusCode);

      if (ctx.containsKey("error.exception")) {

         Object e = ctx.get("error.exception");

         log.warn("Error during filtering", Throwable.class.cast(e));

        request.setAttribute("javax.servlet.error.exception", e);

      if (ctx.containsKey("error.message")) {

         String message = (String) ctx.get("error.message");

        request.setAttribute("javax.servlet.error.message", message);

      RequestDispatcher dispatcher = request.getRequestDispatcher(

            this.errorPath);

         ctx.set(SEND_ERROR_FILTER_RAN, true);

            dispatcher.forward(request, ctx.getResponse());

b)    SendResponseFilter

該過濾器會檢查請求上下文中是否包含請求響應相關的頭資訊、響應資料流或是響應體,隻要包含其中一個的時候執行處理邏輯。

該過濾器的處理邏輯是利用請求上下文的響應資訊來組織需要發送回用戶端的響應内容。 

return !RequestContext.getCurrentContext().getZuulResponseHeaders().isEmpty()

         || RequestContext.getCurrentContext().getResponseDataStream() != null

         || RequestContext.getCurrentContext().getResponseBody() != null;

      addResponseHeaders();

      writeResponse();

4   禁用過濾器 

預設情況下,過濾器都是啟用狀态的。那麼有哪些方式來禁用過濾器呢?

1)   shouldFilter傳回false

        return false;

這種方式對于自定義過濾器來說,可以實作禁用的功能,但是不太友善,因為每次修改都要重新修改代碼,重新打包。

2)   配置項

Zuul中特别提供了配置項來禁用過濾器,該配置格式如下: 

zuul.<SimpleClassName>.<filterType>.disable=true

<SimpleClassName>代表過濾器的類名,< filterType >代表過濾器類型。如果自定義了一個前置過濾器類AccessFilter,當希望禁用此過濾器類時,可以這樣配置:

zuul.AccessFilter.pre.disable=true

三 參考文檔

1  部落格

zuul wiki How It Works 2.0

2  書籍

《Spring Cloud微服務實戰》

代碼:https://github.com/dyc87112/SpringCloudBook.git