Zuul是一个网关组件,是微服务的入口,网关会根据配置将请求转发给指定的服务。本章分析Zuul组件是如何实现请求过滤和转发的
参考源码:<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
1、过滤
spring-cloud-netflix-zuul依赖包下有一个spring.factories文件,文件内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
根据springboot自动配置的原理可知,ZuulServerAutoConfiguration会被标记成了一个自动配置类,其中和Zuul组件核心功能相关的是ZuulServlet
@Bean
@ConditionalOnMissingBean(
name = {"zuulServlet"}
)
@ConditionalOnProperty(
name = {"zuul.use-filter"},
havingValue = "false",
matchIfMissing = true
)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(), new String[]{this.zuulProperties.getServletPattern()});
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
ZuulServlet会处理所有的请求,在service方法内依次调用preRoute、route、postRoute方法,分别对应前置,路由和后置处理器
public class ZuulServlet extends HttpServlet {
public ZuulServlet() {
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true");
this.zuulRunner = new ZuulRunner(bufferReqs);
}
// 处理请求
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
this.route();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
void preRoute() throws ZuulException {
this.zuulRunner.preRoute();
}
}
以preRoute方法为例,zuul网关会对请求做前置处理,遍历ZuulFilter接口的实现类执行runFilter方法
调用链:
-> ZuulServlet.preRoute
-> ZuulRunner.preRoute
-> FilterProcessor.preRoute
-> runFilters
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
// 获取指定类型的filter
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
// 遍历ZuulFilter实现类,前置处理请求
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= (Boolean)result;
}
}
}
return bResult;
}
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
...
// 调用ZuulFilter实现类的runFilter方法
ZuulFilterResult result = filter.runFilter();
...
}
public ZuulFilterResult runFilter() {
...
// run方法来自IZuulFilter接口
Object res = this.run();
...
}
请求传递给了ZuulFitler的实现类,遍历这些实现类时调用它们的run方法,请求过滤的逻辑一般就写在run方法中
2、转发
2.1 获取路由信息
PreDecorationFilter是zuul依赖包下的一个Filter,它实现了ZuulFilter抽象类,会在处理请求时被调用
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
// 获取请求的路由信息
Route route = this.routeLocator.getMatchingRoute(requestURI);
String location;
...
}
this.routeLocator指向的是SimpleRouteLocator对象,查看它的getMatchingRoute方法
public Route getMatchingRoute(final String path) {
return this.getSimpleMatchingRoute(path);
}
protected Route getSimpleMatchingRoute(final String path) {
if (log.isDebugEnabled()) {
log.debug("Finding route for path: " + path);
}
this.getRoutesMap();
if (log.isDebugEnabled()) {
log.debug("servletPath=" + this.dispatcherServletPath);
log.debug("zuulServletPath=" + this.zuulServletPath);
log.debug("RequestUtils.isDispatcherServletRequest()=" + RequestUtils.isDispatcherServletRequest());
log.debug("RequestUtils.isZuulServletRequest()=" + RequestUtils.isZuulServletRequest());
}
String adjustedPath = this.adjustPath(path);
// 获取路由信息
ZuulProperties.ZuulRoute route = this.getZuulRoute(adjustedPath);
return this.getRoute(route, adjustedPath);
}
protected ZuulProperties.ZuulRoute getZuulRoute(String adjustedPath) {
// 匹配路由地址
if (!this.matchesIgnoredPatterns(adjustedPath)) {
// 遍历路由map
Iterator var2 = this.getRoutesMap().entrySet().iterator();
while(var2.hasNext()) {
Map.Entry<String, ZuulProperties.ZuulRoute> entry = (Map.Entry)var2.next();
String pattern = (String)entry.getKey();
log.debug("Matching pattern:" + pattern);
if (this.pathMatcher.match(pattern, adjustedPath)) {
return (ZuulProperties.ZuulRoute)entry.getValue();
}
}
}
return null;
}
原始的路由信息来源于SimpleRouteLocator的routes成员,是一个map对象,调用getRoutesMap方法时如果routes对象为空会对它进行初始化
protected Map<String, ZuulProperties.ZuulRoute> getRoutesMap() {
if (this.routes.get() == null) {
// 初始化routes
this.routes.set(this.locateRoutes());
}
return (Map)this.routes.get();
}
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap();
// 获取配置文件中的路由信息
Iterator var2 = this.properties.getRoutes().values().iterator();
while(var2.hasNext()) {
ZuulProperties.ZuulRoute route = (ZuulProperties.ZuulRoute)var2.next();
routesMap.put(route.getPath(), route);
}
return routesMap;
}
配置文件中的路由信息封装在了ZuulProperties对象内,供初始化使用。拿到route后,PreDecorationFilter会在请求上下文中放入路由信息,这些路由信息会为后续ZuulFilter转发服务提供依据
public Object run() {
...
if (route != null) {
location = route.getLocation();
if (location != null) {
ctx.put("requestURI", route.getPath());
ctx.put("proxy", route.getId());
...
if (route.getRetryable() != null) {
ctx.put("retryable", route.getRetryable());
}
if (!location.startsWith("http:") && !location.startsWith("https:")) {
if (location.startsWith("forward:")) {
ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
ctx.setRouteHost((URL)null);
return null;
}
// 置入serviceId
ctx.set("serviceId", location);
ctx.setRouteHost((URL)null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
} else {
// 置入路由url
ctx.setRouteHost(this.getUrl(location));
ctx.addOriginResponseHeader("X-Zuul-Service", location);
}
if (this.properties.isAddProxyHeaders()) {
this.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)) {
xforwardedfor = xforwardedfor + ", " + remoteAddr;
}
ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader("Host", this.toHostHeader(ctx.getRequest()));
}
}
}
...
}
2.2 具体转发
Zuul支持以下三种类型的转发
转发类型 | ZuulFilter实现类 | 配置文件案例 |
serviceId转发 | RibbonRoutingFilter | zuul.routes.test-service.serviceId=test-service |
url转发 | SimpleHostRoutingFilter | zuul.routes.test-service.url=http://localhost:8080/ |
forward转发 | SendForwardFilter | forward:/test |
以RibbonRoutingFilter为例,当请求上下文中包含serviceId时,会调用RibbonRoutingFilter对象的run方法转发请求,具体的请求和负载均衡相关,就不加赘述了
// 判断是否启用过滤器
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// 上下文包含serviceId
return ctx.getRouteHost() == null && ctx.get("serviceId") != null
&& ctx.sendZuulResponse();
}
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders(new String[0]);
try {
// 转发请求,获取响应信息
RibbonCommandContext commandContext = this.buildCommandContext(context);
ClientHttpResponse response = this.forward(commandContext);
this.setResponse(response);
return response;
} catch (ZuulException var4) {
throw new ZuulRuntimeException(var4);
} catch (Exception var5) {
throw new ZuulRuntimeException(var5);
}
}
3、总结
Zuul组件以ZuulServlet为核心处理请求,将处理请求的过程分化成了请求前置、路由、后置三个阶段,而具体的处理逻辑则下放在ZuulFilter的实现类中