路由器和過濾器:Zuul【譯】

原文鏈接:http://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.0.1.RELEASE/single/spring-cloud-netflix.html#_router_and_filter_zuul

8. 路由器和過濾器:Zuul

路由是微服務架構不可或缺的一部分。 例如,/可以映射到您的Web應用程序丰歌,/api /users映射到用戶服務败潦,/api/shop映射到商店服務。 Zuul是Netflix的基于JVM的路由器和服務器端負載均衡器聚请。

Netflix使用Zuul進行以下操作:

  • 認證
  • Insights
  • 壓力測試
  • Canary測試
  • 動態(tài)路由
  • 服務遷移
  • 負載脫落
  • 安全
  • 靜態(tài)響應處理
  • 主動/主動流量管理

Zuul的規(guī)則引擎允許規(guī)則和過濾器基本上以任何JVM語言編寫荧库,內置支持Java和Groovy。

配置屬性zuul.max.host.connections已被兩個新屬性zuul.host.maxTotalConnectionszuul.host.maxPerRouteConnections取代涡拘,它們分別默認為200和20。

所有路由的默認Hystrix隔離模式(ExecutionIsolationStrategy)都是SEMAPHORE据德。 如果首選隔離模式鳄乏,則可以將zuul.ribbonIsolationStrategy更改為THREAD跷车。

8.1 如何引入Zuul

要在項目中包含Zuul,請使用組ID為org.springframework.cloud的啟動器和spring-cloud-starter-netflix-zuul的工件ID橱野。 有關使用當前Spring Cloud Release Train設置構建系統(tǒng)的詳細信息朽缴,請參閱Spring Cloud Project頁面

8.2 嵌入式Zuul反向代理

Spring Cloud創(chuàng)建了一個嵌入式Zuul代理水援,以簡化UI應用程序想要對一個或多個后端服務進行代理調用的常見用例的開發(fā)密强。 此功能對于用戶界面代理其所需的后端服務非常有用,從而無需為所有后端獨立管理CORS和身份驗證問題蜗元。

要啟用它或渤,請使用@EnableZuulProxy注釋Spring Boot主類。 這樣做會導致本地呼叫轉發(fā)到適當?shù)姆铡?按照慣例奕扣,具有用戶ID的服務從位于/users的代理接收請求(帶有前綴剝離)薪鹦。 代理使用功能區(qū)來定位要通過發(fā)現(xiàn)轉發(fā)的實例。 所有請求都在hystrix命令中執(zhí)行惯豆,因此Hystrix指標中會出現(xiàn)故障距芬。 電路打開后,代理不會嘗試聯(lián)系該服務循帐。

Zuul啟動器不包含發(fā)現(xiàn)客戶端框仔,因此,對于基于服務ID的路由拄养,您還需要在類路徑中提供其中一個(Eureka是一種選擇)离斩。

要跳過自動添加的服務,請將zuul.ignored-services設置為服務ID模式列表瘪匿。 如果服務被忽略但仍包含在顯式配置的路由映射中的模式匹配跛梗,則它就不會被忽略的,如以下示例所示:

application.yml.

zuul:
  ignoredServices: '*'
  routes:
    users: /myusers/**

在前面的示例中棋弥,users外核偿,將忽略所有服務。

要擴充或更改代理路由顽染,可以添加外部配置漾岳,如下所示:

application.yml.

zuul:
  routes:
    users: /myusers/**

前面的示例意味著對/myusers的HTTP調用被轉發(fā)到用戶服務(例如/myusers/101被轉發(fā)到/101)。

要對路由進行更細粒度的控制粉寞,可以單獨指定路徑和serviceId尼荆,如下所示:

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
      serviceId: users_service

前面的示例意味著對/myusers的HTTP調用將轉發(fā)到users_service服務。 路徑必須具有可以指定為ant樣式模式的路徑唧垦,因此/myusers/*僅匹配一個級別捅儒,但/myusers/**是分層匹配的。

后端的位置可以指定為serviceId(用于發(fā)現(xiàn)服務)或url(用于物理位置),如以下示例所示:

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
      url: http://example.com/users_service

這些簡單的url-routes不會作為HystrixCommand執(zhí)行巧还,也不會使用Ribbon對多個URL進行負載均衡鞭莽。 要實現(xiàn)這些目標,您可以使用靜態(tài)服務器列表指定serviceId麸祷,如下所示:

application.yml.

zuul:
  routes:
    echo:
      path: /myusers/**
      serviceId: myusers-service
      stripPrefix: true
 
hystrix:
  command:
    myusers-service:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: ...
 
myusers-service:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    listOfServers: http://example1.com,http://example2.com
    ConnectTimeout: 1000
    ReadTimeout: 3000
    MaxTotalHttpConnections: 500
    MaxConnectionsPerHost: 100

另一種方法是指定服務路由并為serviceId配置Ribbon客戶端(這樣做需要在Ribbon中禁用Eureka支持 - 請參閱上面的更多信息)澎怒,如以下示例所示:

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
      serviceId: users
 
ribbon:
  eureka:
    enabled: false
 
users:
  ribbon:
    listOfServers: example.com,google.com

您可以使用regexmapperserviceId和路由之間提供約定。 它使用正則表達式命名組從serviceId中提取變量并將它們注入路由模式摇锋,如以下示例所示:

ApplicationConfiguration.java.

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

上面的示例表示myusers-v1serviceId映射到路由/v1/myusers/**。 接受任何正則表達式站超,但所有命名組必須同時出現(xiàn)在servicePatternroutePattern中荸恕。 如果servicePatternserviceId不匹配,則使用默認行為死相。 在前面的示例中融求,myusersserviceId映射到/myusers/**路由(未檢測到版本)。 默認情況下禁用此功能算撮,僅適用于已發(fā)現(xiàn)的服務生宛。

要為所有映射添加前綴,請將zuul.prefix設置為值肮柜,例如/api陷舅。 默認情況下,在轉發(fā)請求之前审洞,會從請求中刪除代理前綴(您可以使用zuul.stripPrefix = false關閉此行為)莱睁。 您還可以關閉從各個路由中剝離特定于服務的前綴,如以下示例所示:

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
      stripPrefix: false

zuul.stripPrefix僅適用于zuul.prefix中設置的前綴芒澜。 它對給定路由的path中定義的前綴沒有任何影響仰剿。

在前面的示例中,對/myusers/101的請求將轉發(fā)到用戶服務上的/myusers/101痴晦。

zuul.routes條目實際上綁定到ZuulProperties類型的對象南吮。 如果查看該對象的屬性,可以看到它還具有retryable的標志誊酌。 將該標志設置為true以使Ribbon客戶端自動重試失敗的請求部凑。 當您需要修改使用功能區(qū)客戶端配置的重試操作的參數(shù)時,也可以將該標志設置為true碧浊。

默認情況下砚尽,X-Forwarded-Host標頭會添加到轉發(fā)的請求中。 要將其關閉辉词,請設置zuul.addProxyHeaders = false必孤。 默認情況下,前綴路徑被剝離,對后端的請求選擇X-Forwarded-Prefix標頭(前面顯示的示例中為/myusers)敷搪。

如果設置默認路由(/)兴想,則具有@EnableZuulProxy的應用程序可以充當獨立服務器。 例如赡勘,zuul.route.home:/會將所有流量(/ **)路由到home服務嫂便。

如果需要更細粒度的忽略,則可以指定要忽略的特定模式闸与。 這些模式在路徑定位過程開始時進行評估毙替,這意味著前綴應包含在模式中以保證匹配。 忽略的模式跨越所有服務并取代任何其他路由規(guī)范践樱。 以下示例顯示如何創(chuàng)建忽略的模式:

application.yml.

zuul:
  ignoredPatterns: /**/admin/**
  routes:
    users: /myusers/**

上述示例表示所有呼叫(例如/myusers/101)都轉發(fā)到用戶服務上的/101厂画。 但是,包括/admin/的呼叫無法解決拷邢。

如果您需要保留其訂單的路由袱院,則需要使用YAML文件,因為使用屬性文件時排序會丟失瞭稼。 以下示例顯示了這樣的YAML文件:

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
    legacy:
      path: /**

如果您要使用屬性文件忽洛,則legacy路徑可能最終位于users路徑前面,從而導致users路徑無法訪問环肘。

8.3 Zuul Http客戶端

Zuul使用的默認HTTP客戶端現(xiàn)在由Apache HTTP Client支持欲虚,而不是不推薦使用的Ribbon RestClient。 要使用RestClientokhttp3.OkHttpClient悔雹,請分別設置ribbon.restclient.enabled = trueribbon.okhttp.enabled = true苍在。 如果要自定義Apache HTTP客戶端或OK HTTP客戶端,請?zhí)峁?code>ClosableHttpClient或OkHttpClient類型的bean荠商。

8.4 Cookie和敏感標題

您可以在同一系統(tǒng)中的服務之間共享標頭寂恬,但您可能不希望敏感標頭向下游泄漏到外部服務器。 您可以在路由配置中指定忽略的標頭列表莱没。 Cookie起著特殊的作用初肉,因為它們在瀏覽器中具有良好定義的語義,并且它們始終被視為敏感饰躲。 如果您的代理的消費者是瀏覽器牙咏,那么下游服務的cookie也會給用戶帶來問題,因為它們都混雜起來(所有下游服務看起來都來自同一個地方)嘹裂。

如果您對服務的設計非常小心(例如妄壶,如果只有一個下游服務設置了cookie),您可以讓它們從后端一直流到調用者寄狼。 此外丁寄,如果您的代理設置了cookie并且所有后端服務都是同一系統(tǒng)的一部分氨淌,那么簡單地共享它們就很自然(例如,使用Spring Session將它們鏈接到某個共享狀態(tài))伊磺。 除此之外盛正,由下游服務設置的任何cookie都可能對調用者沒用,因此建議您(至少)將Set-CookieCookie設置為不屬于您的域的路由的敏感標頭屑埋。 即使是屬于您域名的路由豪筝,也要在讓cookie和代理之間流動之前仔細考慮它的含義。

可以將敏感標頭配置為每個路由的逗號分隔列表摘能,如以下示例所示:

zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders: Cookie,Set-Cookie,Authorization
      url: https://downstream

這是sensitiveHeaders的默認值续崖,因此除非您希望它不同,否則無需進行設置团搞。 這是Spring Cloud Netflix 1.1中的新功能(在1.0中严望,用戶無法控制標題,并且所有Cookie都在兩個方向上流動)莺丑。

sensitiveHeaders是黑名單著蟹,默認不為空墩蔓。 因此梢莽,要使Zuul發(fā)送所有標頭(ignore的標頭除外),您必須將其明確設置為空列表奸披。 如果要將cookie或授權標頭傳遞到后端昏名,則必須這樣做。 以下示例顯示了如何使用sensitiveHeaders

application.yml.

zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders:
      url: https://downstream

您還可以通過設置zuul.sensitiveHeaders來設置敏感標頭阵面。 如果在路由上設置了sensitiveHeaders轻局,它將覆蓋全局sensitiveHeaders設置。

8.5 忽略標題

除路由敏感標頭外样刷,您還可以為與下游服務交互期間應丟棄的值(請求和響應)設置名為zuul.ignoredHeaders的全局值仑扑。 默認情況下,如果Spring Security不在類路徑中置鼻,則它們?yōu)榭铡?否則镇饮,它們被初始化為一組眾所周知的“安全”頭文件(例如,涉及緩存)箕母,如Spring Security所指定的那樣储藐。 在這種情況下的假設是下游服務也可能添加這些頭,但我們想要代理的值嘶是。 要在Spring Security位于類路徑時不丟棄這些眾所周知的安全標頭钙勃,可以將zuul.ignoreSecurityHeaders設置為false。 如果您在Spring Security中禁用了HTTP安全響應標頭并希望下游服務提供的值聂喇,那么這樣做會非常有用辖源。

8.6 管理端點

默認情況下,如果將@EnableZuulProxy與Spring Boot Actuator一起使用,則可以啟用另外兩個端點:

  • 路由
  • 過濾

8.6.1 路由結點

/routes的路由端點的GET返回映射路由的列表:

GET /routes.

{
  /stores/**: "http://localhost:8081"
}

可以通過將?format = details查詢字符串添加到/routes來請求其他路由詳細信息同木。 這樣做會產(chǎn)生以下輸出:

GET /routes/details.

{
  "/stores/**": {
    "id": "stores",
    "fullPath": "/stores/**",
    "location": "http://localhost:8081",
    "path": "/**",
    "prefix": "/stores",
    "retryable": false,
    "customSensitiveHeaders": false,
    "prefixStripped": true
  }
}

/routesPOST強制刷新現(xiàn)有路由(例如浮梢,當服務目錄中有更改時)。 您可以通過將endpoints.routes.enabled設置為false來禁用此端點彤路。

路由應自動響應服務目錄中的更改秕硝,但POST/routes是一種強制更改立即發(fā)生的方法。

8.6.2 過濾結點

/filters處的過濾器端點的GET按類型返回Zuul過濾器的映射洲尊。 對于映射中的每種過濾器類型远豺,您將獲得該類型的所有過濾器及其詳細信息的列表。

8.7 扼殺模式和本地前鋒

遷移現(xiàn)有應用程序或API時的一種常見模式是“扼殺”舊端點坞嘀,慢慢用不同的實現(xiàn)替換它們躯护。 Zuul代理是一個有用的工具,因為您可以使用它來處理來自舊端點的客戶端的所有流量丽涩,但將一些請求重定向到新的端點棺滞。

以下示例顯示“扼殺”方案的配置詳細信息:

application.yml.

zuul:
  routes:
    first:
      path: /first/**
      url: http://first.example.com
    second:
      path: /second/**
      url: forward:/second
    third:
      path: /third/**
      url: forward:/3rd
    legacy:
      path: /**
      url: http://legacy.example.com

在前面的示例中,我們扼殺了“遺留”應用程序矢渊,該應用程序映射到與其他模式之一不匹配的所有請求继准。 /first/**中的路徑已被提取到具有外部URL的新服務中。 轉發(fā)/second/**中的路徑矮男,以便可以在本地處理它們(例如移必,使用正常的Spring @RequestMapping)。 /third/**中的路徑也被轉發(fā)但具有不同的前綴(/third/foo被轉發(fā)到/3rd/foo)毡鉴。

忽略的模式不會被完全忽略崔泵,它們只是不由代理處理(因此它們也可以在本地有效轉發(fā))。

8.8 通過Zuul上傳文件

如果您使用@EnableZuulProxy猪瞬,您可以使用代理路徑上傳文件憎瘸,只要文件很小,它就可以工作陈瘦。 對于大型文件幌甘,有一個替代路徑繞過/zuul/*中的Spring DispatcherServlet(以避免多部分處理)。 換句話說甘晤,如果你有zuul.routes.customers = /customers/**含潘,那么你可以將大文件POST/zuul/customers/*。 servlet路徑通過zuul.servletPath外部化线婚。 如果代理路由引導您完成功能區(qū)負載平衡器遏弱,則極大文件也需要提升超時設置,如以下示例所示:

application.yml.

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

請注意塞弊,要使用大型文件進行流式處理漱逸,您需要在請求中使用分塊編碼(默認情況下某些瀏覽器不會這樣做)泪姨,如以下示例所示:

$ curl -v -H "Transfer-Encoding: chunked" \
    -F "file=@mylarge.iso" localhost:9999/zuul/simple/file

8.9 查詢字符串編碼

處理傳入請求時,將對查詢參數(shù)進行解碼饰抒,以便它們可用于Zuul過濾器中的可能修改肮砾。 然后對它們進行重新編碼,在路由過濾器中重建后端請求袋坑。 如果(例如)它是使用Javascript的encodeURIComponent()方法編碼的仗处,則結果可能與原始輸入不同。 雖然這在大多數(shù)情況下不會引起任何問題枣宫,但某些Web服務器可能會因復雜查詢字符串的編碼而變得挑剔婆誓。

要強制查詢字符串的原始編碼,可以將特殊標志傳遞給ZuulProperties也颤,以便使用HttpServletRequest::getQueryString方法將查詢字符串視為原樣洋幻,如以下示例所示:

application.yml.

zuul:
  forceOriginalQueryStringEncoding: true

此特殊標志僅適用于SimpleHostRoutingFilter。 此外翅娶,您無法使RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)輕松覆蓋查詢參數(shù)文留,因為查詢字符串現(xiàn)在直接在原始HttpServletRequest上獲取。

8.10 普通嵌入式Zuul

如果使用@EnableZuulServer(而不是@EnableZuulProxy)竭沫,您還可以運行Zuul服務器燥翅,而無需代理或有選擇地切換代理平臺的某些部分。 您添加到ZuulFilter類型的應用程序的任何bean都會自動安裝(與@EnableZuulProxy一樣)输吏,但不會自動添加任何代理過濾器权旷。

application.yml.

zuul:
  routes:
    api: /api/**

8.11 禁用Zuul過濾器

Zuul for Spring Cloud在代理和服務器模式下都默認啟用了許多ZuulFilter bean替蛉。 有關可以啟用的過濾器列表贯溅,請參閱Zuul過濾器包。 如果要禁用一個躲查,請設置zuul.<SimpleClassName>.<filterType>.disable = true它浅。 按照慣例,filters后的包是Zuul過濾器類型镣煮。 例如姐霍,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,請設置zuul.SendResponseFilter.post.disable = true典唇。

8.12 為路由提供Hystrix后備

當Zuul中給定路徑的電路跳閘時镊折,您可以通過創(chuàng)建FallbackProvider類型的bean來提供回退響應。 在此bean中介衔,您需要指定回退所針對的路由ID恨胚,并提供ClientHttpResponse作為回退返回。 以下示例顯示了一個相對簡單的FallbackProvider實現(xiàn):

class MyFallbackProvider implements FallbackProvider {
 
    @Override
    public String getRoute() {
        return "customers";
    }
 
    @Override
    public ClientHttpResponse fallbackResponse(String route, final 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 {
                return new ByteArrayInputStream("fallback".getBytes());
            }
 
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

以下示例顯示了上一個示例的路由配置可能如何顯示:

zuul:
  routes:
    customers: /customers/**

如果要為所有路由提供默認回退炎咖,可以創(chuàng)建FallbackProvider類型的bean并使getRoute方法返回*null赃泡,如以下示例所示:

class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }
 
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
 
            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }
 
            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }
 
            @Override
            public void close() {
 
            }
 
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }
 
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

8.13 Zuul超時

如果要為通過Zuul代理的請求配置套接字超時和讀取超時寒波,則有兩種選擇,具體取決于您的配置:

  • 如果Zuul使用服務發(fā)現(xiàn)升熊,則需要使用ribbon.ReadTimeoutribbon.SocketTimeout功能區(qū)屬性配置這些超時俄烁。

如果通過指定URL配置了Zuul路由,則需要使用zuul.host.connect-timeout-milliszuul.host.socket-timeout-millis级野。

8.14 重寫Location標頭

如果Zuul面向Web應用程序页屠,則當Web應用程序通過HTTP狀態(tài)代碼3XX重定向時,您可能需要重新寫入Location標頭蓖柔。 否則卷中,瀏覽器會重定向到Web應用程序的URL而不是Zuul URL。 您可以配置LocationRewriteFilter Zuul過濾器以將Location標頭重新寫入Zuul的URL渊抽。 它還會添加剝離的全局和路由特定前綴蟆豫。 以下示例使用Spring配置文件添加過濾器:

import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
...
 
@Configuration
@EnableZuulProxy
public class ZuulConfig {
    @Bean
    public LocationRewriteFilter locationRewriteFilter() {
        return new LocationRewriteFilter();
    }
}

小心
仔細使用此過濾器。 過濾器作用于所有3XX響應代碼的Location標頭懒闷,這可能不適用于所有情況十减,例如將用戶重定向到外部URL時。

8.15 Metrics

對于路由請求時可能發(fā)生的任何故障愤估,Zuul將在Actuator指標端點下提供指標帮辟。 可以通過點擊/actuator/metrics來查看這些指標。 度量標準的名稱格式為ZUUL::EXCEPTIONerrorCause:statusCode玩焰。

8.16 Zuul開發(fā)人員指南

有關Zuul如何工作的一般概述由驹,請參閱Zuul Wiki

8.16.1 Zuul Servlet

Zuul是作為Servlet實現(xiàn)的昔园。 對于一般情況蔓榄,Zuul嵌入到Spring Dispatch機制中。 這讓Spring MVC可以控制路由默刚。 在這種情況下甥郑,Zuul緩沖請求。 如果需要在沒有緩沖請求的情況下通過Zuul(例如荤西,對于大型文件上載)澜搅,Servlet也會安裝在Spring Dispatcher之外。 默認情況下邪锌,servlet的地址為/zuul勉躺。 可以使用zuul.servlet-path屬性更改此路徑。

8.16.2 Zuul RequestContext

為了在過濾器之間傳遞信息觅丰,Zuul使用RequestContext饵溅。 它的數(shù)據(jù)保存在特定于每個請求的ThreadLocal中。 有關在哪里路由請求舶胀,錯誤以及實際的HttpServletRequestHttpServletResponse的信息都存儲在那里概说。 RequestContext擴展了ConcurrentHashMap碧注,因此任何東西都可以存儲在上下文中。 FilterConstants包含Spring Cloud Netflix安裝的過濾器使用的密鑰(稍后將詳細介紹)糖赔。

8.16.3 @EnableZuulProxy vs. @EnableZuulServer

Spring Cloud Netflix安裝了許多過濾器萍丐,具體取決于使用哪個注釋來啟用Zuul。 @EnableZuulProxy@EnableZuulServer的超集放典。 換句話說逝变,@EnableZuulProxy包含@EnableZuulServer安裝的所有過濾器。 “代理”中的其他過濾器啟用路由功能奋构。 如果你想要一個“空白”Zuul壳影,你應該使用@EnableZuulServer

8.16.4 @EnableZuulServer過濾

@EnableZuulServer創(chuàng)建一個SimpleRouteLocator弥臼,用于從Spring Boot配置文件加載路由定義宴咧。

安裝了以下過濾器(與普通的Spring Bean一樣):

  • 預過濾器:
    • ServletDetectionFilter: 檢測請求是否通過Spring Dispatcher。 使用FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY的鍵設置布爾值径缅。
    • FormBodyWrapperFilter: 解析表單數(shù)據(jù)并為下游請求重新編碼掺栅。
    • DebugFilter: 如果設置了debug 請求參數(shù),則將RequestContext.setDebugRouting()RequestContext.setDebugRequest()設置為true纳猪。
  • 路由過濾器:
    • SendForwardFilter: 使用Servlet RequestDispatcher轉發(fā)請求氧卧。 轉發(fā)位置存儲在RequestContext屬性FilterConstants.FORWARD_TO_KEY中。 這對于轉發(fā)到當前應用程序中的端點非常有用氏堤。
  • 后置過濾器:
    • SendResponseFilter: 將代理請求的響應寫入當前響應沙绝。
  • 錯誤過濾器:
    • SendErrorFilter: 如果RequestContext.getThrowable()不為null,則轉發(fā)到/error(默認情況下)鼠锈。 您可以通過設置error.path屬性來更改默認轉發(fā)路徑(/error)闪檬。

8.16.5 @EnableZuulProxy過濾

創(chuàng)建DiscoveryClientRouteLocator,用于從DiscoveryClient(例如Eureka)以及屬性加載路徑定義脚祟。 為DiscoveryClient中的每個serviceId創(chuàng)建一個路由谬以。 添加新服務后强饮,將刷新路由由桌。

除了前面描述的過濾器之外,還安裝了以下過濾器(與普通的Spring Bean一樣):

  • 預過濾器:
    • PreDecorationFilter: 確定路由的位置和方式邮丰,具體取決于提供的RouteLocator行您。 它還為下游請求設置各種與代理相關的標頭。
  • 路由過濾器:
    • RibbonRoutingFilter: 使用Ribbon剪廉,Hystrix和可插入HTTP客戶端發(fā)送請求娃循。 服務ID位于RequestContext屬性FilterConstants.SERVICE_ID_KEY中。 此過濾器可以使用不同的HTTP客戶端:
      • Apache HttpClient: 默認客戶端斗蒋。
      • Squareup OkHttpClient v3: 通過在類路徑上設置com.squareup.okhttp3:okhttp庫并設置ribbon.okhttp.enabled = true來啟用捌斧。
      • Netflix Ribbon HTTP client: 通過設置ribbon.restclient.enabled = true啟用笛质。 此客戶端具有限制,包括它不支持PATCH方法捞蚂,但它也具有內置重試妇押。
    • SimpleHostRoutingFilter:

8.16.6 自定義Zuul過濾器示例

下面的大多數(shù)“如何寫”示例包括Sample Zuul Filters項目。 還有一些操作該存儲庫中的請求或響應主體的示例姓迅。

本節(jié)包括以下示例:

如何編寫預過濾器

預過濾器在RequestContext中設置數(shù)據(jù)敲霍,以便在下游過濾器中使用。 主要用例是設置路由過濾器所需的信息丁存。 以下示例顯示了Zuul預過濾器:

public class QueryParamPreFilter extends ZuulFilter {
    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
    }
 
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
 
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
                && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
    }
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        if (request.getParameter("sample") != null) {
            // put the serviceId in `RequestContext`
            ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
        }
        return null;
    }
}

前面的過濾器從sample請求參數(shù)填充SERVICE_ID_KEY肩杈。 在實踐中,您不應該進行這種直接映射解寝。 相反扩然,應該從sample的值中查找服務ID。

現(xiàn)在已填充SERVICE_ID_KEY聋伦,PreDecorationFilter不會運行并且RibbonRoutingFilter會運行与学。

如果要路由到完整URL,請改為調用ctx.setRouteHost(url)嘉抓。

要修改路由過濾器轉發(fā)的路徑索守,請設置REQUEST_URI_KEY

如何編寫路由過濾器

路由過濾器在預過濾器之后運行并向其他服務發(fā)出請求抑片。 這里的大部分工作是將請求和響應數(shù)據(jù)轉換為客戶端所需的模型卵佛。 以下示例顯示了Zuul路由過濾器:

public class OkHttpRoutingFilter extends ZuulFilter {
    @Autowired
    private ProxyRequestHelper helper;
 
    @Override
    public String filterType() {
        return ROUTE_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return RequestContext.getCurrentContext().getRouteHost() != null
                && RequestContext.getCurrentContext().sendZuulResponse();
    }
 
    @Override
    public Object run() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                // customize
                .build();
 
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
 
        String method = request.getMethod();
 
        String uri = this.helper.buildZuulRequestURI(request);
 
        Headers.Builder headers = new Headers.Builder();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            Enumeration<String> values = request.getHeaders(name);
 
            while (values.hasMoreElements()) {
                String value = values.nextElement();
                headers.add(name, value);
            }
        }
 
        InputStream inputStream = request.getInputStream();
 
        RequestBody requestBody = null;
        if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
            MediaType mediaType = null;
            if (headers.get("Content-Type") != null) {
                mediaType = MediaType.parse(headers.get("Content-Type"));
            }
            requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
        }
 
        Request.Builder builder = new Request.Builder()
                .headers(headers.build())
                .url(uri)
                .method(method, requestBody);
 
        Response response = httpClient.newCall(builder.build()).execute();
 
        LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();
 
        for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
            responseHeaders.put(entry.getKey(), entry.getValue());
        }
 
        this.helper.setResponse(response.code(), response.body().byteStream(),
                responseHeaders);
        context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
        return null;
    }
}

前面的過濾器將Servlet請求信息轉換為OkHttp3請求信息,執(zhí)行HTTP請求敞斋,并將OkHttp3響應信息轉換為Servlet響應截汪。

如何編寫后置過濾器

后置過濾器通常會操縱響應。 以下過濾器添加隨機UUID作為X-Sample標頭:

public class AddResponseHeaderFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return POST_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return SEND_RESPONSE_FILTER_ORDER - 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletResponse servletResponse = context.getResponse();
        servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
        return null;
    }
}

其他操作(例如轉換響應體)要復雜得多且計算量大植捎。

8.16.7 Zuul錯誤如何工作

如果在Zuul過濾器生命周期的任何部分期間拋出異常衙解,則執(zhí)行錯誤過濾器。 僅當RequestContext.getThrowable()不為null時焰枢,才會運行SendErrorFilter蚓峦。 然后,它在請求中設置特定的javax.servlet.error.*屬性济锄,并將請求轉發(fā)到Spring Boot錯誤頁面暑椰。

8.16.8 Zuul Eager應用程序上下文加載

Zuul內部使用Ribbon來調用遠程URL。 默認情況下荐绝,Spring Cloud在第一次調用時會延遲加載Ribbon客戶端一汽。 可以使用以下配置更改Zuul的此行為,這會導致在應用程序啟動時急切加載與子功能區(qū)相關的應用程序上下文低滩。 以下示例顯示如何啟用預先加載:

application.yml.

zuul:
  ribbon:
    eager-load:
      enabled: true
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末召夹,一起剝皮案震驚了整個濱河市岩喷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌监憎,老刑警劉巖均驶,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枫虏,居然都是意外死亡妇穴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門隶债,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腾它,“玉大人,你說我怎么就攤上這事死讹÷鞯危” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵赞警,是天一觀的道長涝婉。 經(jīng)常有香客問我厢破,道長雳旅,這世上最難降的妖魔是什么埠胖? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮笤虫,結果婚禮上旁瘫,老公的妹妹穿的比我還像新娘。我一直安慰自己琼蚯,他們只是感情好酬凳,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遭庶,像睡著了一般宁仔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上峦睡,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天翎苫,我揣著相機與錄音,去河邊找鬼赐俗。 笑死拉队,一個胖子當著我的面吹牛,可吹牛的內容都是我干的阻逮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秩彤,長吁一口氣:“原來是場噩夢啊……” “哼叔扼!你這毒婦竟也來了事哭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瓜富,失蹤者是張志新(化名)和其女友劉穎鳍咱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體与柑,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡谤辜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了价捧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丑念。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖结蟋,靈堂內的尸體忽然破棺而出脯倚,到底是詐尸還是另有隱情,我是刑警寧澤嵌屎,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布推正,位于F島的核電站,受9級特大地震影響宝惰,放射性物質發(fā)生泄漏植榕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一尼夺、第九天 我趴在偏房一處隱蔽的房頂上張望内贮。 院中可真熱鬧,春花似錦汞斧、人聲如沸夜郁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竞端。三九已至,卻和暖如春庙睡,著一層夾襖步出監(jiān)牢的瞬間事富,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工乘陪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留统台,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓啡邑,卻偏偏與公主長得像贱勃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容