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.maxTotalConnections
和zuul.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
您可以使用regexmapper
在serviceId
和路由之間提供約定。 它使用正則表達式命名組從serviceId
中提取變量并將它們注入路由模式摇锋,如以下示例所示:
ApplicationConfiguration.java.
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
上面的示例表示myusers-v1
的serviceId
映射到路由/v1/myusers/**
。 接受任何正則表達式站超,但所有命名組必須同時出現(xiàn)在servicePattern
和routePattern
中荸恕。 如果servicePattern
與serviceId
不匹配,則使用默認行為死相。 在前面的示例中融求,myusers
的serviceId
映射到/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
。 要使用RestClient
或okhttp3.OkHttpClient
悔雹,請分別設置ribbon.restclient.enabled = true
或ribbon.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-Cookie
和Cookie
設置為不屬于您的域的路由的敏感標頭屑埋。 即使是屬于您域名的路由豪筝,也要在讓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
}
}
對/routes
的POST
強制刷新現(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.ReadTimeout
和ribbon.SocketTimeout
功能區(qū)屬性配置這些超時俄烁。
如果通過指定URL配置了Zuul路由,則需要使用zuul.host.connect-timeout-millis
和zuul.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
中。 有關在哪里路由請求舶胀,錯誤以及實際的HttpServletRequest
和HttpServletResponse
的信息都存儲在那里概说。 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
: 使用ServletRequestDispatcher
轉發(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方法捞蚂,但它也具有內置重試妇押。
- Apache
-
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