阅读过SpringCloud基于zuul的实现,大体能够了解其中主要逻辑,但是直白的来讲不知如何下笔。我们通过问题的方式,一步一步的剖析其中的细节。
看这块内容前,你要对Spring MVC中的DispatchServlet,HandlerMapping及其相关子类有所了解。简单说一下:
DispatchServlet通过handlerMapping的实现类,把所有controller加载为handler,这样每次过来请求时,根据url寻找对应controller(handler),以此响应用户。
那么下面就是一个小高潮,
先暂且不管Route。ZuulHandlerMapping和ZuulController是SpringCloudZuul实现了的两个类。这里的ZuulHandlerMapping将ZuulController与所有的route.getFullPath()对应起来,意思就是所有能访问到的路径,都会先走zuulController这个handler。
那么我们接着来看ZuulController的实现,
ZuulController继承了ServletWrappingController,由ServletWrappingController可知,所有的请求都会走到servletInstance的service方法。那么这个servletInstance就是com.netflix.zuul.http.ZuulServlet。看过我上篇文章NetFlix Zuul 源码阅读。
1.至此,你应该能够理解为什么所有的请求都能被Filter处理?
那么,我们接着看,你还记不记得上文中的Route类了?上文中有一个操作是
private void registerHandlers() {
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
registerHandler(route.getFullPath(), this.zuul);
}
}
}
这里获取了所有的route.fullpath注册到了zuulController。这里边的Route其实就是一个路由信息的类,你看他的字段就可以得知他的作用了。
比他更关键的是SimpleRouteLocator和DiscoveryClientRouteLocator,SimpleRouteLocator能够将你配置文件中配置的所有路由信息转为Route对象的一个列表,这样你就能知道配置文件声明的所有路由表啦。那么如果你接入了注册中心,顾名思义,DiscoveryClientRouteLocator则是能够通过DiscoveryClient去注册中心获取到所有能访问的api路径。
so,通俗点来说,Route就是访问的一个接口路径+location地址的一个信息,RouteLocator更像是统一加载系统中的所有的接口路径信息的匹配器。
好,我们接下来就是另外一个小高潮。游泳健身(pre---filter)了解一下:
这里主要看PreDecorationFilter这个过滤器:
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
//看见了没?这里有没有根据当前请求的requestURI来找到对应的路由信息
Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
String location = route.getLocation();
if (location != null) {
ctx.put(REQUEST_URI_KEY, route.getPath());
ctx.put(PROXY_KEY, 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_KEY, route.getRetryable());
}
if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader(SERVICE_HEADER, location);
}
else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
ctx.set(FORWARD_TO_KEY,
StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
ctx.setRouteHost(null);
return null;
}
else {
// set serviceId for use in filters.route.RibbonRequest
ctx.set(SERVICE_ID_KEY, location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
}
if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
String remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
}
else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
xforwardedfor += ", " + remoteAddr;
}
ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
}
}
}
PreDecorationFilter主要是在请求来到时,将需要准备的信息放到线程内RequestContext。然后为下一步的route类型的filter准备数据。
看一下SimpleHostRoutingFilter的实现:
这说明这个filter生效的原因是:线程存储中有routehost这个属性并且在sendZuulResponse()为true。sendZuulResponse这个家伙主要在pre类型的filter中使用,如果sendZuulResponse未false,说明不用继续通过其他的filter直接返回结果。
这里很明显的观察到,CloseableHttpClient对request进行了转发请求,并将CloseableHttpResponse返回。
再看一下RibbonRoutingFilter
只要有注册中心服务中的serviceId,routehost==null,sendZuulResponse()为true,该filter会生效。