共计 3097 个字符,预计需要花费 8 分钟才能阅读完成。
今年第三季度工作上完成了一个比较有意思的项目,类似于外包的性质,主要任务就是提供一大堆 API,其中一个 API 是上传附件,完成开发后,对方的程序员问我,这个 API 怎么调用,当时我就愣住了,因为自己也没想过这个问题,一般情况下,我就是用 Curl 命令行或 Postman 测试 API 的。
针对文件上传,我使用 Curl 测试,比如:
# 使用 @引用一个文件 $ curl -F”param=value” -F”file=@/path/file.png” http://localhost/api.php 如果使用 Postman 测试,如下图:
注意观察 form-data 和 File 标签。
看上去是不是很简单,现在换个角度,你想以代码的方式上传文件 API,怎么办?也非常简单,很多开发语言有很多现成的库,比如 PHP 通过 Curl 库上传文件非常容易。再深入想一想,如果不使用这些库,怎么上传文件?可能会难倒很多人,所以这篇文章简单讲讲文件上传的原理,其实就是根据 HTTP 协议的定义,封装一个 HTTP 消息体。
MIME
首先必须先讲下 MIME(Multipurpose Internet Mail Extensions),它并不是 HTTP 协议的一部分,就像我们每个人都是独一无二的,有自己的属性,互联网上每个资源也有属性,比如有些资源是图片,有些是视频,有些是 HTML 页面,MIME 规定了每种资源的类型,这个类型不是随便定义的,由 IANA 负责登记和维护。
说的有点难理解,比如你看到一个 URL 地址,http://localhost/image.png,我们其实并不是通过.png 后缀判断资源类型的,而是通过 MIME 来获知该资源类型的,这个图片的 MIME 可能就是 image/png(至于客户端如何知晓资源的 MIME 类型,后面会讲),现在是不是有了点感性的认识了。
MIME 类型结构如下:
type/subtype
type 相当于某些类型的集合,而 subtype 相当于子类型。以 image/png 为例,image 表示图片类型集合,png 表示某种类型图片。
让我们看几个比较重要的 MIME 类型:
text/plain
text/html
application/octet-stream
multipart/form-data
其实本篇文章的主角就是 multipart/form-data,再等一等,先别着急,再一次说说 MIME,从它的英文全称来看,它和 mail 有关系,是由 mail 应用定义而来的,一封邮件由多种资源组成,为了将不同类型的资源组成在邮件中,MIME 产生了。随着互联网 Web 的发展,MIME 的作用越来越多,扩展也越来越多,MIME 概念也逐步移到了 Web。
Content Type
现在我们定义了每种资源的 MIME 类型,那么客户端如何知晓每种资源的 MIME 类型呢?这时候就要使用 Content-Type HTTP Header 头了,比如我们请求一个资源,Web 服务器在发送资源的时候,发送了“content-type:image/png”Header 头,这样客户端就知道该资源是一个 png 图片了。
如果客户端发送了一个“Content-Type: multipart/form-data;”,代表客户端要上传一个附件。
也就是说 Content-Type 后面的值就是一个 MIME 类型,聪明的同学也猜到了,上传附件和 multipart/form-data MIME 类型有关,确实是!
multipart/form-data
multipart/form-data 这个 MIME 类型并不是标准的 MIME 类型,而是因为 Web 的需要扩展而来的,我们在开发网页的时候为了上传一个文件,会输入以下的 HTML 标签:
<form action=”upload.php” method=”post” enctype=”multipart/form-data”>
Select image to upload:
<input type=”file” name=”fileToUpload” id=”fileToUpload”>
<input type=”submit” value=”Upload Image” name=”submit”>
</form>
关于 HTML 表单上传可以参考 https://www.w3.org/TR/html5/s… 或 RFC 1867(Form-based File Upload in HTML,该 RFC 已经废弃了)。
那么 multipart/form-data 表示什么呢?multipart 互联网上的混合资源,就是资源由多种元素组成,form-data 表示可以使用 HTML Forms 和 POST 方法上传文件,具体的定义可以参考 RFC 7578。
multipart/form-data 结构
说了那么多,从 HTTP 协议的角度,最后看下文件上传的 HTTP 消息体,使用 Postman 也容易看出,如下:
POST /api.php HTTP/1.1
Host: localhst
Cache-Control: no-cache
Content-Type: multipart/form-data; boundary=—-FormBoundary
——FormBoundary
Content-Disposition: form-data; name=”file”; filename=”file.png”
Content-Type: image/png
< 图片二进制内容 >
——FormBoundary
Content-Disposition: form-data; name=”param1″
value1
——FormBoundary
Content-Disposition: form-data; name=”param2″
value2
——FormBoundary–
消息体什么意思呢,如果你自行想使用代码实现文件上传,要根据定义自行封装 HTTP 消息,接下去我们简单描述一下。
Content-Type: multipart/form-data; boundary=——FormBoundary 表示要上传附件,其中 boundary 表示分隔符,如果要上传多个表单项,就要使用 boundary 分割,每个表单项由———FormBoundary 开始,以———FormBoundary 结尾。每一个表单项又由 Content-Type 和 Content-Disposition 组成。
——FormBoundary
Content-Disposition: form-data; name=”param1″
value1
——FormBoundary
表示普通的一个表单元素,最重要的是理解 Content-Disposition HTTP 消息头,其中第一个参数总是固定不变的 form-data,name 表示表单元素属性名,回车换行符后面的内容就是元素的值。
接下去重点描述和文件有关的:
——FormBoundary
Content-Disposition: form-data; name=”file”; filename=”file.png”
Content-Type: image/png
< 图片二进制内容 >——FormBoundary 其中多了一个 filename 参数,表示文件名,Content-Type 告诉服务器这是一个图片,内容就是图片的二进制数据。
其实 Content-Disposition 这个 HTTP header 头用途也很广泛,在本文就不重点描述了。
其实要自行封装文件上传,最好的办法就是用自己熟悉的开发语言实现一下,这样印象才更深刻,希望这篇文章对你有用。