详解Spring Boot中PATCH上传文件的问题
SpringBoot中上传multipart/form-data文件只能是Post提交,而不针对PATCH,这个问题花了作者26个小时才解决这个问题,最后不得不调试Spring源代码来解决这个问题。
需求:在网页中构建一个表单,其中包含一个文本输入字段和一个用于文件上载的输入。很简单。这是表单:
RestController中的方法:
@RequestMapping(value="/f",method=PATCH) publicvoidupload( @RequestPart("definition")MultipartFiledefinition, @RequestPart("company")Stringcompany ){...}
注意它是PATCH的方法(根据要求)而不是POST,部分要求是提交的ajax请求,并不是表单提交,代码如下:
varfileInput=...;//thisishtmlelementthatholdsthefiles vartextInput=...;//thiisthestring varfd=newFormData(); fd.append('definition',fileInput.files[0]); fd.append('name',textInput); xhr=newXMLHttpRequest(); xhr.open('PATCH',uploadForm.action,true); xhr.send(fd);
但无论怎么做,我都无法让它发挥作用。总是遇到以下异常:
MissingServletRequestPartException:Requiredrequestpart‘definition'isnotpresent
我做的第一件事就是将这个问题分解为最简单的问题。所以我将请求类型更改为POST,并删除了textInput。将MultiPart解析器的实现进行更改,从org.springframework.web.multipart.support.StandardServletMultipartResolver改为org.springframework.web.multipart.commons.CommonsMultipartResolver
@Configuration publicclassMyConfig{ @Bean publicMultipartResolvermultipartResolver(){ returnnewCommonsMultipartResolver(); } }
这还需要将commons-fileupload库添加到类路径中。
但每当我添加一个字符串变量返回错误:thestringfieldnotthefilefield
这说明multipartrequestresolver没有发现这部分字段。
这是由于Javascript的FormData问题,在FormData对象上调用的Append方法接受两个参数name和value(有第三个但不重要),该value字段可以是一个USVString或Blob(包括子类等File)。更改代码为:
varfileInput=...;//thisishtmlelementthatholdsthefiles vartextInput==newBlob(['theinfo'],{ type:'text/plain' }); ;//thiisthestring varfd=newFormData(); fd.append('definition',fileInput.files[0]); fd.append('name',textInput); xhr=newXMLHttpRequest(); xhr.open('PATCH',uploadForm.action,true); xhr.send(fd);
它突然开始工作:)。
看一下浏览器发送的内容:
———WebKitFormBoundaryHGN3YjdgsELbgmZH
Content-Disposition:form-data;name=”definition”;filename=”test.csv”Content-Type:text/csv
thisisthecontentofafile,browserhidesit.
———WebKitFormBoundaryHGN3YjdgsELbgmZHContent-Disposition:form-data;name=”name”
thisisthestring
———WebKitFormBoundaryHGN3YjdgsELbgmZH—
你能注意到内容处置标题中缺少的内容吗?文件名和内容类型。在servlet处理期间,multi-part表单变成MultipartFile。在commons-fileupload中有一行:
StringsubContentType=headers.getHeader(CONTENT_TYPE); if(subContentType!=null...){}
这是get的内容类型,如果它是null,则处理是通过不同的路由将我们的上传部分不是转为MultipartFile,而是转换为MultipartParameter(放在不同的Map中,而spring没有找到它),然后spring为每个参数创建单独的实例,形成在调用rest方法时实现绑定的表单。
RequestPartServletServerHttpRequest构造函数中可以找到抛出异常的位置:
HttpHeadersheaders=this.multipartRequest.getMultipartHeaders(this.partName); if(headers==null){ thrownewMissingServletRequestPartException(partName); }
重要的是getMultipartHeaders只查看multipart的文件files而不是参数parameters。
这就是为什么添加具有特定类型的blob解决了问题的原因:
vartextInput==newBlob(['theinfo'],{ type:'text/plain' });
现在回过来,前面我提到我必须切换到使用POST才正常,但当我改为PATCH时,问题又回来了。错误是一样的。
我很困惑。所以找到了源代码(毕竟这是最终的文档)。
请记住,在本文开头切换到了CommonsMultipartResolver。事实证明,在请求处理期间,调用此方法:
publicstaticfinalbooleanisMultipartContent( HttpServletRequestrequest){ if(!POST_METHOD.equalsIgnoreCase(request.getMethod())){ returnfalse; } returnFileUploadBase.isMultipartContent(newServletRequestContext(request)); }
如果它不是POST请求,则立即确定该请求没有multipart内容。
那么久通过覆盖调用上面静态方法的方法解决了这个问题。
所以现在configbean看起来像这样:
@Bean publicMultipartResolvermultipartResolver(){ returnnewCommonsMultipartResolverMine(); } publicstaticclassCommonsMultipartResolverMineextendsCommonsMultipartResolver{ @Override publicbooleanisMultipart(HttpServletRequestrequest){ finalStringheader=request.getHeader("Content-Type"); if(header==null){ returnfalse; } returnheader.contains("multipart/form-data"); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。