目录
前言
zuul作为网关在项目中的使用还是比较多的,本文主要从源码上分析zuul接收请求然后转发的过程。
一、@EnableZuulProxy
首先从EnableZuulProxy注解开始分析,比较重要的是引入了ZuulProxyMarkerConfiguration配置类。
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
在ZuulProxyMarkerConfiguration类中引入了Marker类。
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
在上述代码的实际作用为引入了ZuulProxyAutoConfiguration配置类(直接引入不好吗),ZuulProxyAutoConfiguration继承了ZuulServerAutoConfiguration类,下面主要针对两个配置类引入的组件进行分析。
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
二、ZuulServerAutoConfiguration
在ZuulServerAutoConfiguration中引入的bean实现了接收请求,将请求转发至zuul中的一系列filter进行处理。
public class ZuulServerAutoConfiguration {
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
new ZuulServlet(), this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
}
在ZuulHandlerMapping继承了AbstractUrlHandlerMapping,在DispatcherServlet中接收到请求之后会使用getHandle方法获取controller和intercepter,getHandle方法就是使用HandlerMapping,通过请求的url地址匹配对应处理器,AbstractUrlHandlerMapping的getHandlerInternal方法中调用了lookupHandler方法返回对应controller。
ZuulHandlerMapping对lookupHandler方法进行了重写,在使用父方法查找之前调用了registerHandlers注册了一些handler。
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception {
if (this.errorController != null
&& urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
return null;
}
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
return super.lookupHandler(urlPath, request);
}
registerHandlers将配置的路径与ZuulController组合注册到handlerMap中,接下来的处理都是在ZuulController中。
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);
}
}
}
DispatcherServlet中通过适配器模式调用Controller的方法,ZuulController对应的是SimpleControllerHandlerAdapter,可以看到,在处理请求的时候直接调用handleRequest方法。
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
在handleRequest方法中,经过层层调用,最终调用到ZuulServlet的service方法。
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
Assert.state(this.servletInstance != null, "No Servlet instance");
this.servletInstance.service(request, response);
return null;
}
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
在service方法中,基本就是不断调用zuul中的各种filter进行处理,首先调用preFilter的方法,然后是routeFilter,最后是postFilter,如果在在执行过程中抛出ZuulException,会依次执行errorFilter和postFilter的方法。
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
最后在runFilters方法中,会对同类型的过滤器进行排序,排序的依据是filter的filterOrder属性,所以在自定义filter时可以手动控制filter的加载顺序,从而方便处理。
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
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;
}
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
//排序
Collections.sort(list); // sort by priority
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
三、ZuulProxyAutoConfiguration
在ZuulProxyAutoConfiguration配置类中引入了一系列的filter用于对请求进行处理,本文针对其中的一部分进行说明。
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
// pre filters
@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(), this.zuulProperties,
proxyRequestHelper);
}
// route filters
@Bean
@ConditionalOnMissingBean(RibbonRoutingFilter.class)
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
CloseableHttpClient.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
ZuulProperties zuulProperties,
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
ApacheHttpClientFactory httpClientFactory) {
return new SimpleHostRoutingFilter(helper, zuulProperties,
connectionManagerFactory, httpClientFactory);
}
@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
}
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
}
在DebugFilter的shouldFilter方法中调用了getParameter方法,能够将request中的param转为context的paramMap,所以如果想添加param,可以选择将filter的执行顺序放在DebugFilter的后面。
public class DebugFilter extends ZuulFilter {
private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return DEBUG_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
return true;
}
return ROUTING_DEBUG.get();
}
}
在SimpleHostRoutingFilter中,context中的参数,请求头等取出,重新组合request请求。
public class SimpleHostRoutingFilter extends ZuulFilter
implements ApplicationListener<EnvironmentChangeEvent> {
@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 (getContentLength(request) < 0) {
context.setChunkedRequestBody();
}
String uri = this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
setResponse(response);
}
catch (Exception ex) {
throw new ZuulRuntimeException(handleException(ex));
}
return null;
}
}
总结
本文主要针对zuul处理请求的过程进行源码分析,zuul接受请求的过程基于springmvc的结构,如果在外层filter做拦截,会影响到zuul转发,本质上zuul就是一个接收请求,然后转发的过程,不过我们可以统一做鉴权和过滤等处理。