网页上传文件的原理详解

今年第三季度工作上完成了一个比较有意思的项目,类似于外包的性质,主要任务就是提供一大堆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头用途也很广泛,在本文就不重点描述了。

其实要自行封装文件上传,最好的办法就是用自己熟悉的开发语言实现一下,这样印象才更深刻,希望这篇文章对你有用。

相关推荐