`
ouyangfei0426
  • 浏览: 127502 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

无组件java上传文件初探

    博客分类:
  • java
阅读更多

      最近闲来无事,突然对文件上传感兴趣起来,不由的想着自己不借助第三方包写个java文件上传的工具类,经过对http协议的一番研究,总算有点小成果。

      这是页面表单代码片段:

        <form action="gb/test.do" method="post" enctype="multipart/form-data">
        <h1>Hello World!</h1>
        &nbsp;&nbsp;name:<input type="text" name="name" value="" /><br>
        &nbsp;&nbsp;address:<input type="text" name="address" value="" /><br>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;age:<input type="text" name="age" value="" /><br>
        photo1:<input type="file" name="f1"><br>
        photo2:<input type="file" name="f2"><br>
        验证码:<input type="text" name="image"><img src="RandomImageServlet">
        <input type="submit">
        </form>

 在页面填写完数据后,提交表单,通过httpwatch我们可以看到如下数据:

POST /testWeb/gb/test.do HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */*
Referer: http://localhost:8084/testWeb/
Accept-Language: zh-cn
Content-Type: multipart/form-data; boundary=---------------------------7db1e4183026c
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.3; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: localhost:8084
Content-Length: 7647320
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=FEF31749D9D8CBE22C2A49C7A6799A15

-----------------------------7db1e4183026c
Content-Disposition: form-data; name="name"

灏忓己
-----------------------------7db1e4183026c
Content-Disposition: form-data; name="address"

杈藉畞澶ц繛
-----------------------------7db1e4183026c
Content-Disposition: form-data; name="age"

23
-----------------------------7db1e4183026c
Content-Disposition: form-data; name="f1"; filename="C:\Documents and Settings\mark\妗岄潰\JavaEE_CN.chm"
Content-Type: application/octet-stream

ITSF   `      鳋
3  ?|獅?? 犐"骒?|獅?? 犐"骒`              x       T?     汤     ?      籵             ITSP   T   
            ;       :   <   	  j?].!?濝 犐"骒T   PMGL>          /   /#IDXHDR樳? /#ITBITS   	/#STRINGS橍臥嚽/#SYSTEM ??/#TOPICS樳?冔/#URLSTR樻猶帤_/#URLTBL樸?備X	/#WINDOWS栰?丩/$FIftiMain桏?佽孶	/$OBJINST桏??/$WWAssociativeLinks/   /$WWAssociativeLinks/Property桏?/$WWKeywordLinks/   /$WWKeywordLinks/BTree栰?壚L/$WWKeywordLinks/Data桍扥亼l/$WWKeywordL掜o 簔螓6礩?晢彌?瘓戠7?@T怶御<删2w搋?50*xN樾sa捏?洙{)藽缃镎菻Z粺拺??I荷o柦雛?檀}mxJ暾???瑩巧坮汎煘	絴?                            `...
-----------------------------7db1e4183026c
Content-Disposition: form-data; name="f2"; filename="C:\Documents and Settings\mark\妗岄潰\pietty0327.exe"
Content-Type: application/octet-stream

MZ                @                                     ? ???L?This program cannot be run in DOS mode.

$       ?垵頽嫖頽嫖頽嫖齠徫靚嫖隻單靚嫖隻槲鬾嫖隻刮nn嫖mf刮飊嫖齠晃靚嫖mf晃黱嫖M坞n嫖頽缥猳嫖隻呂猲嫖e肝飊嫖頽嫖靚嫖隻嘉飊嫖Rich頽嫖                PE  L S醐B         
 ?      ? P?  ?  ?   @                      ?    I/                               芎
 ?   ? ?                                                     ?庿xn?{+0c7$(~m兩姅躆?鵈Tn琏籡E (e...
-----------------------------7db1e4183026c
Content-Disposition: form-data; name="image"

7hti
-----------------------------7db1e4183026c--

 其中我们仔细观看上面的数据

Content-Type: multipart/form-data; boundary=---------------------------7db1e4183026c

这里有个值boundary,这个单词意为分界之意,不难理解,它的值“

---------------------------7db1e4183026c

”是个分界符,用于把表单数据分为不同的段,但是如果仔细观察,你会发现正文里的分界符的长度比这个值多了2个字节长度,也就是多 了两个“-”,而且正文中的分界符每个分界符结尾都有一个回车换行符(http协议指明了行应当由回车/换行对结束 ),占两个字节,这里还要注意的是最后一个边界符——终结符,比前面的边界符多了两个“-”,但是,这里要注意的是

       终结符!=边界符+“--”;

      而是

      终结符=边界符-回车换行符+“--”+回车换行符!

这里估计很多人刚开始都是会处理错误的。

      此外我们还发现上传文件的数据段是这样的:

Content-Disposition: form-data; name="f1"; filename="C:\Documents and Settings\mark\妗岄潰\JavaEE_CN.chm"
Content-Type: application/octet-stream

    而只是普通的文本框输入的数据的话,它并没有filename=“...”,而且也没有下面的Content-Type这行数据:

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

    而我们的任务就是要处理这个数据流,把参数名,跟参数值从数据流里提取出来,下面是处理数据流的java代码:

 

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import javax.servlet.ServletInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author mark
 */
public class UploadFile {

    private static Log log = LogFactory.getLog(UploadFile.class);

    /**
     * 上传文件组件,调用该方法的servlet在使用该方法前必须先调用request.setCharacterEncoding()方法,设置编码格式。该编码格式须与页面编码格式一致。
     * @param sis 数据流
     * @param encoding 编码方式。必须与jsp页面编码方式一样,否则会有乱码。
     * @param length 数据流长度
     * @param upLoadPath 文件保存路径
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static HashMap uploadFile(ServletInputStream sis, String encoding, int length, String upLoadPath) throws IOException {
        HashMap paramMap = new HashMap();

        boolean isFirst = true;
        String boundary = null;//分界符
        byte[] tmpBytes = new byte[4096];//tmpBytes用于存储每行读取到的字节。
        int[] readBytesLength = new int[1];//数组readBytesLength中的元素i[0],用于保存readLine()方法中读取的实际字节数。
        int readStreamlength = 0;//readStreamlength用于记录已经读取的流的长度。
        String tmpString = null;

        tmpString = readLine(tmpBytes, readBytesLength, sis, encoding);
        readStreamlength = readStreamlength + readBytesLength[0];
        while (readStreamlength < length) {
            if (isFirst) {
                boundary = tmpString;
                isFirst = false;
            }
            if (tmpString.equals(boundary)) {
                String contentDisposition = readLine(tmpBytes, readBytesLength, sis, encoding);
                readStreamlength = readStreamlength + readBytesLength[0];
                String contentType = readLine(tmpBytes, readBytesLength, sis, encoding);
                readStreamlength = readStreamlength + readBytesLength[0];
                //当时上传文件时content-Type不会是null
                if (contentType != null && contentType.trim().length() != 0) {
                    String paramName = getPramName(contentDisposition);
                    String fileName = getFileName(getFilePath(contentDisposition));

                    paramMap.put(paramName, fileName);

                    //跳过空格行
                    readLine(tmpBytes, readBytesLength, sis, encoding);
                    readStreamlength = readStreamlength + readBytesLength[0];

                    /*
                     * 文件名不为空,则上传了文件。
                     */
                    if (fileName != null && fileName.trim().length() != 0) {
                        fileName = upLoadPath + fileName;

                        //开始读取数据
                        byte[] cash = new byte[4096];
                        int flag = 0;
                        FileOutputStream fos = new FileOutputStream(fileName);
                        tmpString = readLine(tmpBytes, readBytesLength, sis, encoding);
                        readStreamlength = readStreamlength + readBytesLength[0];
                        /*
                         *分界符跟结束符虽然看上去只是结束符比分界符多了“--”,其实不是,
                         *分界符是“-----------------------------45931489520280”后面有2个看不见的回车换行符,即0D 0A
                         *而结束符是“-----------------------------45931489520280--”后面再跟2个看不见的回车换行符,即0D 0A
                         *
                         */
                        while (tmpString.indexOf(boundary.substring(0, boundary.length() - 2)) == -1) {
                            for (int j = 0; j < readBytesLength[0]; j++) {
                                cash[j] = tmpBytes[j];
                            }
                            flag = readBytesLength[0];
                            tmpString = readLine(tmpBytes, readBytesLength, sis, encoding);
                            readStreamlength = readStreamlength + readBytesLength[0];
                            if (tmpString.indexOf(boundary.substring(0, boundary.length() - 2)) == -1) {
                                fos.write(cash, 0, flag);
                                fos.flush();
                            } else {
                                fos.write(cash, 0, flag - 2);
                                fos.flush();
                            }
                        }
                        fos.close();
                    } else {
                        //跳过空格行
                        readLine(tmpBytes, readBytesLength, sis, encoding);
                        readStreamlength = readStreamlength + readBytesLength[0];

                        //读取分界符或者结束符
                        tmpString = readLine(tmpBytes, readBytesLength, sis, encoding);
                        readStreamlength = readStreamlength + readBytesLength[0];
                    }
                } //当不是长传文件时
                else {
                    String paramName = getPramName(contentDisposition);
                    String value = readLine(tmpBytes, readBytesLength, sis, encoding);
                    //去掉回车换行符(最后两个字节)
                    byte[] valueByte=value.getBytes(encoding);
                    value =new String(valueByte, 0, valueByte.length-2, encoding);
                    
                    readStreamlength = readStreamlength + readBytesLength[0];
                    paramMap.put(paramName, value);
                    tmpString = readLine(tmpBytes, readBytesLength, sis, encoding);
                    readStreamlength = readStreamlength + readBytesLength[0];
                }
            }

        }
        sis.close();
        return paramMap;
    }

    /**
     * 从流中读取一行数据。
     * @param bytes 字节数组,用于保存从流中读取到的字节。
     * @param index 一个整型数组,只有一个元素,即index[0],用于保存从流中实际读取的字节数。
     * @param sis 数据流
     * @param encoding 组建字符串时所用的编码
     * @return 将读取到的字节经特定编码方式组成的字符串。
     */
    private static String readLine(byte[] bytes, int[] index, ServletInputStream sis, String encoding) {
        try {
            index[0] = sis.readLine(bytes, 0, bytes.length);//readLine()方法把读取的内容保存到bytes数组的第0到第bytes.length处,返回值是实际读取的 字节数。
            if (index[0] < 0) {
                return null;
            }
        } catch (IOException e) {
            log.error("read line ioexception");
            return null;
        }
        if (encoding == null) {
            return new String(bytes, 0, index[0]);
        } else {
            try {
                return new String(bytes, 0, index[0], encoding);
            } catch (UnsupportedEncodingException ex) {
                log.error("Unsupported Encoding");
                return null;
            }
        }

    }

    private static String getPramName(String contentDisposition) {
        String s = contentDisposition.substring(contentDisposition.indexOf("name=\"") + 6);
        s = s.substring(0, s.indexOf('\"'));
        return s;
    }

    private static String getFilePath(String contentDisposition) {
        String s = contentDisposition.substring(contentDisposition.indexOf("filename=\"") + 10);
        s = s.substring(0, s.indexOf('\"'));
        return s;
    }

    private static String getFileName(String filePath) {
        String rtn = null;
        if (filePath != null) {
            int index = filePath.lastIndexOf("/");//根据name中包不包含/来判断浏览器的类型。
            if (index != -1)//包含/,则此时可以判断文件由火狐浏览器上传
            {
                rtn = filePath.substring(index + 1);//获得文件名
            } else//不包含/,可以判断文件由ie浏览器上传。
            {
                index = filePath.lastIndexOf("\\");
                if (index != -1) {
                    rtn = filePath.substring(index + 1);//获得文件名
                } else {
                    rtn = filePath;
                }
            }
        }
        return rtn;
    }
}
分享到:
评论

相关推荐

    基于计算机软件开发的JAVA编程应用初探.pdf

    在计算机软件开发中,Java编程语言的应用是极为广泛与深远的。Java语言自问世以来就以其独特的特性和优势吸引了大量开发者的注意,这些特性包括但不限于其平台独立性、内存管理优化、面向对象的编程设计以及其强大的...

    基于计算机软件开发的JAVA编程应用初探.zip

    这份资料"基于计算机软件开发的JAVA编程应用初探"将引领我们深入理解Java在软件开发中的核心概念、应用领域以及实战技巧。 首先,Java的基础知识是学习的重点。Java是一种强类型、静态类型的编程语言,它的语法与...

    Java教学方法初探.pdf

    Java教学方法初探 Java课程是一门实践性课程,旨在培养学生的学习兴趣和应用知识能力。本文结合实际教学经验,探讨了Java教学方法的几点思考。 首先,学习兴趣的培养是至关重要的。只有当学生对学习内容产生兴趣时...

    基于游戏开发的Java语言教学初探.docx

    基于游戏开发的Java语言教学初探 本文讨论了基于游戏开发的Java语言教学初探,旨在探讨如何培养学生的理论能力、分析能力、开发能力和实践能力。文章从教学设想和开发工具两个方面入手,介绍了Java语言的游戏构架、...

    java文件读写操作

    在Java编程语言中,文件读写操作是程序与外部数据交互的基本能力。这篇学习笔记将带你初探这个领域,适合新手入门。我们将讨论如何使用Java进行文件的读取、写入以及一些常见的应用场景。 首先,Java提供了java.io...

    基于游戏开发的Java语言教学初探.zip

    这篇"基于游戏开发的Java语言教学初探"主题探讨了如何将Java语言的学习与游戏开发相结合,以提高学生的学习兴趣和编程技能。游戏开发是一个综合性的过程,涉及到图形渲染、物理模拟、人工智能、网络通信等多个方面,...

    高职Java课程设计初探.pdf

    高职Java课程设计初探 本文探讨高职院校Java课程设计的重要性和实施策略。课程建设与改革是高职院校提高教学质量的核心,也是教学改革的重点和难点。长期以来,传统学科性课程始终统治着我国的职业教育,但职业教育...

    物联网专业的Java课程改革初探.pdf

    物联网专业的Java课程改革初探 本文探讨了物联网专业的Java课程改革,旨在解决当前Java课程教学中存在的问题,并提出了改革的思路和目标。改革思路分为三个方面:教师角色转变、教学内容项目化改造、教学过程的三个...

    基于游戏开发的Java语言教学初探

    【基于游戏开发的Java语言教学初探】 Java语言教学在当今计算机教育中占有重要地位,尤其是在高校的程序设计课程中。然而,Java的学习曲线相对较陡,理论知识复杂且理论部分可能显得枯燥,这使得激发学生的学习兴趣...

    eclipse 下实现java JNI 初探

    这篇博客"eclipse 下实现java JNI 初探"将引导我们了解如何在Eclipse环境中开发和使用JNI。 首先,我们需要了解JNI的基本概念。JNI为Java程序员提供了一种方式来编写可以调用本地(非Java)代码的Java方法。这些...

    Android开发教程之桌面组件【widget】初探--千锋培训

    在Android开发中,桌面组件(Widget)是一种可以让用户在手机或平板电脑的主屏幕上直接查看和操作的应用小部件。本文将深入探讨如何在Android中创建和使用Widget,以及相关的框架类和方法。 首先,我们来看App...

    软件工程专业Java教学初探.pdf

    "软件工程专业Java教学初探" 本文探讨了软件工程专业Java教学的初步研究,旨在解决高校Java课程无法满足企业实际需求的问题。文章首先指出了中国软件人才的需求严重不足,特别是Java人才的缺口非常大。然后,文章...

    初探WSDL2JAVA工具的使用

    ### 初探WSDL2JAVA工具的使用:深入解析与实践指南 在现代软件开发领域,Web服务(WebService)已成为企业级应用间进行交互的重要手段。Web服务定义语言(WSDL,Web Service Definition Language)是一种基于XML的...

    基于游戏开发的Java语言教学初探.pdf

    Java语言作为一种编程语言,它具备编译型语言和解释型语言的双重特性。在Java的开发和教学过程中,编译型语言的特点意味着需要处理复杂的编译过程,而解释型语言的特点则在于程序运行时会即时解释执行。这导致Java...

    Java高级编程课程思政案例教学初探.pdf

    Java高级编程课程思政案例教学初探.pdf

    无功补偿装置设计初探 无功补偿装置设计初探

    无功补偿装置设计初探:关键技术点解析 无功补偿装置设计是电力系统中的关键环节,对于提升电网的功率因数、节约电能、提高电网可靠性和安全性具有至关重要的作用。本文基于李博(2008)的研究,深入探讨无功补偿...

    基于Java的编译原理课程案例教学方法初探.pdf

    基于 Java 的编译原理课程案例教学方法初探 本文探讨基于 Java 的编译原理课程案例教学方法,旨在提高学生对编译原理的理解和掌握。文章首先介绍了编译原理在软件科学中的重要性,然后探讨了基于 Java 的编译原理...

    Java本地接口工作方式初探

    Java本地接口(Java Native Interface (JNI))允许运行在Java虚拟机(Java Virtual Machine (JVM))上的代码调用本地程序和类库,或者被它们调用,这些程序和类库可以是其它语言编写的,比如C、C++或者汇编语言。...

    淘宝开放平台架构组件体系初探

    ### 淘宝开放平台架构组件体系初探 #### 一、引言 淘宝作为中国乃至全球最大的电子商务平台之一,其技术架构不仅支撑着庞大的交易量,还面临着日益增长的第三方开发者需求。为此,淘宝构建了一个开放平台——淘宝...

Global site tag (gtag.js) - Google Analytics