专栏首页java达人Spring Cloud Zuul中DispatcherServlet和ZuulServlet

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

本文分享自微信公众号 - java达人(drjava)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring cloud zuul为什么需要FormBodyWrapperFilter

    Spring cloud zuul里面有一些核心过滤器,以前文章大致介绍了下各个过滤器的作用,武林外传—武三通的zuul之惑。这次重点讲解下FormBodyWr...

    java达人
  • 透过源码学习设计模式1—Servlet Filter和责任链模式

    相信大家都熟悉Servlet中Filter过滤器,我们可以在servlet和servlet容器之间插入Filter过滤器来包装、预处理请求,或者包装、处理响应。...

    java达人
  • Java注解概述

    Java 注解可以理解为元数据,所谓元数据即是描述数据的数据,如我们平时用的hibernate,就可以注解的方式描述model信息: @Entity @Tabl...

    java达人
  • 原生js上传文件 发送JSON,XML,对请求的表单进行URL编码详解

    HTML表单,当用户提交表单时,表单中的数据将会编码到字符串中,一并伴随着请求发送。

    mySoul
  • Apache Struts2 Remote Code Execution (S2-045)

    风流
  • IDEA 新建 Spring MVC 工程项目与 SpringMVC 运行流程

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    剑影啸清寒
  • flask 流式响应 RuntimeError: working outside of request context

    1、问题 最近要实现这样一个功能:某个 cgi 处理会很耗时,需要把处理的结果实时的反馈给前端,而不能等到后台全完成了再咔一下全扔前端,那样的用户体验谁都没法接...

    用户1177713
  • 2.01-request_header

    hankleo
  • RocketMq之NameSever浅析

    NameSever 是一种路由服务,类似于dubbo中的注册中心zk,它存储了Broker的路由信息,供Producer和Consumer使用,不然Produc...

    大王叫下
  • koa源码阅读[0]

    Node.js也是写了两三年的时间了,刚开始学习Node的时候,hello world就是创建一个HttpServer,后来在工作中也是经历过Express、K...

    贾顺名

扫码关注云+社区

领取腾讯云代金券