fetch 实践补充篇:文件上传 Content-type multipart/form-data 怎么设置

发布于 2023-08-18 17:13:40 字数 5421 浏览 38 评论 0

原谅我是一个标题党,其实这个坑和 fetch 关系不大,归根结底是发送 http 响应的问题,闲话少说,直接说事。前面自己为了好玩,在自己的练手项目里,将 vue-resource 替换成原生 JS 的 fetch API,使用效果还是极佳的,完全没有其他原生 API 那种晦涩感。

夯实基础

前面一篇 文章 fetch 已入过门,所以这只说重点,之前使用 vue-resource 和 fetch 时,在 Conten-type 设置上吃过不少亏,所以自己做了大量功课,重要的事情说三遍,post 请求 content-type,即数据请求的格式主要设置方式:

  • application/x-www-form-urlencoded(大多数请求可用:eg:'name=Denzel&age=18')
  • multipart/form-data(文件上传,这次重点说)
  • application/json(json 格式对象,eg:{'name':'Denzel','age':'18'})
  • text/xml(现在用的很少了,发送 xml 格式文件或流,webservice 请求用的较多)

问题描述

我想通过 fetch 异步上传一张图片到服务器保存,然后返回服务的响应地址(需求很简单,有没有)。于是我这样写的代码:

let data =new FormData();
data.append('file',$("#realFile").files[0]);
data.append('name','denzel'),
data.append('flag','test')
const option = {
  method:'post',
  mode:'cors', 
  headers: {
    'Content-Type': 'multipart/form-data'
  },                                                                          
  body:data
};
fetch('http://localhost:8089/Analyse/imgUploadServlet',option)
  .then(function(response){
    if(response.ok){
        console.log('suc')
        return response.text();
    }else{
        console.log('网络错误,请稍后再试')
        return ;
    }
  }).then(function(data){
    console.log('imgUrl',data);
  })

但在服务器上打印的是这样的错误信息(后端幸好用了 try-catch,不然蹦一大堆错误,找不死你):

  • 错误信息: the request was rejected because no multipart boundary was
    found

很无厘头有没有,后端代码获取数据前,已经对请求的 content-type 做了检查,而且没有报错,那说明发送的是文件上传的请求,没毛病啊,而且这个上传文件的后端代码,以前在 jsp 页面中用过啊,没毛病啊,再在谷歌 dev-tools 查看一下请求:

if (!ServletFileUpload.isMultipartContent(request)) {
 // 如果不是则停止
 PrintWriter writer = response.getWriter();
 writer.println("Error: 表单必须包含 content-type=multipart/form-data");
 writer.flush();
 return;
} 

dev-tools 请求信息:

    Access-Control-Allow-Methods:POST, GET, OPTIONS, DELETE
    Access-Control-Allow-Origin:*
    Content-Length:0
    Content-Type:text/html;charset=UTF-8
    Date:Sun, 16 Jul 2017 01:51:51 GMT
    Server:Apache-Coyote/1.1
    Request Headers
    view source
    Accept:*/*
    Accept-Encoding:gzip, deflate, br
    Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
    Connection:keep-alive
    Content-Length:68172
    content-type:multipart/form-data
    Host:localhost:8089
    Origin:http://localhost
    Referer:http://localhost/
    User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
    Request Payload
    ------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
    Content-Disposition: form-data; name="file"; filename="chn.PNG"
    Content-Type: image/png
    
    
    ------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
    Content-Disposition: form-data; name="name"
    
    denzel
    ------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
    Content-Disposition: form-data; name="flag"
    
    test
    ------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U--   

好像都没问题,那 no multipart boundary 到底是个什么鬼,只有百度一下

问题症结

原来不是我一个人遇到这样的问题,大家都引用的是这样一句话:

you should never set that header yourself. We set the header properly with the boundary. If you set that header, we won't and your server won't know what boundary to expect (since it is added to the header). Remove your custom Content-Type header and you'll be fine.

翻译过来就是:

你不应该自己设置请求头(what ?),我们会为请求头正确设置边界,但如果你设置了,我们和你的服务器都没法预知你的边界是什么(因为边界是被自动加到请求头的),删除你的自定义 Content-Type 请求头设置,问题将会解决(翻译渣,虽然英语六级飘过,但那已经是六年前了)。

好了,知道问题是啥了,删除请求头相关设置(Content-type),再发送,天啦,真的是耶,怎么会这样,不是说每个 http 请求都应该正确的设置自己的请求数据类型吗?我以前的书是不是白看了,啊,冷静,别人说的正确,那再通过 dev-tools 查看一下请求信息吧。

    Accept:*/*
    Accept-Encoding:gzip, deflate, br
    Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
    Connection:keep-alive
    Content-Length:88623
    content-type:multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd
    Host:localhost:8089
    Origin:http://localhost
    Referer:http://localhost/
    User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
    Request Payload
    ------WebKitFormBoundaryAnydWsQ1ajKuGoCd
    Content-Disposition: form-data; name="file"; filename="Screenshot_2017-05-23-11-41-22-090_com.wacai365.png"
    Content-Type: image/png
    
    
    ------WebKitFormBoundaryAnydWsQ1ajKuGoCd
    Content-Disposition: form-data; name="name"
    
    denzel
    ------WebKitFormBoundaryAnydWsQ1ajKuGoCd
    Content-Disposition: form-data; name="flag"
    
    test
    ------WebKitFormBoundaryAnydWsQ1ajKuGoCd--

与上面失败的请求一比较,发现 content-type 后面居然多跟了 boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd 这样一串火星字符,再看发送的数据,数据之间都被请求数据类型的那个 boundary 字符串分割开,好像,我是有点懂,什么叫边界了,就是发 http 请求规定的数据交换规则.类似于:A 发送请求给 B,并告诉 B,我给你送来了三个快递(但为了好搬运,我将它捆成了一个包裹),包裹拆分的规则在快递单上有说明,于是 B 就按 A 说的规则,进行包裹拆分。

问题讨论:post 请求 Content-type 到底该不该设置

我得出的结论是,要正确设置。fetch 发送 post 字符类请求时,

  1. 非文件上传时,无关你发送的数据格式是 application/x-www-form-urlencoded 或者 application/json 格式数据,你不设置请求头,fetch 会给你默认加上一个 Content-type = text/xml 类型的请求头,有些第三方 JAX 可以自己识别发送的数据,并自己转换,但 feth 绝对不会,不行,你可以试一下;
  2. 文件上传请求时,因为不知道那个 boundary 的定义方式,所以就如建议的一样,我们不设置 Content-type。

如果本文有描述不正确的,欢迎指正,一起讨论,毕竟自己知识有限

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

家住魔仙堡

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文