spring cloud 網(wǎng)關宣增,依賴于netflix 下的zuul 組件
zuul 的流程是,自定義 了ZuulServletFilter和zuulServlet兩種方式霞幅,讓開發(fā)者可以去實現(xiàn)蒜撮,并調(diào)用
先來看下ZuulServletFilter
的實現(xiàn)片段
@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();
}
}
從上面的代碼可以看到,比較關心的是preRouting
、routing
,postRouting
三個方法 前方,這三個方法會調(diào)用 注冊為ZuulFilter
的子類滓侍,首先來看下這三個方法
preRouting: 是路由前會做一些內(nèi)容
routing():開始路由事項
postRouting:路由結(jié)束蒋川,不管是否有錯誤都會經(jīng)過該方法
那這三個方法是怎么和ZuulFilter
聯(lián)系在一起的呢?
先來分析下 preRouting:
void postRouting() throws ZuulException {
zuulRunner.postRoute();
}
同時 ZuulRunner
再來調(diào)用
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
最終調(diào)用 FilterProcessor
的 runFilters
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
看到了runFilters 是通過 filterType(pre ,route ,post )來過濾出已經(jīng)注冊的 ZuulFilter:
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
//通過sType獲取 zuulFilter的列表
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
再來看下 ZuulFilter
的定義
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();
/**
* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
*
* @return true by default
*/
public boolean isStaticFilter() {
return true;
}
只列出了一部分字段撩笆,但可以看到filterType和filterOrder兩個字段捺球,這兩個分別是指定filter是什么類型缸浦,排序
這兩個決定了實現(xiàn)的ZuulFilter會在什么階段被執(zhí)行,按什么順序執(zhí)行
當選擇好已經(jīng)注冊的ZuulFilter后氮兵,會調(diào)用ZuulFilter的runFilter
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
其中run 是一個ZuulFilter
的一個抽象方法
public interface IZuulFilter {
/**
* a "true" return from this method means that the run() method should be invoked
*
* @return true if the run() method should be invoked. false will not invoke the run() method
*/
boolean shouldFilter();
/**
* if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
*
* @return Some arbitrary artifact may be returned. Current implementation ignores it.
*/
Object run();
}
所以裂逐,實現(xiàn)ZuulFilter的子類要重寫 run方法,我們來看下 其中一個階段的實現(xiàn) PreDecorationFilter
這個類是Spring Cloud封裝的在使用Zuul 作為轉(zhuǎn)發(fā)的代碼服務器時進行封裝的對象泣栈,目的是為了決定當前的要轉(zhuǎn)發(fā)的請求是按ServiceId絮姆,Http請求,還是forward來作轉(zhuǎn)發(fā)
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
String location = route.getLocation();
if (location != null) {
ctx.put("requestURI", route.getPath());
ctx.put("proxy", route.getId());
if (!route.isCustomSensitiveHeaders()) {
this.proxyRequestHelper
.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
}
else {
this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
}
if (route.getRetryable() != null) {
ctx.put("retryable", route.getRetryable());
}
// 如果配置的轉(zhuǎn)發(fā)地址是http開頭秩霍,會設置 RouteHost
if (location.startsWith("http:") || location.startsWith("https:")) {
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader("X-Zuul-Service", location);
}
// 如果配置的轉(zhuǎn)發(fā)地址forward篙悯,則會設置forward.to
else if (location.startsWith("forward:")) {
ctx.set("forward.to",
StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
ctx.setRouteHost(null);
return null;
}
else {
// 否則以serviceId進行轉(zhuǎn)發(fā)
// set serviceId for use in filters.route.RibbonRequest
ctx.set("serviceId", location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
}
if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
String remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
}
else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
xforwardedfor += ", " + remoteAddr;
}
ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader("Host", toHostHeader(ctx.getRequest()));
}
}
}
else {
log.warn("No route found for uri: " + requestURI);
String fallBackUri = requestURI;
String fallbackPrefix = this.dispatcherServletPath; // default fallback
// servlet is
// DispatcherServlet
if (RequestUtils.isZuulServletRequest()) {
// remove the Zuul servletPath from the requestUri
log.debug("zuulServletPath=" + this.properties.getServletPath());
fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
log.debug("Replaced Zuul servlet path:" + fallBackUri);
}
else {
// remove the DispatcherServlet servletPath from the requestUri
log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
}
if (!fallBackUri.startsWith("/")) {
fallBackUri = "/" + fallBackUri;
}
String forwardURI = fallbackPrefix + fallBackUri;
forwardURI = forwardURI.replaceAll("http://", "/");
ctx.set("forward.to", forwardURI);
}
return null;
}
這個前置處理,是為了后面決定以哪種ZuulFilter來處理當前的請求 铃绒,如 SimpleHostRoutingFilter
鸽照,這個的filterType是post
,當 ``PreDecorationFilter設置了requestContext中的 RouteHost,如
SimpleHostRoutingFilter
中的判斷
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
在 SimpleHostRoutingFilter
中的run中颠悬,真正實現(xiàn)地址轉(zhuǎn)發(fā)的內(nèi)容矮燎,其實質(zhì)是調(diào)用 httpClient進行請求
@Override
public Object run() {
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);
}
return null;
}
最后如果是成功能,會調(diào)用 注冊 為post
的ZuulFilter
,目前有兩個 SendErrorFilter
和 SendResponseFilter
這兩個了赔癌,一個是處理錯誤诞外,一個是處理成功的結(jié)果