前提
之前在写一个通用HTTP组件的时候遇到过媒体(Media)类型multipart/form-data的封装问题,这篇文章主要简单介绍一下HTTP协议中媒体类型multipart/form-data的定义、应用和简单实现。
multipart/form-data的定义
媒体类型multipart/form-data遵循multipart MIME数据流定义(该定义可以参考Section 5.1 - RFC2046),大概含义就是:媒体类型multipart/form-data的数据体由多个部分组成,这些部分由一个固定边界值(Boundary)分隔。
multipart/form-data请求体布局
multipart/form-data请求体的布局如下:
# 请求头 - 这个是必须的,需要指定Content-Type为multipart/form-data,指定唯一边界值
Content-Type: multipart/form-data; boundary=${Boundary}
# 请求体
--${Boundary}
Content-Disposition: form-data; name="name of file"
Content-Type: application/octet-stream
bytes of file
--${Boundary}
Content-Disposition: form-data; name="name of pdf"; filename="pdf-file.pdf"
Content-Type: application/octet-stream
bytes of pdf file
--${Boundary}
Content-Disposition: form-data; name="key"
Content-Type: text/plain;charset=UTF-8
text encoded in UTF-8
--${Boundary}--
媒体类型multipart/form-data相对于其他媒体类型如application/x-www-form-urlencoded等来说,最明显的不同点是:
- 请求头的Content-Type属性除了指定为multipart/form-data,还需要定义boundary参数
- 请求体中的请求行数据是由多部分组成,boundary参数的值模式--${Boundary}用于分隔每个独立的分部
- 每个部分必须存在请求头Content-Disposition: form-data; name="${PART_NAME}";,这里的${PART_NAME}需要进行URL编码,另外filename字段可以使用,用于表示文件的名称,但是其约束性比name属性低(因为并不确认本地文件是否可用或者是否有异议)
- 每个部分可以单独定义Content-Type和该部分的数据体
- 请求体以boundary参数的值模式--${Boundary}--作为结束标志
{% note warning flat %} RFC7578中提到两个multipart/form-data过期的使用方式,其一是Content-Transfer-Encoding请求头的使用,这里也不展开其使用方式,其二是请求体中单个表单属性传输多个二进制文件的方式建议换用multipart/mixed(一个"name"对应多个二进制文件的场景) {% endnote %}
特殊地:
- 如果某个部分的内容为文本,其的Content-Type为text/plain,可指定对应的字符集,如Content-Type: text/plain;charset=UTF-8
- 可以通过_charset_属性指定默认的字符集,用法如下:
Content-Disposition: form-data; name="_charset_"
UTF-8
--ABCDE--
Content-Disposition: form-data; name="field"
...text encoded in UTF-8...
ABCDE--
Boundary参数取值规约
Boundary参数取值规约如下:
- Boundary的值必须以英文中间双横杠--开头,这个--称为前导连字符
- Boundary的值除了前导连字符以外的部分不能超过70个字符
- Boundary的值不能包含HTTP协议或者URL禁用的特殊意义的字符,例如英文冒号:等
- 每个--${Boundary}之前默认强制必须为CRLF,如果某一个部分的文本类型请求体以CRLF结尾,那么在请求体的二级制格式上,必须显式存在两个CRLF,如果某一个部分的请求体不以CRLF结尾,可以只存在一个CRLF,这两种情况分别称为分隔符的显式类型和隐式类型,说的比较抽象,见下面的例子:
# 请求头
Content-type: multipart/data; boundary="--abcdefg"
--abcdefg
Content-Disposition: form-data; name="x"
Content-type: text/plain; charset=ascii
It does NOT end with a linebreak # <=== 这里没有CRLF,隐式类型
--abcdefg
Content-Disposition: form-data; name="y"
Content-type: text/plain; charset=ascii
It DOES end with a linebreak # <=== 这里有CRLF&#x
文章评论