您的位置:首页 > 编程语言 > Java开发

用 Java Servlet 实现文件上载(老文新发) 推荐

2006-03-13 22:05 253 查看
作者按:
******************************************************************
近日上网冲浪,发现还有人在问“B/S 中如何上传文件”,于是把 2001 年时写的一篇文章翻了出来。该文已在某网络媒体上发表过(不好意思,忘了媒体的名称了)。我确实是原作者,不是抄袭的,发表于 BLOG 中,并无侵权之意。 现在的开发人员,可能不会费这么大劲自己去写上传程序,通常会直接使用 SmartUpload 等工具,真是幸福啊。发表本文的目的,仅为了解释一下原理。 *******************************************************************

[align=center]用Java Servlet实现文件上载 [/align]

各位大侠可能会对263电子邮箱中的“上传附件”功能有印象,就是:在浏览器中点击“浏览”,弹出一个对话框,选中文件后,单击“确定”,文件就被上传到了服务器端。

因为需要,就到网上找了几个控件,如 SmartUpload 等,但都觉得不好用,或者说是不合用,决定自己做一个。近日看到网上也有人提问怎么上载文件,于是把编制过程整理一遍,希望对大家有所帮助,不足之处,请多多指教。

一、准备

侦听工具,如SpyNet(包括 CaptureNet 和 PeepNet ),目的是用于分析数据包格式;
Java环境:至少要包括一个Servlet引擎,一套JDK;如果没有,可以访问 “http://www.jsp001.com/article/Application_Server_Comparison_Matrix_20010226.html” 从这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 文档。

第一个包



[align=left] 再看第二个包,可以看到,所要传的参数都在。下文只分析这个包。[/align]
[align=center] [/align]
第二个包



以下对各标号作出说明:
[align=left] (1)开始,这是整个能得到的输入流的开端;[/align]
[align=left] (2)第1段结束。每一段包含一个参数的信息,这些信息包括类型、名称、内容等。[/align]
[align=left] (3)和(4)与(2)是一样的。[/align]
[align=left] (4)以后就是输入流的结束标志:boundary。[/align]
[align=left] (5)为从输入流中能读到的最后一个字符。[/align]
[align=left] [/align]
[align=left] 注意了第一个包中,有一项叫做“boundary”。顾名思义,这个boundary是 “分界”标志了。每一段的开头都会有一个boundary,然后是 0D 0A,然后是一些相关信息,接着是 0D 0A 0D 0A,紧跟着参数的实际值,然后是下一个boundary,标志着下一段的开始。而整个输入流呢,以一个boundary结束。如果只有一个参数,那输入流的结构应该是下面这样的:[/align]
[align=left] [/align]
边界



[align=left]
三、编程[/align]
[align=left] [/align]
[align=left] 明白了数据流的结构,编程就简单了,以下给出一段源程序。该程序易于使用, (当然,也不必交版权费啦……)。先给出如下的调用示例,而把源程序附于末尾。[/align]
[align=left] [/align]
[align=left] 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");
……
}[/align]
[align=left] [/align]
[align=left] 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。[/align]
[align=left] [/align]
以下是源程序:

Code:
DecodeRequestStream.java
---------------------------------------------------------------------------
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class DecodeRequestStream
{
public Hashtable Decode(HttpServletRequest req, int paramcount)
throws java.io.IOException
{
byte[] body = null;
int bodyLen = 0;
byte[] bound = null;
int boundLen = 0;
int index = 0;
int count = 0;

bodyLen = req.getContentLength();
body = new byte[bodyLen];
BufferedInputStream dataIn= new BufferedInputStream(
req.getInputStream());
int readed = 0;
int cur_read = 0;
while (readed < bodyLen)
{
cur_read = dataIn.read(body, readed, bodyLen-readed);
if (cur_read <0)
{
break;
}
readed = readed + cur_read;
}
int i = 0;
while (i <= bodyLen)
{
if (body[i] == 13 && body[i+1] == 10)
break;
else
i ++;
}
if (i > bodyLen) return null;
boundLen = i;
bound = new byte[boundLen];
for (int j=0; j<boundLen; j++)
{
bound[j] = body[j + index]; //decode bound
}
i = i+2; //plus 2 to skip the following bytes "0D 0A"
index = i; //point to the beginning of first parameter
Hashtable hashtable = new Hashtable();
boolean moved = false;
while (i < bodyLen)
{
if (!moved && count == paramcount)
{
i = bodyLen-boundLen-5; //subst more than 4, but little than 10
moved = true;
}
if (!compareByteArray(copybyte(body, i, boundLen), bound))
{
i++;
}
else
{
count ++;
int j = index;
while ((j < i) && (body[j] != 13 || body[j+1] != 10 ||
body[j+2]!=13 || body[j+3] != 10))
{
j ++;
}
if (j >= i) break;
String paramHeader = new String(body, index, j-index+2);
index = j;
int m = paramHeader.indexOf("name=\"");
if (m < 0) break;
m = m+6; //point to name value
int n = paramHeader.indexOf("\"", m);
if (n <= m) break;
String name = paramHeader.substring(m, n); //get name
boolean isFile = false;
String filename = "";
String filetype = "";
m = paramHeader.indexOf("filename=\"", n+1);
if (m > n)
{
isFile = true;
m = m+10; //skip (filename=")
n = paramHeader.indexOf("\"", m);
if (n > m) filename = paramHeader.substring(m, n);
m = paramHeader.indexOf("Content-Type: ", n+1);
if (m > n)
{
m = m+14;
n = m;
while ((n < paramHeader.length())
&& (paramHeader.charAt(n) != 13
|| paramHeader.charAt(n+1) != 10))
{
n++;
}
if (n <= paramHeader.length())
filetype=paramHeader.substring(m, n);
}
}
/*
status: j point to the start of end flag (0D 0A 0D 0A) of current parameter's
header after j + 0D 0A 0D 0A, is the start of current parameter's value
(byte format) i point to the start of next boundary, that is,
"(current header) 0D 0A 0D 0A (current value) 0D 0A (next boundary)"
↑ ↑ ↑
index j i
the following code gets current value
*/
j = j+4; //skip 0D 0A 0D 0A, point to parameter value;
byte[] value = copybyte(body, j, i-j-2);
if (!isFile)
{
String tmpstr = new String(value);
hashtable.put(name, tmpstr);
}
else
{
hashtable.put(name, value);
hashtable.put("filename", filename);
hashtable.put("filetype", filetype);
break;
}
i = i + boundLen + 2;
index = i;
} //end else
} //end while
dataIn.close();
return hashtable;
}

public boolean compareByteArray(byte[] a, byte[] b)
{
if (a.length != b.length) return false;
for (int i=0; i<a.length; i++)
if (a[i] != b[i]) return false;
return true;
}

public byte[] copybyte(byte[] a, int from, int len)
{
int copylen = len;
if ((a.length-from) < copylen) copylen = a.length-from;
byte[] b = new byte[copylen];
for (int i=0; i<copylen; i++) b[i] = a[from+i];
return b;
}
}
[Ctrl+A Select All]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  servlet upload 上载