作者按:
******************************************************************
近日上网冲浪,发现还有人在问“B/S 中如何上传文件”,于是把 2001 年时写的一篇文章翻了出来。该文已在某网络媒体上发表过(不好意思,忘了媒体的名称了)。我确实是原作者,不是抄袭的,发表于 BLOG 中,并无侵权之意。 现在的开发人员,可能不会费这么大劲自己去写上传程序,通常会直接使用 SmartUpload 等工具,真是幸福啊。发表本文的目的,仅为了解释一下原理。 *******************************************************************
 
用Java Servlet实现文件上载                            
 
    各位大侠可能会对263电子邮箱中的“上传附件”功能有印象,就是:在浏览器中点击“浏览”,弹出一个对话框,选中文件后,单击“确定”,文件就被上传到了服务器端。 
 
    因为需要,就到网上找了几个控件,如 SmartUpload 等,但都觉得不好用,或者说是不合用,决定自己做一个。近日看到网上也有人提问怎么上载文件,于是把编制过程整理一遍,希望对大家有所帮助,不足之处,请多多指教。  
 
    一、准备
 
    侦听工具,如SpyNet(包括 CaptureNet 和 PeepNet ),目的是用于分析数据包格式;
    Java环境:至少要包括一个Servlet引擎,一套JDK;如果没有,可以访问 “[url]http://www.jsp001.com/article/Application_Server_Comparison_Matrix_20010226.html[/url]” 从这36款中随便找出一种来,安装运行即可。JSP服务器都会支持SERVLET,因为JSP本身就是先被编译成SERVLET再执行的。  
 
    二、分析
 
    1、制作HTML页面,用于上传文件。需要注意:要指定enctype属性为“multipart /form-data”,因为数据流的格式是不一样的。  
 
    <form action="/java/servlet/UploadFile" method=post enctype="multipart/form-data">
      <p><input type=radio name=type value=0>model
         <input type=radio name=type value=1>report      
         <input name=id >
         <input type=file name=file value="test">
      </p>
     <input type=submit>
   </form>
 
    2、HTML页面做好后,就可以开始分析数据流了。先打开侦听器,然后在浏览器(IE, Netscape)中打开本页面,随意选择一个文件,单击“确定”,看看侦听器听到了什么。在跳过前面几个包后,可以得到下面这两个相关的包。
 
    第一个包的很容易明白,在Servlet中,用getHeader(String)能得到的内容就在这里面。不过这个包,用HttpServletRequest的getInputStream是得不到的。关于HTTP协议的更多信息,可以查阅 RFC 文档。
 
第一个包
10_6504_618a785874919cb.jpg
  再看第二个包,可以看到,所要传的参数都在。下文只分析这个包。
 
第二个包
10_6504_f0347346d94926f.jpg
    以下对各标号作出说明:
    (1)开始,这是整个能得到的输入流的开端;
    (2)第1段结束。每一段包含一个参数的信息,这些信息包括类型、名称、内容等。
    (3)和(4)与(2)是一样的。
    (4)以后就是输入流的结束标志:boundary。
    (5)为从输入流中能读到的最后一个字符。
 
    注意了第一个包中,有一项叫做“boundary”。顾名思义,这个boundary是 “分界”标志了。每一段的开头都会有一个boundary,然后是 0D 0A,然后是一些相关信息,接着是 0D 0A 0D 0A,紧跟着参数的实际值,然后是下一个boundary,标志着下一段的开始。而整个输入流呢,以一个boundary结束。如果只有一个参数,那输入流的结构应该是下面这样的:
 
 边界
10_6504_bee4323a8dc313c.jpg

     三、编程
 
    明白了数据流的结构,编程就简单了,以下给出一段源程序。该程序易于使用, (当然,也不必交版权费啦……)。先给出如下的调用示例,而把源程序附于末尾。
 
      public void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, java.io.IOException
      {
        //新建一个对象,其实,若写成static的,连这一步都可省了
        DecodeRequestStream decode = new DecodeRequestStream(); 
        //调用Decode方法,返回一个哈希表
        Hashtable hashtable = decode.Decode(req, 2);
        ……
        //获取type的值
        String type = (String)hashtable.get("type");
        //获取id的值
        String id = (String)hashtable.get("id");
        //以字节数据的方式获得文件的内容
        byte[] filecontent = (byte[])hashtable.get("file");
        //获得文件名
        String filename = (String)hashtable.get("filename");
        //获得文件类型
        String filetype = (String)hashtable.get("filetype");
        ……
      }
 
      Decode函数的声明如下:
      入参:
          (1)HttpServletRequest: 从这个参数中可以得到输入流;
          (2)int ParamsCount: 这个参数表示输入流中除文件外,普通参数的个数
             提供这个参数是从性能的角度出发的,下文中会有说明;
      出参:
          一个哈希表。如果是普通参数,则以(string name, string value)的方式
          保存,如果是文件,则以(string name, byte[] value)的方式保存;
      
      对DecodeRequestStream类,作如下说明:
      1、本类一次只能处理一个文件的上载。如果有多个文件,将会保存在一个字
         节数组里面。实际上,可以很容易地把本程序改写成支持多文件的;
         
      2、文件必须是作为最后一个参数。此前有多少个参数必须在调用时通过Param
         sCount参数指定。细心的大侠会发现这个参数也是为了性能。因为确定边
         界boundary的位置是一个很费时的操作,需要先拷贝某个位置起与boundary
         相同长度的字节数组,然后再与boundary比较。在确定文件内容的结束位置
         时,要从文件流的开始处一直搜索到文件的结束处,对于大的文件,这是
         很费时的。所以本程序中做了一点小动作,那就是,对于第ParamsCount+1
         的那个参数(也就是文件参数),不用常规方法搜索,而是直接跳到输入流的
         末尾(末尾是boundary 0D 0A),再往前倒数boundary的长度外加4个字节。
         然后从这个位置开始定位boundary(一找一个准)。程序中,用了5个字节,
         是"留有余地"的想法,其实不用。
         
      3、本程序在 Tomcat 3.2.1 + Sun JDK 1.3.0_02 下运行通过,客户端浏览器
         为Internet Exploere 5.0、Netscape Communacator 4.77 和 Netscape 6。
 
 以下是源程序: 

Code:

[Ctrl+A Select All]