天天看点

formdata类型参数_Golang Post Multipart/formdata

问题描述

需求是上传一张图片到我们的对象存储服务器。

对方给了接口协议

  1. post 请求
  2. Content-Type 为 multipart/form-data
  3. form-data, key = "filecontent",value = 图片内容

之前自己用 node.js 写过,几行代码搞定,以为没啥问题,结果用 Golang 遇到了坑

node.js

node.js 发送请求,只需要这几行

const options = {    method: "POST",    url: "xxxxxxxxxxxxx",    headers: {      "Content-Type": "multipart/form-data"    },    timeout: 3000,    formData: {      // key:filecontent value:stream      "filecontent": fs.createReadStream(zip_file_path)    }  };  request(options, function (err, res, body) {    if (err) {      console.log(err);    } else {      let data = JSON.parse(body);      if (data.errcode) {        console.log(data)      } else {        console.log('cdn链接:\t' + data.download_url);      }    }  });
           

golang

当需要在 golang 上发送这个请求的时候,就遇到了各种问题

网上搜到了一些方法,诸如,multipart.createFormFile 等等,关键是还是不对。

先看看 golang 的 net/http 包自带的方法,如何发一个 post 请求,如何设置 Content-Type

buf := new(bytes.Buffer)w := multipart.NewWriter(buf)http.NewRequest("POST", GIFT_URL, buf)contentType := w.FormDataContentType()req.Header.Set("Content-Type", contentType)
           

但是,你会发发现,即使你设置了你的 Content-Type 是 multipart/form-data,debug 也能看到你的 body 里面有这个文件的所有数据,但是对方服务器还是不一定能解析出来,完全看服务端的同学怎么实现、怎么解析。比如,服务器是一个对象存储系统,并且对上传的文件类型有严格判断,这样的话,大概率是错误的,为什么?

抓包看协议

先来看正确的请求

formdata类型参数_Golang Post Multipart/formdata

再来看一个出错的golang代码的请求

formdata类型参数_Golang Post Multipart/formdata

MIME

上图中可以看到,HTTP 协议之后,还带上了一个 MIME 协议。当我们的 HTTP 的 Content-Type 设置为 multipart/form-data 时,我们的请求后会跟着 MIME 协议。这是对于 HTTP 协议的扩展,通过搜资料,说是以前 HTTP 不能传送各种文件等等的东西,而 MIME 是邮件协议,对 HTTP 进行了扩展,让 HTTP 也可以传输各种文件等资源。

所以问题的本质是什么?

那既然抓包发现了 MIME,那么也很清晰的看到了 golang 发出的请求的错误原因,没有指定 MIME 的 Content-Type。Content-Type 类型很多,如下表格

类型 典型示例
text text/plain, text/html, text/css, text/javascript
image

image/gif, image/png, image/jpeg, image/bmp,

image/webp, image/x-icon, 

image/vnd.microsoft.icon

audio

audio/midi, audio/mpeg, audio/webm, 

audio/ogg, audio/wav

video video/webm, video/ogg
application

application/octet-stream, application/pkcs12,

application/vnd.mspowerpoint,

application/xhtml+xml, application/xml,

application/pdf

那我们上传的是图片,所以 Content-Type 为 image/xxx

神奇的 boundary 是什么

我们在抓到的包中还会看到有个叫做 boundary 的字符串,这个其实是一个分隔符,让我们能正确解析上传的文件

一个请求的具体信息大致如下

Content-Type: multipart/form-data; boundary=aBoundaryString(other headers associated with the multipart document as a whole)--aBoundaryStringContent-Disposition: form-data; name="myFile"; filename="img.jpg"Content-Type: image/jpeg(data)--aBoundaryStringContent-Disposition: form-data; name="myField"(data)--aBoundaryString(more subparts)--aBoundaryString--
           

每个字段/文件都被 boundary 分成单独的段,这样可以知道每个文件的范围。

Content-Disposition 又是什么

在常规的 HTTP 应答中, Content-Disposition 消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。

Content-Disposition 作为 multipart body 中的消息头时,第一个参数总是固定不变的 form-data;附加的参数不区分大小写,并且拥有参数值,参数名与参数值用等号(=)连接,参数值用双引号括起来。参数之间用分号(;)分隔,如上图。

修改一下我们的原始代码

那其实核心原因就是 MIME 的 Content-Type 了,我们的对象存储服务器,只接受一般的图片类型,要加上图片文件的类型,否则后端无法解析,就会报错,所以代码修改如下:

header := make(textproto.MIMEHeader)header.Set("Content-Disposition", fmt.Sprintf(`form-data;name="%s";filename="%s"`,"filecontent", baseFileName))mimeType := mime.TypeByExtension(ext)header.Set("Content-Type", mimeType)
           

加上 MIME 的 Type 即可。