`
ailongni
  • 浏览: 62176 次
  • 性别: 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就是全路径名

    java文件异步上传

    在Java端,我们需要创建一个处理文件上传的服务器端接口。这通常涉及到Servlet或Spring MVC中的控制器方法。当文件上传请求到达服务器时,这些方法会接收文件流,保存到服务器的磁盘上,或者将其存储到数据库或云...

    实现文件上传的java代码

    本篇文章将详细解析如何实现文件上传的Java代码,主要关注`Upload.java`这个类的实现。 首先,理解文件上传的基本原理至关重要。在HTTP协议中,文件上传通常依赖于多部分/形式数据(Multipart/form-data)的请求...

    Java自带的HttpURLConnection访问接口实现文件上传

    在Java编程语言中,HTTPURLConnection是Java标准库提供...虽然这需要对HTTP协议有一定的理解,但它是理解网络编程和文件上传原理的一个好起点。在实际开发中,我们还需要考虑如安全性、错误处理和性能优化等更多因素。

    java实现的文件上传

    本教程将详细介绍如何使用Java实现文件上传功能。 一、基本原理 文件上传通常涉及到HTTP协议中的`multipart/form-data`编码类型。这种编码方式允许在一个表单中发送多个部分的数据,包括文本和二进制数据(如文件...

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

    Java 文件上传进度条实现的基本原理主要涉及到客户端与服务器之间的数据传输、多线程处理和用户界面更新。在本文中,我们将深入探讨这个过程,并提供一个简单的实现思路。 首先,了解文件上传的基本流程:用户选择...

    java实现拖拽上传

    总之,拖拽上传是一种提升用户交互体验的有效方式,通过HTML5的拖放API和Java的文件上传处理,可以轻松实现这一功能。在实现过程中,注意前端与后端的协同工作,确保文件安全、高效地上传至服务器。

    java实现利用HTTP基于servlet上传文件至服务器.pdf

    "Java 实现利用 HTTP 基于 Servlet 上传文件至服务器" 文件上传概述 文件上传是指客户端将文件传输到服务器端的过程。...通过本文,读者可以了解 Java 实现文件上传的基本原理和方法,并应用于实际开发中。

    文件上传java代码实现

    本文将详细解析一个具体的Java文件上传代码示例,包括其实现原理、关键技术和注意事项。 #### 二、技术栈与环境配置 1. **开发工具**:MyEclipse,一个基于Eclipse的IDE,广泛用于Java Web项目的开发。 2. **语言...

    java实现文件上传到ftp

    本教程将详细介绍如何使用Java实现文件上传到FTP服务器,这适用于初学者熟悉FTP客户端编程的基础概念。 首先,我们要了解FTP的基本工作原理。FTP允许客户端连接到服务器,发送文件,接收文件,或者列出服务器上的...

    HTTP上传文件原理.doc

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

    java实现的多文件上传

    在Java编程语言中,实现多文件上传是一项常见的任务,尤其在构建Web应用程序时。这个功能允许用户一次性上传多个文件,极大地提高了用户体验。本篇文章将详细解释如何使用Java来实现这一功能,包括必要的技术和步骤...

    如何利用Java实现QQ文件传输功能

    本知识点将围绕Java网络编程中的Socket通信和文件传输的实现原理进行详细探讨。 1. Java网络编程基础 Java网络编程提供了两个基本的类来实现网络通信,分别是Socket和ServerSocket类。Socket代表客户端的套接字,它...

    带进度条的文件上传下载组件(JAVA)

    理解这些协议的工作原理对于实现上传下载功能至关重要,包括请求和响应的结构,以及如何处理POST请求(用于文件上传)和GET请求(用于文件下载)。 5. **多线程**:为了实现进度条的效果,通常需要在后台异步处理...

    java实现文件上传jar包

    这个场景中提到的"java实现文件上传jar包"是指使用Java库来处理文件上传功能。主要涉及到两个关键的开源库:Apache Commons IO和Apache Commons FileUpload。下面将详细阐述这两个库以及如何使用它们来实现文件上传...

    一个java实现的分布式文件存储系统,可以实现文件分布存储在不同的服务器中,进行上传、下载、删除

    文件上传过程中,客户端将文件分割并发送到各个节点,系统会处理文件的完整性校验和冗余备份,保证数据的一致性。 文件访问是分布式文件系统中的另一个重要方面。文件下载时,系统需要合并从不同节点接收到的数据块...

    java实现文件上传与下载

    1. Struts2文件上传原理:Struts2通过`org.apache.struts2.components.File`组件来处理文件上传。当用户在表单中提交带有`enctype="multipart/form-data"`属性的表单时,浏览器会将文件作为多部分请求发送到服务器。...

    java 附件文件上传

    2. **文件上传原理** 文件上传是通过HTTP协议的POST请求来实现的。在HTTP请求中,文件数据被编码为多部分/形式数据(multipart/form-data),每个部分都包含一个描述其内容类型的头部和文件内容。 3. **Struts中的...

    基于Java文件输入输出流实现文件上传下载功能

    本文将详细介绍基于Java文件输入输出流实现文件上传下载功能的相关知识点,包括文件上传和下载的实现原理、Java文件输入输出流的基础操作、Servlet的使用等。 文件上传 文件上传是指客户端将文件传输到服务器端的...

    java上传文件简单代码

    ### Java文件上传的基本原理 在Java Web开发中,文件上传是一个常见的需求。通常情况下,文件上传可以通过HTTP请求来完成,而处理这些请求则需要服务器端的程序来进行解析与存储。在Java Servlet中,可以使用`...

Global site tag (gtag.js) - Google Analytics