作者按:
******************************************************************
近日上网冲浪,发现还有人在问“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 文档。
第一个包
再看第二个包,可以看到,所要传的参数都在。下文只分析这个包。
第二个包
以下对各标号作出说明:
以下对各标号作出说明:
(1)开始,这是整个能得到的输入流的开端;
(2)第1段结束。每一段包含一个参数的信息,这些信息包括类型、名称、内容等。
(3)和(4)与(2)是一样的。
(4)以后就是输入流的结束标志:boundary。
(5)为从输入流中能读到的最后一个字符。
注意了第一个包中,有一项叫做“boundary”。顾名思义,这个boundary是 “分界”标志了。每一段的开头都会有一个boundary,然后是 0D 0A,然后是一些相关信息,接着是 0D 0A 0D 0A,紧跟着参数的实际值,然后是下一个boundary,标志着下一段的开始。而整个输入流呢,以一个boundary结束。如果只有一个参数,那输入流的结构应该是下面这样的:
边界
三、编程
明白了数据流的结构,编程就简单了,以下给出一段源程序。该程序易于使用, (当然,也不必交版权费啦……)。先给出如下的调用示例,而把源程序附于末尾。
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");
……
}
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。
入参:
(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]
Code:
[Ctrl+A Select All]
版权声明:原创作品,如需转载,请注明出处。否则将追究法律责任
4
收藏
猜你喜欢
发表评论
1楼 2007-01-21 01:12:27
看了这篇文章,参考了网上的一些技术文章,写下了下面基于web的文件上传程序。谢谢豪哥。
main.html:
<body>
<form action="jsp1.jsp" enctype="MULTIPART/FORM-DATA" method=post>
作者: <input type="text" name="author" />
<br />
公司: <input type="text" name="company" />
<br />
选择要上载的文件 <input type="file" name="filename" />
<br />
<input type="submit" value="上载" />
</form>
</body>
jsp1.jsp:
<jsp:useBean id="thebean" scope="page" class="com.zzl.SimpleBean" />
<%
thebean.doUpload(request);
%>
SimpleBean.java
package com.zzl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
public class SimpleBean {
public void doUpload(HttpServletRequest req) throws ServletException,IOException {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("D:\\Demo.out")));
ServletInputStream in = req.getInputStream();
int i=in.read();
while(i!=-1) {
pw.print((char)i);
i=in.read();
}
pw.close();
}
}
FileUpload.java:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FileUploadBean {
private String savePath, filepath, filename, contentType;
private Dictionary fields;
public String getFilename() {
return filename;
}
public String getFilepath() {
return filepath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public String getContentType() {
return contentType;
}
public String getFieldValue(String fieldName) {
if (fields == null || fieldName == null)
return null;
return (String) fields.get(fieldName);
}
private void setFilename(String s) {
if (s==null)
return;
int pos = s.indexOf("filename=\"");
if (pos != -1) {
filepath = s.substring(pos+10, s.length()-1);
// Windows浏览器发送完整的文件路径和名字
// 但Linux/Unix和Mac浏览器只发送文件名字
pos = filepath.lastIndexOf("\\");
if (pos != -1)
filename = filepath.substring(pos + 1);
else
filename = filepath;
}
}
private void setContentType(String s) {
if (s==null)
return;
int pos = s.indexOf(": ");
if (pos != -1)
contentType = s.substring(pos+2, s.length());
}
public void doUpload(HttpServletRequest request) throws IOException {
ServletInputStream in = request.getInputStream();
byte[] line = new byte[128];
int i = in.readLine(line, 0, 128);
//第一行应该是分界符,而且如果没有错误的话,
//它的长度应该大于3。如果它的长度小于3,我们可以认为出现了错误,doUpload方法应该立即返回
if (i < 3)
return;
int boundaryLength=i-2;
String boundary = new String(line, 0, boundaryLength); //-2丢弃换行字符
fields = new Hashtable();
while (i!=-1) {
String newLine = new String(line, 0, i);
if (newLine.startsWith("Content-Disposition: form-data; name=\"")) {
if (newLine.indexOf("filename=\"") != -1) {
setFilename(new String(line, 0, i-2));
if (filename==null)
return;
//文件内容
i = in.readLine(line, 0, 128);
setContentType(new String(line, 0, i-2));
i = in.readLine(line, 0, 128);
//空行
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符
// 因此我们必须检查当前行是否是最 // 后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4)&&(new String(line, 0, i).startsWith(boundary)))
pw.print(newLine.substring(0, newLine.length()-2));
else
pw.print(newLine);
newLine = new String(line, 0, i);
}
pw.close();
}
else {
// 普通表单输入元素
// 获取输入元素名字
int pos = newLine.indexOf("name=\"");
String fieldName = newLine.substring(pos+6, newLine.length()-3);
i = in.readLine(line, 0, 128);
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
StringBuffer fieldValue = new StringBuffer(128);
while (i != -1 && !newLine.startsWith(boundary)) {
// 最后一行包含换行字符
// 因此我们必须检查当前行是否是最后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4) && (new String(line, 0, i).startsWith(boundary)))
fieldValue.append(newLine.substring(0, newLine.length()-2));
else
fieldValue.append(newLine);
newLine = new String(line, 0, i);
}
fields.put(fieldName, fieldValue.toString());
}
}
i = in.readLine(line, 0, 128);
}
}
}
main.html:
<body>
<form action="jsp1.jsp" enctype="MULTIPART/FORM-DATA" method=post>
作者: <input type="text" name="author" />
<br />
公司: <input type="text" name="company" />
<br />
选择要上载的文件 <input type="file" name="filename" />
<br />
<input type="submit" value="上载" />
</form>
</body>
jsp1.jsp:
<jsp:useBean id="thebean" scope="page" class="com.zzl.SimpleBean" />
<%
thebean.doUpload(request);
%>
SimpleBean.java
package com.zzl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
public class SimpleBean {
public void doUpload(HttpServletRequest req) throws ServletException,IOException {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("D:\\Demo.out")));
ServletInputStream in = req.getInputStream();
int i=in.read();
while(i!=-1) {
pw.print((char)i);
i=in.read();
}
pw.close();
}
}
FileUpload.java:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FileUploadBean {
private String savePath, filepath, filename, contentType;
private Dictionary fields;
public String getFilename() {
return filename;
}
public String getFilepath() {
return filepath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public String getContentType() {
return contentType;
}
public String getFieldValue(String fieldName) {
if (fields == null || fieldName == null)
return null;
return (String) fields.get(fieldName);
}
private void setFilename(String s) {
if (s==null)
return;
int pos = s.indexOf("filename=\"");
if (pos != -1) {
filepath = s.substring(pos+10, s.length()-1);
// Windows浏览器发送完整的文件路径和名字
// 但Linux/Unix和Mac浏览器只发送文件名字
pos = filepath.lastIndexOf("\\");
if (pos != -1)
filename = filepath.substring(pos + 1);
else
filename = filepath;
}
}
private void setContentType(String s) {
if (s==null)
return;
int pos = s.indexOf(": ");
if (pos != -1)
contentType = s.substring(pos+2, s.length());
}
public void doUpload(HttpServletRequest request) throws IOException {
ServletInputStream in = request.getInputStream();
byte[] line = new byte[128];
int i = in.readLine(line, 0, 128);
//第一行应该是分界符,而且如果没有错误的话,
//它的长度应该大于3。如果它的长度小于3,我们可以认为出现了错误,doUpload方法应该立即返回
if (i < 3)
return;
int boundaryLength=i-2;
String boundary = new String(line, 0, boundaryLength); //-2丢弃换行字符
fields = new Hashtable();
while (i!=-1) {
String newLine = new String(line, 0, i);
if (newLine.startsWith("Content-Disposition: form-data; name=\"")) {
if (newLine.indexOf("filename=\"") != -1) {
setFilename(new String(line, 0, i-2));
if (filename==null)
return;
//文件内容
i = in.readLine(line, 0, 128);
setContentType(new String(line, 0, i-2));
i = in.readLine(line, 0, 128);
//空行
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符
// 因此我们必须检查当前行是否是最 // 后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4)&&(new String(line, 0, i).startsWith(boundary)))
pw.print(newLine.substring(0, newLine.length()-2));
else
pw.print(newLine);
newLine = new String(line, 0, i);
}
pw.close();
}
else {
// 普通表单输入元素
// 获取输入元素名字
int pos = newLine.indexOf("name=\"");
String fieldName = newLine.substring(pos+6, newLine.length()-3);
i = in.readLine(line, 0, 128);
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
StringBuffer fieldValue = new StringBuffer(128);
while (i != -1 && !newLine.startsWith(boundary)) {
// 最后一行包含换行字符
// 因此我们必须检查当前行是否是最后一行
i = in.readLine(line, 0, 128);
if ((i==boundaryLength+2 || i==boundaryLength+4) && (new String(line, 0, i).startsWith(boundary)))
fieldValue.append(newLine.substring(0, newLine.length()-2));
else
fieldValue.append(newLine);
newLine = new String(line, 0, i);
}
fields.put(fieldName, fieldValue.toString());
}
}
i = in.readLine(line, 0, 128);
}
}
}
本文转自 豪客 51CTO博客,原文链接:http://blog.51cto.com/wakan/7209,如需转载请自行联系原作者
Ctrl+Enter 发布
发布
取消