`
ailongni
  • 浏览: 61918 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

HTTP 文件上传原理 Java 实现

    博客分类:
  • http
 
阅读更多

前言:

文件上传用的已经很多,java web 大概用到如下

  1. Struts
  2. Spring MVC  CommonsMultipartResolver
  3. Commons-fileupload

  Struts/Spring MVC 实现都是基于Commons-fileupload,但背后的原理,大多数估计没有关注,最近阅读一些开源源码也发现,只有基础才是最重要的,万变不离其宗,在it领域不然会被漫天的新技术,冲昏了头,不知所措,下面开始。

 

HTTP:

  1. 表单form 类似

 

<form action="/file/upload" method="post" enctype="multipart/form-data">

<input type="text" name="name"><br>

<input type="file" name="file1"><br>

<input type="file" name="file2"><br>

<input type="submit" value="提交">

</form>

 

    2. 用浏览器追踪表单提交,会发现如下

    

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Content-Type:multipart/form-data; boundary=----WebKitFormBoundary4PCP0w0H0qxg16VB
Origin:http://localhost:8080
Referer:http://localhost:8080/sys/template/tem/create
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36


------WebKitFormBoundary4PCP0w0H0qxg16VB
Content-Disposition: form-data; name="id"


------WebKitFormBoundary4PCP0w0H0qxg16VB
Content-Disposition: form-data; name="name"

测试
------WebKitFormBoundary4PCP0w0H0qxg16VB
Content-Disposition: form-data; name="type"

INDEX
------WebKitFormBoundary4PCP0w0H0qxg16VB
Content-Disposition: form-data; name="layoutFile"; filename="confirm-btn.png"
Content-Type: image/png


------WebKitFormBoundary4PCP0w0H0qxg16VB
Content-Disposition: form-data; name="temFile"; filename="login.html"
Content-Type: text/html


------WebKitFormBoundary4PCP0w0H0qxg16VB--

   

 

    重要部位红色已经标注,表单提交时http 头部的 Content-Type 会有一个boundary分隔符,分隔符会分割表单提交的每项内容(也就是每个input域),如是文件则Content-Disposition会出现一个filename,同时带上Content-Type描述文件类型,否则没有,大体的解析格式如下(为了显示观看,故意换行显示,实际上没有)

-----------分隔符\r\n
Content-Disposition: form-data; name="XX"\r\n
Content-Type: image/png\r\n
\r\n
具体内容
------------分隔符\r\n
Content-Disposition: form-data; name="XX"; filename="XX"\r\n
Content-Type: image/png\r\n
\r\n
具体内容
------------分隔符\r\n
Content-Disposition: form-data; name="XX"; filename="XX"\r\n
Content-Type: image/png\r\n
\r\n
具体内容
------------分隔符--\r\n

 

注:最后一行会多出--,例如---------------分隔符--\r\n,同时------------分隔符会比boundary=----分隔符 多--两个,总体可以理解以--boundary进行分割的

 

 

JAVA Servlet 实现:

@WebServlet(urlPatterns="/file/upload")
public class FileServlet extends HttpServlet{
    
    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        String contentType = request.getContentType();
        
        //文件上传(类似:Content-Type:multipart/form-data; boundary=----WebKitFormBoundary4PCP0w0H0qxg16VB)
        if(contentType != null && contentType.startsWith("multipart/form-data")){
            try {
                List<FileItem> fileItems = FileItemParse.parseForm(request);
                System.out.println(fileItems);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
    }
    
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.getRequestDispatcher("/WEB-INF/views/file/upload.jsp")
                .forward(request, response);
    }
    
}

 

 

public class FileItemParse {
    
    //获取边界值
    public static String getBoundary(HttpServletRequest request) {  
        String rtnStr = null;  
        String tmpType = request.getContentType();  
        if (null != tmpType) {  
            rtnStr = tmpType.contains("boundary=") ? tmpType.split("boundary=")[1] : null;  
        }  
        return "--".concat(rtnStr); //此处应该是规范,比ContentType中多2个-
    }  

    //解析表单
    public static List<FileItem> parseForm(HttpServletRequest request) throws Exception{
        List<FileItem> fileItems = new ArrayList<FileItem>();
        
        byte[] boundaryBytes = getBoundary(request).getBytes();
        int boundaryBytesLen = boundaryBytes.length;
        
        BufferedInputStream input = null;  
        ByteArrayOutputStream out = new ByteArrayOutputStream();  
        try {  
            input = new BufferedInputStream(request.getInputStream());  
            int tmpI = -1;  
            int tmpL = -1; 
            FileItem item = null;
            
            //跳过分界线
            input.skip(boundaryBytesLen);
            while ((tmpI = input.read()) != -1) {  
                if (tmpI == 13) {  
                    tmpL = (input.read());  
                    if (tmpL == 10) {
                        if (out.size() == 0) { //跳过空行分隔符
                            continue;
                        }
                        
                        String bufferStr = out.toString("UTF-8");
                        //Content-Disposition
                        if(bufferStr.contains("Content-Disposition:")){
                            item = new FileItem();
                            String[] tmpStr = bufferStr.split(";");
                            String nameV = tmpStr[1].split("=")[1];
                            item.setParamName(nameV.substring(1, nameV.length() - 1)); //去除"
                            
                            if(bufferStr.contains("filename")){//文件表单域
                                String filenameV = tmpStr[2].split("=")[1];
                                item.setFileName(filenameV.substring(1, filenameV.length() - 1)); //去除"
                            }else{//普通表单域
                                fetchContent(item, input, boundaryBytes);
                                fileItems.add(item);
                            }
                            out.reset();
                            continue;
                        }
                        //Content-Type
                        if(bufferStr.contains("Content-Type:")){
                            item.setMimeType(bufferStr.split(":")[1].trim());
                            fetchContent(item, input, boundaryBytes);
                            fileItems.add(item);
                            //文件存储
                            out.reset();
                            continue;
                        }
                    }  
                    out.write(tmpI);  
                    out.write(tmpL); 
                }  
                out.write(tmpI);  
            }  
        } catch (IOException ioe) {  
            ioe.printStackTrace();  
        } finally {  
            if (null != input) {  
                try {  
                    out.close();  
                    input.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
        return fileItems;  
    }
    
    //内容提取
    private static void fetchContent(FileItem item, BufferedInputStream input, byte[] boundaryBytes) throws IOException{
        input.skip(2); //跳过空行分隔符
        
        int i = -1;
        int l = -1;
        ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
        byte[] tempByte = new byte[boundaryBytes.length]; 
        while((i = input.read()) != -1){
            if (13 == i) { 
                l = input.read();
                if (10 == l && isBoundary(input, boundaryBytes, tempByte)) {
                    break;
                } 
                else {  
                    tempOut.write(i);
                    tempOut.write(l);
                    if (10 == l) { //如不是分解符,则写入存储
                        tempOut.write(tempByte);
                    }
                    continue;
                }  
            }  
            tempOut.write(i); 
        }
        
        if(item.getMimeType() != null){ //文件
            //此处测试环境,故直接写入本地文件,正式应写入系统java.io.temp目录
            String url = "d:/temp/" + item.getFileName();
            File file = new File(url);
            if(!file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            FileOutputStream out = new FileOutputStream(file);
            out.write(tempOut.toByteArray());
            out.flush();
            out.close();
            item.setSimpleField(false);
            item.setFilePath(url);
        }
        else{
            item.setParamValue(new String(tempOut.toByteArray(), "UTF-8"));
            item.setSimpleField(true);
        }
    }
    
    private static boolean isBoundary(BufferedInputStream input, byte[] sourceBoundaryBytes, byte[] temp) throws IOException{
        int count = input.read(temp);
        
        for (int i = 0; i < count; i++) {
            if (sourceBoundaryBytes[i] != temp[i]) {
                return false;
            }
        }
        return true;
    }
    
}

 

public class FileItem {

    //file
    private String mimeType; //文件类型
    private String filePath; //存储路径
    private String fileName; //上传文件名
    
    //true:非file表单项, false:file表单项
    private boolean isSimpleField;
    
    private String paramName; 
    private String paramValue;
 
   //get set

}

 

 

以上只是一个简单的不完全实现,主要是针对HTTP 文件上传数据协议的一个解析过程,更多的可以去看Commons-fileupload源码,里面有更进一步的数据封装(例如进度条)。

 

参考文献:

http://www.ietf.org:80/rfc/rfc1867.txt

http://www.ietf.org:80/rfc/rfc2045.txt

http://blog.csdn.net/ybygjy/article/details/5869158

 

 

    

 

 

分享到:
评论

相关推荐

    只需要用一张图片素材文档选择器.zip

    只需要用一张图片素材文档选择器.zip

    浙江大学842真题09-24 不含答案 信号与系统和数字电路

    浙江大学842真题09-24 不含答案 信号与系统和数字电路

    无标题baci和jbaci

    无标题baci和jbaci

    完整的雷达系统仿真程序,完整的雷达系统仿真程序 matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    实体商品销售源码最新优化.zip

    实体商品销售源码最新优化.zip

    戴尔存储MD1400机柜维护操作与安全指导

    内容概要:本文档详细介绍了戴尔存储MD1400机柜的安全注意事项、电源指示灯解释、故障排除方法以及硬件维护步骤,包括卸下和安装直流电源设备、硬盘驱动器和背板的具体操作流程。 适用人群:IT运维人员、数据中心管理员和技术支持工程师。 使用场景及目标:在维护和管理戴尔存储MD1400机柜时作为参考指南,确保正确安装和故障排查,避免安全隐患和设备损坏。 其他说明:文档提供了丰富的图文指导,帮助使用者更好地理解和执行相关操作。

    PyClass 课程计划.zip

    PyClass 课程计划Noisebridge Python 课程每周一晚上 7 点至 9 点(太平洋时间)在旧金山 Noisebridge 二楼电子室举行。自 2024 年 8 月起,该课程目前暂停。请参阅 wiki 页面了解更多信息。本课程免费!如果您希望捐款,请捐赠给 Noisebridge。建议捐款15 美元、50 美元、200 美元以上建议每月捐款每月 10 美元、20 美元、40 美元、80 美元以上所有 Python 课程均遵循Noisebridge 反骚扰政策、 Noisebridge 冲突解决指南和 recurse.org 社交规则课后,我们欢迎您提供反馈! 在此提交表格内容课程课程描述新生阅读迭代次数Noisebridge Python 课程至少早在 2015 年就已经存在,拥有许多不同的讲师和版本。从 2017 年到 2018 年,该课程似乎由Jared Garst负责。(?)。从 2023 年到 2024 年,该课程由Travis Briggs负责。如果您有其他关于此类历史的信息想要分享,请在此处创建 PR、

    自动化部署管道创建的代码库(含 Concourse 和 Jenkins 相关).zip

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。

    一种新的混合优化算法,即瞬态三角哈里斯鹰优化器(Tthho) matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    1-中国各地万达广场地理分布数据2006-2021-社科数据.zip

    万达广场作为城市综合体的代表,在中国各地的地理分布数据集覆盖了2006至2021年。这些数据详细记录了万达广场的多个关键指标,包括项目名称、项目信息、具体地点、开业时间、商业面积以及精确的经度和纬度。万达广场不仅是商业地产开发的先行者,还以其成熟的商业模式、完善的产业链和丰富的商业资源,在全国范围内形成了独立的大型商圈。这些综合体集购物、餐饮、文化、娱乐等多种功能于一体,对提升城市商业档次、增加就业岗位、创造税收以及丰富群众消费需求等方面产生了显著的社会效益。数据集提供了420条样本,为研究中国区域经济发展特征及其未来趋势提供了宝贵的信息资源。

    正在月下弹琴的古装美女flash场景动画.zip

    正在月下弹琴的古装美女flash场景动画.zip

    理光Ricoh-MP C8003打印机驱动下载

    理光 MP C8003 是一款彩色激光多功能数码复合机。 【基础性能】 打印复印速度:黑白和彩色打印 / 复印速度均可达 80 页每分钟,能够快速高效地完成大量文档的输出任务,有效提高工作效率 分辨率:拥有 1200x4800dpi 的高分辨率,可输出色彩鲜艳、细节丰富、图像清晰的文档和图像,满足专业级的打印和复印需求,尤其适合对色彩精度要求较高的设计图纸、宣传资料等文件的输出 首张输出时间:黑白首张复印时间为 4.7 秒,彩色首张复印时间为 6.3 秒,在启动打印或复印任务时无需长时间等待,可迅速响应,进一步提升工作效率 纸张容量:标准配置的纸张容量为单 2500 页抽屉和双 550 页抽屉,还可通过扩展将纸张容量从 3700 页提升至 8100 页,能够满足不同规模的打印任务需求,减少纸张添加的频率 【功能多样性】 多功能一体:集复印、打印、扫描、传真功能于一身,可满足办公室多样化的文档处理需求,一台设备即可替代多台单一功能的设备,节省空间和成本 扫描功能:具备高速扫描能力,可通过多种扫描至选项将原件扫描并以电子形式分发,支持将扫描后的文件直接发送至个人移动设备

    《The Annotated Transformer》环境配置

    《The Annotated Transformer》环境配置

    基于深度学习resnet50和vgg16卷积神经网络的汉字书法识别项目源码+训练集+测试集 【可用于课设-毕设】

    深度学习大作业基于resnet50和vgg16卷积神经网络的汉字书法识别项目源码+训练集+测试集 操作步骤 将下载的训练集和测试集,解压到工程中 运行对应的data.py文件,进行转录,将原始数据集转录为numpy矩阵,生成data.npy及label.npy 运行对应的train.py进行训练 运行test.py使用训练完成的网络测试。

    直接序列扩频(DSSS) matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    代码

    代码

    ECharts柱状图-基础柱状图.rar

    图表效果及代码实现讲解链接:https://blog.csdn.net/zhangjiujiu/article/details/143996614

    Spring Data Key Value 特性的示例项目.zip

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。

    营销策划 -魔術絲-黑松露香蕉莓果饮品-新品发布会产品介绍-终版.pptx

    营销策划 -魔術絲-黑松露香蕉莓果饮品-新品发布会产品介绍-终版.pptx

    成都市数据条例.docx

    成都市数据条例.docx

Global site tag (gtag.js) - Google Analytics