【纯干货】4年前想解决的事情,今天才实验成功:浏览器原生分块上传文件

第一份软件开发工作的第一个星期(不算做试用期的一个星期,无薪水试用)。因为不是软件专业,也没有经过培训和相关工作经验。老板不放心,但还是让我试一试。做的第一件事情就是上传文件,实时看进度,并且上传后预览。预览的文件类型有word,ppt,excel,flash,视频按帧获取预览图。office文件是在服务器端转成html后显示出来。

做的还满意,就留下来了,后来就在那个公司待了两年。

没解决的事情

上传大文件,分块上传,浏览器原生不支持,需要借助第三方插件。最根本的原因就是浏览器端的js考虑的安全问题,不允许读取文件内容。

现在IE10和其他主流浏览器支持html5了,js内置Filereader对象,websocket,这些听听都酷毙了。

重大意义的时刻:

我实现了原生分块上传,道路非常崎岖,结果很美好。

这一实现标识着,浏览器未来无可替代的地位。

我只介绍干货部分,其他细节,相信有兴趣的人已经知道了。

首先,分块上传,必须能在js内读取文件内容,filereader对象是关键。这是一个异步读取方法,必须在onload事件内获取文件内容。要真真的分块上传,靠onprogress读取文件进度是不够的,并且文件过大的时候浏览器会卡死。file的slice函数是关键,把文件内容分块,每一个onload事件触发,标志着一块内容读取完毕,且可以在该事件内把文件进一步处理,如上传。

FileReader有四个read方法,

asText,只建议用来读取文本文件

asDataUrl,读取到的媒体文件可以直接用于src属性,或者html文件内容也可以读成DataURL

asArrayBuffer,官方介绍说不能直接使用,需要借助DataView,如int8Array或int32Array。实际上这话是有条件的,既然设计出来肯定是有用处的,当websocket的binaryType=‘arraybuffer‘时,该event.target.result是可以直接被send到服务器端的,服务器端接收类型是byte[],对应superwebsocket的NewDataReceived事件。目前的问题,当文件大小是2k的时候,服务器端可以接收;而当文件为18K的时候,superwebsocket报protocolerror错误。尚不知道临界值是什么,或者需要设置什么参数,rfc6455协议好像也没限制最大大小吧。还是去年看过这个协议,待会查查。

webSocket的关键设置:

client = new WebSocket("ws://127.0.0.1:2014");
client.binaryType = "arraybuffer";//如果不设置该属性,就不能直接send字节数组到服务端。如果superwebsocket服务端报protocolerror则有可能是这个原因引起的

webSocket.send方法在onload事件中调用:

client.send(this.result);

结合slice和onload事件分块上传的核心代码:

         var res = this.result;
            loaded += res.byteLength;
            if (loaded < fileSize)//继续读取下一块
            {
                readBlob(loaded);
                times += 1;
                console.log("next block,times:" + times);
            }
            else {
                //读取完成
                console.log("done loaded:" + loaded + ",size:" + fileSize);
            }    

slice的使用方法:

function readBlob(start)
{
    var blob = currentFile.slice(start, start + step);

    reader.readAsArrayBuffer(blob);
}
     // 0             1                   2                   3
     // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     //+-+-+-+-+-------+-+-------------+-------------------------------+
     //|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     //|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     //|N|V|V|V|       |S|             |   (if payload len==126/127)   |
     //| |1|2|3|       |K|             |                               |
     //+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     //|     Extended payload length continued, if payload len == 127  |
     //+ - - - - - - - - - - - - - - - +-------------------------------+
     //|                               |Masking-key, if MASK set to 1  |
     //+-------------------------------+-------------------------------+
     //| Masking-key (continued)       |          Payload Data         |
     //+-------------------------------- - - - - - - - - - - - - - - - +
     //:                     Payload Data continued ...                :
     //+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     //|                     Payload Data continued ...                |
     //+---------------------------------------------------------------+

从帧结构来看,有8个字节表示内容长度,所以内容长度限制在哪设置的?有谁熟悉superwebsocket

asBinaryString 现在不赞成使用的方法,且IE中也没有该方法。

如果服务器端,不支持websocket又不想使用第三方支持如superwebsocket这样组件,可以直接使用ajax真真分块上传,确定是每次请求都会建立新的连接。

使用ajax上传注意事项干货:

建议把arraybuffer转成int32array,这样客户端转换较快。服务器端用int32[]接收;如果int8array服务端也可以用int32接收。

重点:async:false 保证顺序;也可以每次上传的时候额外带参序号,服务器端重新组装顺序。

Int8array的话,服务端可以直接把每个元素转成byte

  [System.Web.Mvc.HttpPost]
        public ActionResult File(int[] datas)
        {
            if (datas != null)
            {
                var d = datas.ToList().ConvertAll(x => (byte)x).ToArray();
            }
            return Content("OK");
        }

int32array的话,服务端可以借助bitconverter.getbytes(int)方法。

 [System.Web.Mvc.HttpPost]
        public ActionResult File(int[] datas)
        {
            if (datas != null)
            {
                List<byte> bs = new List<byte>();//接收到的文件缓冲对象
                for (int i = 0; i < datas.Length; i++)
                {
                    foreach (var item in BitConverter.GetBytes(datas[i]))
                    {
                        bs.Add(item);
                    }
                }
            }
            return Content("OK");
        }

ajax客户端上传关键方法:

  $.ajax("/Home/File", {
                data: { datas: new Int8Array(this.result) }, success: function (res) {
                    console.log(res);
                },
                async: false,
                type:"post"
            });

这个调用是位于FileReader的onload事件中。

如果觉得有意义,别忘了点【推荐】

时间: 2024-10-06 04:55:11

【纯干货】4年前想解决的事情,今天才实验成功:浏览器原生分块上传文件的相关文章

解决微信内嵌浏览器无法响应上传文件(图片)的思路(2种办法)

进园一年多来,第一次写博客,好激动.原因主要是自己平时都是有写笔记,不习惯写博客,这次想写博客的原因是,这个问题确实我做了很久,已经做了近两周才解决了这个问题,,而且两周时间里尝试过了很多种办法,然后由于网上又没有多少人分享这个,决定自己写一下. 先自我介绍下,我是惠州学院大二(准大三)的学生,在去年暑假时候加了一个工作室开始写代码,也是在那个时候加入博客园,最近因为工作室要做微信端网页的开发需要一个上传图片功能,然后编码的时候发现用平时的方法做的上传功能在Android的微信端上实现不了,百度

Selenium 上传文件失败,解决办法一

昨个改程序遇到一个问题,UI上面有需要上传文件的地方.但是我不知道怎么让Selenium完成 点击上传文件按钮->在弹出的文件选择窗口中选择路径和文件,点确定. 要知道弹出窗口属于window的范畴,Selenium只能处理Web page. Jeremy原先的代码用的是Selenium RC中的 type “filepath+filename”方法.可不知咋搞的反正运行到这里是进行不下去了. 正好借这个机会来研究一下在Webdriver里怎么做. 我自己写了个网页,里面就一句 我先是用Sele

上传文件form表单enctype=&quot;multipart/form-data&quot;传值解决办法(代原代码)

最近做的一个项目里遇到一个问 题,就是如何在上传文件的表单里传递其它的变量,因为一但form表单用了enctype="multipart/form-data"类型后,所有的值 都是以二进制进行传递的,所以当我们想取出这个表单里传递过来的其它变量的时候,就会遇到一个问题,那就是用request取不到传递过来的变量.在网上 找了很多资料,也都是大至说一下,在这里我就借花献佛发个完整的原代码解决这个问题!!! 工程目录如下: 两个jar可以去apache上下载: http://commons

怎样用纯HTML和CSS更改默认的上传文件按钮样式

如果你曾经试过,你就会知道,用纯CSS样式加HTML实现统一的上传文件按钮可能会很麻烦.看看下面的不同浏览器的截图.很明显的,他们长得很不一样. 我们的目标是创造一个简洁,用纯CSS实现的,在所有浏览器里的样子和布局是一样的上传文件按钮.我们可以这样: 步骤1.创建一个简单的HTML标记 1 2 3 4 <div class="fileUpload btn btn-primary">     <span>Upload</span>     <i

php上传文件中文文件名乱码的解决方法

想必很多朋友在进行utf8编码的php开发上传功能的时候,都会遇到这样的一个问题,就是上传中文文件名的文件时,文件名会变成乱码,其实我们可以用iconv函数对文件名进行重新编码就解决问题了 可能会有不少朋友碰到一些问题就是上传文件时如果是英文倒好原文名不会有问题,如果是中文可能就会出现乱码了,今天我来给大家总结一下导致乱码php上传文件中文文件名乱码的原因与解决办法吧. 这几天在windows下安装了XAMPP,准备初步学习一下php的相关内容.这几天接触到了php上传文件,但是出现了一个郁闷问

jQuery 关于IE9上传文件无法进入后台原因及解决办法(ajaxfileupload.js第四弹)

第四弹的诞生完全不在自己最初的计划之中,是有个网友看了先前关于<ajaxfileupload.js系列>的文章后提出的问题,由于自己一直是用chrome浏览器去测试demo,完全忽略IE浏览器(其实是故意的,懒得想浏览器兼容的问题,哈哈~),所以当我使用IE9去运行demo的时候,确实发现了同样的问题,就是ajax异步提交表单无法进入后台. 下面是解决整个问题的过程,以我在<jQuery 自制上传头像插件-附带Demo实例(ajaxfileupload.js第三弹) >中上传的de

jQuery 关于IE9上传文件无法进入后台问题的原因及解决办法(ajaxfileupload.js第四弹)

第四弹的诞生完全不在自己最初的计划之中,是有个网友看了先前关于<ajaxfileupload.js系列>的文章后提出的问题,由于自己一直是用chrome浏览器去测试demo,完全忽略IE浏览器(其实是故意的,懒得想浏览器兼容的问题,哈哈~),所以当我使用IE9去运行demo的时候,确实发现了同样的问题,就是ajax异步提交表单无法进入后台. 下面是解决整个问题的过程,以我在<jQuery 自制上传头像插件-附带Demo实例(ajaxfileupload.js第三弹) >中上传的de

解决Jqueryify插件在火狐,360浏览器无法上传文件问题

反复调试,发现其问题所在:jquery uploadify在ie下可以正常上传,在实现异步上传的时候,每一个文件在上传时都会提交给服务器一个请求.每个请求都需要安全验证,session 和cookie的校验.是的,就是这样.由于jquery uploadify是借助flash来实现上传的,每一次向后台发送数据流请求时,ie会自动把本地cookie存储捆绑在一起发送给服务器.但 firefox.chrome不会这样做,他们会认为这样不安全.哈,这就是原因. 解决办法,在Global.asax.cs

关于python3.6上传文件时报错:HTTPSConnectionPool(host=&#39;httpbin.org&#39;, port=443): Max retries exceeded with url: /post (Caused by SSLError(SSLError(1, &#39;[SSL: CERTIFICATE_VERIFY_FAIL解决办法

第一个报错: 最近在练习post请求中上传文件时遇到了一个奇葩事情,两台电脑上写了一模一样的代码,一个运行正常,另一个一片红. 最后了解了一下原因以及解决办法.先记录下关键代码: files = {"files":(r"F:\test.txt","xixihaha")} #直接将目标文件内容xixihaha通过文件test.txt进行上传 r = requests.post(url,files=files)print(r.headers) #前边