Struts2 上传和下载文件(一)

 

 

一、 文件上传的原理

 

1. 表单元素的 enctype 属性

 

表单的 enctype 属性指定的是表单数据的编码方式 ,该属性有 3 个值:

    1. application/x-www-form-urlencoded : 这是默认的编码方式,它只处理表单域里的 value 属性值,采用这种编码方式的表单会将表单域的值处理成 URL 编码方式。

    2. multipart/form-data : 这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。

    3. text/plain : 这种编码方式当表单的 action 属性为 mailto:URL 的形式时比较方便,这种方式主要适用于直接通过表单发送邮件的方式

 

 

下面来介绍 application/x-www-form-urlencoded multipart/form-data 的区别。如下:

 

application.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title> enctype属性测试 </title>
	<meta name="author" content="Yeeku.H.Lee" />
	<meta name="website" content="http://www.crazyit.org" />
	<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
</head>
<body>
<form id="form1" name="form1" 
	enctype="application/x-www-form-urlencoded"
	method="post" action="pro.jsp">
	上传文件:<input type="file" name="file" /><br />  
	请求参数:<input type="text" name="wawa" /><br />
	<input name="dd" type="submit" value="提交" />
</form>
</body>
</html>

 

下面是处理的 jsp

pro.jsp

<%@ page contentType="text/html;charset=GBK" %>
<%@ page import="java.io.*"%>
<%
//获取HTTP请求的输入流
InputStream is = request.getInputStream();
//以HTTP请求输入流建立一个BufferedReader对象
BufferedReader br = new BufferedReader(
	new InputStreamReader(is));
//读取HTTP请求内容
String buffer = null;
while( (buffer = br.readLine()) != null)
{
	//在页面中显示读取到的请求参数
	out.println(buffer + "<br />");
}
%>
 

    上面的处理页面直接通过二进制流来处理该 HTTP 请求,这是一种更底层的处理方式 ,当通过 HttpServletRequest 的 getParameter() 方法来获取请求参数时,实际上是 Web 服务器替我们处理了这种底层的二进制流 ,并将二进制流转换成对应的请求参数值。

 

  如果上面 html 的 上传文件 是 logo.jpg , 请求参数 是 “crazyit标志” 提交后 :

   pro.jsp 页面将显示:

file=logo.jpg&wawa=crazyit%EA%D6%BE&dd=%CC%E1%BD%BB


如上,即使通过底层的二进制输入流,一样可以读到该请求的内容

一个普通字符串,包含了 3 个请求参数,即 file, wawa 和 dd

 

提示: 即使是“提交”按钮,表单一样将其当成一个表单域,一样转换成一个请求参数。浏览器会将表单里所有具有 name 属性的表单控件转换成请求参数,因此“提交”按钮也有 name 属性,因此也被转化请求参数

 

 

   Java 提供了 2 个类: URLDecoder URLEncoder

     来处理 " %CC%E1%BD%BB " 字符串。如下:

 

TestURLEncoder.java

import java.net.*;

public class TestURLEncoder
{
	public static void main(String[] args) throws Exception
	{
		//定义一个字符串,该字符串的值为上面的dd请求参数的值
	   	String encodeStr = "%CC%E1%BD%BB";
		//使用URLDecoder类来处理该dd请求参数值
		String decodeStr = URLDecoder.decode(encodeStr ,"GBK");
		System.out.println(decodeStr);
		//定义一个字符串,该字符串的值为在图7.1中的文本框中输入的内容
		String rawStr = "crazyit标志";
		//使用URLEncoder类处理该字符串
		System.out.println(URLEncoder.encode(rawStr , "GBK"));
	}
}
 

大部分时候,程序中直接通过 HttpServletRequest 的 getParameter 方法即可获得正确的请求参数,而那些底层的二进制流处理,以及使用 URLDecoder 处理请求参数,都由 Web 服务器来替我们完成了

 

但是如果我们设置表单元素的 enctype 属性为 "multipart/form-data ",该请求将会把文件域里浏览到的文件内容作为请求参数发送。显然此时无法直接通过 request.getParameter() 方法来获取请求参数。

 

   pro.jsp 页面将显示:

-----------------------------7cf87224d2020a
Content-Disposition:   form-data;   name= "email "  
PhCollignon@email.com
-----------------------------7cf87224d2020a
Content-Disposition:   form-data;   name= "file";   filename= "c:\windows常用健.txt "
Content-Type:   text/plain

 

Ctrl+L 打开“打开”面版(可以在当前页面打开Iternet地址或其他文件...)
Ctrl+N 新建一个空白窗口(可更改,Maxthon选项→标签→新建)
Ctrl+O 打开“打开”面版(可以在当前页面打开Iternet地址或其他文件...)
Ctrl+P 打开“打印”面板(可以打印网页,图片什么的...)
Ctrl+Q 打开“添加到过滤列表”面板(将当前页面地址发送到过滤列表)
Ctrl+R 刷新当前页面
Ctrl+S 打开“保存网页”面板(可以将当前页面所有内容保存下来)
Ctrl+T 垂直平铺所有窗口
Ctrl+V 粘贴当前剪贴板内的内容
Ctrl+W 关闭当前标签(窗口)
Ctrl+X 剪切当前选中内容(一般只用于文本操作)
Ctrl+Y 重做刚才动作(一般只用于文本操作)
Ctrl+Z 撤消刚才动作(一般只用于文本操作)

-----------------------------7cf87224d2020a  
Content-Disposition:   form-data;   name= "Enter "  
Submit   Query  
-----------------------------7cf87224d2020a--  

 

 

 

2.手动上传

 

 下面将直接通过底层的二进制流来取得上传文件内容,并将该文件内容放到 Web 应用所在路径。

 

由上面 “ pro.jsp 页面将显示:   可以发现每个表单域的内容总以 “-----------------------------7cf87224d2020a ” 式样的字符串开始,后面的字符串可能变化,但前面的中画线总是不会变的。 对每个文件域而言,在第二行内容中总会包含 filename = "" 的字符串

 

下面是处理上传文件的 JSP :

uploadpro.jsp

<%@ page contentType="text/html;charset=GBK" %>
<%@ page import="java.io.*"%>
<%
//取得HttpServletRequest的InputStream输入流
InputStream is = request.getInputStream();
//以InputStream输入流为基础,建立一个BufferedReader对象
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String buffer = null;
//循环读取请求内容的每一行内容
while( (buffer = br.readLine()) != null)
{
	//如果读到的内容以-----------------------------开始,
	//且以--结束,表明已到请求内容尾
	if(buffer.endsWith("--") && buffer
		.startsWith("-----------------------------"))
	{
		//跳出循环
		break;
	}
	//如果读到的内容以-----------------------------开始,表明开始了一个表单域
	if(buffer.startsWith("-----------------------------"))
	{
		//如果下一行内容中有filename字符串,表明这是一个文件域
		if (br.readLine().indexOf("filename") > 1)
		{
			//跳过两行,开始处理上传的文件内容
			br.readLine();
			br.readLine();
			//以系统时间为文件名,创建一个新文件
			File file = new File(request.getRealPath("/") 
				+ System.currentTimeMillis());
			//创建一个文件输出流
			PrintStream ps = new PrintStream(new FileOutputStream(file));
			String content = null;
			//接着开始读取文件内容
			while( (content = br.readLine()) != null)
			{
				//如果读取的内容以-----------------------------开始,
				//表明开始了下一个表单域内容
				if(content.startsWith("-----------------------------"))
				{
					//跳出处理
					break;
				}
				//将读到的内容输出到文件中
				ps.println(content);
			}
			//关闭输出
			ps.flush();
			ps.close();
		}
	}
}
br.close();
%>
 

通过上面 JSP ,就可以把一个文件上传到 Web 应用的根路径下。值得注意的是,上面用的是 BufferedReader 字符流( 字符流在处理二进制文件时会出现问题 ),因此上面的上传处理只能处理文本文件的上传。


如果将上面采用字符流 处理文件的上传逻辑,改为以字节流 来处理文件上传,则上传处理将可以处理任何文件的上传

 

对于一个成熟的文件上传框架而言,它需要完成的逻辑非常简单: 通过分析 HttpServletRequest 的二进制流,解析出二进制流中所包含的全部表单域,分析出每个表单域的类型(是文件还是普通表单域),并允许开发者以简单的方式来取得文件域的内容字节,文件名和文件内容等信息,也可以取得其他表单域的值。



3.使用上传框架完成上传

 

对于 java 而言,比较常用的上传框架有 2 个Common-FileUpload COS ,不管使用哪个,它都负责解析出 HttpServletRequest 请求中的所有域 —— 不管是文件域还是普通表单域。


【1】 Common-FileUpload

 

     这个框架是 Apache 组织下 jakarta-commons 项目组下的一个小项目,该框架可以方便地将 multipart/form-data 类型请求中的各种表单域解析出来。该项目还依赖于另一个项目: Common-IO。

 

步骤:

登录 http://jakarta.apache.org/commons/fileupload/ 站点,下载 Common-FileUpload 项目最新发布版,下载后得到一个压缩文件,将下载的压缩文件打开,看到:

   lib : 该路径下存放的是 Common-FileUpload 项目的二进制类库和源文件等。

   site : 该路径下存放的是 Common-FileUpload 项目的各种文档,包括使用手册和 API 文档等。

   注意和 LICENSE 等文件

 

将上面文件路径中的 lib\commons-fileupload-1.2.1.jar 文件复制到 Web 应用的 WEB-INF/lib 路径下

 

登录 http://jakarta.apache.org/commons/io/ 站点,下载 Common-IO 项目的最新发布版本。打开下载文件,看到如下结构:

   docs:该路径下存放了该项目的文档文件,包括使用说明和 API 文档等

   commons-io-1.4.jar:该文件是该项目的二进制类库文件

   commons-io-1.4-javadoc.jar:该项目 API 文档压缩包

   commons-io-1.4-sources.jar:该项目的全部源文件压缩包

   注意和 LICENSE 等文件

 

将上面解压出来的 commons-io-1.4.jar 文件复制到 Web 应用的 WEB-INF/lib 路径下

 

经过上面 4 个步骤,即可在 Web 应用中使用该框架来完成文件上传了。该框架完成文件上传的关键类是 ServletFileUpload ,该类可以对 HttpServletRequest 请求进行分析,分析出该请求中的全部表单域

 

commonUpload.jsp

<%@ page contentType="text/html;charset=GBK" %>
<%@ page import="java.io.*,java.util.*"%>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置上传工厂的限制
factory.setSizeThreshold(1024 * 1024  * 20);
factory.setRepository(new File(request.getRealPath("/")));
//创建一个上传文件的ServletFileUpload对象
ServletFileUpload upload = new ServletFileUpload(factory);
//设置上传文件的最大接受20M
upload.setSizeMax(1024 * 1024 * 20);
//处理HTTP请求,items是所有的表单项
List items = upload.parseRequest(request);
//遍历所有的表单项
for (Iterator it = items.iterator(); it.hasNext() ; )
{
	FileItem item = (FileItem)it.next();
	if (item.isFormField())
	{
		String name = item.getFieldName();
		String value = item.getString("GBK");
		out.println("表单域的name=value对为:"
			+ name + "=" + value + "<br />");
	}
	else
	{
		//取得文件域的表单域名
		String fieldName = item.getFieldName();
		//取得文件名
		String fileName = item.getName();
		//取得文件类型
		String contentType = item.getContentType();
		//以当前时间来生成上传文件的文件名
		FileOutputStream fos = new FileOutputStream(
			request.getRealPath("/") + System.currentTimeMillis()
			+ fileName.substring(fileName.lastIndexOf(".")
			, fileName.length()));
		//如果上传文件域对应文件的内容已经在内存中
		if (item.isInMemory() )
		{
			fos.write(item.get());
		}
		//如果文件内容不完全在内存中
		else
		{
			//获取上传文件内容的输入流
			InputStream is = item.getInputStream();
			byte[] buffer = new byte[1024];
			int len;
			//读取上传文件的内容,并将其写入服务器的文件中
			while ((len = is.read(buffer)) > 0 )
			{
				fos.write(buffer , 0 , len);
			}
			is.close();
			fos.close();
		}
	}
}
%>

 

不管是文件域还是普通表单域 Common-FileUpload 框架都把它们当成 FileItem 对象处理,如果该对象的 isFormField() 方法返回 true ,表明该表单域是一个普通表单域,否则将是一个文件域

 

FileItem 类包含了如下几个方法               

 

   ●  getFieldName():取得该表单域的 name 属性值          

   ●  getString(String encoding): 取得该表单域的 value 属性值,其中 encoding 参数设置该表单域的编码集        

   ●  getName():仅当该表单域是文件域,才有效,该方法返回上传文件的文件名         

 

   ●  getContentType():返回上传文件的文件类型      

 

   ●  get():返回上传文件的文件类型       

   ●  getInputStream():返回上传文件对应的输入流

 

通过上面的几个方法可以很轻易的完成文件上传

 

 

【2】 COS

 

这个框架是 Oreilly 组织下的一个小项目,该项目同样可以将 multipart/form-data 类型请求中的各个表单域解析出来。

 

为了在 Web 应用中使用 COS 项目,有如下步骤。

    在 http://www.servlets.com/cos/ ,下载 COS 项目的最新版本。得到一个压缩文件,解压后:

        ● doc : 该项目的 API 文档

        ● lib : classes 路径下所有 class 文件打包后的文件

        ● src : 所有源文件

        ●注意LICENSE 等文件

    将上面文件结构中的 lib/cos.jar 复制到 WEB-INF/lib 下

        COS 实现文件上传更简单,它的核心类是 MultipartParser, 该类用于解析 HttpservletRequest 请求。

        COS 用 Part 实例代表了所有的表单域,不管是普通表单域还是文件域。part类有2个子类:ParamPart 和 FilePart ,它们分别代表了 普通表单域和文件域


Part 类包含的常用方法:

    ● getName() :  获取表单域的 name 属性。

ParamPart 类包含的常用方法:

    ● getStringValue(String encoding) : 取得该表单域的 value 属性值,encoding 参数是表单域的编码集

FilePart 类包含的常用方法:

    ● getFileName(): 返回 上传文件的文件名

    ● getFilePath() : 返回上传文件的文件路径

    ● getContentType() : 返回上传文件的文件类型

 

cosUpload.jsp

<%@ page contentType="text/html;charset=GBK" %>
<%@ page import="java.io.*,java.util.*" %>
<%@ page import="com.oreilly.servlet.multipart.*" %>
<%@ page import="com.oreilly.servlet.*" %>
<%
//设置POST请求的内容最大字节为10M,该类用于解析HTTP请求
MultipartParser mp = new MultipartParser(request
	, 10*1024*1024); 
//所有表单域都是Part实例
Part part;
//遍历请求中的所有表单域
while ((part = mp.readNextPart()) != null)
{
	//取得表单域的name属性值
	String name = part.getName();
	//对于普通表单域
	if (part.isParam())
	{
		//取得普通表单域的值
		ParamPart paramPart = (ParamPart) part;
		String value = paramPart.getStringValue("GBK");
		out.print("普通表单域部分: <br />name="
			+ name + ";value=" + value + "<br />");
	}
	//对于文件域
	else if(part.isFile())
	{
		//取得文件上传域
		FilePart filePart = (FilePart) part;
		String fileName = filePart.getFileName();
		if (fileName != null)
		{
			//输出文件内容
			long size = filePart.writeTo(new File(
				request.getRealPath("/")));
			out.println("上传文件:<br /> 文件域的名="
				+ name + ";文件名=" + fileName 
				+ "<br />" + "上传文件的内容=" 
				+ filePart.getFilePath() + "<br />" 
				+ "文件内容类型=" + filePart.getContentType()
				+ "<br />" + "文件大小=" + size + "<br />");
		}
		//文件名为空
		else
		{ 
			//该文件域没有输入文件名
			out.println("名为" + name + "的文件不存在!");
		}
		out.flush();
	}
}
%>
 

注意: 如果使用 COS 来上传,文件名包含中文字符,会出现异常,可能是 COS 本身的问题。对于中文开放环境,推荐使用 Common-FileUpload 来上传文件

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值