上一篇:《Spring Cloud入門(mén)教程(五):API服務(wù)網(wǎng)關(guān)(Zuul) 上》
本人和同事撰寫(xiě)的《Spring Cloud微服務(wù)架構(gòu)開(kāi)發(fā)實(shí)戰(zhàn)》一書(shū)也在京東、當(dāng)當(dāng)?shù)葧?shū)店上架,大家可以點(diǎn)擊這里前往購(gòu)買(mǎi),多謝大家支持和捧場(chǎng)!
Zuul給我們的第一印象通常是這樣:它包含了對(duì)請(qǐng)求的路由和過(guò)濾兩個(gè)功能,其中路由功能負(fù)責(zé)將外部請(qǐng)求轉(zhuǎn)發(fā)到具體的微服務(wù)實(shí)例上,是實(shí)現(xiàn)外部訪問(wèn)統(tǒng)一入口的基礎(chǔ)。過(guò)濾器功能則負(fù)責(zé)對(duì)請(qǐng)求的處理過(guò)程進(jìn)行干預(yù),是實(shí)現(xiàn)請(qǐng)求校驗(yàn)、服務(wù)聚合等功能的基礎(chǔ)。然而實(shí)際上,路由功能在真正運(yùn)行時(shí),它的路由映射和請(qǐng)求轉(zhuǎn)發(fā)都是由幾個(gè)不同的過(guò)濾器完成的。其中,路由映射主要是通過(guò)PRE類(lèi)型的過(guò)濾器完成,它將請(qǐng)求路徑與配置的路由規(guī)則進(jìn)行匹配,以找到需要轉(zhuǎn)發(fā)的目標(biāo)地址。而請(qǐng)求轉(zhuǎn)發(fā)的部分則是由Route類(lèi)型的過(guò)濾器來(lái)完成,對(duì)PRE類(lèi)型過(guò)濾器獲得的路由地址進(jìn)行轉(zhuǎn)發(fā)。所以,過(guò)濾器可以說(shuō)是Zuul實(shí)現(xiàn)API網(wǎng)關(guān)功能最重要的核心部件,每一個(gè)進(jìn)入Zuul的請(qǐng)求都會(huì)經(jīng)過(guò)一系列的過(guò)濾器處理鏈得到請(qǐng)求響應(yīng)并返回給客戶(hù)端。
1. 過(guò)濾器簡(jiǎn)介
1.1 過(guò)濾器特性
Zuul過(guò)濾器的關(guān)鍵特性有:
- Type: 定義在請(qǐng)求執(zhí)行過(guò)程中何時(shí)被執(zhí)行;
- Execution Order: 當(dāng)存在多個(gè)過(guò)濾器時(shí),用來(lái)指示執(zhí)行的順序,值越小就會(huì)越早執(zhí)行;
- Criteria: 執(zhí)行的條件,即該過(guò)濾器何時(shí)會(huì)被觸發(fā);
- Action: 具體的動(dòng)作。
過(guò)濾器之間并不會(huì)直接進(jìn)行通信,而是通過(guò)RequestContext來(lái)共享信息,RequestContext是線程安全的。
對(duì)應(yīng)上面Zuul過(guò)濾器的特性,我們?cè)趯?shí)現(xiàn)一個(gè)自定義過(guò)濾器時(shí)需要實(shí)現(xiàn)的方法有:
/**
* Zuul Pre-Type Filter
*
* @author CD826(CD826Dong@gmail.com)
* @since 1.0.0
*/
public class PreTypeZuulFilter extends ZuulFilter {
protected Logger logger = LoggerFactory.getLogger(PreTypeZuulFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
this.logger.info("This is pre-type zuul filter.");
return null;
}
}
其中:
- filterType()方法是該過(guò)濾器的類(lèi)型;
- filterOrder()方法返回的是執(zhí)行順序;
- shouldFilter()方法則是判斷是否需要執(zhí)行該過(guò)濾器;
- run()則是所要執(zhí)行的具體過(guò)濾動(dòng)作。
1.2 過(guò)濾器類(lèi)型
Zuul中定義了四種標(biāo)準(zhǔn)的過(guò)濾器類(lèi)型,這些過(guò)濾器類(lèi)型對(duì)應(yīng)于請(qǐng)求的典型生命周期。
-
PRE過(guò)濾器: 在請(qǐng)求被路由之前調(diào)用, 可用來(lái)實(shí)現(xiàn)身份驗(yàn)證、在集群中選擇請(qǐng)求的微服務(wù)、記錄調(diào)試信息等; -
ROUTING過(guò)濾器: 在路由請(qǐng)求時(shí)候被調(diào)用; -
POST過(guò)濾器: 在路由到微服務(wù)以后執(zhí)行, 可用來(lái)為響應(yīng)添加標(biāo)準(zhǔn)的HTTP Header、收集統(tǒng)計(jì)信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶(hù)端等; -
ERROR過(guò)濾器: 在處理請(qǐng)求過(guò)程時(shí)發(fā)生錯(cuò)誤時(shí)被調(diào)用。
Zuul過(guò)濾器的類(lèi)型其實(shí)也是Zuul過(guò)濾器的生命周期,通過(guò)下面這張圖來(lái)了解它們的執(zhí)行過(guò)程。

除了上面給出的四種默認(rèn)的過(guò)濾器類(lèi)型之外,Zuul還允許我們創(chuàng)建自定義的過(guò)濾器類(lèi)型。例如,我們可以定制一種STATIC類(lèi)型的過(guò)濾器,直接在Zuul中生成響應(yīng),而不將請(qǐng)求轉(zhuǎn)發(fā)到后端的微服務(wù)。
1.3 自定義過(guò)濾器示例代碼
筆者自己沒(méi)有單獨(dú)構(gòu)建一個(gè)過(guò)濾器示例的場(chǎng)景,我們看一下官方給出的幾個(gè)示例。
PRE類(lèi)型示例
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("foo") != null) {
// put the serviceId in `RequestContext`
ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}
這個(gè)是官方給出的一個(gè)示例,從請(qǐng)求的參數(shù)foo中獲取需要轉(zhuǎn)發(fā)到的服務(wù)Id。當(dāng)然官方并不建議我們這么做,這里只是方便給出一個(gè)示例而已。
ROUTE類(lèi)型示例
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;
}
}
這個(gè)示例是將HTTP請(qǐng)求轉(zhuǎn)換為使用OkHttp3進(jìn)行請(qǐng)求,并將服務(wù)端的返回轉(zhuǎn)換成Servlet的響應(yīng)。
注意: 官方說(shuō)這僅僅是一個(gè)示例,功能不一定正確。
POST類(lèi)型示例
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-Foo", UUID.randomUUID().toString());
return null;
}
}
這個(gè)示例很簡(jiǎn)單就是返回的頭中增加一個(gè)隨機(jī)生成X-Foo。
1.4 禁用過(guò)濾器
只需要在application.properties(或yml)中配置需要禁用的filter,格式為:zuul.[filter-name].[filter-type].disable=true。如:
zuul.FormBodyWrapperFilter.pre.disable=true
1.5 關(guān)于Zuul過(guò)濾器Error的一點(diǎn)補(bǔ)充
當(dāng)Zuul在執(zhí)行過(guò)程中拋出一個(gè)異常時(shí),error過(guò)濾器就會(huì)被執(zhí)行。而SendErrorFilter只有在RequestContext.getThrowable()不為空的時(shí)候才會(huì)執(zhí)行。它將錯(cuò)誤信息設(shè)置到請(qǐng)求的javax.servlet.error.*屬性中,并轉(zhuǎn)發(fā)Spring Boot的錯(cuò)誤頁(yè)面。
Zuul過(guò)濾器實(shí)現(xiàn)的具體類(lèi)是ZuulServletFilter,其核心代碼如下:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
try {
preRouting();
} catch (ZuulException e) {
error(e);
postRouting();
return;
}
// Only forward onto to the chain if a zuul response is not being sent
if (!RequestContext.getCurrentContext().sendZuulResponse()) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
try {
routing();
} catch (ZuulException e) {
error(e);
postRouting();
return;
}
try {
postRouting();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
從這段代碼中可以看出,error可以在所有階段捕獲異常后執(zhí)行,但是如果post階段中出現(xiàn)異常被error處理后則不再回到post階段執(zhí)行,也就是說(shuō)需要保證在post階段不要有異常,因?yàn)橐坏┯挟惓:缶蜁?huì)造成該過(guò)濾器后面其它post過(guò)濾器將不再被執(zhí)行。
一個(gè)簡(jiǎn)單的全局異常處理的方法是: 添加一個(gè)類(lèi)型為error的過(guò)濾器,將錯(cuò)誤信息寫(xiě)入RequestContext,這樣SendErrorFilter就可以獲取錯(cuò)誤信息了。代碼如下:
public class GlobalErrorFilter extends ZuulFilter {
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return 10;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
Throwable throwable = context.getThrowable();
this.logger.error("[ErrorFilter] error message: {}", throwable.getCause().getMessage());
context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
context.set("error.exception", throwable.getCause());
return null;
}
}
2. @EnableZuulServer VS. @EnableZuulProxy
Zuul為我們提供了兩個(gè)主應(yīng)用注解: @EnableZuulServer和@EnableZuulProxy,其中@EnableZuulProxy包含@EnableZuulServer的功能,而且還加入了@EnableCircuitBreaker和@EnableDiscoveryClient。當(dāng)我們需要運(yùn)行一個(gè)沒(méi)有代理功能的Zuul服務(wù),或者有選擇的開(kāi)關(guān)部分代理功能時(shí),那么需要使用 @EnableZuulServer 替代 @EnableZuulProxy。 這時(shí)候我們可以添加任何 ZuulFilter類(lèi)型實(shí)體類(lèi)都會(huì)被自動(dòng)加載,這和上一篇使用@EnableZuulProxy是一樣,但不會(huì)自動(dòng)加載任何代理過(guò)濾器。
2.1 @EnableZuulServer默認(rèn)過(guò)濾器
當(dāng)我們使用@EnableZuulServer時(shí),默認(rèn)所加載的過(guò)濾器有:
2.1.1 PRE類(lèi)型過(guò)濾器
- ServletDetectionFilter
該過(guò)濾器是最先被執(zhí)行的。其主要用來(lái)檢查當(dāng)前請(qǐng)求是通過(guò)Spring的DispatcherServlet處理運(yùn)行的,還是通過(guò)ZuulServlet來(lái)處理運(yùn)行的。判斷結(jié)果會(huì)保存在isDispatcherServletRequest中,值類(lèi)型為布爾型。
- FormBodyWrapperFilter
該過(guò)濾器的目的是將符合要求的請(qǐng)求體包裝成FormBodyRequestWrapper對(duì)象,以供后續(xù)處理使用。
- DebugFilter
PRE類(lèi)型過(guò)濾器。當(dāng)請(qǐng)求參數(shù)中設(shè)置了debug參數(shù)時(shí),該過(guò)濾器會(huì)將當(dāng)前請(qǐng)求上下文中的RequestContext.setDebugRouting()和RequestContext.setDebugRequest()設(shè)置為true,這樣后續(xù)的過(guò)濾器可以根據(jù)這兩個(gè)參數(shù)信息定義一些debug信息,當(dāng)生產(chǎn)環(huán)境出現(xiàn)問(wèn)題時(shí),我們就可以通過(guò)增加該參數(shù)讓后臺(tái)打印出debug信息,以幫助我們進(jìn)行問(wèn)題分析。對(duì)于請(qǐng)求中的debug參數(shù)的名稱(chēng),我們可以通過(guò)zuul.debug.parameter進(jìn)行自定義。
2.1.2 ROUTE類(lèi)型過(guò)濾器
- SendForwardFilter
該過(guò)濾器只對(duì)請(qǐng)求上下文中存在forward.to(FilterConstants.FORWARD_TO_KEY)參數(shù)的請(qǐng)求進(jìn)行處理。即處理之前我們路由規(guī)則中forward的本地跳轉(zhuǎn)。
2.1.3 POST類(lèi)型過(guò)濾器
- SendResponseFilter
該過(guò)濾器就是對(duì)代理請(qǐng)求所返回的響應(yīng)進(jìn)行封裝,然后作為本次請(qǐng)求的相應(yīng)發(fā)送回給請(qǐng)求者。
2.1.4 Error類(lèi)型過(guò)濾器
- SendErrorFilter
該過(guò)濾器就是判斷當(dāng)前請(qǐng)求上下文中是否有異常信息(RequestContext.getThrowable()不為空),如果有則默認(rèn)轉(zhuǎn)發(fā)到/error頁(yè)面,我們也可以通過(guò)設(shè)置error.path來(lái)自定義錯(cuò)誤頁(yè)面。
2.2 @EnableZuulProxy默認(rèn)過(guò)濾器
@EnableZuulProxy則在上面的基礎(chǔ)上增加以下過(guò)濾器:
2.2.1 PRE類(lèi)型過(guò)濾器
- PreDecorationFilter
該過(guò)濾器根據(jù)提供的RouteLocator確定路由到的地址,以及怎樣去路由。該路由器也可為后端請(qǐng)求設(shè)置各種代理相關(guān)的header。
2.2.2 ROUTE類(lèi)型過(guò)濾器
- RibbonRoutingFilter
該過(guò)濾器會(huì)針對(duì)上下文中存在serviceId(可以通過(guò)RequestContext.getCurrentContext().get(“serviceId”)獲取)的請(qǐng)求進(jìn)行處理,使用Ribbon、Hystrix和可插拔的HTTP客戶(hù)端發(fā)送請(qǐng)求,并將服務(wù)實(shí)例的請(qǐng)求結(jié)果返回。也就是之前所說(shuō)的只有當(dāng)我們使用serviceId配置路由規(guī)則時(shí)Ribbon和Hystrix方才生效。
- SimpleHostRoutingFilter
該過(guò)濾器檢測(cè)到routeHost參數(shù)(可通過(guò)RequestContext.getRouteHost()獲取)設(shè)置時(shí),就會(huì)通過(guò)Apache HttpClient向指定的URL發(fā)送請(qǐng)求。此時(shí),請(qǐng)求不會(huì)使用Hystrix命令進(jìn)行包裝,所以這類(lèi)請(qǐng)求也就沒(méi)有線程隔離和斷路器保護(hù)。
你可以到這里下載本篇的代碼。
