- 浏览: 260188 次
- 性别:
- 来自: 济南
文章分类
最新评论
-
MR3CHEN:
gaojiehigh 写道正在找这样的方法,我不过发现了一个问 ...
Java删除文件夹以及文件夹下的子目录与文件 -
gaojiehigh:
正在找这样的方法,我不过发现了一个问题,嘿嘿
[img][/i ...
Java删除文件夹以及文件夹下的子目录与文件 -
mimang2007110:
这个方法很实用,刚才适用了一下,挺好的,多谢
Java删除文件夹以及文件夹下的子目录与文件 -
sblig:
int icount = toKenizer.countT ...
Java拆分字符串返回数组 -
haiyangyiba:
文件夹中有中文文件不行,
Java解压缩ZIP文件同时包含Jar包解决ZIP包中含有中文名称信息的文件
1.2.5 文件上传编程实例
下面参考图1.2中看到的示例代码编写一个使用Apache文件上传组件来上传文件的例子程序。
:动手体验:使用Apache文件上传组件
(1)在<tomcat安装目录>\webapps\fileupload目录中按例程1-1编写一个名为FileUpload.html的HTML页面,该页面用于提供文件上传的FORM
表单,表单的enctype属性设置值为“multipart/form-data”,表单的action属性设置为“servlet/UploadServlet”。
例程1-1 FileUpload.html
<html>
<head>
<title>upload experiment</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<h3>测试文件上传组件的页面</h3>
<form action="servlet/UploadServlet"
enctype="multipart/form-data" method="post">
作者:<input type="text" name="author"><br>
来自:<input type="text" name="company"><br>
文件1:<input type="file" name="file1"><br>
文件2:<input type="file" name="file2"><br>
<input type="submit" value="上载">
</form>
</body>
</html>
(2)在<tomcat的安装目录>\webapps\fileupload\src目录中按例程1-2创建一个名为UploadServlet.java的Servlet程序,UploadServlet.java
调用Apache文件上传组件来处理FORM表单提交的文件内容和普通字段数据。
例程1-2 UploadServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
import java.util.*;
public class UploadServlet extends HttpServlet
{
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException
{
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
//设置保存上传文件的目录
String uploadDir = getServletContext().getRealPath("/upload");
if (uploadDir == null)
{
out.println("无法访问存储目录!");
return;
}
File fUploadDir = new File(uploadDir);
if(!fUploadDir.exists())
{
if(!fUploadDir.mkdir())
{
out.println("无法创建存储目录!");
return;
}
}
if (!DiskFileUpload.isMultipartContent(request))
{
out.println("只能处理multipart/form-data类型的数据!");
return ;
}
DiskFileUpload fu = new DiskFileUpload();
//最多上传200M数据
fu.setSizeMax(1024 * 1024 * 200);
//超过1M的字段数据采用临时文件缓存
fu.setSizeThreshold(1024 * 1024);
//采用默认的临时文件存储位置
//fu.setRepositoryPath(...);
//设置上传的普通字段的名称和文件字段的文件名所采用的字符集编码
fu.setHeaderEncoding("gb2312");
//得到所有表单字段对象的集合
List fileItems = null;
try
{
fileItems = fu.parseRequest(request);
}
catch (FileUploadException e)
{
out.println("解析数据时出现如下问题:");
e.printStackTrace(out);
return;
}
//处理每个表单字段
Iterator i = fileItems.iterator();
while (i.hasNext())
{
FileItem fi = (FileItem) i.next();
if (fi.isFormField())
{
String content = fi.getString("GB2312");
String fieldName = fi.getFieldName();
request.setAttribute(fieldName,content);
}
else
{
try
{
String pathSrc = fi.getName();
/*如果用户没有在FORM表单的文件字段中选择任何文件,
那么忽略对该字段项的处理*/
if(pathSrc.trim().equals(""))
{
continue;
}
int start = pathSrc.lastIndexOf('\\');
String fileName = pathSrc.substring(start + 1);
File pathDest = new File(uploadDir, fileName);
fi.write(pathDest);
String fieldName = fi.getFieldName();
request.setAttribute(fieldName, fileName);
}
catch (Exception e)
{
out.println("存储文件时出现如下问题:");
e.printStackTrace(out);
return;
}
finally //总是立即删除保存表单字段内容的临时文件
{
fi.delete();
}
}
}
//显示处理结果
out.println("用户:" + request.getAttribute("author") + "<br>");
out.println("来自:" + request.getAttribute("company") + "<br>");
/*将上传的文件名组合成"file1,file2"这种形式显示出来,如果没有上传
*任何文件,则显示为"无",如果只上传了第二个文件,显示为"file2"。*/
StringBuffer filelist = new StringBuffer();
String file1 = (String)request.getAttribute("file1");
makeUpList(filelist,file1);
String file2 = (String)request.getAttribute("file2");
makeUpList(filelist,file2);
out.println("成功上传的文件:" +
(filelist.length()==0 ? "无" : filelist.toString()));
}
/**
*将一段字符串追加到一个结果字符串中。如果结果字符串的初始内容不为空,
*在追加当前这段字符串之前先最加一个逗号(,)。在组合sql语句的查询条件时,
*经常要用到类似的方法,第一条件前没有"and",而后面的条件前都需要用"and"
*作连词,如果没有选择第一个条件,第二个条件就变成第一个,依此类推。
*
*@param result 要将当前字符串追加进去的结果字符串
*@param fragment 当前要追加的字符串
*/
private void makeUpList(StringBuffer result,String fragment)
{
if(fragment != null)
{
if(result.length() != 0)
{
result.append(",");
}
result.append(fragment);
}
}
}
在Windows资源管理器窗口中将UploadServlet.java源文件拖动到compile.bat文件的快捷方式上进行编译,修改Javac编译程序报告的错误,直
到编译成功通过为止。
(3)修改<tomcat的安装目录>\webapps\fileupload\WEB-INF\classes\web.xml文件,在其中注册和映射UploadServlet的访问路径,如例程1-3
所示。
例程1-3 web.xml
<web-app>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/servlet/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>
(4)重新启动Tomcat,并在浏览器地址栏中输入如下地址:
http://localhost:8080/fileupload/FileUpload.html
填写返回页面中的FORM表单,如图1.4所示,单击“上载”按钮后,浏览器返回的页面信息如图1.5所示。
图1.4
图1.5(这些图的标题栏中的it315改为fileupload)
查看<tomcat安装目录>\webapps\it315\upload目录,可以看到刚才上传的两个文件。
(4)单击浏览器工具栏上的“后退”按钮回到表单填写页面,只在第二个文件字段中选择一个文件,单击“上载”按钮,浏览器返回的显示结果
如图1.6所示。
图1.6
M脚下留心:
上面编写的Servlet程序将上传的文件保存在了当前WEB应用程序下面的upload目录中,这个目录是客户端浏览器可以访问到的目录。如果用户
通过浏览器上传了一个名称为test.jsp的文件,那么用户接着就可以在浏览器中访问这个test.jsp文件了,对于本地浏览器来说,这个jsp文件
的访问URL地址如下所示:
http://localhost:8080/fileupload/upload/test.jsp
对于远程客户端浏览器而言,只需要将上面的url地址中的localhost改写为Tomcat服务器的主机名或IP地址即可。用户可以通过上面的Servlet
程序来上传自己编写的jsp文件,然后又可以通过浏览器来访问这个jsp文件,如果用户在jsp文件中编写一些有害的程序代码,例如,查看服务
器上的所有目录结构,调用服务器上的操作系统进程等等,这将是一个非常致命的安全漏洞和隐患,这台服务器对外就没有任何安全性可言了
。
1.3 Apache文件上传组件的源码赏析
经常阅读一些知名的开源项目的源代码,可以帮助我们开阔眼界和快速提高编程能力。Apache文件上传组件是Apache组织开发的一个开源项目
,从网址http://jakarta.apache.org/commons/fileupload可以下载到Apache组件的源程序包,在本书的附带带光盘中也提供了该组件的源程
序包,文件名为commons-fileupload-1.0-src.zip。该组件的设计思想和程序编码细节包含有许多值得借鉴的技巧,为了便于有兴趣的读者学
习和研究该组件的源码,本节将分析Apache文件上传组件的源代码实现。对于只想了解如何使用Apache文件上传组件来上传文件的读者来说,
不必学习本节的内容。在学习本节内容之前,读者需要仔细学习了笔者编著的《深入体验java Web开发内幕——核心基础》一书中的第6.7.2节
中讲解的“分析文件上传的请求消息结构”的知识。
1.3.1 Apache文件上传组的类工作关系
Apache文件上传组件总共由两个接口,十二个类组成。在Apache文件上传组件的十二个类中,有两个抽象类,四个的异常类,六个主要类,其
中FileUpLoad类用暂时没有应用,是为了以后扩展而保留的。Apache文件上传组件中的各个类的关系如图1.7所示,图中省略了异常类。
图 1.7
DiskFileUpload类是文件上传组件的核心类,它是一个总的控制类,首先由Apache文件上传组件的使用者直接调用DiskFileUpload类的方法,
DiskFileUpload类再调用和协调更底层的类来完成具体的功能。解析类MultipartStream和工厂类DefaultFileItemFactory就是DiskFileUpload
类调用的两个的底层类。MultipartStream类用于对请求消息中的实体数据进行具体解析,DefaultFileItemFactory类对MultipartStream类解
析出来的数据进行封装,它将每个表单字段数据封装成一个个的FileItem类对象,用户通过FileItem类对象来获得相关表单字段的数据。
DefaultFileItem是FileItem接口的实现类,实现了FileItem接口中定义的功能,用户只需关心FileItem接口,通过FileItem接口来使用
DefaultFileItem类实现的功能。DefaultFileItem类使用了两个成员变量来分别存储表单字段数据的描述头和主体内容,其中保存主体内容的
变量类型为DeferredFileOutputStream类。DeferredFileOutputStream类是一个输出流类型,在开始时,DeferredFileOutputStream类内部使
用一个ByteArrayOutputStream类对象来存储数据,当写入它里面的主体内容的大小大于DiskFileUpload.setSizeThreshold方法设置的临界值
时,DeferredFileOutputStream类内部创建一个文件输出流对象来存储数据,并将前面写入到ByteArrayOutputStream类对象中的数据转移到文
件输出流对象中。这个文件输出流对象关联的文件是一个临时文件,它的保存路径由DiskFileUpload.setRepositoryPath方法指定。
Apache文件上传组件的处理流程如图1.8所示。
图1.8
图1.8中的每一步骤的详细解释如下:
(1)Web容器接收用户的HTTP请求消息,创建request请求对象。
(2)调用DiskFileUpload类对象的parseRequest方法对request请求对象进行解析。该方法首先检查request请求对象中的数据内容是否是
“multipart/form-data”类型,如果是,该方法则创建MultipartStream类对象对request请求对象中的请求体 进行解析。
(3)MultipartStream类对象对request请求体进行解析,并返回解析出的各个表单字段元素对应的内容。
(4)DiskFileUpload类对象的parseRequest方法接着创建DefaultFileItemFactory类对象,用来将MultipartStream类对象解析出的每个表单
字段元素的数据封装成FileItem类对象。
(5)DefaultFileItemFactory工厂类对象把MultipartStream类对象解析出的各个表单字段元素的数据封装成若干DefaultFileItem类对象,然
后加入到一个List类型的集合对象中,parseRequest方法返回该List集合对象。
实际上,步骤(3)和步骤(5)是交替同步进行的,即在MultipartStream类对象解析每个表单字段元素时,都会调用DefaultFileItemFactory
工厂类把该表单字段元素封装成对应的FileItem类对象。
1.3.2 Apache文件上传组件的核心编程问题
WEB服务器端程序接收到“multipart/form-data”类型的HTTP请求消息后,其核心和基本的编程工作就是读取请求消息中的实体内容,然后解
析出每个分区的数据,接着再从每个分区中解析出描述头和主体内容部分。
在读取HTTP请求消息中的实体内容时,只能调用HttpServletRequest.getInputStream方法返回的字节输入流,而不能调用
HttpServletRequest.getReader方法返回的字符输入流,因为不管上传的文件类型是文本的、还是其他各种格式的二进制内容,WEB服务器程序
要做的工作就是将属于文件内容的那部分数据原封不动地提取出来,然后原封不动地存储到本地文件系统中。如果使用
HttpServletRequest.getReader方法返回的字符输入流对象来读取HTTP请求消息中的实体内容,它将HTTP请求消息中的字节数据转换成字符后
再返回,这主要是为了方便要以文本方式来处理本来就全是文本内容的请求消息的应用,但本程序要求的是“原封不动”,显然不能使用
HttpServletRequest.getReader方法返回的字符输入流对象来进行读取。
另外,不能期望用一个很大的字节数组就可以装进HTTP请求消息中的所有实体内容,因为程序中定义的字节数组大小总是有限制的,但应该允
许客户端上传超过这个字节数组大小的实体内容。所以,只能创建一个一般大小的字节数组缓冲区来逐段读取请求消息中的实体内容,读取一
段就处理一段,处理完上一段以后,再读取下一段,如此循环,直到处理完所有的实体内容,如图1.9所示。
图 1.9
在图1.9中,buffer即为用来逐段读取请求消息中的实体内容的字节数组缓冲区。因为读取到缓冲区中的数据处理完后就会被抛弃,确切地说,
是被下一段数据覆盖,所以,解析和封装过程必须同步进行,程序一旦识别出图1.3中的一个分区的开始后,就要开始将它封装到一个FileItem
对象中。
程序要识别出图1.3中的每一个分区,需要在图1.9所示的字节数组缓冲区buffer中寻找分区的字段分隔界线,当找到一个字段分隔界线后,就
等于找到了一个分区的开始。笔者在《深入体验java Web开发内幕——核心基础》一书中的第6.7.2节中已经讲过,上传文件的请求消息的
Content-Type头字段中包含有用作字段分隔界线的字符序列,如下所示:
content-type : multipart/form-data; boundary=---------------------------7d51383203e8
显然,我们可以通过调用HttpServletRequest.getHeader方法读取Content-Type头字段的内容,从中分离出分隔界线的字符序列,然后在字节
数组缓冲区buffer中寻找分区的字段分隔界线。content-type头字段的boundary参数中指定的字段分隔界线是浏览器随机产生的,浏览器保证
它不会与用户上传的所有数据中的任何部分出现相同。在这里有一点需要注意,图1.3中的实体内容内部的字段分隔界线与content-type头中指
定的字段分隔界线有一点细微的差别,前者是在后者前面增加了两个减号(-)字符而形成的,这倒不是什么编程难点。真正的编程难点在于在
字节数组缓冲区buffer中寻找分隔界线时,可能会遇到字节数组缓冲区buffer中只装入了分隔界线字符序列的部分内容的情况,如图1.10所示
。
图1.10
要解决这个问题的方法之一就是在查找字段分隔界线时,如果发现字节数组缓冲区buffer中只装入了分隔界线字符序列的部分内容,那么就将
这一部分内容留给字节数组缓冲区buffer的下一次读取,如图1.11所示。
图1.11
这种方式让字节数组缓冲区buffer下一次读取的内容不是紧接着上一次读取内容的后面,而是重叠上一次读取的一部分内容,即从上一次读取
内容中的分隔界线字符序列的第一个字节处开始读取。这种方式在实际的编程处理上存在着相当大的难度,程序首先必须确定字节数组缓冲区
buffer上一次读取的数据的后一部分内容正好是分隔界线字符序列的前面一部分内容,而这一部分内容的长度是不确定的,可能只是分隔界线
字符序列的第一个字符,也可能是分隔界线字符序列的前面n-1个字符,其中n为分隔界线字符序列的整个长度。另外,即使确定字节数组缓冲
区buffer上一次读取的数据的后一部分内容正好是分隔界线字符序列的前面一部分内容,但它们在整个输入字节流中的后续内容不一定就整个
分隔界线字符序列的后一部分内容,出现这种情况的可能性是完全存在,程序必须进行全面和严谨的考虑。
Apache文件上传组件的解决方法比较巧妙,它在查找字段分隔界线时,如果搜索到最后第n个字符时,n为分隔界线字符序列的长度,发现最后n
个字符不能与分隔界线字符序列匹配,则将最后的n-1个字符留给字节数组缓冲区buffer的下一次读取,程序再对buffer的下一次读取的整个内
容从头开始查找字段分隔界线,如图1.12所示。
图1.12
Apache文件上传组件查找字段分隔界线的具体方法,读者可以请参见MultipartStream类的findSeparator()方法中的源代码。
当找到一个分区的开始位置后,程序还需要分辨出分区中的描述头和主体内容,并对这两部分内容分开存储。如何分辨出一个分区的描述头和
主体部分呢?从图1.3中可以看到,每个分区中的描述头和主体内容之间有一空行,再加上描述头后面的换行,这就说明描述头和主体部分之间
是使用“\n”、“\r”、“\n”、“\r”这四个连续的字节内容进行分隔。因此,程序需要把“\n”、“\r”、“\n”、“\r”这四个连续的
字节内容作为描述头和主体部分之间的分隔界线,并在字节数组缓冲区buffer中寻找这个特殊的分隔界线来识别描述头和主体部分。
当识别出一个分区中的描述头和主体部分后,程序需要解决的下一个问题就是如何将描述头和主体部分的数据保存到FileItem对象中,以便用
户以后可以调用FileItem类的方法来获得这些数据。主体部分的数据需要能够根据用户上传的文件大小有伸缩性地进行存储,因此,程序要求
编写一个特殊的类来封装主体部分的数据,对于这个问题的具体实现细节,读者可参见1.2.4小节中讲解的DeferredFileOutputStream类来了解
。
下面参考图1.2中看到的示例代码编写一个使用Apache文件上传组件来上传文件的例子程序。
:动手体验:使用Apache文件上传组件
(1)在<tomcat安装目录>\webapps\fileupload目录中按例程1-1编写一个名为FileUpload.html的HTML页面,该页面用于提供文件上传的FORM
表单,表单的enctype属性设置值为“multipart/form-data”,表单的action属性设置为“servlet/UploadServlet”。
例程1-1 FileUpload.html
<html>
<head>
<title>upload experiment</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<h3>测试文件上传组件的页面</h3>
<form action="servlet/UploadServlet"
enctype="multipart/form-data" method="post">
作者:<input type="text" name="author"><br>
来自:<input type="text" name="company"><br>
文件1:<input type="file" name="file1"><br>
文件2:<input type="file" name="file2"><br>
<input type="submit" value="上载">
</form>
</body>
</html>
(2)在<tomcat的安装目录>\webapps\fileupload\src目录中按例程1-2创建一个名为UploadServlet.java的Servlet程序,UploadServlet.java
调用Apache文件上传组件来处理FORM表单提交的文件内容和普通字段数据。
例程1-2 UploadServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
import java.util.*;
public class UploadServlet extends HttpServlet
{
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException
{
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
//设置保存上传文件的目录
String uploadDir = getServletContext().getRealPath("/upload");
if (uploadDir == null)
{
out.println("无法访问存储目录!");
return;
}
File fUploadDir = new File(uploadDir);
if(!fUploadDir.exists())
{
if(!fUploadDir.mkdir())
{
out.println("无法创建存储目录!");
return;
}
}
if (!DiskFileUpload.isMultipartContent(request))
{
out.println("只能处理multipart/form-data类型的数据!");
return ;
}
DiskFileUpload fu = new DiskFileUpload();
//最多上传200M数据
fu.setSizeMax(1024 * 1024 * 200);
//超过1M的字段数据采用临时文件缓存
fu.setSizeThreshold(1024 * 1024);
//采用默认的临时文件存储位置
//fu.setRepositoryPath(...);
//设置上传的普通字段的名称和文件字段的文件名所采用的字符集编码
fu.setHeaderEncoding("gb2312");
//得到所有表单字段对象的集合
List fileItems = null;
try
{
fileItems = fu.parseRequest(request);
}
catch (FileUploadException e)
{
out.println("解析数据时出现如下问题:");
e.printStackTrace(out);
return;
}
//处理每个表单字段
Iterator i = fileItems.iterator();
while (i.hasNext())
{
FileItem fi = (FileItem) i.next();
if (fi.isFormField())
{
String content = fi.getString("GB2312");
String fieldName = fi.getFieldName();
request.setAttribute(fieldName,content);
}
else
{
try
{
String pathSrc = fi.getName();
/*如果用户没有在FORM表单的文件字段中选择任何文件,
那么忽略对该字段项的处理*/
if(pathSrc.trim().equals(""))
{
continue;
}
int start = pathSrc.lastIndexOf('\\');
String fileName = pathSrc.substring(start + 1);
File pathDest = new File(uploadDir, fileName);
fi.write(pathDest);
String fieldName = fi.getFieldName();
request.setAttribute(fieldName, fileName);
}
catch (Exception e)
{
out.println("存储文件时出现如下问题:");
e.printStackTrace(out);
return;
}
finally //总是立即删除保存表单字段内容的临时文件
{
fi.delete();
}
}
}
//显示处理结果
out.println("用户:" + request.getAttribute("author") + "<br>");
out.println("来自:" + request.getAttribute("company") + "<br>");
/*将上传的文件名组合成"file1,file2"这种形式显示出来,如果没有上传
*任何文件,则显示为"无",如果只上传了第二个文件,显示为"file2"。*/
StringBuffer filelist = new StringBuffer();
String file1 = (String)request.getAttribute("file1");
makeUpList(filelist,file1);
String file2 = (String)request.getAttribute("file2");
makeUpList(filelist,file2);
out.println("成功上传的文件:" +
(filelist.length()==0 ? "无" : filelist.toString()));
}
/**
*将一段字符串追加到一个结果字符串中。如果结果字符串的初始内容不为空,
*在追加当前这段字符串之前先最加一个逗号(,)。在组合sql语句的查询条件时,
*经常要用到类似的方法,第一条件前没有"and",而后面的条件前都需要用"and"
*作连词,如果没有选择第一个条件,第二个条件就变成第一个,依此类推。
*
*@param result 要将当前字符串追加进去的结果字符串
*@param fragment 当前要追加的字符串
*/
private void makeUpList(StringBuffer result,String fragment)
{
if(fragment != null)
{
if(result.length() != 0)
{
result.append(",");
}
result.append(fragment);
}
}
}
在Windows资源管理器窗口中将UploadServlet.java源文件拖动到compile.bat文件的快捷方式上进行编译,修改Javac编译程序报告的错误,直
到编译成功通过为止。
(3)修改<tomcat的安装目录>\webapps\fileupload\WEB-INF\classes\web.xml文件,在其中注册和映射UploadServlet的访问路径,如例程1-3
所示。
例程1-3 web.xml
<web-app>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/servlet/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>
(4)重新启动Tomcat,并在浏览器地址栏中输入如下地址:
http://localhost:8080/fileupload/FileUpload.html
填写返回页面中的FORM表单,如图1.4所示,单击“上载”按钮后,浏览器返回的页面信息如图1.5所示。
图1.4
图1.5(这些图的标题栏中的it315改为fileupload)
查看<tomcat安装目录>\webapps\it315\upload目录,可以看到刚才上传的两个文件。
(4)单击浏览器工具栏上的“后退”按钮回到表单填写页面,只在第二个文件字段中选择一个文件,单击“上载”按钮,浏览器返回的显示结果
如图1.6所示。
图1.6
M脚下留心:
上面编写的Servlet程序将上传的文件保存在了当前WEB应用程序下面的upload目录中,这个目录是客户端浏览器可以访问到的目录。如果用户
通过浏览器上传了一个名称为test.jsp的文件,那么用户接着就可以在浏览器中访问这个test.jsp文件了,对于本地浏览器来说,这个jsp文件
的访问URL地址如下所示:
http://localhost:8080/fileupload/upload/test.jsp
对于远程客户端浏览器而言,只需要将上面的url地址中的localhost改写为Tomcat服务器的主机名或IP地址即可。用户可以通过上面的Servlet
程序来上传自己编写的jsp文件,然后又可以通过浏览器来访问这个jsp文件,如果用户在jsp文件中编写一些有害的程序代码,例如,查看服务
器上的所有目录结构,调用服务器上的操作系统进程等等,这将是一个非常致命的安全漏洞和隐患,这台服务器对外就没有任何安全性可言了
。
1.3 Apache文件上传组件的源码赏析
经常阅读一些知名的开源项目的源代码,可以帮助我们开阔眼界和快速提高编程能力。Apache文件上传组件是Apache组织开发的一个开源项目
,从网址http://jakarta.apache.org/commons/fileupload可以下载到Apache组件的源程序包,在本书的附带带光盘中也提供了该组件的源程
序包,文件名为commons-fileupload-1.0-src.zip。该组件的设计思想和程序编码细节包含有许多值得借鉴的技巧,为了便于有兴趣的读者学
习和研究该组件的源码,本节将分析Apache文件上传组件的源代码实现。对于只想了解如何使用Apache文件上传组件来上传文件的读者来说,
不必学习本节的内容。在学习本节内容之前,读者需要仔细学习了笔者编著的《深入体验java Web开发内幕——核心基础》一书中的第6.7.2节
中讲解的“分析文件上传的请求消息结构”的知识。
1.3.1 Apache文件上传组的类工作关系
Apache文件上传组件总共由两个接口,十二个类组成。在Apache文件上传组件的十二个类中,有两个抽象类,四个的异常类,六个主要类,其
中FileUpLoad类用暂时没有应用,是为了以后扩展而保留的。Apache文件上传组件中的各个类的关系如图1.7所示,图中省略了异常类。
图 1.7
DiskFileUpload类是文件上传组件的核心类,它是一个总的控制类,首先由Apache文件上传组件的使用者直接调用DiskFileUpload类的方法,
DiskFileUpload类再调用和协调更底层的类来完成具体的功能。解析类MultipartStream和工厂类DefaultFileItemFactory就是DiskFileUpload
类调用的两个的底层类。MultipartStream类用于对请求消息中的实体数据进行具体解析,DefaultFileItemFactory类对MultipartStream类解
析出来的数据进行封装,它将每个表单字段数据封装成一个个的FileItem类对象,用户通过FileItem类对象来获得相关表单字段的数据。
DefaultFileItem是FileItem接口的实现类,实现了FileItem接口中定义的功能,用户只需关心FileItem接口,通过FileItem接口来使用
DefaultFileItem类实现的功能。DefaultFileItem类使用了两个成员变量来分别存储表单字段数据的描述头和主体内容,其中保存主体内容的
变量类型为DeferredFileOutputStream类。DeferredFileOutputStream类是一个输出流类型,在开始时,DeferredFileOutputStream类内部使
用一个ByteArrayOutputStream类对象来存储数据,当写入它里面的主体内容的大小大于DiskFileUpload.setSizeThreshold方法设置的临界值
时,DeferredFileOutputStream类内部创建一个文件输出流对象来存储数据,并将前面写入到ByteArrayOutputStream类对象中的数据转移到文
件输出流对象中。这个文件输出流对象关联的文件是一个临时文件,它的保存路径由DiskFileUpload.setRepositoryPath方法指定。
Apache文件上传组件的处理流程如图1.8所示。
图1.8
图1.8中的每一步骤的详细解释如下:
(1)Web容器接收用户的HTTP请求消息,创建request请求对象。
(2)调用DiskFileUpload类对象的parseRequest方法对request请求对象进行解析。该方法首先检查request请求对象中的数据内容是否是
“multipart/form-data”类型,如果是,该方法则创建MultipartStream类对象对request请求对象中的请求体 进行解析。
(3)MultipartStream类对象对request请求体进行解析,并返回解析出的各个表单字段元素对应的内容。
(4)DiskFileUpload类对象的parseRequest方法接着创建DefaultFileItemFactory类对象,用来将MultipartStream类对象解析出的每个表单
字段元素的数据封装成FileItem类对象。
(5)DefaultFileItemFactory工厂类对象把MultipartStream类对象解析出的各个表单字段元素的数据封装成若干DefaultFileItem类对象,然
后加入到一个List类型的集合对象中,parseRequest方法返回该List集合对象。
实际上,步骤(3)和步骤(5)是交替同步进行的,即在MultipartStream类对象解析每个表单字段元素时,都会调用DefaultFileItemFactory
工厂类把该表单字段元素封装成对应的FileItem类对象。
1.3.2 Apache文件上传组件的核心编程问题
WEB服务器端程序接收到“multipart/form-data”类型的HTTP请求消息后,其核心和基本的编程工作就是读取请求消息中的实体内容,然后解
析出每个分区的数据,接着再从每个分区中解析出描述头和主体内容部分。
在读取HTTP请求消息中的实体内容时,只能调用HttpServletRequest.getInputStream方法返回的字节输入流,而不能调用
HttpServletRequest.getReader方法返回的字符输入流,因为不管上传的文件类型是文本的、还是其他各种格式的二进制内容,WEB服务器程序
要做的工作就是将属于文件内容的那部分数据原封不动地提取出来,然后原封不动地存储到本地文件系统中。如果使用
HttpServletRequest.getReader方法返回的字符输入流对象来读取HTTP请求消息中的实体内容,它将HTTP请求消息中的字节数据转换成字符后
再返回,这主要是为了方便要以文本方式来处理本来就全是文本内容的请求消息的应用,但本程序要求的是“原封不动”,显然不能使用
HttpServletRequest.getReader方法返回的字符输入流对象来进行读取。
另外,不能期望用一个很大的字节数组就可以装进HTTP请求消息中的所有实体内容,因为程序中定义的字节数组大小总是有限制的,但应该允
许客户端上传超过这个字节数组大小的实体内容。所以,只能创建一个一般大小的字节数组缓冲区来逐段读取请求消息中的实体内容,读取一
段就处理一段,处理完上一段以后,再读取下一段,如此循环,直到处理完所有的实体内容,如图1.9所示。
图 1.9
在图1.9中,buffer即为用来逐段读取请求消息中的实体内容的字节数组缓冲区。因为读取到缓冲区中的数据处理完后就会被抛弃,确切地说,
是被下一段数据覆盖,所以,解析和封装过程必须同步进行,程序一旦识别出图1.3中的一个分区的开始后,就要开始将它封装到一个FileItem
对象中。
程序要识别出图1.3中的每一个分区,需要在图1.9所示的字节数组缓冲区buffer中寻找分区的字段分隔界线,当找到一个字段分隔界线后,就
等于找到了一个分区的开始。笔者在《深入体验java Web开发内幕——核心基础》一书中的第6.7.2节中已经讲过,上传文件的请求消息的
Content-Type头字段中包含有用作字段分隔界线的字符序列,如下所示:
content-type : multipart/form-data; boundary=---------------------------7d51383203e8
显然,我们可以通过调用HttpServletRequest.getHeader方法读取Content-Type头字段的内容,从中分离出分隔界线的字符序列,然后在字节
数组缓冲区buffer中寻找分区的字段分隔界线。content-type头字段的boundary参数中指定的字段分隔界线是浏览器随机产生的,浏览器保证
它不会与用户上传的所有数据中的任何部分出现相同。在这里有一点需要注意,图1.3中的实体内容内部的字段分隔界线与content-type头中指
定的字段分隔界线有一点细微的差别,前者是在后者前面增加了两个减号(-)字符而形成的,这倒不是什么编程难点。真正的编程难点在于在
字节数组缓冲区buffer中寻找分隔界线时,可能会遇到字节数组缓冲区buffer中只装入了分隔界线字符序列的部分内容的情况,如图1.10所示
。
图1.10
要解决这个问题的方法之一就是在查找字段分隔界线时,如果发现字节数组缓冲区buffer中只装入了分隔界线字符序列的部分内容,那么就将
这一部分内容留给字节数组缓冲区buffer的下一次读取,如图1.11所示。
图1.11
这种方式让字节数组缓冲区buffer下一次读取的内容不是紧接着上一次读取内容的后面,而是重叠上一次读取的一部分内容,即从上一次读取
内容中的分隔界线字符序列的第一个字节处开始读取。这种方式在实际的编程处理上存在着相当大的难度,程序首先必须确定字节数组缓冲区
buffer上一次读取的数据的后一部分内容正好是分隔界线字符序列的前面一部分内容,而这一部分内容的长度是不确定的,可能只是分隔界线
字符序列的第一个字符,也可能是分隔界线字符序列的前面n-1个字符,其中n为分隔界线字符序列的整个长度。另外,即使确定字节数组缓冲
区buffer上一次读取的数据的后一部分内容正好是分隔界线字符序列的前面一部分内容,但它们在整个输入字节流中的后续内容不一定就整个
分隔界线字符序列的后一部分内容,出现这种情况的可能性是完全存在,程序必须进行全面和严谨的考虑。
Apache文件上传组件的解决方法比较巧妙,它在查找字段分隔界线时,如果搜索到最后第n个字符时,n为分隔界线字符序列的长度,发现最后n
个字符不能与分隔界线字符序列匹配,则将最后的n-1个字符留给字节数组缓冲区buffer的下一次读取,程序再对buffer的下一次读取的整个内
容从头开始查找字段分隔界线,如图1.12所示。
图1.12
Apache文件上传组件查找字段分隔界线的具体方法,读者可以请参见MultipartStream类的findSeparator()方法中的源代码。
当找到一个分区的开始位置后,程序还需要分辨出分区中的描述头和主体内容,并对这两部分内容分开存储。如何分辨出一个分区的描述头和
主体部分呢?从图1.3中可以看到,每个分区中的描述头和主体内容之间有一空行,再加上描述头后面的换行,这就说明描述头和主体部分之间
是使用“\n”、“\r”、“\n”、“\r”这四个连续的字节内容进行分隔。因此,程序需要把“\n”、“\r”、“\n”、“\r”这四个连续的
字节内容作为描述头和主体部分之间的分隔界线,并在字节数组缓冲区buffer中寻找这个特殊的分隔界线来识别描述头和主体部分。
当识别出一个分区中的描述头和主体部分后,程序需要解决的下一个问题就是如何将描述头和主体部分的数据保存到FileItem对象中,以便用
户以后可以调用FileItem类的方法来获得这些数据。主体部分的数据需要能够根据用户上传的文件大小有伸缩性地进行存储,因此,程序要求
编写一个特殊的类来封装主体部分的数据,对于这个问题的具体实现细节,读者可参见1.2.4小节中讲解的DeferredFileOutputStream类来了解
。
发表评论
-
java输出文件
2010-12-01 16:51 1358try { File file = new Fil ... -
Java字符串将不是字符数字的字符转换为十六进制字符
2010-07-12 10:28 1326char data[] = "asdfasdf中国1 ... -
Java解压ZIP文件同时解决压缩文件中含有中文名文件乱码的问题
2010-04-22 13:12 5347在使用Java对ZIP压缩文件进行解压的方式中有两种,一种是使 ... -
Java解析XML格式字符串返回Document类型对象
2008-05-23 14:46 9013package com.yc.util; import ja ... -
Java进行WebService通讯
2008-05-19 15:10 2171package com.yc.ycportal.cqkf.se ... -
Java实现MD5算法加密
2008-05-16 09:17 1230/** * MD5 算法的Java Bean */ pac ... -
Java手工打Jar包
2008-05-10 11:28 1881用法:jar {ctxu}[vfm0Mi] [jar-文件] ... -
Java解压缩ZIP文件同时包含Jar包解决ZIP包中含有中文名称信息的文件
2008-05-07 13:42 3319我们知道压缩文件中有第一个文件夹为原始文件夹:例如我们对一个目 ... -
Java上传文件实例
2008-05-05 10:08 4007package com.yc.eap.util; impor ... -
Servlet中文API文档
2008-04-26 15:47 2238基本类和接口 一、javax.servlet.Servlet ... -
ServletConfig和ServletConfig参数访问
2008-04-26 14:20 1356HttpServletRequest,HttpServletR ... -
Java上传文件同时含有表单域的实现
2008-04-25 17:08 2108package com.yc.eap.util; impor ... -
Jsp通过文件流实现文件下载
2008-04-23 10:44 2881<%@ page contentType="a ... -
Commons-fileupload工具的API与开发实例解析(三)
2008-04-23 10:25 19311.3.3 MultipartStream类 Multipar ... -
Commons-fileupload工具的API与开发实例解析(一)
2008-04-23 10:22 3493文件上传组件的应用与编写 在许多Web站点应用中都需要为 ... -
Java解析XML文件
2008-04-23 10:15 1785package com.yc.ycportal.ge.util ... -
Java压缩与解压缩文件
2008-04-23 10:05 1717package com.yc.ycportal.ge.util ... -
Java只获取系统时间的年份
2008-04-23 10:04 5574public static String getYear(){ ... -
Java连接Oracle数据库调用存储过程获取数据集
2008-04-23 10:03 2366package com.yc.ycportal.ge.util ... -
Java实现Ftp文件下载
2008-04-23 10:02 4592从http://www.enterprisedt.com/下载 ...
相关推荐
Apache Commons IO则是Apache Commons项目中的另一个关键组件,它提供了大量与I/O(输入/输出)相关的实用工具类。`commons-io-2.5.jar` 包含了各种I/O操作的通用功能,如文件读写、流操作、文件比较、文件过滤等。...
而`commons-io-2.4`是Apache Commons IO库的2.4版本,它是处理输入/输出操作的工具集,与FileUpload配合处理文件上传时,常被用到。 文件上传在Web应用中的实现并不直接内置在Servlet API中,因此,`commons-...
总之,Apache Commons FileUpload是Java Web开发中处理文件上传不可或缺的工具,它提供了高效、灵活且安全的文件上传解决方案。通过理解和熟练运用这个库,开发者可以轻松地在自己的应用中实现复杂的文件上传功能。
Apache Commons FileUpload与Apache Commons IO是Java开发中处理文件上传的两个重要库。它们为开发者提供了强大而灵活的工具,使得在Web应用中处理文件上传变得简单易行。 `commons-fileupload-1.2.1.jar`是Apache ...
总之,`commons-fileupload-1.2.2.jar`和`commons-io-2.4.jar`是Java Web开发中处理文件上传和下载不可或缺的工具。尽管它们的版本可能不是最新的,但依然能提供可靠的功能,并且在很多现有的系统中广泛使用。理解并...
在Java Web开发中,文件上传是一项常见的功能,Apache Commons FileUpload库是实现这一功能的强大工具。本示例将深入解析如何结合Maven构建一个基于Servlet的文件上传演示项目。Maven是一个项目管理工具,它可以帮助...
在"commons-fileupload-1.2.1.jar"这个版本中,它提供了一个简洁的API,使得开发者可以轻松地解析请求并获取上传的文件。 具体来说,FileUpload库允许你创建一个`DiskFileItemFactory`实例来配置临时存储参数,然后...
`commons-file`标签可能是指与`commons-fileupload`相关的所有操作,包括文件读写、文件处理等。而`图片上传`则特指使用该库上传图片文件的场景。 至于文件列表中提到的"Roger",这可能是实际的项目文件或代码示例...
"commons-fileupload-1.3.2.jar" 提供了处理这种类型请求的API,它能够解析请求体,将文件和文本字段分开,然后将文件数据存储到临时文件或内存中。这个库支持大文件上传,可以设置内存阈值,超过该阈值时,文件会被...
使用Commons-Fileupload,你需要配置Servlet容器来解析多部分请求,并创建`DiskFileItemFactory`来处理临时文件存储,接着创建`FileUpload`实例进行文件解析。 在选择使用哪个组件时,应考虑项目需求和团队的熟悉...
Apache Commons FileUpload是一个Java库,专门用于处理HTTP...总的来说,Apache Commons FileUpload是Java Web开发中不可或缺的工具,它极大地简化了文件上传的处理,使得开发者能够更专注于业务逻辑,而非底层细节。
总的来说,Apache Commons FileUpload和Commons IO是Java Web开发中处理文件上传不可或缺的工具,它们简化了这个过程,提高了代码的可读性和可维护性。通过合理配置和使用这两个库,开发者可以构建安全、高效、用户...
总的来说,`commons-fileupload.jar`和`commons-io-1.4.jar`是Java Web开发中实现文件上传功能必不可少的工具,它们为开发者提供了强大且灵活的接口,简化了文件上传的实现过程。了解并熟练掌握这两个库的使用,有助...
6. **API使用**:使用FileUpload库通常涉及创建一个`ServletFileUpload`实例,然后使用`parseRequest()`方法解析请求,得到`FileItem`对象。每个`FileItem`代表表单的一个字段或上传的文件,可以从中获取名称、内容...
`commons-io-1.3.2.jar`是Apache Commons IO库,它是`commons-fileupload`依赖的库,提供了各种与I/O操作相关的工具类。例如,`FileUtils`类提供了文件和目录的便捷操作,如复制、移动、删除等。在处理文件上传时,...
Apache Commons FileUpload与Apache Commons IO是Java开发中用于处理文件上传功能的重要库。这两个库的组合在处理HTTP请求中的文件上传部分尤其有用,特别是在Web应用中。下面将详细阐述这两个库的功能、使用方法...
Apache Commons FileUpload是Java中处理HTTP请求中文件上传的一个强大工具,尤其在Web开发中扮演着重要角色。这个组件是Apache软件基金会的Commons项目的一部分,旨在简化从HTTP请求中提取上传文件的任务。在本文中...
在Java Web开发中,文件上传是一项常见的功能,Apache Commons FileUpload库为开发者提供了方便、灵活的文件上传解决方案。本教程将深入讲解如何使用`commons-fileupload`库处理中文乱码问题以及实现多文件上传。 ...
总的来说,Apache Commons FileUpload库是Java Web开发中不可或缺的工具,它极大地简化了文件上传功能的实现,让开发者可以更专注于业务逻辑的开发,而不是底层的细节处理。通过熟练掌握这个库,开发者能够创建更...