xmlhttprequest2.0 可以支持文件上传. 这东东很方便, 但是在实际使用中碰到了一些问题. 这里记录下.
正常情况下我们是这样生成 2 进制文件的.
1 2 3 4 5 6 7 8 9 10 11 12 |
//data为文件的base64编码 function dataURLtoBlob(data) { var tmp = data.split(','); tmp[1] = tmp[1].replace(/\s/g,''); var binary = atob(tmp[1]); var array = []; for(var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } return new Blob([new Uint8Array(array)], {type: 'image/jpeg'}); } |
但是在 android 手机上可能会由于没有 blob 对象导致无法生成 blob. 怎么办捏. 可以使用以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function newBlob(data, datatype){ var out; try { out = new Blob([data], {type: datatype}); //一切正常,直接使用blob. } catch (e) { window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if (e.name == 'TypeError' && window.BlobBuilder) { var bb = new BlobBuilder(); bb.append(data.buffer); out = bb.getBlob(datatype); //还可以抢救一下..使用blobbuilder来生成文件.. } else { //没救了,放弃治疗. } } return out; } |
ok. 现在文件已经 ready 了.
我们创建一个 formadata
1 2 |
var file = dataURLtoBlob(img); fd.append('img',file); |
愉快的上传.... 然后... 然后... 没有然后了... 抓包看下.
1 2 3 4 5 6 |
<span style="color:rgb(0, 0, 0)">------</span><span style="color:rgb(43, 145, 175)">WebKitFormBoundarysToAVAYMLPFfJF96</span><span style="color:rgb(0, 0, 0)"> </span><span style="color:rgb(43, 145, 175)">Content</span><span style="color:rgb(0, 0, 0)">-</span><span style="color:rgb(43, 145, 175)">Disposition</span><span style="color:rgb(0, 0, 0)">:</span><span style="color:rgb(0, 0, 0)"> form</span><span style="color:rgb(0, 0, 0)">-</span><span style="color:rgb(0, 0, 0)">data</span><span style="color:rgb(0, 0, 0)">;</span><span style="color:rgb(0, 0, 0)"> name</span><span style="color:rgb(0, 0, 0)">=</span><span style="color:rgb(128, 0, 0)">"img"</span><span style="color:rgb(0, 0, 0)">;</span><span style="color:rgb(0, 0, 0)"> filename</span><span style="color:rgb(0, 0, 0)">=</span><span style="color:rgb(128, 0, 0)">"blob"</span><span style="color:rgb(0, 0, 0)"> </span><span style="color:rgb(43, 145, 175)">Content</span><span style="color:rgb(0, 0, 0)">-</span><span style="color:rgb(43, 145, 175)">Type</span><span style="color:rgb(0, 0, 0)">:</span><span style="color:rgb(0, 0, 0)"> application</span><span style="color:rgb(0, 0, 0)">/</span><span style="color:rgb(0, 0, 0)">octet</span><span style="color:rgb(0, 0, 0)">-</span><span style="color:rgb(0, 0, 0)">stream </span><span style="color:rgb(0, 0, 0)">------</span><span style="color:rgb(43, 145, 175)">WebKitFormBoundarysToAVAYMLPFfJF96</span><span style="color:rgb(0, 0, 0)">--</span> |
文件的内容呢...
好吧.. 既然没法愉快用 formdata.. 那么就自己动手生成一个 post 的包体吧... 下面是相关的代码..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
function FormDataShim () { var o = this, parts = [],// Data to be sent boundary = Array(5).join('-') + (+new Date() * (1e16*Math.random())).toString(32), oldSend = XMLHttpRequest.prototype.send; this.append = function (name, value, filename) { parts.push('--' + boundary + '\r\nContent-Disposition: form-data; name="' + name + '"'); if (value instanceof Blob) { parts.push('; filename="'+ (filename || 'blob') +'"\r\nContent-Type: ' + value.type + '\r\n\r\n'); parts.push(value); } else { parts.push('\r\n\r\n' + value); } parts.push('\r\n'); }; //把xhr的send方法重写一下. XMLHttpRequest.prototype.send = function (val) { var fr, data, oXHR = this; if (val === o) { // 最后加一下boundary..注意这里一定要在最后加\r\n..否则服务器有可能会解析参数失败.. parts.push('--' + boundary + '--\r\n'); data = new XBlob(parts); fr = new FileReader(); fr.onload = function () { oldSend.call(oXHR, fr.result); }; fr.onerror = function (err) { throw err; }; fr.readAsArrayBuffer(data); // 设置content-type this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); XMLHttpRequest.prototype.send = oldSend; } else { oldSend.call(this, val); } }; } |
最后完整的代码长这样.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
function newBlob(data, datatype){ var out; try { out = new Blob([data], {type: datatype}); } catch (e) { window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if (e.name == 'TypeError' && window.BlobBuilder) { var bb = new BlobBuilder(); bb.append(data.buffer); out = bb.getBlob(datatype); } else if (e.name == "InvalidStateError") { out = new Blob([data], {type: datatype}); } else { } } return out; } // 判断是否需要blobbuilder var needsFormDataShim = (function () { var bCheck = ~navigator.userAgent.indexOf('Android') && ~navigator.vendor.indexOf('Google') && !~navigator.userAgent.indexOf('Chrome'); return bCheck && navigator.userAgent.match(/AppleWebKit\/(\d+)/).pop() <= 534; })(), blobConstruct = !!(function () { try { return new Blob(); } catch (e) {} })(), XBlob = blobConstruct ? window.Blob : function (parts, opts) { var bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder); parts.forEach(function (p) { bb.append(p); }); return bb.getBlob(opts ? opts.type : undefined); }; function FormDataShim () { // Store a reference to this var o = this, parts = [],// Data to be sent boundary = Array(5).join('-') + (+new Date() * (1e16*Math.random())).toString(32), oldSend = XMLHttpRequest.prototype.send; this.append = function (name, value, filename) { parts.push('--' + boundary + '\r\nContent-Disposition: form-data; name="' + name + '"'); if (value instanceof Blob) { parts.push('; filename="'+ (filename || 'blob') +'"\r\nContent-Type: ' + value.type + '\r\n\r\n'); parts.push(value); } else { parts.push('\r\n\r\n' + value); } parts.push('\r\n'); }; // Override XHR send() XMLHttpRequest.prototype.send = function (val) { var fr, data, oXHR = this; if (val === o) { //注意不能漏最后的\r\n ,否则有可能服务器解析不到参数. parts.push('--' + boundary + '--\r\n'); data = new XBlob(parts); fr = new FileReader(); fr.onload = function () { oldSend.call(oXHR, fr.result); }; fr.onerror = function (err) { throw err; }; fr.readAsArrayBuffer(data); this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); XMLHttpRequest.prototype.send = oldSend; } else { oldSend.call(this, val); } }; } //把图片转成formdata 可以使用的数据... //这里要把\s替换掉..要不然atob的时候会出错.... function dataURLtoBlob(data) { var tmp = data.split(','); tmp[1] = tmp[1].replace(/\s/g,''); var binary = atob(tmp[1]); var array = []; for(var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } return new newBlob(new Uint8Array(array), 'image/jpeg'); } function uploadFile(img){ var fd = needsFormDataShim ? new FormDataShim() : new FormData(); var file = dataURLtoBlob(img); fd.append('img',file); var prog = function(e){ /*你的逻辑*/ } var load = function(e){ /*你的逻辑*/ } var error = function(e){ /*你的逻辑*/ } var abort = function(e){ /*你的逻辑*/ } var xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress',prog,false); xhr.addEventListener('load',load,false); xhr.addEventListener('error',error,false); xhr.addEventListener('abort',abort,false); xhr.onreadystatechange = function(){ /*你的逻辑*/ } xhr.open('POST','/upload',true); xhr.send(fd); } |
参考:
cmcc_01 2017 年 9 月 20 日
这段代码你们确定能正常上传图片,亲测是有问题的
barret 2016 年 11 月 11 日
非常好,就是我想找的问题,在 android4.4 以下用得着
移动端图片上传的实践 | 神刀安全网 2016 年 6 月 2 日
[…] http://www.alloyteam.com/2015/04/ru-he-zai-yi-dong-web-shang-shang-chuan-wen-jian/ […]