Spring Cloud Zuul中DispatcherServlet和ZuulServlet

时间:2022-06-18
本文章向大家介绍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<RouteLocator> 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<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,会影响性能。因为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