学习common-upload源码,理解上传原理

之前介绍了只使用一段代码实现上传文件的方法。最近又试了几次,才发现这个是有问题的。
例如,要上传原文件如下的文件:

使用jsp上传后发现文件变成了下面这个样子,图片下面的像素没有了。

然后我又用common-upload1.2试了下,能够正常上传,文件不会变样。怎么会这个样子呢?

要解决这个问题先了解下背景知识,浏览器发http请求时一般的post方法会把输入控件的name与value拼成一个字符串做为请求体传给服务器。而 对于method="post" enctype="multipart/form-data"这种上传文件的http请求的请求头中加了类似这样的信息:
Content-Length    5137
Content-Type    multipart/form-data; boundary=---------------------------203012335616103


这个boundary代表请求体里的分隔符,就是整个请求体以这个分隔符的值把真正的文件内容包起来。
请求体是如下格式:

-----------------------------221842651231148
Content-Disposition: form-data; name="upfile"; filename="srcfile (2).jpg"
Content-Type: image/jpeg
<!—这里上传文件内容(字节数组)—》
-----------------------------221842651231148


可以看到分隔符里的内容除了文件字节,还在文件内容之前加一些文件基本信息。


下面分析下为什么jsp没有正确上传文件,先看看jsp上传代码:
1.取得contextLength的所有字节数组
in = new DataInputStream(request.getInputStream());
                int dataLength = request.getContentLength();
while (totalBytesRead < dataLength) {
                    byteRead = in.read(dataBytes, totalBytesRead,
                            dataLength);
                    totalBytesRead += byteRead;
                }
2.取文件的开始位置与结束位置
int pos;
                pos = file.indexOf("filename=\"");
                pos = file.indexOf("\n", pos) + 1;
                pos = file.indexOf("\n", pos) + 1;
                pos = file.indexOf("\n", pos) + 1;
                int boundaryLocation = file.indexOf(boundary, pos) - 4;                int startPos = ((file.substring(0, pos)).getBytes()).length;
                int endPos = ((file.substring(0, boundaryLocation))
                        .getBytes()).length;
3.把开始位置到结束位置的字节数组转换为文件,输出到磁盘。
FileOutputStream fos=new FileOutputStream(fileName)
fos.write(dataBytes, startPos, (endPos - startPos));
                fileOut.close();

那么common-upload又怎么做的呢,代码较多,这里只分析一些关键逻辑代码:
1.    解析文件,并请求头中取出分隔符
//rquest对象转为FileItemStreamIterator对象
org.apache.commons.fileupload.servlet.ServletFileUpload[line:146]
FileItemIterator fii = new ServletFileUpload().getItemIterator(request);
//取Header
org.apache.commons.fileupload.FileUploadBase[line:976]
FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
//取Header里的ContentType里的分隔符
org.apache.commons.fileupload.FileUploadBase[line:397]
protected byte[] getBoundary(String contentType) {


2.    找出请求体里分隔符之间的有效数据。
//找分隔符的方法如下,buffer里的字节要与boundary里的字节完全一样才认为是分隔符:
org.apache.commons.fileupload.MultipartStream[line:708]
protected int findSeparator() {
        int first;
        int match = 0;
        int maxpos = tail - boundaryLength;
        for (first = head;
        (first <= maxpos) && (match != boundaryLength);
        first++) {
            first = findByte(boundary[0], first);
            if (first == -1 || (first > maxpos)) {
                return -1;
            }
            for (match = 1; match < boundaryLength; match++) {
                if (buffer[first + match] != boundary[match]) {
                    break;
                }
            }
        }
        if (match == boundaryLength) {
            return first - 1;
        }
        return -1;
}
3.    把分隔符分隔的每段有效数据汇总成为一个字节数组,最后形成文件。这里使用了如下的类和方法来汇总:
org.apache.commons.fileupload.MultipartStream[line:784]
public class ItemInputStream extends InputStream implements Closeable
public int read(byte[] b, int off, int len) throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            if (len == 0) {
                return 0;
            }
            int res = available();
            if (res == 0) {
                res = makeAvailable();
                if (res == 0) {
                    return -1;
                }
            }
            res = Math.min(res, len);
            System.arraycopy(buffer, head, b, off, res);
            head += res;
            total += res;
            return res;
        }
最后total数就是最终的文件字节数了,再用FileOutputStream即可写为文件。这个available函数和怎么覆盖InputStream的还没弄得太明白,还需进一步了解流的底层流程。
由上面可以看出jsp上传代码错误主要是分隔符识别不正确导致的。

转载于:https://www.cnblogs.com/chengzhong/p/4267104.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值