转载

Spring Boot中PATCH上传文件的问题

Spring Boot中上传multipart/form-data文件只能是Post提交,而不针对PATCH,这个问题花了作者26个小时才解决这个问题,最后不得不调试Spring源代码来解决这个问题。

需求:在网页中构建一个表单,其中包含一个文本输入字段和一个用于文件上载的输入。很简单。这是表单:

<form id=”data” method=”PATCH” action=”/f” >
   <input type=<font>"text"</font><font> required name=</font><font>"company"</font><font> >
   <input type=</font><font>"file"</font><font> required name=</font><font>"definition"</font><font> />
</form>
</font>

RestController中的方法:

@RequestMapping(value = <font>"/f"</font><font>,method = PATCH)
<b>public</b> <b>void</b> upload(
      @RequestPart(</font><font>"definition"</font><font>) MultipartFile definition,
      @RequestPart(</font><font>"company"</font><font>) String company
) {...}
</font>

注意它是PATCH的方法(根据要求)而不是POST,部分要求是提交的ajax请求,并不是表单提交,代码如下:

<b>var</b> fileInput = ...; <font><i>//this is html element that holds the files</i></font><font>
<b>var</b> textInput = ...; </font><font><i>//thi is the string</i></font><font>
<b>var</b> fd = <b>new</b> FormData();
fd.append('definition',fileInput.files[0]);
fd.append('name', textInput );
xhr = <b>new</b> XMLHttpRequest();
xhr.open( 'PATCH', uploadForm.action, <b>true</b> );
xhr.send( fd );
</font>

但无论怎么做,我都无法让它发挥作用。总是遇到以下异常:

MissingServletRequestPartException: Required request part ‘definition’ is not present

我做的第一件事就是将这个问题分解为最简单的问题。所以我将请求类型更改为POST,并删除了textInput。将MultiPart解析器的实现进行更改,从org.springframework.web.multipart.support.StandardServletMultipartResolver 改为org.springframework.web.multipart.commons.CommonsMultipartResolver

@Configuration
<b>public</b> <b>class</b> MyConfig {

   @Bean
   <b>public</b> MultipartResolver multipartResolver() {
      <b>return</b> <b>new</b> CommonsMultipartResolver();
   }
}

这还需要将commons-fileupload库添加到类路径中。

但每当我添加一个字符串变量返回错误:the string field not the file field

这说明multi part request resolver 没有发现这部分字段。

这是由于Javascript的FormData问题,在FormData对象上调用的Append方法接受两个参数name和value(有第三个但不重要),该value字段可以是一个 USVString 或 Blob (包括子类等 File )。更改代码为:

<b>var</b> fileInput = ...; <font><i>//this is html element that holds the files</i></font><font>
<b>var</b> textInput = = <b>new</b> Blob(['the info'], {
   type: 'text/plain'
});
; </font><font><i>//thi is the string</i></font><font>
<b>var</b> fd = <b>new</b> FormData();
fd.append('definition',fileInput.files[0]);
fd.append('name', textInput );
xhr = <b>new</b> XMLHttpRequest();
xhr.open( 'PATCH', uploadForm.action, <b>true</b> );
xhr.send( fd );
</font>

它突然开始工作:)。

看一下浏览器发送的内容:

— — — WebKitFormBoundaryHGN3YjdgsELbgmZH
Content-Disposition: form-data; name=”definition”; filename=”test.csv” Content-Type: text/csv
<b>this</b> is the content of a file, browser hides it.
— — — WebKitFormBoundaryHGN3YjdgsELbgmZH Content-Disposition: form-data; name=”name” 
<b>this</b> is the string
— — — WebKitFormBoundaryHGN3YjdgsELbgmZH —

你能注意到内容处置标题中缺少的内容吗?文件名和内容类型。在servlet处理期间,multi-part表单变成MultipartFile。在commons-fileupload中有一行:

String subContentType = headers.getHeader(CONTENT_TYPE);
<b>if</b> (subContentType != <b>null</b> ... ){}

这是get的内容类型,如果它是null,则处理是通过不同的路由将我们的上传部分不是转为MultipartFile,而是转换为MultipartParameter(放在不同的Map中,而spring没有找到它),然后spring为每个参数创建单独的实例,形成在调用rest方法时实现绑定的表单。

RequestPartServletServerHttpRequest构造函数中可以找到抛出异常的位置:

HttpHeaders headers = <b>this</b>.multipartRequest.getMultipartHeaders(<b>this</b>.partName);
<b>if</b> (headers == <b>null</b>) {
   <b>throw</b> <b>new</b> MissingServletRequestPartException(partName);
}

重要的是getMultipartHeaders只查看multipart的文件files而不是参数parameters。

这就是为什么添加具有特定类型的blob解决了问题的原因:

<b>var</b> textInput = = <b>new</b> Blob(['the info'], {
   type: 'text/plain'
});

现在回过来,前面我提到我必须切换到使用POST才正常,但当我改为PATCH时,问题又回来了。错误是一样的。

我很困惑。所以找到了源代码(毕竟这是最终的文档)。

请记住,在本文开头切换到了CommonsMultipartResolver。事实证明,在请求处理期间,调用此方法:

<b>public</b> <b>static</b> <b>final</b> <b>boolean</b> isMultipartContent(
        HttpServletRequest request) {
    <b>if</b> (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
        <b>return</b> false;
    }
    <b>return</b> FileUploadBase.isMultipartContent(<b>new</b> ServletRequestContext(request));
}

如果它不是POST请求,则立即确定该请求没有multipart内容。

那么久通过覆盖调用上面静态方法的方法解决了这个问题。

所以现在config bean看起来像这样:

@Bean
<b>public</b> MultipartResolver multipartResolver() {
   <b>return</b> <b>new</b> CommonsMultipartResolverMine();
}


<b>public</b> <b>static</b> <b>class</b> CommonsMultipartResolverMine <b>extends</b> CommonsMultipartResolver {

   @Override
   <b>public</b> <b>boolean</b> isMultipart(HttpServletRequest request) {
      <b>final</b> String header = request.getHeader(<font>"Content-Type"</font><font>);
      <b>if</b>(header == <b>null</b>){
         <b>return</b> false;
      }
      <b>return</b> header.contains(</font><font>"multipart/form-data"</font><font>);
   }

}
</font>
原文  https://www.jdon.com/51169
正文到此结束
Loading...