许多基于B/S结构的Web系统都有直接通过网页上传文件的功能。Web程序员通常都是调用现成的第三方文件上传组件来实现,就JSP来说,国内使用的一般是JspSmart的JavaBean组件,目前JspSmart已经不再提供该类型组件,今后再有类似的开发需求就需要开发人员自行实现。其实,这些Beans采用的都是基于Http协议的文件上传技术,其原理及规范已在RFC文档中说明,实现并不很难。本文将通过一个实例来介绍如何编写一个JavaBean来实现Web上传技术。
一、上传原理
在最初的Http 协议中,没有上传文件方面的功能。rfc1867 (http://www.ietf.org/rfc/rfc1867.txt) 为 http 协议添加了这个功能。客户端的浏览器,如 Microsoft IE、 Mozila、 Opera 等,按照此规范将用户指定的文件发送到服务器。服务器端的网页程序,如PHP、ASP、JSP 等,可以按照此规范,解析出用户发送来的文件。Microsoft IE、Mozila、Opera 已经支持此协议,在网页中使用一个特殊的 form 就可以发送文件。绝大部分 Web服务器 ,包括 tomcat ,已经支持此协议,可接受发送来的文件。
要上传文件,提交数据的form的enctype必须为“multipart/form-data”,method必须是“post”,同时要在form中添加type=file的input标记,如下例(行01、03、05):
Line 01: <form method=post action="show1.jsp" enctype="multipart/form-data">
Line 02: file,name=f<br>
Line 03: <input type=file name=f size=50><br>
Line 04: file,name=f1<br>
Line 05: <input type=file name=f1 size=50><br>
Line 06: text,name=mytext<br>
Line 07: <input type=text name=mytext size=50><br>
Line 08: checkbox, name=mycheck<br>
Line 09: <input type="checkbox" name="mycheck" value=check1>value=check1<br>
Line 10: <input type="checkbox" name="mycheck" value=check2>value=check<br>
Line 11: <input type="checkbox" name="mycheck" value=check3>value=check<br>
Line 12: <input type="submit" value="upload">
Line 13: </form>
通过该表单,浏览器向Web服务器上传一个包含文件数据的报文,报文主体格式如下:
-----------------------------7d52e01220324
Content-Disposition: form-data; name="f"; filename="filename1"
Content-Type: plain/Text
文件数据
-----------------------------7d52e01220324
Content-Disposition: form-data; name="f1"; filename="filename2"
Content-Type: application/octet-stream
文件数据
-----------------------------7d52e01220324
Content-Disposition: form-data; name="myText"
变量值
-----------------------------7d52e01220324
Content-Disposition: form-data; name="mycheck"
变量值
-----------------------------7d52e01220324
Content-Disposition: form-data; name="mycheck"
变量值
-----------------------------7d52e01220324—
其中,------------------7d52e01220324是分隔符,分隔多个文件、表单项,报文的每一行都以“\r\n”结束。我们只需编写一个Java Bean在服务器端运行,解析报文,将文件数据保存为相应的文件即可。
二、程序实现
主要包括三个类:
1.Class Parameter ,每一个Parameter对象负责客户端传过来的非文件类型变量的信息存取操作,包括变量名、变量值等。
主要方法包括:
public void setParaName(String name) 赋变量名
public String getParaName() 取变量名
public void addParaValue() 添加变量值(变量可以是多值变量,故变量的名值是一对多的关系)
public String[] getParaValues() 取变量值,返回一组数值
2.Class UploadFile,每一个UploadFile对象描述一个文件类型数据的相关信息
主要方法包括:
public void setRemotePathName(String str) 赋上传文件在客户端的路径名和文件名
public String getRemotePathName() 取上传文件在客户端的路径名
public Striang getFileName() 取文件名
public void setLocalPathName(String str) 赋上传文件在服务器端的存储路径
public String getLocalPathName() 取上传文件在服务器端的存储路径
public void setContentDisp(String str) 赋Content-Disposition 值
public String getContentDisp() 取Content-Disposition 值
public void setContentType(String str) 赋Content-Type 值
public String getContentType() 取Content-Type 值
3.Class Upload,解析上传的http报文,将要上传的文件存储到服务器端指定的路径。下主要方法包括:
public void init(PageContext pc) 初始化
public Enumeration getFiles() 获取UploadFile对象集合
public int getFileCount() 获取UploadFile对象数量,即上传文件数
public Enumeration getParameterNames() 获取非文件类型变量名集合
public String[] getParameterValues(String key) 获取变量名为key的变量值
public void uploadFiles() throws Exception 上传文件
public void setPath(String path) 设置上传文件在服务器上的保存路径
Upload通过方法init来取得request对象和服务器当前路径,通过方法uploadFiles在服务器端保存文件。
由于篇幅有限,现在只介绍类Upload 中的解析报文保存文件的核心程序。
(1)Upload要引用的类库
import edu.tjut.cs.minipuss.UploadFile;// 自定义的UploadFile类
import javax.servlet.jsp.PageContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletInputStream;
import java.io.*;
import java.util.*;
其中javax.servlet类包不在J2SE中,通常在我们使用的JSP服务器(如Tomcat)中会包含该类包的jar,本文所使用的服务器是Resin2.1.0,在Resin的lib目录下的jsdk23.jar即包含该类包,编译时在classpath中指定该jar即可。
(2)成员变量和类的初始化
1)类Upload 包括如下5个成员变量:
private PageContext pageContext;//pageContext对象
private ServletRequest request;// request对象
private Vector vector;// 非文件类型数据列表
private Vector files; // 文件类型数据列表
private String savePath;// 文件上传路径
2)构造函数,用于初始化成员变量
public Upload(){
pageContext = null;
request = null;
vector = new Vector();// 创建列表
files = new Vector();// 创建列表
savePath = null;
}
3) 初始化函数
public void init(PageContext pc){
// 初始化
if(pc !=null){
this.pageContext = pc;
this.request = this.pageContext.getRequest();
// 取服务器端当前路径为上传路径
this.savePath = this.request.getRealPath("/");
}
}
(3)ploadFiles函数及其调用的私有函数
1)getNoteString, 取一行报文,不含回车换行符
private String getNoteString(ServletInputStream in){
StringBuffer sb = new StringBuffer();
int c;
try{
while( (c= in.read())!= -1){
char ch =(char) c;
sb.append(ch);
if(ch=='\n') break;
}
}catch(IOException e){
}
//截取子串,去掉回车换行符
String str = sb.toString();
int endIndex = str.lastIndexOf("\r\n");
if (endIndex==-1)
endIndex = str.lastIndexOf('\n');
return str.substring(0,endIndex);
}
2)getLine,读取一行报文,含回车换行符
private int getLine(ServletInputStream in,StringBuffer sb){
int c=0;
sb.delete(0,sb.length());// 清空
try{
while( (c= in.read())!= -1){
char ch =(char) c;
sb.append(ch);
if(ch=='\n') break;
}
}catch(IOException e){
return -1;
}
return c;// 若报文读完,则C=-1
}
3)getFileName,解析传入的字串,获取文件路径名
private String getFileName(String str){
//str:Content-Disposition: form-data; name="xx"; filename="xxxx"
int endIndex = str.lastIndexOf('"');
int startIndex = str.indexOf("filename")+10;
return str.substring(startIndex,endIndex);
}
类似函数还有getName、getContentDisp、getContentType,处理方法同上。
4)向非文件型数据列表添加Parameter对象,并返回该对象的引用
private Parameter addParameter(String str){
//若列表中有数据名=str的对象,则返回该对象的引用
int size = this.vector.size();
for(int i=0;i<size;i++){
Parameter p = (Parameter) this.vector.get(i);
if(str.compareTo(p.getParaName())==0)
return p;
}
//若列表中没有数据名=str的对象,则创建之
Parameter pa = new Parameter();
pa.setParaName(str);// 令数据名为str
this.vector.add(pa);// 加入列表
return pa;
}
5) uploadFiles,保存上传文件和非文件型数据
public void uploadFiles() throws Exception{
// 按二进制流读取报文
ServletInputStream in = this.request.getInputStream();
StringBuffer sb = new StringBuffer();
String noteString = getNoteString(in);// 取起始标记串
int c=0;
UploadFile uf = null;
Parameter para = null;
// 读取报文并做相应处理直到报文结束
while((c=getLine(in,sb))!=-1){
// 如果当前段描述的是一个文件型数据,则保存文件到服务器
if( sb.indexOf("filename")!=-1){
uf = new UploadFile();// 创建文件描述对象
uf.setRemotePathName(getFileName(sb.toString()));
uf.setContentDisp(getContentDisp(sb.toString()));
uf.setLocalPathName(this.savePath);
c = getLine(in,sb);// 取Content-Type
uf.setContentType(getContentType(sb.toString()));
c = getLine(in,sb);// 除去空行
//如果上传文件名不为空,写文件
if(uf.getFileName().length()!=0){
files.add(uf); // 将文件描述对象加入文件列表
try{
FileOutputStream fo = new
FileOutputStream(this.savePath+"\\"+uf.getFileName());
//读一行,写入一行,直到遇到段结束标记
String strTemp = null;
c = getLine(in,sb);// 读文件的第一行
do{
strTemp = sb.toString();
c = getLine(in,sb);// 读下一行
if(sb.indexOf(noteString)==-1){
for(int i=0;i<strTemp.length();i++)
fo.write((byte)strTemp.charAt(i));
}else{
// 如果下一行是段结束标记,
// 不将前一行行末的回车换行符写入文件
for(int i=0;i<strTemp.length()-2;i++)
fo.write((byte)strTemp.charAt(i));
break;// 文件结束,跳出
}
}while(true);
fo.close();// 关闭文件
}catch(IOException ioe){
// 若文件处理失败,将文件描述对象从列表中移除
files.remove(uf);
return;
}
}else{ // 上传文件名为空
c = getLine(in,sb);// 除去正文空行
c = getLine(in,sb);// 取段结束标记行
}
}else{
//若不是文件型数据,将数据存为Parameter对象,并加入列表
para = addParameter(getName(sb.toString()));
c = getLine(in,sb);// 除去空行
para.addParaValue(getNoteString(in));// 存储数据值
c = getLine(in,sb);// 取段结束标记行
}
}
}
(4)编译执行
上述代码在Windows2000 Professional,j2sdk1.4环境下编译成功,只需将编译好的类作为Bean在JSP页中调用即可。本文所用的Web服务器为Resin 2.1.0,具体的环境配置方法可参考Resin的文档。
四、结语
目前软件架构正在向基于B/S结构的Web应用模式发展,越来越多的企业级应用开始采用Web技术。应用Java技术实现Web上传很有实用价值。本文给出了通用的方法,读者可以根据实际应用的需要进行必要的改变而加以利用。
|