L
O
A
D
I
N
G

Content-Type multipart-formdata的采坑经历


今天在从后端调用图床的接口给图床上传图片的时候,出现了图床返回200但是却无法保存图片的诡异结果。由于后端和图床都是自己手搓出来的,并且中间的交互还经过了nginx、CloudFlare、Docker等一系列反代和中转的流程,而这一系列流程的配置内容也都是自己写的,因此我根本无法定位错误究竟出在哪里。那一刻我的心里是崩溃的。。。

但是,我怎么可能放弃对吧(

接下来我便开始了长达7个小时的漫长debug阶段,期间先修改了python请求的格式,不行

然后开始怀疑是服务器那边nginx的锅,改了nginx的配置,不行

接下来目光转向根源——Docker,直接进到Docker里面开始找log,找到log之后锁定了出现错误的地方,然后进到出错的文件里面,输出了一下request请求体和解析出来的文件,这时候诡异的事情出现了,request里面包含了需要的所有内容,但是解析之后的列表里面是空的!!!

一头雾水之下,我决定使用postman发送post请求进行调试,结果无论怎么改参数返回永远只有一个——200但falied,无奈之下我又在图床的网页端开始研究,使用网页端的post参数模拟向后端发送请求,这时候请求竟然成功了,从而我判断问题并不出在后端,而是前端发送的form-data数据格式还是有问题(请记住这个form-data),但是我对比发现,postman发送的请求内容和网页图床发送的内容几乎完全一致,肉眼根本看不出什么差别,就在我要绝望的时候,无意间看到的一篇文章。

给了我灵感:

一、问题与解决方案

起初我在整理java项目的接口文档时,整理到了文件上传的接口。作为刚接触java没多久的我,为了保证功能逻辑没有写错,于是调试该接口。

key(headers) value
Content-Type multipart/form-data

结果接口提示“Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found”的错误。

查了资料后了解到,使用post上传文件时,不仅需要指定mutipart/form-data来进行编码,还需要在Content-Type中定义boundary作为表单参数的分隔符。

于是加上了自定义的boundary内容:

key(headers) value
Content-Type multipart/form-data; boundary=—-WebKitFormBoundary7TMYhSONfkAM2z3a

结果接口又提示“Required request part ‘file’ is not present”(其中file就是body中上传文件对应的key)。

由于我不会写前端,没法通过写个前端页面使用浏览器请求,从而绕过postman。而我之前用c#其实也写过类似的功能,于是我用.NET 5构建了一个上传文件的接口,再用postman请求。

首先我也是通过postman发送没有boundary的请求,结果和java一样,提供了类似的报错信息(Missing content-type boundary.)。

key(headers) value
Content-Type multipart/form-data

接着将自定义的boundary加上,再次请求,结果和java一样,提供了类似的报错信息(Unexpected end of Stream, the content may have already been read by another component.)。

key(headers) value
Content-Type multipart/form-data; boundary=—-WebKitFormBoundary7TMYhSONfkAM2z3a

这样就基本排除了语言或框架的差异,确实调用存在问题。

针对这个问题,我请教了级别更高的java同事也得不到解决方案。直到看到了这篇文章(https://blog.csdn.net/sun_977759/article/details/88868509)终于解决。

解决方案:

上传文件的请求中去掉headers中自定义的content-type。

但是问题的原因并没有解决方案那么简单,实际是因为postman(5.5.5)的bug引起了这个问题,下面具体描述。


二、思考与验证

这个问题是解决了,但是又让我有了新的疑问,为什么用postman上传文件不能设置头信息content-type?

根据http标准定义,用户可以在发送上传文件请求时自定义boundary。看资料,别人对这块的理解也是用户可以自定义boundary(https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data)。

正当搜索了一圈资料找不到答案时,偶然看到这个issue(https://github.com/postmanlabs/postman-app-support/issues/6140)

难道是老版本postman存在bug?于是下了最新的postman(8.0.6)进行测试,测试结果符合预期,即根据我定义的boundary作为分隔符。

postman(8.0.6)的抓包结果

img

5.5.5版本的postman由于我更新了插件后无法回退进行测试(5.5.5的桌面版在官网下不到),故我通过下了桌面客户端做了测试。

发现功能虽然正常,但是请求中boundary并不是我定义的内容,而是postman自己随机生成了字符串作为boundary。

postman(6.7.4)的抓包结果

img

postman(5.5.3)的抓包结果

img


三、总结

1)请求上传文件的接口时,需要使用post;

2)请求上传文件的接口时,需要在header信息中的Content-Type指明数据以mutipart/form-data进行编码,同时定义boundary作为分隔符(如果没有指定Content-Type,浏览器或postman会自动生成);

3)java异常中的“the request was rejected because no multipart boundary was found”、.NET中的“Missing content-type boundary.”,一般是Content-Type中没有定义boundary引起的;

4)java异常中的“Required request part ‘file’ is not present”(其中file就是body中上传文件对应的key)、.NET中的“Unexpected end of Stream, the content may have already been read by another component.”在我遇到的这个问题中是因为postman(5.5.5)在请求时分隔表单的分隔符使用了自动生成的字符串、而header使用了用户自定义的内容,导致接口根据头信息的boundary无法解析表单的内容。

没错,一直出错的原因就是因为我按照图床api的要求手动设置了Content-Typemultipart/form-data,因此它覆盖掉了系统自动产生的Content-Type字段,同时还修改了自动产生的boundary分界符,结果就是服务器那边无法将form-data中的内容分割开来,只能认为你什么都没有传递过来,从而造成了长达7小时的高效debug悲剧。

这个问题的解决方案也很简单,只要轻轻删掉Content-Type,一切就都迎刃而解了,我***

好了,因为这个bug让我感到自己非常愚蠢,因此在这里记录一下,希望能帮到和我一样的可怜人吧。


文章作者: 叁月柒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叁月柒 !
评论
  目录