`
ailongni
  • 浏览: 61242 次
  • 性别: 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

 

 

    

 

 

分享到:
评论

相关推荐

    JAVA文件上传原理源代码

    纯java代码,演示上传文件,适合任何文件,主要是了解HTTP请求的信息,然后解析请求的字符串,此事例只考虑了现在的两种主要的浏览器的请求,因为浏览器不一样文件名会有差异,IE就只有文件名,而FF就是全路径名

    HTTP上传文件原理.doc

    使用java通过HTTP协议上传文件 使用java通过HTTP协议上传文件 使用java通过HTTP协议上传文件 使用java通过HTTP协议上传文件 使用java通过HTTP协议上传文件 使用java通过HTTP协议上传文件

    java文件上传进度条实现基本原理

    该附件可以直接下载使用,体现文件上传进度条实现的基本技术以及原理

    java通用文件上传功能技术实现

    本文将介绍如何使用Java的HttpPost方法实现文件上传,并提供相关的代码示例。 二、文件上传的原理: 文件上传的原理是将文件的内容通过HTTP协议发送给服务器,服务器再对接收到的文件进行处理。在使用Java的...

    java实现 上传和下载

    经典的java文件上传下载实例。 通过阅读详细的注释,可以比较容易地理解java文件上传和下载的原理和一般的做法。

    基于swfupload 和extjs的多文件(跨域)文件上传(java)

    只是一次不能选取多个文件,而且界面不够美观,大家可以下载下来看看实现的原理,http://download.csdn.net/detail/cbai0722/5077523或者到我的资源下查找:java跨全域兼容ie/ff/chrome浏览器多文件上传. 当前这个依托...

    Java实现文件的上传下载

    该代码是一个使用Java开发的上传下载的一个demo,一共有两种方法,第一种是使用最基础的IO流进行文件的上传,可以了解一下原理。第二种是使用apache的commons-IO包进行文件的上传,其实只要了解了这两种上传文件的...

    Java文件上传下载实例(含详细注释)

    经典的java文件上传下载实例。 通过阅读详细的注释,可以比较容易地理解java文件上传和下载的原理和一般的做法。

    Java网络编程之TCP协议下—上传文件到服务器程序

    Java网络编程之TCP协议下—上传文件到服务器程序,欢迎大家下载和知道

    java 编写文件上传类简单易用

    本文将从文件传输的基本原理入手,分析如何用 java进行文件的上传,并提出解决方案。 一、基本原理 通过 HTML 上载文件的基本流程如下图所示。浏览器端提供了供用户选择提交内容的界面(通常是一个表单),在用户...

    web文件上传原理讲解与代码

    web文件上传原理讲解与代码

    Java文件上传与文件下载实现方法详解

    主要介绍了Java文件上传与文件下载实现方法,结合实例形式详细分析了Java文件上传与文件下载相关操作原理、实现方法及相关操作注意事项,需要的朋友可以参考下

    JAVA文件上传下载代码

    趁着有空的时候,学习了一些以前没有掌握的东西,因为自觉这个东西,应该用的比较多,所以学了一下,参考了一些网上其他人的代码,写了这个,比较初级,抽空可以看看。工具用的是myeclipse8.5+tomcat6.0

    SERVLET 上传文件原理,Cache-Contro,java编码,jbpm原理

    SERVLET 上传文件原理,Cache-Contro,java编码,jbpm原理,是一份综合资料,用于学习,如使用,请下载

    java源码包---java 源码 大量 实例

     Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的注释,对学习有帮助。 Java实现的FTP连接与数据浏览程序 1个...

    小练习--java程序实现文件剪切功能

    个人编写的实现文件剪切功能的java代码,这也是我们的月考题之一,里面有好几个需要注意的地方,上传此文档供大家参考

    JAVA上百实例源码以及开源项目

     Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的注释,对学习有帮助。 Java实现的FTP连接与数据浏览程序 1个...

    java源码包4

     Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的注释,对学习有帮助。 Java实现的FTP连接与数据浏览程序 1个...

    Java实现的远程监控系统软件程序源代码设计说明监控端硬盘文件的上传下载用户监视和操作 资源概述:此资源集包含了一套完整

    Java实现的远程监控系统软件程序源代码设计说明监控端硬盘文件的上传下载用户监视和操作 资源概述:此资源集包含了一套完整的Java实现的远程监控系统软件项目源代码、相关的设计文档以及详尽的使用说明。它旨在...

    java源码包3

     Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的注释,对学习有帮助。 Java实现的FTP连接与数据浏览程序 1个...

Global site tag (gtag.js) - Google Analytics