学习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上传代码错误主要是分隔符识别不正确导致的。

时间: 2024-11-02 23:19:26

学习common-upload源码,理解上传原理的相关文章

深入springMVC源码------文件上传源码解析(下篇)

在上篇<深入springMVC------文件上传源码解析(上篇) >中,介绍了springmvc文件上传相关.那么本篇呢,将进一步介绍springmvc 上传文件的效率问题. 相信大部分人在处理文件上传逻辑的时候会直接获取输入流直接进行操作,伪代码类似这样: @RequestMapping(value = "/upload", method = RequestMethod.POST) public ResultView upload(@RequestParam("

Asp.net core 学习笔记 ( upload/download files 文件上传与下载 )

2017-09-25 refer : https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads https://www.codeproject.com/Articles/1203408/Upload-Download-Files-in-ASP-NET-Core 这里只说说小文件上传. 先看看前端 js 代码 <input id="inputFile" type="file" /&g

文件上传与下载学习笔记(1)---文件上传原理及配置

一:原理:将客户端的文件上传到服务器端的临时目录,再将服务器端的临时文件移动到指定目录. 二:客户端的配置 表单的method必须为post方法 表单必须添加enctype="multipart/form-data"属性 二者缺一不可. 三:将临时文件移动到指定目录 两种方法:1:move_uploaded_file($filename, $destination) 2:copy($source, $dest) 四:php.ini中的配置 在php.ini中搜索uploads ,会看到

型学习笔记5:C源码理解

型学习笔记5:C源码理解 http://jfsqhwhat2.eju.cn/ http://13660038501.i.sohu.com/v2/guestbook/index.htm http://15306736050.i.sohu.com/v2/guestbook/index.htm http://15090269366.i.sohu.com/v2/guestbook/index.htm http://13377896359.i.sohu.com/v2/guestbook/index.htm

php文件上传原理详解(含源码)

1.文件上传原理 将客户端的文件上传到服务器,再将服务器的临时文件上传到指定目录 2.客户端配置 提交表单 表单的发送方式为post 添加enctype="multipart/form-data" 3.服务器端配置 file_uploads = On,支持HTTP上传 uoload_tmp_dir = ,临时文件保存目录 upload_max_filesize = 2M,允许上传文件的最大值 max_file_uploads = 20 ,允许一次上传到的最大文件数 post_max_s

Hadoop学习笔记(9) ——源码初窥

Hadoop学习笔记(9) ——源码初窥 之前我们把Hadoop算是入了门,下载的源码,写了HelloWorld,简要分析了其编程要点,然后也编了个较复杂的示例.接下来其实就有两条路可走了,一条是继续深入研究其编程及部署等,让其功能使用的淋漓尽致.二是停下来,先看看其源码,研究下如何实现的.在这里我就选择第二条路. 研究源码,那我们就来先看一下整个目录里有点啥: 这个是刚下完代码后,目录列表中的内容. 目录/文件 说明 bin 下面存放着可执行的sh命名,所有操作都在这里 conf 配置文件所在

从源码理解Spring原理,并用代码实现简易Spring框架

前言(本文为原创,转载请注明出处) 个人之前对于框架的学习,就停留在配置,使用阶段.说实话过段时间就会忘得荡然无存.也不知道框架的运行逻辑,就是知道添加个注解,就可以用了. 由于实习,时间比较多,也感恩遇到个好老师,教并给我时间看源码,虽然没有做过多少业务,但是感觉比做业务更有意义.慢慢的去跟代码, 对Spring 运行流程大致有个解.现分享给大家,不足之处,希望各位补充,相互学习. 从源码看Spring 可能我们很少在意,ClassPathXmlApplicationContext这个类,其实

HashMap源码理解

导语 HashMap是常用的数据结构,了解HashMap,对提高代码的效率有很大的帮助.HashMap在JDK1.8中对数据结构进行了优化:提高了查询和删除的效率.当然,这也导致了结构更加的复杂:但通过认真阅读源码,还是可以掌握其要领的. 读完本篇文章,你应该理解的内容 点击这里查看大图 说明:HashMap的数据结构是个Hash表(可以理解为数组),每个槽中存放着一些节点. 一般情况下,一个槽中存放一个节点: 数据量较大时,一个槽中可能存放多个节点,此时,各个节点以链表的方式连接在一起: 当一

spark 源码理解2 进一步窥探Master、Worker通信机制

上一篇文章 spark 源码理解1 从spark启动脚本开始 是分析执行start_all.sh时,集群中启动了哪些进程,下面我们再深入一点看看这些进程都是做什么用的,它们之间又是如何通信的? 一.Master进程的启动 Master进程,它主要负责对Worker.Driver.App等资源的管理并与它们进行通信,这篇文章中我打算着重讲一下它与Worker的通信,其它的部分放在以后的章节再加以描述. spark-daemon.sh start org.apache.spark.deploy.ma