Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器文件上传 #7

Open
ZengTianShengZ opened this issue Dec 4, 2019 · 0 comments
Open

浏览器文件上传 #7

ZengTianShengZ opened this issue Dec 4, 2019 · 0 comments

Comments

@ZengTianShengZ
Copy link
Owner

ZengTianShengZ commented Dec 4, 2019

浏览器文件上传

最近项目有用到文件上传,发现对这一块内容不是很了解,所以花时间整理一份这方面的知识体系

一、预备知识

1、HTTP 请求和响应

image

请求

常见的HTTP请求报文头属性

Accept

请求报文可通过一个“Accept”报文头属性告诉服务端客户端接受什么类型的响应。Accept属性的值可以为一个或多个MIME类型的值,关于MIME类型,大家请参考:http://en.wikipedia.org/wiki/MIME_type

Accept: application/javascript
Accept: application/json
Accept: application/x-www-form-urlencodedtext/css
Accept: text/htm
Accept: image/pn
Accept: multipart/form-data
// ...

Cookie

客户端的Cookie就是通过这个报文头属性传给服务端的哦!如下所示:

Cookie: $Version=1; Skin=new;jsessionid=5F4771183629C9834F8382E23BE13C4C  

Cache-Control

对缓存进行控制,如一个请求希望响应返回的内容在客户端要被缓存一年,或不希望被缓存就可以通过这个报文头达到目的。

Cache-Control: no-cache  // 不缓存
Cache-Control: max-age=600  // 缓存内容将在xxx秒后失效

Content-Type

Content-Type用于指定内容类型,一般是指网页中存在的Content-Type,Content-Type属性指定请求和响应的HTTP内容类型。如果未指定 ContentType,默认为text/html。
常见的 Content-Type 如下:

Content-Type: text/html
Content-Type: text/plain
Content-Type: text/css
Content-Type: text/javascript
Content-Type: application/x-www-form-urlencoded
Content-Type: multipart/form-data
Content-Type: application/json
Content-Type: application/xml

Content-Type 是重点,对我们理解数据上传,或文件上传有帮助,下面重点讲一下 Content-Type

application/x-www-form-urlencoded

application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
比如一个简单的表单提交

<form enctype="application/x-www-form-urlencoded" action="http://homeway.me/post.php" method="POST">
    <input type="text" name="name" value="homeway">
    <input type="text" name="key" value="nokey">
    <input type="submit" value="submit">
</form>

请求主体如下:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,gl;q=0.2,de;q=0.2
Cache-Control:no-cache
Connection:keep-alive
Content-Length:17
Content-Type:application/x-www-form-urlencoded

那么服务器收到的raw body会是,name=homeway&key=nokey,在php中,通过$_POST就可以获得数组形式的数据。

text/xml
微信用的是这种数据格式发送请求的。

POST http://www.homeway.me HTTP/1.1 
Content-Type: text/xml
<?xml version="1.0"?>
<resource>
    <id>123</id>
    <params>
        <name>
            <value>homeway</value>
        </name>
        <age>
            <value>22</value>
        </age>
    </params>
</resource>

multipart/form-data

multipart/form-data用在发送文件的POST包。
通过控制台,可以看到发送一个文件的数据内容如下:

POST http://www.homeway.me HTTP/1.1
Content-Type:multipart/form-data; boundary=------WebKitFormBoundaryOGkWPJsSaJCPWjZP

------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key2"
456
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key1"
123
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="file"; filename="index.png"

这里Content-Type告诉我们,发包是以multipart/form-data格式来传输,另外,还有boundary用于分割数据。
当文件太长,HTTP无法在一个包之内发送完毕,就需要分割数据,分割成一个一个chunk发送给服务端
那么--用于区分数据快,而后面的数据 WebKitFormBoundaryOGkWPJsSaJCPWjZP 就是标示区分包作用。

更多请求报文属性请参考 http://en.wikipedia.org/wiki/List_of_HTTP_header_fields

响应

常见的HTTP响应报文头属性

Cache-Control

请求报文头也有个 Cache-Control ,请求的 Cache-Control 用于告诉服务器我需要缓存这个请求资源,服务端接受到这个属性后也给客服端响应一个 Cache-Control 属性,通过该报文头属告诉客户端如何控制响应内容的缓存。
比如设置了 Cache-Control: max-age=3600 让客户端对响应内容缓存3600秒,也即在3600秒内,如果客户再次访问该资源,直接从客户端的缓存中返回内容给客户,不要再从服务端获取(当然,这个功能是靠客户端实现的,服务端只是通过这个属性提示客户端“应该这么做”,做不做,还是决定于客户端,如果是自己宣称支持HTTP的客户端,则就应该这样实现)。

ETag

一个代表响应服务端资源(如页面)版本的报文头属性,如果某个服务端资源发生变化了,这个ETag就会相应发生变化。它是Cache-Control的有益补充,可以让客户端“更智能”地处理什么时候要从服务端取资源,什么时候可以直接从缓存中返回响应。

ETag: "737060cd8c284d8af7ad3082f209582d"  

Set-Cookie

服务端可以设置客户端的Cookie,其原理就是通过这个响应报文头属性实现的:

Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1  

2、input file 知识

HTML5 添加了一些强大的 File API

FileList

FileList 对象针对表单的 file 控件。当用户通过 file 控件选取文件后,这个控件的 files 属性值就是 FileList 对象。它在结构上类似于数组,包含用户选取的多个文件。如果 file 控件没有设置 multiple 属性,那么用户只能选择一个文件,FileList 对象也就只有一个元素了。

<input type='file' />
<script>
    document.querySelector('input').onchange = function() {
      console.log(this.files);
    };
</script>

image

由控制台可以看到 FileList 是一个数组,数组包含文件的一些信息

File

我们看到一个 FileList 对象包含了我们选中的 File 对象,那么一个 File 又有哪些属性呢?我们可以打印出来看看。

image

name:文件名,该属性只读。

size:文件大小,单位为字节,该属性只读。

type:文件的 MIME 类型,如果分辨不出类型,则为空字符串,该属性只读。

lastModified:文件的上次修改时间,格式为时间戳。

lastModifiedDate:文件的上次修改时间,格式为 Date 对象实例。

Blob

上图中我们看到,File 对象是继承自 Blob 对象的,Blob 又是什么鬼?
Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的 API(比如 File 对象),都是建立在 Blob 对象基础上的,继承了它的属性和方法。
生成 Blob 对象有两种方法:一种是使用 Blob 构造函数,另一种是对现有的 Blob 对象使用 slice 方法切出一部分。

var a = ["hello", "world"];
var myBlob = new Blob(a, { "type" : "text/xml" });
console.log(myBlob);

image

Blob 对象有两个只读属性:

size:二进制数据的大小,单位为字节。(文件上传时可以在前端判断文件大小是否合适)

type:二进制数据的 MIME 类型,全部为小写,如果类型未知,则该值为空字符串。(文件上传时可以在前端判断文件类型是否合适)

FileReader

FileReader API 才是我们接下去完成一些任务的关键。FileReader API 用于读取文件,即把文件内容读入内存。它的参数是 File 对象或 Blob 对象。

var reader = new FileReader();
reader.abort();

URL

URL 对象居然也属于File API ,我也很吃惊,不过下面的API估计我们或多或少有用过

var objecturl =  window.URL.createObjectURL(blob);

上面的代码会对二进制数据生成一个 URL,这个 URL 可以放置于任何通常可以放置 URL 的地方,比如 img 标签的 src 属性。需要注意的是,即使是同样的二进制数据,每调用一次 URL.createObjectURL 方法,就会得到一个不一样的 URL。
这个 URL 的存在时间,等同于网页的存在时间,一旦网页刷新或卸载,这个 URL 就失效。(File 和 Blob 又何尝不是这样呢)除此之外,也可以手动调用 URL.revokeObjectURL 方法,使 URL 失效。

二、文件上传

介绍了那么多,实际用到的知识很少,但有个大概的内容体系才不多对自己写的代码一知半解不是吗。下面我们用个文件上传的demo实际操作一下:

我们用 form 表单和 ajax 方式来分别实现文件上传

image

    <section>
        <h1>form 表单方式</h1>
        <form method="POST" action="/api/uploadFile" enctype="multipart/form-data">
            <p>file upload</p>
            <span>picName:</span><input name="picName" type="text" /><br/>
            <input name="file" type="file" /><br/><br/>
            <button type="submit">submit</button>
        </form>
    </section>
    <section>
        <h1>formData 方式</h1>
        <input id="J_file_type1" name="file" type="file" /><br/><br/>
        <button id="J_btn_upload_type1">上传</button>
    </section>

1、form 表单方式

点击页面的 <button type="submit">submit</button> 就实现了文件上传。
form 表单设置了 action 上传路径 enctype 上传类型(表现在请求头中)这个在文章的最开始部分咱们也就介绍了就不多说了。
form 表单上传文件有个不好的地方是form 表单提交会刷新页面,也就对用户很不友好了,下面咱们再用 formData 方式 来实现文件上传

2、formData 方式

        const file = document.querySelector('#J_file_type1').files[0]
        const formData = new FormData()
        // 建立一个upload表单项,值为上传的文件
        formData.append('file', file)
        formData.append('name', file.name)
        const xhr = new XMLHttpRequest()
        xhr.open('POST', '/api/uploadFile')
        // 定义上传完成后的回调函数
        xhr.onload = function () {
            if (xhr.status === 200) {
                alert('上传成功')
            } else {
                alert('出错了')
            }
        }
        xhr.send(formData);

这里我们用到了 FormData 来实现文件上传。
FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据(keyed data),而独立于表单使用。如果表单enctype属性设为multipart/form-data ,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。

3、如果不使用 formData 方式呢

如果不使用FormData对象的情况下,通过AJAX序列化和提交表单也是可以实现表单上传,不过这也太变态了,因为要自己序列化上面提到的文件上传的请求主体

POST http://www.homeway.me HTTP/1.1
Content-Type:multipart/form-data; boundary=------WebKitFormBoundaryOGkWPJsSaJCPWjZP

------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key2"
456
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key1"
123
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="file"; filename="index.png"

感兴趣的可以点开链接查看 点开链接查看

运行项目

1、获取项目分支

git clone https://github.com/ZengTianShengZ/My-Blog.git
git checkout -b demo-file-upload origin/demo-file-upload

2、项目构建

cd demo                // 切换至 demo 目录
npm install
node app.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant