Spring Cloud Zuul中DispatcherServlet和ZuulServlet

Zuul是通过Servlet机制实现的。一般情况下,ZuulServet被嵌入到Spring Dispatch机制中,由DispatcherServlet分派处理,这样Spring MVC可以控制路由,并且Zuul缓冲请求。如果需要绕过multipart处理,在不缓冲请求的情况下通过Zuul(例如,对于大文件上传),ZuulServlet也可以装载在Spring Dispatcher之外,让请求绕过DispatcherServlet。默认情况下,ZuulServlet的mapping地址是/zuul。此路径可以使用zuul.servletPath更改。相关bean的配置如下:

ZuulConfiguration:

@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
      Collection routeLocators) {
  return new CompositeRouteLocator(routeLocators);
}
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
  return new SimpleRouteLocator(this.server.getServletPrefix(),
        this.zuulProperties);
}
@Bean
public ZuulController zuulController() {
  return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
  ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
  mapping.setErrorController(this.errorController);
  return mapping;
}

如上所示,在ZuulConfiguration中,配置了zuulController和ZuulHandlerMapping,zuulController继承了ServletWrappingController,它封装了zuulServlet实例,由它进行内部管理。ZuulHandlerMapping将请求路径映射到转发的服务上,因此需要将包含route path信息的RouteLocator的实例注入(如下registerHandlers方法)。对zuul的请求都是经由DispatcherServlet处理分派到zuulController中。

ZuulHandlerMapping:

@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
  if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
      return null;
  }
  String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]);
  if (PatternMatchUtils.simpleMatch(ignored, urlPath)) {
      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);
}
private void registerHandlers() {
  Collection 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,会影响性能。因为DispatcherServlet为了方便后续处理流程使用,会将multipart/form请求根据RFC1867规则进行统一分析处理,并且返回MultipartHttpServletRequest实例,通过它可以获取file和其他参数。

processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

以下是处理multipart/form请求的代码:

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
  if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
      if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
        logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
              "this typically results from an additional MultipartFilter in web.xml");
      }
      else if (hasMultipartException(request) ) {
        logger.debug("Multipart resolution failed for current request before - " +
              "skipping re-resolution for undisturbed error rendering");
      }
      else {
        try {
            return this.multipartResolver.resolveMultipart(request);
        }
        catch (MultipartException ex) {
            if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
              logger.debug("Multipart resolution failed for error dispatch", ex);
              // Keep processing error dispatch with regular request handle below
            }
            else {
              throw ex;
            }
        }
      }
  }
  // If not returned before: return original request.
  return request;
}

但有时候网关不需要获取MultipartHttpServletRequest,特别是大文件,这样会比较影响性能,可以直接用ZuulServlet处理,实际上ZuulConfiguration也配置了zuulservlet。

@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
  ServletRegistrationBean 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;
}

spring cloud里面提供了配置项,zuul.servletPath,其默认是/zuul,只要路径前缀是zuul.servletPath,根据Servlet映射匹配的优先级,就会绕过DispatcherServlet,通过ZuulServlet进行处理,因此,对于multipart/form类型请求,就略过了解析MultipartHttpServletRequest的过程,对于其他的请求,由于buffer-requests为false,在FormBodyWrapperFilter中也不会缓冲request(需要HttpServletRequestWrapper包装请求),详细代码如下:

@Override
public boolean shouldFilter() {
  RequestContext ctx = RequestContext.getCurrentContext();
  HttpServletRequest request = ctx.getRequest();
  String contentType = request.getContentType();
  // Get类型请求不执行过滤
  if (contentType == null) {
      return false;
  }
  //对Content-Type为multipart/form-data的请求,只reencode被DispatcherServlet处理过的请求。
  try {
      MediaType mediaType = MediaType.valueOf(contentType);
      return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
            || (isDispatcherServletRequest(request)
                  && MediaType.MULTIPART_FORM_DATA.includes(mediaType));
  }
  catch (InvalidMediaTypeException ex) {
      return false;
  }
}
private boolean isDispatcherServletRequest(HttpServletRequest request) {
  return request.getAttribute(
        DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
@Override
public Object run() {
  RequestContext ctx = RequestContext.getCurrentContext();
  HttpServletRequest request = ctx.getRequest();
  FormBodyRequestWrapper wrapper = null;
  if (request instanceof HttpServletRequestWrapper) {
      HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
            .getField(this.requestField, request);
      wrapper = new FormBodyRequestWrapper(wrapped);
      ReflectionUtils.setField(this.requestField, request, wrapper);
      if (request instanceof ServletRequestWrapper) {
        ReflectionUtils.setField(this.servletRequestField, request, wrapper);
      }
  }
  else {
    //该请求没有通过HttpServletRequestWrapper缓冲。
      wrapper = new FormBodyRequestWrapper(request);
      ctx.setRequest(wrapper);
  }
  if (wrapper != null) {
      ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
  }
  return null;
}

有关FormBodyWrapperFilter的内容,请看Spring cloud zuul为什么需要FormBodyWrapperFilter

java达人

ID:drjava

(长按或扫码识别)

你可能感兴趣的:(Spring Cloud Zuul中DispatcherServlet和ZuulServlet)