Spring Cloud Document翻譯(九)--路由器和過濾器:Zuul

18.路由器和過濾器:Zuul

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

Netflix使用Zuul進行以下操作:

  • 認證
  • 洞察
  • 壓力測試
  • 金絲雀測試
  • 動態路由
  • 服務遷移
  • 負載脫落
  • 安全
  • 靜態響應處理
  • 主動/主動流量管理

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

[注意]

配置屬性zuul.max.host.connections已被取代的兩個新的屬性,zuul.host.maxTotalConnections並且zuul.host.maxPerRouteConnections,分別其默認爲200和20。

[注意]

ExecutionIsolationStrategy所有路由的默認Hystrix隔離模式()是SEMAPHORE。 如果首選隔離模式,則zuul.ribbonIsolationStrategy可以更改爲THREAD

18.1如何包含Zuul

要在項目中包含Zuul,請使用組ID爲ID org.springframework.cloud且工件ID爲的starter spring-cloud-starter-netflix-zuul。有關使用當前Spring Cloud Release Train設置構建系統的詳細信息,請參閱Spring Cloud Project頁面

18.2嵌入式Zuul反向代理

Spring Cloud創建了一個嵌入式Zuul代理,以簡化UI應用程序想要對一個或多個後端服務進行代理調用的常見用例的開發。此功能對於用戶界面代理其所需的後端服務非常有用,從而無需爲所有後端獨立管理CORS和身份驗證問題。

要啓用它,請使用註釋Spring Boot主類@EnableZuulProxy。這樣做會導致本地呼叫轉發到適當的服務。按照慣例,具有ID的服務users接收來自位於/users(帶有前綴剝離)的代理的請求。代理使用功能區來查找要通過發現轉發的實例。所有請求都在hystrix命令中執行,因此Hystrix指標中會出現故障。電路打開後,代理不會嘗試聯繫該服務。

[注意]

Zuul啓動器不包含發現客戶端,因此,對於基於服務ID的路由,您還需要在類路徑中提供其中一個(Eureka是一種選擇)。

要跳過自動添加的服務,請設置zuul.ignored-services爲服務ID模式列表。如果服務與忽略但仍包含在顯式配置的路由映射中的模式匹配,則它是不帶號的,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  ignoredServices</span>:<span style="color:#2a00ff">'*' </span>
<span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

在上面的例子中,所有的服務都將被忽略,除了users

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

前面的示例表示HTTP調用將/myusers轉發到users服務(例如/myusers/101轉發到/101)。

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:users_service</span></span>

 

前面的示例表示HTTP調用將/myusers轉發到users_service服務。路徑必須具有path可以指定爲ant樣式的模式,因此/myusers/*只匹配一個級別,但是/myusers/**分層匹配。

後端的位置可以指定爲a serviceId(用於發現服務)或a url(用於物理位置),如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:<span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      url</span>:https :
 <span style="color:#7f007f">//example.com/users_service</span></span></span>

 

這些簡單的url-routes不會作爲a執行HystrixCommand,也不會使用Ribbon對多個URL進行負載平衡。要實現這些目標,您可以使用serviceId靜態服務器列表指定a ,如下所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    echo</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:myusers-service
 <span style="color:#7f007f">      stripPrefix</span>:<span style="color:#7f0055"><strong>true</strong></span>

<span style="color:#7f007f">hystrix</span>:
 <span style="color:#7f007f">  command</span>:
 <span style="color:#7f007f">    myusers-service</span>:
 <span style="color:#7f007f">      execution</span>:
 <span style="color:#7f007f">        isolation</span>:
 <span style="color:#7f007f">          thread</span>:
 <span style="color:#7f007f">            timeoutInMilliseconds</span>:...

<span style="color:#7f007f">myusers-service</span>:
 <span style="color:#7f007f">  ribbon</span>:
 <span style="color:#7f007f">    NIWSServerListClassName</span>:com.netflix.loadbalancer.ConfigurationBasedServerList
 <span style="color:#7f007f">    listOfServers</span>:https://example1.com,http://example2.com
 <span style="color:#7f007f">    ConnectTimeout</span>:1000 
<span style="color:#7f007f">    ReadTimeout</span>:3000 
<span style="color:#7f007f">    MaxTotalHttpConnections</span>:500 
<span style="color:#7f007f">    MaxConnectionsPerHost</span>:100</span></span>

 

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:users

<span style="color:#7f007f">ribbon</span>:
 <span style="color:#7f007f">  eureka</span>:
 <span style="color:#7f007f">    enabled</span>:<span style="color:#7f0055"><strong>false</strong></span>

<span style="color:#7f007f">用戶</span>:
 <span style="color:#7f007f">  ribbon</span>:
 <span style="color:#7f007f">    listOfServers</span>:example.com,google.com</span></span>

 

您可以使用提供serviceId路徑和路線之間的約定regexmapper。它使用正則表達式命名組從中提取變量serviceId並將其注入路由模式,如以下示例所示:

ApplicationConfiguration.java。 

<span style="color:#333333"><span style="color:#000000"><em><span style="color:gray">@Bean</span></em>
<span style="color:#7f0055"><strong> public</strong></span> PatternServiceRouteMapper serviceRouteMapper(){
    <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> PatternServiceRouteMapper(
        <span style="color:#2a00ff"> “?<name> ^。+) - (?<version> v。+ $)”</span>,
        <span style="color:#2a00ff"> “$ {version} / $ {name}”</span>);
}</span></span>

 

上面的示例是指一個serviceIdmyusers-v1映射來路由/v1/myusers/**。接受任何正則表達式,但所有命名組必須同時存在於servicePatternroutePattern。如果servicePattern不匹配a serviceId,則使用默認行爲。在前面的例子中,一個serviceIdmyusers被映射到“/ myusers / **”路線(沒有檢測到版本)。默認情況下禁用此功能,僅適用於已發現的服務。

要爲所有映射添加前綴,請設置zuul.prefix爲值,例如/api。默認情況下,在請求轉發之前,會從請求中刪除代理前綴(您可以關閉此行爲zuul.stripPrefix=false)。您還可以關閉從各個路由中剝離特定於服務的前綴,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      stripPrefix</span>:<span style="color:#7f0055"><strong>false</strong></span></span></span>

 

[注意]

zuul.stripPrefix僅適用於設置的前綴zuul.prefix。它對給定路徑中定義的前綴沒有任何影響path

在前面的例子中,請求/myusers/101被轉發到/myusers/101users服務。

zuul.routes條目實際上結合到類型的對象ZuulProperties。如果查看該對象的屬性,可以看到它還有一個retryable標誌。將該標誌設置true爲讓Ribbon客戶端自動重試失敗的請求。您還可以true在需要修改使用功能區客戶端配置的重試操作的參數時將該標誌設置爲。

默認情況下,X-Forwarded-Host標頭會添加到轉發的請求中。要關閉它,請設置zuul.addProxyHeaders = false。默認情況下,前綴路徑被剝離,對後端的請求會獲取一個X-Forwarded-Prefix標頭(/myusers在前面顯示的示例中)。

如果設置了默認路由(/),則應用程序@EnableZuulProxy可以充當獨立服務器。例如,zuul.route.home: /將所有流量(“/ **”)路由到“home”服務。

如果需要更細粒度的忽略,則可以指定要忽略的特定模式。這些模式在路徑定位過程開始時進行評估,這意味着前綴應包含在模式中以保證匹配。忽略的模式跨越所有服務並取代任何其他路由規範。以下示例顯示如何創建忽略的模式:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  ignoredPatterns</span>:/ ** / admin / **
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

前面的例子是指所有的呼叫(例如/myusers/101)被轉發到/101users服務。但是,電話包括/admin/不解決。

[警告]

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">    legacy</span>:
 <span style="color:#7f007f">      path</span>:/ **</span></span>

 

如果您要使用屬性文件,則legacy路徑可能最終位於users 路徑前面,從而使users路徑無法訪問。

18.3 Zuul Http客戶端

Zuul使用的默認HTTP客戶端現在由Apache HTTP Client而不是已棄用的功能區支持RestClient。分別使用RestClientokhttp3.OkHttpClient設置ribbon.restclient.enabled=trueribbon.okhttp.enabled=true。如果要自定義Apache HTTP客戶端或OK HTTP客戶端,請提供類型爲ClosableHttpClient或的bean OkHttpClient

18.4 Cookie和敏感標題

您可以在同一系統中的服務之間共享標頭,但您可能不希望敏感標頭向下遊泄漏到外部服務器。您可以在路由配置中指定忽略的標頭列表。Cookie起着特殊的作用,因爲它們在瀏覽器中具有明確定義的語義,並且它們始終被視爲敏感。如果您的代理的消費者是瀏覽器,那麼下游服務的cookie也會給用戶帶來問題,因爲它們都混雜起來(所有下游服務看起來都來自同一個地方)。

如果您對服務的設計非常小心(例如,如果只有一個下游服務設置了cookie),您可以讓它們從後端一直流到調用者。此外,如果您的代理設置了cookie並且所有後端服務都屬於同一系統,則可以很自然地簡單地共享它們(例如,使用Spring Session將它們鏈接到某個共享狀態)。除此之外,也得到由下游服務設置任何cookie可能是沒有用的給調用者,所以建議你做(至少)Set-Cookie,並Cookie轉化爲不屬於域的一部分,路線敏感頭。即使對於屬於您的域的路由,在讓Cookie和代理之間流動之前,請仔細考慮它的含義。

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      sensitiveHeaders</span>:Cookie,Set-Cookie,Authorization
 <span style="color:#7f007f">      url</span>:https:// downstream</span></span>

 

[注意]

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

sensitiveHeaders是一個黑名單,默認是不爲空。因此,要使Zuul發送所有標頭(除了ignored那些標頭),您必須將其明確設置爲空列表。如果要將cookie或授權標頭傳遞給後端,則必須這樣做。以下示例顯示如何使用sensitiveHeaders

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      sensitiveHeaders</span>:
 <span style="color:#7f007f">      url</span>:https:// downstream</span></span>

 

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

18.5忽略標題

除路由敏感標頭外,您還可以設置一個全局值zuul.ignoredHeaders,該值在請求和下游服務交互期間應丟棄的值(請求和響應)。默認情況下,如果Spring Security不在類路徑中,則它們爲空。否則,它們被初始化爲一組衆所周知的“ 安全 ”頭文件(例如,涉及緩存),如Spring Security所指定的那樣。在這種情況下的假設是下游服務也可能添加這些頭,但我們想要代理的值。要在Spring Security位於類路徑時不丟棄這些衆所周知的安全標頭,您可以設置zuul.ignoreSecurityHeadersfalse。如果您在Spring Security中禁用了HTTP安全響應標頭並希望下游服務提供的值,那麼這樣做會非常有用。

18.6管理端點

默認情況下,如果使用@EnableZuulProxySpring Boot Actuator,則啓用另外兩個端點:

  • 路線
  • 過濾器

18.6.1路由端點

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

GET /路線。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>{</strong></span> 
  / stores / **:<span style="color:#2a00ff">“http:// localhost:8081” </span>
<span style="color:#7f0055"><strong>}</strong></span></span></span>

 

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

GET /路線/細節。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>{ </strong></span>
  <span style="color:#2a00ff">“/ stores / **”</span>:<span style="color:#7f0055"><strong>{ </strong></span>
    <span style="color:#2a00ff">“id”</span>:<span style="color:#2a00ff">“stores” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“fullPath”</span>:<span style="color:#2a00ff">“/ stores / **” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“location”</span>:<span style="color:#2a00ff">“http:// localhost:8081” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“path”</span>:<span style="color:#2a00ff">“/ **“ </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”前綴“</span>:<span style="color:#2a00ff">”/ stores“ </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”retryable“</span>:<span style="color:#7f0055"><strong>false </strong></span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”customSensitiveHeaders“</span>:<span style="color:#7f0055"><strong>false </strong></span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”prefixStripped“</span>:<span style="color:#7f0055"><strong>true </strong></span>
  <span style="color:#7f0055"><strong>} </strong></span>
<span style="color:#7f0055"><strong>}</strong></span></span></span>

 

POST/routes現有路線的力量刷新(例如,當在服務目錄進行了變更)。您可以通過設置endpoints.routes.enabled爲禁用此端點false

[注意]

路由應自動響應服務目錄中的更改,但POSTto /routes是強制更改立即發生的方法。

18.6.2過濾器端點

GET到過濾器端點/filters返回按類型的Zuul過濾器的映射。對於地圖中的每種過濾器類型,您將獲得該類型的所有過濾器及其詳細信息的列表。

18.7扼殺模式和本地前鋒

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

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

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    first</span>:
 <span style="color:#7f007f">      path</span>:/ first / **
 <span style="color:#7f007f">      url</span>:https
 :<span style="color:#7f007f">//first.example.com </span><span style="color:#7f007f">    second</span>:
 <span style="color:#7f007f">      path</span>:/ second / **
 <span style="color:#7f007f">      url</span>:forward:/ second
 <span style="color:#7f007f">    third</span>:
 <span style="color:#7f007f">      path</span>:/ third / **
 <span style="color:#7f007f">      url</span>:轉發:/ 3 rd
 <span style="color:#7f007f">    legacy</span>:
 <span style="color:#7f007f">      path</span>:/ **
 <span style="color:#7f007f">      url</span>:https://legacy.example.com</span></span>

 

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

[注意]

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

18.8通過Zuul上傳文件

如果您使用@EnableZuulProxy,您可以使用代理路徑上傳文件,只要文件很小,它就可以使用。對於大文件,有一個替代路徑繞過Spring DispatcherServlet(以避免多部分處理)在“/ zuul / *”中。換句話說,如果你有zuul.routes.customers=/customers/**,那麼你可以將POST大文件/zuul/customers/*。servlet路徑通過外部化zuul.servletPath。如果代理路由引導您完成功能區負載平衡器,則極大文件也需要提升超時設置,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds</span>:60000 
<span style="color:#7f007f">功能區</span>:
 <span style="color:#7f007f">  ConnectTimeout</span>:3000 
<span style="color:#7f007f">  ReadTimeout</span>:60000</span></span>

 

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

<span style="color:#333333"><span style="color:#000000">$捲曲-v -H <span style="color:#2a00ff">“傳輸編碼:分塊”</span> \
    -F <span style="color:#2a00ff">“ [email protected] ”</span> localhost:9999 / zuul / simple / file</span></span>

18.9查詢字符串編碼

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

要強制查詢字符串的原始編碼,可以傳遞一個特殊標誌,ZuulProperties以便使用該HttpServletRequest::getQueryString方法按原樣獲取查詢字符串,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  forceOriginalQueryStringEncoding</span>:<span style="color:#7f0055"><strong>true</strong></span></span></span>

 

[注意]

這個特殊標誌只適用於SimpleHostRoutingFilter。此外,您無法輕鬆覆蓋查詢參數RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters),因爲現在直接在原始查詢字符串上提取查詢字符串HttpServletRequest

18.10請求URI編碼

處理傳入請求時,請求URI在將它們與路由匹配之前進行解碼。然後,在路由過濾器中重建後端請求時,將重新編碼請求URI。如果您的URI包含編碼的“/”字符,這可能會導致一些意外的行爲。

要使用原始請求URI,可以將特殊標誌傳遞給'ZuulProperties',以便URI將按原樣採用該HttpServletRequest::getRequestURI方法,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  decodeUrl</span>:<span style="color:#7f0055"><strong>false</strong></span></span></span>

 

[注意]

如果使用requestURIRequestContext屬性覆蓋請求URI 並且此標誌設置爲false,則不會對請求上下文中設置的URL進行編碼。您有責任確保URL已經編碼。

18.11普通嵌入式Zuul

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

在這種情況下,仍然通過配置“zuul.routes。*”來指定進入Zuul服務器的路由,但是沒有服務發現和代理。因此,將忽略“serviceId”和“url”設置。以下示例將“/ api / **”中的所有路徑映射到Zuul過濾器鏈:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    api</span>:/ api / **</span></span>

 

18.12禁用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

18.13爲路由提供Hystrix後備

當Zuul中給定路徑的電路跳閘時,您可以通過創建類型的bean來提供回退響應FallbackProvider。在此bean中,您需要指定回退所針對的路由ID,並提供ClientHttpResponse返回作爲回退。以下示例顯示了一個相對簡單的FallbackProvider實現:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>class</strong></span> MyFallbackProvider <span style="color:#7f0055"><strong>實現</strong></span> FallbackProvider {

    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> String getRoute(){
        <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#2a00ff">“customers”</span> ;
    }

    <em><span style="color:gray">@覆蓋</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span> ClientHttpResponse fallbackResponse(字符串途徑,<span style="color:#7f0055"><strong>最終</strong></span>的Throwable原因){
        <span style="color:#7f0055"><strong>如果</strong></span>(原因<span style="color:#7f0055"><strong>的instanceof</strong></span> HystrixTimeoutException){
            <span style="color:#7f0055"><strong>返回</strong></span>響應(HttpStatus.GATEWAY_TIMEOUT);
        } <span style="color:#7f0055"><strong>else</strong></span> {
             <span style="color:#7f0055"><strong>return</strong></span> response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    <span style="color:#7f0055"><strong>private</strong></span> ClientHttpResponse響應(<span style="color:#7f0055"><strong>最終</strong></span> HttpStatus 狀態){
         <span style="color:#7f0055"><strong>return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> ClientHttpResponse(){ <em><span style="color:gray">@</span></em>
             Override <span style="color:#7f0055"><strong>public</strong></span> HttpStatus
             getStatusCode()<span style="color:#7f0055"><strong>throws</strong></span> IOException {
                 <span style="color:#7f0055"><strong>return</strong></span> status;
            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> getRawStatusCode()<span style="color:#7f0055"><strong>拋出</strong></span> IOException異常{
                <span style="color:#7f0055"><strong>返回</strong></span> status.value();
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public</strong></span> String getStatusText()<span style="color:#7f0055"><strong>拋出</strong></span> IOException {
                <span style="color:#7f0055"><strong> return</strong></span> status.getReasonPhrase();
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> close(){
            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公衆</strong></span>的InputStream getBody()<span style="color:#7f0055"><strong>拋出</strong></span> IOException異常{
                <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ByteArrayInputStream的(<span style="color:#2a00ff"> “回退”</span> .getBytes());
            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> HttpHeaders getHeaders(){
                HttpHeaders headers = <span style="color:#7f0055"><strong>new</strong></span> HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                <span style="color:#7f0055"><strong>返回</strong></span>標題;
            }
        };
    }
}</span></span>

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

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    customers</span>:/ customers / **</span></span>

如果要爲所有路由提供默認回退,可以創建類型的bean FallbackProvider並使getRoute方法返回*null,如以下示例所示:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>類</strong></span> MyFallbackProvider <span style="color:#7f0055"><strong>實現</strong></span> FallbackProvider {
     <em><span style="color:gray">@覆蓋</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span>字符串getRoute(){
         <span style="color:#7f0055"><strong>回報</strong></span> <span style="color:#2a00ff">“*”</span> ;
    }

    <em><span style="color:gray">@覆蓋</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span> ClientHttpResponse fallbackResponse(字符串路線,拋出的Throwable){
        <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ClientHttpResponse(){
            <em><span style="color:gray"> @覆蓋</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span>的HTTPStatus getStatusCode()<span style="color:#7f0055"><strong>拋出</strong></span> IOException異常{
                <span style="color:#7f0055"><strong>返回</strong></span> HttpStatus.OK;
            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> getRawStatusCode()<span style="color:#7f0055"><strong>拋出</strong></span> IOException異常{
                <span style="color:#7f0055"><strong>返回</strong></span> 200 ;
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public</strong></span> String getStatusText()<span style="color:#7f0055"><strong>拋出</strong></span> IOException {
                <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#2a00ff">“OK”</span> ;
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> close(){

            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公衆</strong></span>的InputStream getBody()<span style="color:#7f0055"><strong>拋出</strong></span> IOException異常{
                <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ByteArrayInputStream的(<span style="color:#2a00ff"> “回退”</span> .getBytes());
            }

            <em><span style="color:gray">@覆蓋</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> HttpHeaders getHeaders(){
                HttpHeaders headers = <span style="color:#7f0055"><strong>new</strong></span> HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                <span style="color:#7f0055"><strong>返回</strong></span>標題;
            }
        };
    }
}</span></span>

18.14 Zuul超時

如果要爲通過Zuul代理的請求配置套接字超時和讀取超時,則根據您的配置有兩個選項:

  • 如果Zuul使用服務發現,則需要使用ribbon.ReadTimeoutribbon.SocketTimeout功能區屬性配置這些超時 。

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

18.15重寫Location標題

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

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>import</strong></span> org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
...

<em><span style="color:gray">@Configuration </span></em>
<em><span style="color:gray">@EnableZuulProxy</span></em>
<span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>class</strong></span> ZuulConfig {
    <em><span style="color:gray"> @Bean</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> LocationRewriteFilter locationRewriteFilter(){
        <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> LocationRewriteFilter();
    }
}</span></span>
[警告] 警告

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

18.16啓用跨源請求

默認情況下,Zuul將所有跨源請求(CORS)路由到服務。如果你想要Zuul來處理這些請求,可以通過提供自定義WebMvcConfigurerbean 來完成:

<span style="color:#333333"><span style="color:#000000"><em><span style="color:gray">@Bean</span></em>
<span style="color:#7f0055"><strong> public</strong></span> WebMvcConfigurer corsConfigurer(){
    <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> WebMvcConfigurer(){
        <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> addCorsMappings(CorsRegistry registry){
            registry.addMapping(<span style="color:#2a00ff">“/ path-1 / **”</span>)
                    .allowedOrigins(<span style="color:#2a00ff">“https://allowed-origin.com”</span>)
                    .allowedMethods(<span style="color:#2a00ff">“GET”</span>,<span style="color:#2a00ff">“POST”</span>);
        }
    };
}</span></span>

在上面的示例中,我們允許GETPOST方法從https://allowed-origin.com開頭髮送跨源請求到端點path-1。您可以使用/**映射將CORS配置應用於特定路徑模式,也可以全局應用於整個應用程序。您可以自定義屬性:allowedOriginsallowedMethodsallowedHeadersexposedHeadersallowCredentialsmaxAge通過此配置。

18.17度量標準

對於路由請求時可能發生的任何故障,Zuul將在Actuator指標端點下提供指標。點擊即可查看這些指標/actuator/metrics。度量標準將具有格式的名稱ZUUL::EXCEPTION:errorCause:statusCode

18.18 Zuul開發人員指南

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

18.18.1 Zuul Servlet

Zuul是作爲Servlet實現的。對於一般情況,Zuul嵌入到Spring Dispatch機制中。這讓Spring MVC可以控制路由。在這種情況下,Zuul緩衝請求。如果需要在沒有緩衝請求的情況下通過Zuul(例如,對於大型文件上載),Servlet也會安裝在Spring Dispatcher之外。默認情況下,servlet的地址爲/zuul。可以使用zuul.servlet-path屬性更改此路徑。

18.18.2 Zuul RequestContext

爲了在過濾器之間傳遞信息,Zuul使用了RequestContext。其數據保存在ThreadLocal每個請求的特定數據中。有關在何處路由請求,錯誤和實際信息HttpServletRequest以及HttpServletResponse存儲在那裏的信息。在RequestContext擴展ConcurrentHashMap,所以什麼都可以存儲在上下文。FilterConstants包含Spring Cloud Netflix安裝的過濾器使用的密鑰(稍後將詳細介紹)。

18.18.3  @EnableZuulProxyvs.@EnableZuulServer

Spring Cloud Netflix安裝了許多過濾器,具體取決於使用哪個註釋來啓用Zuul。@EnableZuulProxy是一個超集@EnableZuulServer。換句話說,@EnableZuulProxy包含安裝的所有過濾器@EnableZuulServer。“ 代理 ”中的其他過濾器啓用路由功能。如果你想要一個“ 空白 ” Zuul,你應該使用@EnableZuulServer

18.18.4  @EnableZuulServer過濾器

@EnableZuulServer創建一個SimpleRouteLocator從Spring Boot配置文件加載路由定義。

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

  • 預過濾器:

    • ServletDetectionFilter:檢測請求是否通過Spring Dispatcher。設置一個鍵爲的布爾值FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
    • FormBodyWrapperFilter:解析表單數據併爲下游請求重新編碼。
    • DebugFilter:如果debug請求參數被設置,設置RequestContext.setDebugRouting()RequestContext.setDebugRequest()true。*路線過濾器:
    • SendForwardFilter:使用Servlet轉發請求RequestDispatcher。轉發位置存儲在RequestContext屬性中FilterConstants.FORWARD_TO_KEY。這對於轉發到當前應用程序中的端點很有用。
  • 過濾後:

    • SendResponseFilter:將代理請求的響應寫入當前響應。
  • 錯誤過濾器:

    • SendErrorFilter/error如果RequestContext.getThrowable()不爲null,則轉發到(默認情況下)。您可以/error通過設置error.path屬性來更改默認轉發路徑()。

18.18.5  @EnableZuulProxy過濾器

創建一個DiscoveryClientRouteLocatorDiscoveryClient(例如Eureka)以及屬性加載路徑定義的方法。路線爲每個創建serviceId從所述DiscoveryClient。添加新服務後,將刷新路由。

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

  • 預過濾器:

    • PreDecorationFilter:根據提供的內容確定路由的位置和方式RouteLocator。它還爲下游請求設置各種與代理相關的標頭。
  • 路線過濾器:

    • RibbonRoutingFilter:使用Ribbon,Hystrix和可插入HTTP客戶端發送請求。可在RequestContext屬性中找到服務ID FilterConstants.SERVICE_ID_KEY。此過濾器可以使用不同的HTTP客戶端:

      • Apache HttpClient:默認客戶端。
      • Squareup OkHttpClientv3:通過com.squareup.okhttp3:okhttp在類路徑和設置上設置庫來啓用ribbon.okhttp.enabled=true
      • Netflix Ribbon HTTP客戶端:通過設置啓用ribbon.restclient.enabled=true。此客戶端具有限制,包括它不支持PATCH方法,但它也具有內置重試。
    • SimpleHostRoutingFilter:通過Apache HttpClient向預定URL發送請求。網址可在中找到RequestContext.getRouteHost()

18.18.6自定義Zuul過濾器示例

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

本節包括以下示例:

如何編寫預過濾器

預過濾器RequestContext在下游過濾器中設置數據。主要用例是設置路由過濾器所需的信息。以下示例顯示了Zuul預過濾器:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>類</strong></span> QueryParamPreFilter <span style="color:#7f0055"><strong>延伸</strong></span> ZuulFilter {
	 <em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		 <span style="color:#7f0055"><strong>返回</strong></span> PRE_DECORATION_FILTER_ORDER - 1 ; <span style="color:#3f5f5f"><em>//在PreDecoration之前運行</em></span>
	}

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> String filterType(){
		<span style="color:#7f0055"><strong> return</strong></span> PRE_TYPE;
	}

	<em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布爾</strong></span> shouldFilter(){
		RequestContext ctx = RequestContext.getCurrentContext();
		<span style="color:#7f0055"><strong>return</strong></span>!ctx.containsKey(FORWARD_TO_KEY)<span style="color:#3f5f5f"><em>//過濾器已經轉發了</em></span> 
				&&!ctx.containsKey(SERVICE_ID_KEY); <span style="color:#3f5f5f"><em>//過濾器已經確定了serviceId</em></span>
	}
    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> Object run(){
        RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		<span style="color:#7f0055"><strong>if</strong></span>(request.getParameter(<span style="color:#2a00ff">“sample”</span>)!= null){
		     <span style="color:#3f5f5f"><em>//將serviceId放在`</em></span> 
    		RequestContext`中ctx.put(SERVICE_ID_KEY,request.getParameter(<span style="color:#2a00ff">“foo”</span>));
    	}
        <span style="color:#7f0055"><strong>return</strong></span> null;
    }
}</span></span>

前面的過濾器SERVICE_ID_KEYsample請求參數填充。在實踐中,您不應該進行這種直接映射。相反,應該從相應的值中查找服務ID sample

現在SERVICE_ID_KEY已填充,PreDecorationFilter不運行並RibbonRoutingFilter運行。

[小費]

如果您想要路由到完整的URL,請致電ctx.setRouteHost(url)

要修改路由過濾器轉發的路徑,請設置REQUEST_URI_KEY

如何編寫路由過濾器

路由過濾器在預過濾器後運行並向其他服務發出請求。這裏的大部分工作是將請求和響應數據轉換爲客戶端所需的模型。以下示例顯示了Zuul路由過濾器:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>類</strong></span> OkHttpRoutingFilter <span style="color:#7f0055"><strong>擴展了</strong></span> ZuulFilter {
	 <em><span style="color:gray">@Autowired</span></em>
	<span style="color:#7f0055"><strong> private</strong></span> ProxyRequestHelper helper;

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> String filterType(){
		<span style="color:#7f0055"><strong> return</strong></span> ROUTE_TYPE;
	}

	<em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		<span style="color:#7f0055"><strong>返回</strong></span> SIMPLE_HOST_ROUTING_FILTER_ORDER - 1 ;
	}

	<em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布爾</strong></span> shouldFilter(){
		<span style="color:#7f0055"><strong>返回</strong></span> RequestContext.getCurrentContext()。getRouteHost()!= NULL
				&& RequestContext.getCurrentContext()。sendZuulResponse();
	}

    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> Object run(){
		OkHttpClient httpClient = <span style="color:#7f0055"><strong>new</strong></span> OkHttpClient.Builder()
				 <span style="color:#3f5f5f"><em>//自定義</em></span>
				。建立();

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

		String method = request.getMethod();

		String uri = <span style="color:#7f0055"><strong>this</strong></span> .helper.buildZuulRequestURI(request);

		Headers.Builder headers = <span style="color:#7f0055"><strong>new</strong></span> Headers.Builder();
		枚舉<String> headerNames = request.getHeaderNames();
		<span style="color:#7f0055"><strong>while</strong></span>(headerNames.hasMoreElements()){
			String name = headerNames.nextElement();
			枚舉<String> values = request.getHeaders(name);

			<span style="color:#7f0055"><strong>while</strong></span>(values.hasMoreElements()){
				String value = values.nextElement();
				headers.add(name,value);
			}
		}

		InputStream inputStream = request.getInputStream();

		RequestBody requestBody = null;
		<span style="color:#7f0055"><strong>if</strong></span>(inputStream!= null && HttpMethod.permitsRequestBody(method)){
			MediaType mediaType = null;
			<span style="color:#7f0055"><strong>if</strong></span>(headers.get(<span style="color:#2a00ff">“Content-Type”</span>)!= null){
				mediaType = MediaType.parse(headers.get(<span style="color:#2a00ff">“Content-Type”</span>));
			}
			requestBody = RequestBody.create(mediaType,StreamUtils.copyToByteArray(inputStream));
		}

		Request.Builder builder = <span style="color:#7f0055"><strong>new</strong></span> Request.Builder()
				.headers(headers.build())
				.URL(URI)
				.method(method,requestBody);

		響應響應= httpClient.newCall(builder.build())。execute();

		LinkedMultiValueMap <String,String> responseHeaders = <span style="color:#7f0055"><strong>new</strong></span> LinkedMultiValueMap <>();

		<span style="color:#7f0055"><strong>for</strong></span>(Map.Entry <String,List <String >> entry:response.headers()。toMultimap()。entrySet()){
			responseHeaders.put(entry.getKey(),entry.getValue());
		}

		<span style="color:#7f0055"><strong>這個</strong></span> .helper.setResponse(response.code(),response.body()。byteStream(),
				responseHeaders響應);
		context.setRouteHost(NULL); <span style="color:#3f5f5f"><em>//阻止SimpleHostRoutingFilter運行</em></span>
		<span style="color:#7f0055"><strong>return</strong></span> null;
    }
}</span></span>

前面的過濾器將Servlet請求信息轉換爲OkHttp3請求信息,執行HTTP請求,並將OkHttp3響應信息轉換爲Servlet響應。

如何編寫後置過濾器

後置過濾器通常會操縱響應。下面過濾器添加一個隨機UUID作爲X-Sample標頭:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>類</strong></span> AddResponseHeaderFilter <span style="color:#7f0055"><strong>延伸</strong></span> ZuulFilter {
	 <em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span>字符串過濾式(){
		 <span style="color:#7f0055"><strong>返回</strong></span> POST_TYPE;
	}

	<em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		<span style="color:#7f0055"><strong>返回</strong></span> SEND_RESPONSE_FILTER_ORDER - 1 ;
	}

	<em><span style="color:gray">@覆蓋</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布爾</strong></span> shouldFilter(){
		<span style="color:#7f0055"><strong>返回</strong></span>真;
	}

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> Object run(){
		RequestContext context = RequestContext.getCurrentContext();
    	HttpServletResponse servletResponse = context.getResponse();
		servletResponse.addHeader(<span style="color:#2a00ff">“X-Sample”</span>,UUID.randomUUID()。toString());
		<span style="color:#7f0055"><strong>return</strong></span> null;
	}
}</span></span>
[注意]

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

18.18.7 Zuul錯誤如何工作

如果在Zuul過濾器生命週期的任何部分期間拋出異常,則執行錯誤過濾器。SendErrorFilter只有在RequestContext.getThrowable()沒有的情況下才會運行null。然後,它javax.servlet.error.*在請求中設置特定屬性,並將請求轉發到Spring Boot錯誤頁面。

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

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

application.yml。 

<span style="color:#333333"><span style="color:#000000">zuul:
  帶:
    渴望負荷:
      啓用:true</span></span>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章