learn and grow up

springMVC获取content-type为form-data类型的参数和值解析

字数统计: 3.8k阅读时长: 20 min
2019/07/25 Share

写在前面

传统的servlet处理request请求是,会自动帮我们封装get请求、post且Content-Type为application/x-www-form-urlencoded请求参数至request parameter map。但是当Content-Type为其他类型时,需要基于servlet的框架比如struts、spring来帮我们封装,不按的话得我们自己获取请求流来处理。

之所以会这样,是因为:Content-Type不是application/x-www-form-urlencoded时数据格式不固定,不一定是key=value对的方式,所以服务器无法知道具体的处理方式,所以只能通过获取原始数据流的方式来进行解析。
jquery在执行post请求时,会设置Content-Type为application/x-www-form-urlencoded,所以服务器能够正确解析,而使用原生ajax请求时,如果不显示的设置Content-Type,那么默认是text/plain,这时服务器就不知道怎么解析数据了,所以才只能通过获取原始数据流的方式来进行解析请求数据。

正文

springMvc内实现了个针对不同Content-Type请求的handler类,所以不需要我们关心即可随意获取request中的参数。

但是有一次用postMan使用form-data的post请求springMVC接口,后台无法获取参数和参数的值。

3)4

先来分析下为什么会出现这种情况,通过查看debug可以看到springMVC在此处开始处理前端的request,

5

下面是其他的filter,我们可以先不管他。从此源代码开始跟踪(DispatcherServlet(FrameworkServlet).service(HttpServletRequest, HttpServletResponse) line: 846 ):

  1. 入口是:org.springframework.web.servlet.FrameworkServlet.service(HttpServletRequest, HttpServletResponse),插一句:FrameworkServlet是Spring web框架的基本servlet实现类,service为实现了HttpServlet的处理http请求的入口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    /*解释:
    因为HttpServlet中是没有定义doPatch方法的,所以规定
    if(是doPatch类型)
    就交由本类(FrameworkServlet)内的doPatch方法运行。
    else
    调用父类(HttpServlet)的service方法。
    */
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    processRequest(request, response);
    }
    else {
    //此处httpMethod为post
    super.service(request, response);
    }
    }
  2. javax.servlet.http.HttpServlet.service(HttpServletRequest, HttpServletResponse):

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      /**
      以下代码处理后最终还是走spring自己的dopost方法
      **/
      protected void service(HttpServletRequest req, HttpServletResponse resp)
      /* */ throws ServletException, IOException
      /* */ {
      /* 700 */ String method = req.getMethod();
      /* */
      /* 702 */ if (method.equals("GET")) {
      /* 703 */ long lastModified = getLastModified(req);
      /* 704 */ if (lastModified == -1L)
      /* */ {
      /* */
      /* 707 */ doGet(req, resp);
      /* */ } else {
      /* 709 */ long ifModifiedSince = req.getDateHeader("If-Modified-Since");
      /* 710 */ if (ifModifiedSince < lastModified / 1000L * 1000L)
      /* */ {
      /* */
      /* */
      /* 714 */ maybeSetLastModified(resp, lastModified);
      /* 715 */ doGet(req, resp);
      /* */ } else {
      /* 717 */ resp.setStatus(304);
      /* */ }
      /* */ }
      /* */ }
      /* 721 */ else if (method.equals("HEAD")) {
      /* 722 */ long lastModified = getLastModified(req);
      /* 723 */ maybeSetLastModified(resp, lastModified);
      /* 724 */ doHead(req, resp);
      /* */ }
      /* 726 */ else if (method.equals("POST")) {

      /* 727 */ doPost(req, resp);
      /* */ }
      /* 729 */ else if (method.equals("PUT")) {
      /* 730 */ doPut(req, resp);
      /* */ }
      /* 732 */ else if (method.equals("DELETE")) {
      /* 733 */ doDelete(req, resp);
      /* */ }
      /* 735 */ else if (method.equals("OPTIONS")) {
      /* 736 */ doOptions(req, resp);
      /* */ }
      /* 738 */ else if (method.equals("TRACE")) {
      /* 739 */ doTrace(req, resp);
      /* */
      /* */
      /* */ }
      /* */ else
      /* */ {
      /* */
      /* */
      /* 747 */ String errMsg = lStrings.getString("http.method_not_implemented");
      /* 748 */ Object[] errArgs = new Object[1];
      /* 749 */ errArgs[0] = method;
      /* 750 */ errMsg = MessageFormat.format(errMsg, errArgs);
      /* */
      /* 752 */ resp.sendError(501, errMsg);
      /* */ }
      /* */ }
  3. org.springframework.web.servlet.FrameworkServlet.doPost(HttpServletRequest, HttpServletResponse)

    1
    2
    3
    4
    5
    6
    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    processRequest(request, response);
    }
  4. org.springframework.web.servlet.FrameworkServlet.processRequest(HttpServletRequest, HttpServletResponse):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    //获取上个请求的localeContext和requestAttributes并整合
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes =RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    //大意:该处是为了处理异步请求的多线程上下文安全的传播
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //大意:前面处理localeContext和requestAttributes备份
    initContextHolders(request, localeContext, requestAttributes);

    try {
    //正在的处理入口
    doService(request, response);
    }
    catch (ServletException ex) {
    failureCause = ex;
    throw ex;
    }
    catch (IOException ex) {
    failureCause = ex;
    throw ex;
    }
    catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
    }

    finally {
    resetContextHolders(request, previousLocaleContext, previousAttributes);
    if (requestAttributes != null) {
    requestAttributes.requestCompleted();
    }

    if (logger.isDebugEnabled()) {
    if (failureCause != null) {
    this.logger.debug("Could not complete request", failureCause);
    }
    else {
    if (asyncManager.isConcurrentHandlingStarted()) {
    logger.debug("Leaving response open for concurrent processing");
    }
    else {
    this.logger.debug("Successfully completed request");
    }
    }
    }

    publishRequestHandledEvent(request, response, startTime, failureCause);
    }
    }
  5. org.springframework.web.servlet.DispatcherServlet.doService(HttpServletRequest, HttpServletResponse)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
    String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
    logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
    attributesSnapshot = new HashMap<String, Object>();
    Enumeration<?> attrNames = request.getAttributeNames();
    while (attrNames.hasMoreElements()) {
    String attrName = (String) attrNames.nextElement();
    if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
    attributesSnapshot.put(attrName, request.getAttribute(attrName));
    }
    }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    try {
    //继续处理
    doDispatch(request, response);
    }
    finally {
    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    // Restore the original attribute snapshot, in case of an include.
    if (attributesSnapshot != null) {
    restoreAttributesAfterInclude(request, attributesSnapshot);
    }
    }
    }
    }
  6. org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
    //转换multipart request,如果不是multipart request,就用原本的
    //可以猜想到这里面对request进行了处理,我们需要进去细看
    //通过debug我们看到这里返回了原本的request,肯定是不对的,我们debug进去看看
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    //核心:获取该请求的HandlerMapping
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null || mappedHandler.getHandler() == null) {
    noHandlerFound(processedRequest, response);
    return;
    }

    // Determine handler adapter for the current request.
    //核心
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (logger.isDebugEnabled()) {
    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
    }
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }
    //执行拦截PreHandle
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // Actually invoke the handler.
    //下一入口。
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
    dispatchException = ex;
    }
    catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
    }
    else {
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
    }
    }
    }
    }
  7. org.springframework.web.servlet.DispatcherServlet.checkMultipart(HttpServletRequest):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    //判断是否是Multipartrequest,看来是这里if没有为true,我们继续跟踪
    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;
    }
  8. org.springframework.web.multipart.commons.CommonsMultipartResolver.isMultipart(HttpServletRequest)——-》org.apache.commons.fileupload.servlet.ServletFileUpload.isMultipartContent(HttpServletRequest)——》org.apache.commons.fileupload.FileUploadBase.isMultipartContent(RequestContext):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static final boolean isMultipartContent(RequestContext ctx) {
    String contentType = ctx.getContentType();
    if (contentType == null) {
    return false;
    }
    //是否startwith multipart/
    if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
    return true;
    }
    return false;
    }

    看到这里发现自己的ContentType竟然是:application/x-www-form-urlencoded,然而参数传送方式却是formdata,两边不一致,导致后面无法没有获取到参数的值。原来postman默认给contentType填充了值,我们其去掉即可,去掉之后再次请求,发现可以取到参数的值了!

    6

    现在我们继续返回来分析为什么需要让这段if返回ture才能取到值呢?

  9. org.springframework.web.servlet.DispatcherServlet.checkMultipart(HttpServletRequest):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    //判断是否是Multipartrequest,看来是这里if没有为true,我们继续跟踪
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
    //判断当前request是否是MultipartHttpServletRequest,如果是则直接跳过不解析处理该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) ) {
    //如果已经报错,则也不继续解析该request
    logger.debug("Multipart resolution failed for current request before - " +
    "skipping re-resolution for undisturbed error rendering");
    }
    else {
    try {
    //开始解析multpartrequest
    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;
    }
  10. 重头戏org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(HttpServletRequest):

    返回了MultipartHttpServletRequest的实现类:DefaultMultipartHttpServletRequest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Override
    public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
    Assert.notNull(request, "Request must not be null");
    if (this.resolveLazily) {
    //配置为false,所以先忽略
    //延迟解析,原理:重写了DefaultMultipartHttpServletRequest实体类里只存入request,实现新initializeMultipart方法。需要后面手工调用initializeMultipart,才能解析request
    return new DefaultMultipartHttpServletRequest(request) {
    @Override
    protected void initializeMultipart() {
    MultipartParsingResult parsingResult = parseRequest(request);
    setMultipartFiles(parsingResult.getMultipartFiles());
    setMultipartParameters(parsingResult.getMultipartParameters());
    setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
    }
    };
    }
    else {
    //
    MultipartParsingResult parsingResult = parseRequest(request);
    return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
    parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
    }
    }

    之所以是走这个resolve,是因为项目配置文件内配置了该bean:

    1
    2
    3
    4
    5
    6
    <!-- 文件上传相关 解析器 -->
    <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--one of the properties available;the maximum upload size in bytes 10M -->
    <property name="maxUploadSize" value="10485760" />
    </bean>
  11. org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(HttpServletRequest)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
    //获取encoding ;第一位:request。第二位:bean配置。第三位:jar包默认:ISO-8859-1
    String encoding = determineEncoding(request);
    //获取文件上传处理类
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
    //
    List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
    //
    return parseFileItems(fileItems, encoding);
    }
    catch (FileUploadBase.SizeLimitExceededException ex) {
    throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
    }
    catch (FileUploadBase.FileSizeLimitExceededException ex) {
    throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
    }
    catch (FileUploadException ex) {
    throw new MultipartException("Failed to parse multipart servlet request", ex);
    }
    }

    org.apache.commons.fileupload.FileUploadBase.parseRequest(RequestContext)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    public List<FileItem> parseRequest(RequestContext ctx)
    throws FileUploadException {
    List<FileItem> items = new ArrayList<FileItem>();
    boolean successful = false;
    try {
    //根据RequestContext数据源得到解析后的数据集合 FileItemIterator
    FileItemIterator iter = getItemIterator(ctx);
    //
    FileItemFactory fac = getFileItemFactory();
    if (fac == null) {
    throw new NullPointerException("No FileItemFactory has been set.");
    }
    //遍历FileItemIterator中的每个item,类型为FileItemStreamImpl,使用FileItemFactory工厂类来将每个FileItemStreamImpl转化成最终的FileItem
    while (iter.hasNext()) {
    //每次调用FileItemIteratorImpl的hasNext()方法,会创建一个新的FileItemStreamImpl赋值给FileItemStreamImpl属性
    //每次调用FileItemIteratorImpl的next()方法,就会返回当前FileItemStreamImpl属性的值
    //创建的每个FileItemStreamImpl都会共享FileItemIteratorImpl的MultipartStream总流,仅仅更新了要读取的起始位置

    final FileItemStream item = iter.next();
    // Don't use getName() here to prevent an InvalidFileNameException.
    final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
    FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
    item.isFormField(), fileName);
    items.add(fileItem);
    try {
    Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
    } catch (FileUploadIOException e) {
    throw (FileUploadException) e.getCause();
    } catch (IOException e) {
    throw new IOFileUploadException(format("Processing of %s request failed. %s",
    MULTIPART_FORM_DATA, e.getMessage()), e);
    }
    final FileItemHeaders fih = item.getHeaders();
    fileItem.setHeaders(fih);
    }
    successful = true;
    //这个时候已经把我们的参数和值读取完成了。见下图
    return items;
    } catch (FileUploadIOException e) {
    throw (FileUploadException) e.getCause();
    } catch (IOException e) {
    throw new FileUploadException(e.getMessage(), e);
    } finally {
    if (!successful) {
    for (FileItem fileItem : items) {
    try {
    fileItem.delete();
    } catch (Throwable e) {
    // ignore it
    }
    }
    }
    }
    }

    8

    org.apache.commons.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemIteratorImpl(FileUploadBase, RequestContext):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    FileItemIteratorImpl(RequestContext ctx)
    throws FileUploadException, IOException {
    //文件处理实现内部类
    if (ctx == null) {
    throw new NullPointerException("ctx parameter");
    }

    //再次判断ContentType
    String contentType = ctx.getContentType();
    if ((null == contentType)
    || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
    throw new InvalidContentTypeException(
    format("the request doesn't contain a %s or %s stream, content type header is %s",
    MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
    }
    //获取输入流
    InputStream input = ctx.getInputStream();

    @SuppressWarnings("deprecation") // still has to be backward compatible
    final int contentLengthInt = ctx.getContentLength();

    final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
    // Inline conditional is OK here CHECKSTYLE:OFF
    ? ((UploadContext) ctx).contentLength()
    : contentLengthInt;
    // CHECKSTYLE:ON
    //判断输入流是否超限
    if (sizeMax >= 0) {
    if (requestSize != -1 && requestSize > sizeMax) {
    throw new SizeLimitExceededException(
    format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
    Long.valueOf(requestSize), Long.valueOf(sizeMax)),
    requestSize, sizeMax);
    }
    input = new LimitedInputStream(input, sizeMax) {
    @Override
    protected void raiseError(long pSizeMax, long pCount)
    throws IOException {
    FileUploadException ex = new SizeLimitExceededException(
    format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
    Long.valueOf(pCount), Long.valueOf(pSizeMax)),
    pCount, pSizeMax);
    throw new FileUploadIOException(ex);
    }
    };
    }

    String charEncoding = headerEncoding;
    if (charEncoding == null) {
    charEncoding = ctx.getCharacterEncoding();
    }
    //获取boundary,见下图
    boundary = getBoundary(contentType);
    if (boundary == null) {
    throw new FileUploadException("the request was rejected because no multipart boundary was found");
    }

    notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
    try {
    multi = new MultipartStream(input, boundary, notifier);
    } catch (IllegalArgumentException iae) {
    throw new InvalidContentTypeException(
    format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
    }
    multi.setHeaderEncoding(charEncoding);

    skipPreamble = true;
    //findNextItem()方法就是创建新的FileItemStreamImpl来替代当前的FileItemStreamImpl,并更新起始位置。此处为第一次调用
    findNextItem();
    }

    7

    org.apache.commons.fileupload.FileUploadBase.FileItemIteratorImpl.findNextItem():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    private boolean findNextItem() throws IOException {
    if (eof) {
    return false;
    }
    if (currentItem != null) {
    currentItem.close();
    currentItem = null;
    }
    for (;;) {
    boolean nextPart;
    if (skipPreamble) {
    nextPart = multi.skipPreamble();
    } else {
    nextPart = multi.readBoundary();
    }
    if (!nextPart) {
    if (currentFieldName == null) {
    // Outer multipart terminated -> No more data
    eof = true;
    return false;
    }
    // Inner multipart terminated -> Return to parsing the outer
    multi.setBoundary(boundary);
    currentFieldName = null;
    continue;
    }
    FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
    if (currentFieldName == null) {
    // We're parsing the outer multipart
    String fieldName = getFieldName(headers);
    if (fieldName != null) {
    String subContentType = headers.getHeader(CONTENT_TYPE);
    if (subContentType != null
    && subContentType.toLowerCase(Locale.ENGLISH)
    .startsWith(MULTIPART_MIXED)) {
    currentFieldName = fieldName;
    // Multiple files associated with this field name
    byte[] subBoundary = getBoundary(subContentType);
    multi.setBoundary(subBoundary);
    skipPreamble = true;
    continue;
    }
    //获取header里的文件名称。
    //如果为null,则为普通form表单(不是file)
    String fileName = getFileName(headers);
    currentItem = new FileItemStreamImpl(fileName,
    fieldName, headers.getHeader(CONTENT_TYPE),
    fileName == null, getContentLength(headers));
    currentItem.setHeaders(headers);
    notifier.noteItem();
    itemValid = true;
    return true;
    }
    } else {
    String fileName = getFileName(headers);
    if (fileName != null) {
    currentItem = new FileItemStreamImpl(fileName,
    currentFieldName,
    headers.getHeader(CONTENT_TYPE),
    false, getContentLength(headers));
    currentItem.setHeaders(headers);
    notifier.noteItem();
    itemValid = true;
    return true;
    }
    }
    multi.discardBodyData();
    }
    }

    org.apache.commons.fileupload.FileUploadBase.getFileName(String):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    private String getFileName(String pContentDisposition) {
    String fileName = null;
    if (pContentDisposition != null) {
    String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH);
    if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
    ParameterParser parser = new ParameterParser();
    parser.setLowerCaseNames(true);
    // Parameter parser can handle null input
    //封装header为MAP:
    Map<String, String> params = parser.parse(pContentDisposition, ';');
    if (params.containsKey("filename")) {
    fileName = params.get("filename");
    if (fileName != null) {
    fileName = fileName.trim();
    } else {
    // Even if there is no value, the parameter is present,
    // so we return an empty file name rather than no file
    // name.
    fileName = "";
    }
    }
    }
    }
    return fileName;
    }

    org.apache.commons.fileupload.ParameterParser.parse(char[], int, int, char):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public Map<String, String> parse(
    final char[] charArray,
    int offset,
    int length,
    char separator) {
    if (charArray == null) {
    return new HashMap<String, String>();
    }
    HashMap<String, String> params = new HashMap<String, String>();
    this.chars = charArray;
    this.pos = offset;
    this.len = length;

    String paramName = null;
    String paramValue = null;
    while (hasChar()) {
    paramName = parseToken(new char[] {
    '=', separator });
    paramValue = null;
    if (hasChar() && (charArray[pos] == '=')) {
    pos++; // skip '='
    paramValue = parseQuotedToken(new char[] {
    separator });

    if (paramValue != null) {
    try {
    paramValue = MimeUtility.decodeText(paramValue);
    } catch (UnsupportedEncodingException e) {
    // let's keep the original value in this case
    }
    }
    }
    if (hasChar() && (charArray[pos] == separator)) {
    pos++; // skip separator
    }
    if ((paramName != null) && (paramName.length() > 0)) {
    if (this.lowerCaseNames) {
    paramName = paramName.toLowerCase(Locale.ENGLISH);
    }

    params.put(paramName, paramValue);
    }
    }
    return params;
    }
  1. org.springframework.web.multipart.commons.CommonsFileUploadSupport.parseFileItems(List, String):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
    MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<String, MultipartFile>();
    Map<String, String[]> multipartParameters = new HashMap<String, String[]>();
    Map<String, String> multipartParameterContentTypes = new HashMap<String, String>();

    // Extract multipart files and multipart parameters.
    for (FileItem fileItem : fileItems) {
    if (fileItem.isFormField()) {
    //判断是不是file
    String value;
    String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
    if (partEncoding != null) {
    try {
    value = fileItem.getString(partEncoding);
    }
    catch (UnsupportedEncodingException ex) {
    if (logger.isWarnEnabled()) {
    logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
    "' with encoding '" + partEncoding + "': using platform default");
    }
    value = fileItem.getString();
    }
    }
    else {
    value = fileItem.getString();
    }
    String[] curParam = multipartParameters.get(fileItem.getFieldName());
    if (curParam == null) {
    // simple form field
    //数组【0】
    multipartParameters.put(fileItem.getFieldName(), new String[] {value});
    }
    else {
    // array of simple form fields
    //数组增加
    String[] newParam = StringUtils.addStringToArray(curParam, value);
    multipartParameters.put(fileItem.getFieldName(), newParam);
    }
    //封装field的ContentType
    multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
    }
    else {
    // multipart file field
    CommonsMultipartFile file = createMultipartFile(fileItem);
    multipartFiles.add(file.getName(), file);
    if (logger.isDebugEnabled()) {
    logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
    " bytes with original filename [" + file.getOriginalFilename() + "], stored " +
    file.getStorageDescription());
    }
    }
    }
    return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
    }

    至此,整个request的请求流封装完毕,被包装为org.springframework.web.multipart.MultipartHttpServletRequest(调用栈开始回归:10—》9—-》7)返回并代替原来的servletRequest继续下面的流程。

    并最终返回给action的request供业务层使用

    最终结果

CATALOG
  1. 1. 写在前面
  2. 2. 正文