Open toxic-johann opened 7 years ago
这些天用webuploader实现一个手机上传图片的功能。做到一半发现,在电脑上是好的,在iOS上也是好的,但是在android上jpg图片会出错。
因为开始的时候报的是后端压缩错误,而我这边的压缩环境也布置的不好。一番捣鼓后,发觉还是不行。
仔细看报错,说我的文件是空文件,但是文件名是存在的。直到我用open命令试图打开这个“一直存在的图片文件”,才发现这个图片文件是空的。
那么bug就转到了前端上面去了。(啊,仔细去看报错啊,不要想当然。)
Google了一下发现是webuploader的固有bug。这个原因在webuploader的源代码上面也有标注。
if ( binary ) { // 强制设置成 content-type 为文件流。 xhr.overrideMimeType && xhr.overrideMimeType('application/octet-stream'); // android直接发送blob会导致服务端接收到的是空文件。 // bug详情。 // https://code.google.com/p/android/issues/detail?id=39882 // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 if ( Base.os.android ) { fr = new FileReader(); fr.onload = function() { xhr.send( this.result ); fr = fr.onload = null; }; fr.readAsArrayBuffer( binary ); } else { xhr.send( binary ); } } else { xhr.send( formData ); }
bug网址点击这里。
根据网上解决方法,这种情况下,只需要将sendAsBinary这个条件设定为true,就可以避免这个bug了。
sendAsBinary {Object} [可选] [默认值:false] 是否已二进制的流的方式发送文件,这样整个上传内容php://input都为文件内容, 其他参数在$_GET数组中。
但是这样子又诱发出另一个问题了。因为这种方式下,我们收到的文件是在post域里的,post会进行一定处理。那么究竟是处理成什么样呢。我们不清楚。
如果我们只是简单地用self.post()方法取出来,会发现是一个已经包装过的对象了。那样子我们就不能确认这个代码究竟还是不是正确。而且如何取出来也是一个问题。
由于我们用thinkjs1.x版本,于是我们唯有探进去看。在core/http.js代码里面我们发现了这么一个处理方式。
/** * 普通的表单上传 * @return {[type]} [description] */ _commonPost: function(){ var buffers = []; var length = 0; var self = this; var deferred = getDefer(); this.req.on('data', function(chunk){ buffers.push(chunk); length += chunk.length; }); this.req.on('end', function(){ self.http.payload = Buffer.concat(buffers).toString(); tag('form_parse', self.http).then(function(){ //默认使用querystring.parse解析 if (isEmpty(self.http.post) && self.http.payload) { self.http.post = self.filterQuery(querystring.parse(self.http.payload)); } var post = self.http.post; var length = Object.keys(post).length; //最大表单数超过限制 if (length > C('post_max_fields')) { self.res.statusCode = 413; self.http.end(); return; } for(var name in post){ //单个表单值长度超过限制 if (post[name] && post[name].length > C('post_max_fields_size')) { self.res.statusCode = 413; self.http.end(); return; } } deferred.resolve(self.http); }) }); return deferred.promise; },
我们可以看到self.http.payload是刚拼接出来的chunk,而这个chunk这个时候存在来了,且以后也没有经过querystring之类的进行处理。我们假设这个可以相信的,那我们就进行处理吧。
结果这个时候我们发现无论处理成binary、base64还是utf-8格式,文件都已经损坏了。而且在代码比对上我们也发现的确有所不同。
这个时候实在无奈了,于是就修改了一下thinkjs源码进行尝试。
我把处理后的代码串的长度打出来进行比对。
console.log(length); console.log(Buffer.concat(buffers).length) console.log(Buffer.concat(buffers).toString().length)
结果发现第三个值教前两个值有所减小。于是上网查了下,发现由于编码问题,这些转码会容易造成数据丢失。
所以要么就是避免走过这里,要么就是更改这段代码。
最后还是找了老六看看有没有什么办法可以弥补。
最后发现thinkjs可以通过在头部打入一个标记标明上传的是文件,躲过这个处理。
post_ajax_filename_header: 'x-filename', //通过ajax上传文件时文件名对应的header,如果有这个header表示是文件上传
于是这个时候就折回去webuploader查看一下有没有方法可以修改header。
最后成功地发现了uploadBeforeSend事件。
于是我们通过这个事件给请求打上header,从而走到正确的文件处理上。
if(type =="uploadBeforeSend"){ var headers = arguments[3]; headers["x-filename"] = arguments[2].name; }
就此修复这个bug…………
花了一天…………醉了………………
这些天用webuploader实现一个手机上传图片的功能。做到一半发现,在电脑上是好的,在iOS上也是好的,但是在android上jpg图片会出错。
因为开始的时候报的是后端压缩错误,而我这边的压缩环境也布置的不好。一番捣鼓后,发觉还是不行。
仔细看报错,说我的文件是空文件,但是文件名是存在的。直到我用open命令试图打开这个“一直存在的图片文件”,才发现这个图片文件是空的。
那么bug就转到了前端上面去了。(啊,仔细去看报错啊,不要想当然。)
Google了一下发现是webuploader的固有bug。这个原因在webuploader的源代码上面也有标注。
bug网址点击这里。
根据网上解决方法,这种情况下,只需要将sendAsBinary这个条件设定为true,就可以避免这个bug了。
但是这样子又诱发出另一个问题了。因为这种方式下,我们收到的文件是在post域里的,post会进行一定处理。那么究竟是处理成什么样呢。我们不清楚。
如果我们只是简单地用self.post()方法取出来,会发现是一个已经包装过的对象了。那样子我们就不能确认这个代码究竟还是不是正确。而且如何取出来也是一个问题。
由于我们用thinkjs1.x版本,于是我们唯有探进去看。在core/http.js代码里面我们发现了这么一个处理方式。
我们可以看到self.http.payload是刚拼接出来的chunk,而这个chunk这个时候存在来了,且以后也没有经过querystring之类的进行处理。我们假设这个可以相信的,那我们就进行处理吧。
结果这个时候我们发现无论处理成binary、base64还是utf-8格式,文件都已经损坏了。而且在代码比对上我们也发现的确有所不同。
这个时候实在无奈了,于是就修改了一下thinkjs源码进行尝试。
我把处理后的代码串的长度打出来进行比对。
结果发现第三个值教前两个值有所减小。于是上网查了下,发现由于编码问题,这些转码会容易造成数据丢失。
所以要么就是避免走过这里,要么就是更改这段代码。
最后还是找了老六看看有没有什么办法可以弥补。
最后发现thinkjs可以通过在头部打入一个标记标明上传的是文件,躲过这个处理。
于是这个时候就折回去webuploader查看一下有没有方法可以修改header。
最后成功地发现了uploadBeforeSend事件。
于是我们通过这个事件给请求打上header,从而走到正确的文件处理上。
就此修复这个bug…………
花了一天…………醉了………………