您的位置:首页 > 运维架构 > Tomcat

java模拟post传输文件到tomcat服务器端servlet接收

2015-06-22 23:53 579 查看
网络上一篇介绍这个的文章,如下文章1其实是有问题的。实际上它的模拟http请求的格式有点问题,如果传输.txt文件就会发现.txt文件里面多出了一些字符,而这些字符是我们的传输文件头内容。为什么会这样的?

参考文章2我们对比发现文章1的传输文件头和文件内容的组合格式有问题的,正确的应该是文章2的样子,但是文章2又缺少了文件尾。最后综合之后,正确的格式应该是:

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition: form-data; name="username"

Patrick

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition: form-data; name="password"

HELLOPATRICK

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition: form-data; name="hobby"

Computer programming

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload1"; filename="C:\123.txt"

Content-Type:application/octet-stream

------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload2"; filename="C:\Asturias.mp3"

Content-Type:application/octet-stream

------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload3"; filename="C:\dyz.txt"

Content-Type:application/octet-stream

------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload4"; filename="C:\full.jpg"

Content-Type:application/octet-stream

------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload5"; filename="C:\Recovery.txt"

Content-Type:application/octet-stream

------------HV2ymHFg03ehbqgZCaKO6jyH--

文章1

在某些情况下,需要用Java applicatioin来模拟form,向服务器(本文以servlet为例)发送http post请求,包括提交表单域中的数据以及上传文件。如果仅仅是传递form中的数据,而不包含上传文件,那是很简单的,比如Java application可以这么写:

package com.pat.postrequestemulator;

importjava.io.BufferedReader;

importjava.io.InputStream;

importjava.io.InputStreamReader;

importjava.io.OutputStreamWriter;

importjava.net.HttpURLConnection;

importjava.net.URL;

public class PostRequestEmulator

{

public static void main(String[] args)throws Exception

{

// 服务地址

URL url = newURL("http://127.0.0.1:8080/test/upload");

// 设定连接的相关参数

HttpURLConnection connection= (HttpURLConnection) url.openConnection();

connection.setDoOutput(true);

connection.setRequestMethod("POST");

OutputStreamWriter out = newOutputStreamWriter(connection.getOutputStream(), "UTF-8");

// 向服务端发送key = value对

out.write("username=kevin&password=pass");

out.flush();

out.close();

// 获取服务端的反馈

String strLine="";

String strResponse ="";

InputStream in =connection.getInputStream();

BufferedReader reader = newBufferedReader(new InputStreamReader(in));

while((strLine =reader.readLine()) != null)

{

strResponse +=strLine +"\n";

}

System.out.print(strResponse);

}

}

服务端的servlet可以这么写:

packagecom.pat.handlinghttprequestservlet;

importjava.io.IOException;

importjava.io.PrintWriter;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

public class HandlingHttpRequestServlet extends HttpServlet

{

private static final longserialVersionUID = 1L;

@Override

protected void doGet(HttpServletRequestreq, HttpServletResponse resp)

throws ServletException, IOException

{

super.doGet(req, resp);

}

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp)

throwsServletException, IOException

{

String username =req.getParameter("username"); //获取username所对应的value

String password =req.getParameter("password"); //获取password所对应的value

System.out.println("Thereceived username and password is: " + username + "/" +password);

// 向请求端发回反馈信息

PrintWriter out =resp.getWriter();

out.print("OK");

out.flush();

out.close();

super.doPost(req, resp);

}

}

一切看起来都不复杂。但是如果要模拟的表单,除了要向服务器传递如上面的“key = value”这样的普通信息,同时还要上传文件,事情就复杂得多。下面详解如下:

1. 准备

玄机逸士很久没有开发web方面的应用了,所以机器上没有现成的环境,为此先要进行这方面的准备。

a) 到http://tomcat.apache.org 上下载tomcat压缩包apache-tomcat-6.0.33.zip,将其解压到指定目录即可,

如:D:\Tomcat6

b) 到http://commons.apache.org上下载用于文件上传的两个包:commons-fileupload-1.2.2-bin.zip

commons-io-2.1-bin.zip, commons-fileupload依赖于commons-io,但在编程的过程中,

不会直接用到commons-io

c) 检查Tomcat的安装是否成功。双击D:\Tomcat6\bin目录中的startup.bat文件,就可以启动tomcat。

打开浏览器,访问http://localhost:8080/,如果出现tomcat相关的页面,则说明tomcat安装成功。

d) 在D:\Tomcat6\webapps目录下创建一个test子目录,我们等会开发的servlet就将部署在这里。在

test目录下再建立两个目录WEB-INF(必须大写)和upload,在WEB-INF下面 创建两个目录classes和lib,

同时新建一个web.xml文件;在upload目录下,创建一个temp子目录,这些工作做完以后,test目录

下面的目录文件结构如下图所示。



其中的classes目录,用来存放将要开发的servlet,lib用来存放commons-fileupload和commons-io相关的jar包,web.xml是一些应用描述信息;upload用于存放客户端(即我们要开发的Java application)上传过来的文件,temp则用于存放上传过程中可能产生的一些临时文件。

e) 将commons-fileupload-1.2.2-bin.zip和commons-io-2.1-bin.zip解压,可以得到commons-fileupload-

1.2.2.jar和commons-io-2.1.jar,我们将这两个文件拷贝到d)中创建的lib目录中。

f) 编辑web.xml使之如下:

<?xmlversion="1.0" encoding="ISO-8859-1"?>

<!--

Licensed to the Apache Software Foundation(ASF) under one or more

contributor license agreements. See the NOTICE file distributed with

this work for additional informationregarding copyright ownership.

The ASF licenses this file to You under theApache License, Version 2.0

(the "License"); you may not usethis file except in compliance with

the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreedto in writing, software

distributed under the License is distributedon an "AS IS" BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express or implied.

See the License for the specific languagegoverning permissions and

limitations under the License.

-->

<web-appxmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

version="2.5">

<servlet>

<servlet-name>Hello</servlet-name>

<servlet-class>com.pat.handlinghttprequestservlet.HandlingHttpRequestServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Hello</servlet-name>

<url-pattern>/upload</url-pattern>

</servlet-mapping>

</web-app>

这个web.xml可以先从D:\Tomcat6\webapps\examples\WEB-INF下面拷贝过来,再进行如上黑体字所示的修改即可。<servlet>标签描述的是servlet代码方面的资料,其中<servlet-name>Hello</servlet-name>是给这个servlet起一个名字Hello,它必须和下面的<servlet-mapping>中的<servlet-name>一致,该名字具体是什么可以随意写定。

<servlet-class>com.pat.handlinghttprequestservlet.HandlingHttpRequestServlet</servlet-class>说明了完整的servlet类名,即servlet的类名为HandlingHttpRequestServlet,它位于包com.pat.handlinghttprequestservlet中。

<url-pattern>/upload</url-pattern>,说明了如果客户端向“http://Web服务器的URL地址:端口号/test/upload”发出了请求,则该请求将由位于包com.pat.handlinghttprequestservlet中的HandlingHttpRequestServlet进行处理。

到此,前期准备工作结束。下面准备写代码。

2. Servlet的代码

packagecom.pat.handlinghttprequestservlet;

importjava.io.File;

importjava.io.IOException;

importjava.io.PrintWriter;

importjava.util.ArrayList;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importorg.apache.commons.fileupload.FileItem;

importorg.apache.commons.fileupload.disk.DiskFileItemFactory;

importorg.apache.commons.fileupload.servlet.ServletFileUpload;

public class HandlingHttpRequestServlet extends HttpServlet

{

private static final longserialVersionUID = 1L;

@Override

protected void doGet(HttpServletRequestreq, HttpServletResponse resp)

throws ServletException, IOException

{

super.doGet(req, resp);

}

@SuppressWarnings({"unchecked", "deprecation" })

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp)

throwsServletException, IOException

{

DiskFileItemFactory factory =new DiskFileItemFactory();

//得到绝对文件夹路径,比如"D:\\Tomcat6\\webapps\\test\\upload"

String path = req.getRealPath("/upload");

//临时文件夹路径

String repositoryPath =req.getRealPath("/upload/temp");

// 设定临时文件夹为repositoryPath

factory.setRepository(newFile(repositoryPath));

// 设定上传文件的阈值,如果上传文件大于1M,就可能在repository

// 所代 表的文件夹中产生临时文件,否则直接在内存中进行处理

factory.setSizeThreshold(1024* 1024);

//System.out.println("----"+ req.getContextPath()); // 得到相对文件夹路径,比如 "/test"

// 创建一个ServletFileUpload对象

ServletFileUpload uploader =new ServletFileUpload(factory);

try

{

// 调用uploader中的parseRequest方法,可以获得请求中的相关内容,

// 即一个FileItem类型的ArrayList。FileItem是在

// org.apache.commons.fileupload中定义的,它可以代表一个文件,

// 也可以代表一个普通的form field

ArrayList<FileItem>list = (ArrayList<FileItem>)uploader.parseRequest(req);

System.out.println(list.size());

for(FileItemfileItem : list)

{

if(fileItem.isFormField()) // 如果是普通的form field

{

Stringname = fileItem.getFieldName();

Stringvalue = fileItem.getString();

System.out.println(name+ " = " + value);

}

else // 如果是文件

{

Stringvalue = fileItem.getName();

intstart = value.lastIndexOf("\\");

StringfileName = value.substring(start + 1);

// 将其中包含的内容写到path(即upload目录)下,

// 名为fileName的文件中

fileItem.write(newFile(path, fileName));

}

}

}

catch(Exception e)

{

e.printStackTrace();

}

// 向客户端反馈结果

PrintWriter out =resp.getWriter();

out.print("OK");

out.flush();

out.close();

super.doPost(req, resp);

}

}

再在classes目录建立如下目录结构com\pat\handlinghttprequestservlet,这用来代表HandlingHttpRequestServlet这个servlet所在的包名,将编译好的HandlingHttpRequestServlet.class,拷贝到这个目录下,然后启动(或者重新启动)tomcat

3. Java application的代码

package com.pat.postrequestemulator;

importjava.io.BufferedReader;

importjava.io.DataInputStream;

importjava.io.File;

import java.io.FileInputStream;

importjava.io.InputStream;

importjava.io.InputStreamReader;

importjava.io.OutputStream;

importjava.io.Serializable;

importjava.net.HttpURLConnection;

importjava.net.URL;

importjava.util.ArrayList;

public classPostRequestEmulator

{

public static void main(String[] args)throws Exception

{

// 设定服务地址

String serverUrl ="http://127.0.0.1:8080/test/upload";

// 设定要上传的普通Form Field及其对应的value

// 类FormFieldKeyValuePair的定义见后面的代码

ArrayList<FormFieldKeyValuePair> ffkvp = new ArrayList<FormFieldKeyValuePair>();

ffkvp.add(newFormFieldKeyValuePair("username", "Patrick"));

ffkvp.add(newFormFieldKeyValuePair("password", "HELLOPATRICK"));

ffkvp.add(newFormFieldKeyValuePair("hobby", "Computer programming"));

// 设定要上传的文件。UploadFileItem见后面的代码

ArrayList<UploadFileItem> ufi = new ArrayList<UploadFileItem>();

ufi.add(newUploadFileItem("upload1", "E:\\Asturias.mp3"));

ufi.add(newUploadFileItem("upload2", "E:\\full.jpg"));

ufi.add(newUploadFileItem("upload3", "E:\\dyz.txt"));

// 类HttpPostEmulator的定义,见后面的代码

HttpPostEmulator hpe = new HttpPostEmulator();

String response =hpe.sendHttpPostRequest(serverUrl, ffkvp, ufi);

System.out.println("Responsefrom server is: " + response);

}

}

classHttpPostEmulator

{

//每个post参数之间的分隔。随意设定,只要不会和其他的字符串重复即可。

private static final String BOUNDARY ="----------HV2ymHFg03ehbqgZCaKO6jyH";

public StringsendHttpPostRequest(String serverUrl,

ArrayList<FormFieldKeyValuePair>generalFormFields,

ArrayList<UploadFileItem>filesToBeUploaded) throws Exception

{

// 向服务器发送post请求

URL url = newURL(serverUrl/*"http://127.0.0.1:8080/test/upload"*/);

HttpURLConnection connection= (HttpURLConnection) url.openConnection();

// 发送POST请求必须设置如下两行

connection.setDoOutput(true);

connection.setDoInput(true);

connection.setUseCaches(false);

connection.setRequestMethod("POST");

connection.setRequestProperty("Connection","Keep-Alive");

connection.setRequestProperty("Charset","UTF-8");

connection.setRequestProperty("Content-Type","multipart/form-data; boundary=" + BOUNDARY);

// 头

String boundary = BOUNDARY;

// 传输内容

StringBuffer contentBody =new StringBuffer("--" + BOUNDARY);

// 尾

String endBoundary ="\r\n--" + boundary + "--\r\n";

OutputStream out =connection.getOutputStream();

// 1. 处理普通表单域(即形如key = value对)的POST请求

for(FormFieldKeyValuePairffkvp : generalFormFields)

{

contentBody.append("\r\n")

.append("Content-Disposition: form-data; name=\"")

.append(ffkvp.getKey() + "\"")

.append("\r\n")

.append("\r\n")

.append(ffkvp.getValue())

.append("\r\n")

.append("--")

.append(boundary);

}

String boundaryMessage1 =contentBody.toString();

out.write(boundaryMessage1.getBytes("utf-8"));

// 2. 处理文件上传

for(UploadFileItem ufi :filesToBeUploaded)

{

contentBody = newStringBuffer();

contentBody.append("\r\n")

.append("Content-Disposition:form-data; name=\"")

.append(ufi.getFormFieldName() +"\"; ") // form中field的名称

.append("filename=\"")

.append(ufi.getFileName() +"\"") //上传文件的文件名,包括目录

.append("\r\n")

.append("Content-Type:application/octet-stream")

.append("\r\n\r\n");

StringboundaryMessage2 = contentBody.toString();

out.write(boundaryMessage2.getBytes("utf-8"));

// 开始真正向服务器写文件

File file = newFile(ufi.getFileName());

DataInputStream dis= new DataInputStream(new FileInputStream(file));

int bytes = 0;

byte[] bufferOut =new byte[(int) file.length()];

bytes =dis.read(bufferOut);

out.write(bufferOut,0, bytes);

dis.close();

contentBody.append("------------HV2ymHFg03ehbqgZCaKO6jyH");

StringboundaryMessage = contentBody.toString();

out.write(boundaryMessage.getBytes("utf-8"));

//System.out.println(boundaryMessage);

}

out.write("------------HV2ymHFg03ehbqgZCaKO6jyH--\r\n".getBytes("UTF-8"));

// 3. 写结尾

out.write(endBoundary.getBytes("utf-8"));

out.flush();

out.close();

// 4. 从服务器获得回答的内容

String strLine="";

String strResponse ="";

InputStream in =connection.getInputStream();

BufferedReader reader = newBufferedReader(new InputStreamReader(in));

while((strLine =reader.readLine()) != null)

{

strResponse +=strLine +"\n";

}

//System.out.print(strResponse);

return strResponse;

}

}

// 一个POJO。用于处理普通表单域形如key = value对的数据

classFormFieldKeyValuePair implements Serializable

{

private static final longserialVersionUID = 1L;

// The form field used for receivinguser's input,

// such as "username" in "<inputtype="text" name="username"/>"

private String key;

// The value entered by user in thecorresponding form field,

// such as "Patrick" the abovementioned formfield "username"

private String value;

public FormFieldKeyValuePair(Stringkey, String value)

{

this.key = key;

this.value = value;

}

public String getKey()

{

return key;

}

public void setKey(String key)

{

this.key = key;

}

public String getValue()

{

return value;

}

public void setValue(String value)

{

this.value = value;

}

}

// 一个POJO。用于保存上传文件的相关信息

classUploadFileItem implements Serializable

{

private static final longserialVersionUID = 1L;

// The form field name in a form used foruploading a file,

// such as "upload1" in "<inputtype="file" name="upload1"/>"

private String formFieldName;

// File name to be uploaded, thefileName contains path,

// such as "E:\\some_file.jpg"

private String fileName;

public UploadFileItem(StringformFieldName, String fileName)

{

this.formFieldName =formFieldName;

this.fileName = fileName;

}

public String getFormFieldName()

{

return formFieldName;

}

public void setFormFieldName(StringformFieldName)

{

this.formFieldName =formFieldName;

}

public String getFileName()

{

return fileName;

}

public void setFileName(StringfileName)

{

this.fileName = fileName;

}

}

4. 运行结果

运行PostRequestEmulator之前,服务器的upload目录下的情况:



运行PostRequestEmulator后,服务器的upload目录下的情况:



PostRequestEmulator从服务端得到的反馈情况:

Response fromserver is: OK

Tomcat控制台输出:



如果上传的文件中有大于1M的情况,第二次执行PostRequestEmulator的时候,就会在temp目录中产生临时文件。

本文参考材料:

http://v.youku.com/v_show/id_XMjc0ODMxMTA4.html

http://www.iteye.com/topic/1116110(在Android中用Application模拟http post请求)

http://apps.hi.baidu.com/share/detail/46728849

http://www.eoeandroid.com/thread-22679-1-1.html

http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html

文章2

在最初的 http 协议中,没有上传文件方面的功能。RFC1867("Form-based File Upload in HTML".)

为 http 协议添加了这个功能。客户端的浏览器,如 Microsoft IE, Mozila, Opera 等,按照此规范将用

户指定的文件发送到服务器。服务器端的网页程序,如 php, asp, jsp 等,可以按照此规范,解析出用户

发送来的文件。

2.1客户端

简单来说,RFC1867规范要求http协议增加了file类型的input标签,用于浏览需要上传的文件。同时

要求FORM表单的enctype属性设置为“multipart/form-data”,method属性设置为“post”即可,下面是我们文

件上传页面的表单代码:
<form action="<%=request.getContextPath()%>/servlet/SimpleUpload" enctype="multipart/form-data"

method="post">
文本1:<input type="text" name="text1" value="文本1"><br>
文件2:<input type="text" name="text2" value="文本2"><br>
文件1:<input type="file" name="file1"><br>
文件2:<input type="file" name="file2"><br>
文件2:<input type="file" name="file3"><br>
<input type="submit" value="开始上传">

</form>

2.2 服务器端

一个文件上传请求的消息实体由一系列根据 RFC1867("Form-based File Upload in HTML".)编码的项目

(文本参数和文件参数)组成。自己编程来解析获取这些数据是非常麻烦的,还需要了解RFC1867规范对请

求数据编码的相关知识。FileUpload 可以帮助我们解析这样的请求,将每一个项目封装成一个实现了FileItem

接口的对象,并以列表的形式返回。所以,我们只需要了解FileUpload的API如何使用即可,不用管它们的底

层实现。让我们来看一个简单文件上传处理代码:

DiskFileItemFactory factory = new DiskFileItemFactory();

ServletFileUpload uploader = new ServletFileUpload(factory);

List<FileItem> list = uploader.parseRequest(request);
if (item.isFormField()){

// 处理普通表单域

String field = item.getFieldName();//表单域名

String value = item.getString("GBK");
} else {

//将临时文件保存到指定目录
String fileName = item.getName();//文件名称
String filepath = "您希望保存的目录/" + fileName;
item.write(new File(filepath));//执行保存

}
怎么样?简单吧!下面我们来继续了解一些必须了解的API。

FileItem接口

org.apache.commons.fileupload.disk.DiskFileItem实现了FileItem接口,用来封装单个表单字段元素的

数据。通过调用FileItem 定义的方法可以获得相关表单字段元素的数据。我们不需要关心DiskFileItem的具

体实现,在程序中可以采用FileItem接口类型来对DiskFileItem对象进行引用和访问。FileItem类还实现了

Serializable接口,以支持序列化操作。
下图是一个文件上传表单:



上图表单提交的http数据包的内容:

POST /demo/servlet/SimpleUpload HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/msword,

application/vnd.ms-excel, application/vnd.ms-powerpoint, */*

Referer: http://127.0.0.1:8080/demo/simpleUpload.jsp
Accept-Language: zh-cn

Content-Type: multipart/form-data; boundary=---------------------------7da1772c5504c6

UA-CPU: x86

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)

Host: 127.0.0.1:8080

Content-Length: 184423

Connection: Keep-Alive

Cache-Control: no-cache

Cookie: JSESSIONID=BD8E58E5BAD9B559C0262077FB5E0B4E

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="text1"

郑州蜂鸟科技有限公司

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="text2"

申林

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file1"; filename="C:\Documents and Settings\All Users\

Documents\My Pictures\示例图片\Blue hills.jpg"

Content-Type: image/pjpeg

大量二进制数据内容,无法复制 …….

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file2"; filename="C:\Documents and Settings\All Users\

Documents\My Pictures\示例图片\Sunset.jpg"

Content-Type: image/pjpeg

大量二进制数据内容,无法复制 …….

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file3"; filename="C:\Documents and Settings\All Users\

Documents\My Pictures\示例图片\Water lilies.jpg"

Content-Type: image/pjpeg

大量二进制数据内容,无法复制 …….
从第一行,也就是请求行,我们可以看出这是一个post请求。在请求头部部分,我们可以看到这样

一个头部信息:
Content-Type: multipart/form-data; boundary=---------------------------7da1772c5504c6
其中红色部分说明该请求是一个multipart/form-data类型即多媒体类型的请求。蓝色部分boundary的值

定义了一个字段分隔界线。在消息体部分可以看出每个表单字段元素数据之间采用字段分隔界线进行分

割,两个分隔界线间的内容称为一个分区,每个分区中的内容包括两部分,一部分是对表单字段元素进

行描述的描述头,另外一部分是表单字段元素的主体内容。
通过对比描述头,我们可以很容易区分文本字段和文件字段。不管是文件字段还是文本字段,都有

name属性,即该字段作为一个表单域的名字。而文件字段还有filename,即上传文件本身的名字。另外,

还有conten-type属性用于指明文件的类型。
每一个表单字段,不管它是文本还是文件,都被封装成 FileItem 对象,我们称之为文件项,当文件

项数据内容尺寸小于DiskFileItemFactory 的sizeThreshold 属性设置的临界值时,直接保存在内存中;否则,

将数据流以临时文件的形式,保存在 DiskFileItemFactory 的 repository 属性指定的临时目录中。临时文件

名形如“upload_00000005(八位或八位以上的数字).tmp”。FileItem类内部提供了维护临时文件名中的

数值不重复的机制,以保证了临时文件名的唯一性。另外,如何保证临时文件能被及时清除,释放宝贵

的系统资源,是非常重要的,我们将在后面讲解。

FileItem类常用的方法:

1. boolean isFormField()方法
isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字

段,如果是普通表单字段则返回true,否则返回false。
2. String getName()方法
getName方法用于获得文件上传字段中的文件名,即表单字段元素描述头中的filename属性值,如“C:\Documents and Settings\All Users\Documents\My Pictures\示例图片\Sunset.jpg”。如果FileItem类对象对

应的是普通表单字段,getName方法将返回null。即使用户没有通过网页表单中的文件字段传递任何

文件,但只要设置了文件表单字段的name属性,浏览器也会将文件字段的信息传递给服务器,

只是文件名和文件内容部分都为空,但这个表单字段仍然对应一个FileItem对象,此时,

getName方法返回结果为空字符串"",读者在调用Apache文件上传组件时要注意考虑这个情况。
注意:上面的数据包是通过IE提交,所以是完整的路径和名称。如

C:\Documents and Settings\All Users\Documents\My Pictures\示例图片\Sunset.jpg。如果是其它浏览

器,如火狐和Chromium,则仅仅是名字,没有路径,如Sunset.jpg。
3. String getFieldName()方法
getFieldName方法用于返回表单字段元素描述头的name属性值,也是表单标签name属性的值。例

如“name=file1”中的“file1”。
4. void write(File file)方法
write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主

体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通

表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
5. String getString()方法
getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
public java.lang.String getString()
public java.lang.String getString(java.lang.String encoding)
throws java.io.UnsupportedEncodingException
前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容

转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个

getString方法,并为之传递正确的字符集编码名称。
6. String getContentType()方法
getContentType 方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,

如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。
7. boolean isInMemory()方法
isInMemory方法用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,

如果存储在内存中则返回true,否则返回false。
8. void delete()方法
delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,

delete方法将删除该临时文件。
尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的

清除临时文件,释放系统存储资源。另外,当系统出现异常时,仍有可能造成有的临时文件被永久

保存在了硬盘中。
9. InputStream getInputStream()方法
以流的形式返回上传文件的数据内容。

10. long getSize()方法

返回该上传文件的大小(以字节为单位)。

DiskFileItemFactory类

将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务

由 org.apache.commons.fileupload.FileItemFactory 接口的默认实现

org.apache.commons.fileupload.disk.DiskFileItemFactory 来完成。当上传的文件项目比较小时,直接保

存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度

慢些,但是内存资源是有限的)。

属性

1) public static final int DEFAULT_SIZE_THRESHOLD :将文件保存在内存还是

磁盘临时文件夹的默认临界值,值为10240,即10kb。

2) private File repository:用于配置在创建文件项目时,当文件项目大于临界值时使

用的临时文件夹,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir

获取。如下代码:
System.getProperty("java.io.tmpdir");

3) private int sizeThreshold:用于保存将文件保存在内存还是磁盘临时文件夹的临界值

构造方法

1) public DiskFileItemFactory():采用默认临界值和系统临时文件夹构造文件项工厂对象。

2) public DiskFileItemFactory(int sizeThreshold,File repository):采用参数指定临界值和系统临时

文件夹构造文件项工厂对象。

FileItem createItem() 方法

根据DiskFileItemFactory相关配置将每一个请求消息实体项目创建 成DiskFileItem 实例,并返回。

该方法从来不需要我们亲自调用,FileUpload组件在解析请求时内部使用。

void setSizeThreshold(int sizeThreshold)

Apache文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析出的数据,以便

在后面进行数据的进一步处理(保存在磁盘特定位置或插入数据库)。因为Java虚拟机默认可以使

用的内存空间是有限的,超出限制时将会抛出“java.lang.OutOfMemoryError”错误。如果上传的文件

很大,例如800M的文件,在内存中将无法临时保存该文件内容,Apache文件上传组件转而采用临时

文件来保存这些数据;但如果上传的文件很小,例如600个字节的文件,显然将其直接保存在内存中

性能会更加好些。
setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值(以字节

为单位的int值),如果从没有调用该方法设置此临界值,将会采用系统默认值10KB。对应的

getSizeThreshold() 方法用来获取此临界值。

void setRepository(File repository)

setRepositoryPath方法用于设置当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以

临时文件形式保存在磁盘上的存放目录。有一个对应的获得临时文件夹的 File getRespository() 方法。
注意:当从没有调用此方法设置临时文件存储目录时,默认采用系统默认的临时文件路径,可以

通过系统属性 java.io.tmpdir 获取。如下代码:
System.getProperty("java.io.tmpdir");
Tomcat系统默认临时目录为“<tomcat安装目录>/temp/”。

ServletFileUpload 类

org.apache.commons.fileupload.servlet.ServletFileUpload类是Apache文件上传组件处理文件上传的

核心高级类(所谓高级就是不需要管底层实现,暴露给用户的简单易用的接口)。
使用其 parseRequest(HttpServletRequest) 方法可以将通过表单中每一个HTML标签提交的数据封装

成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。
如果你希望进一步提高新能,你可以采用 getItemIterator 方法,直接获得每一个文件项的数据输

入流,对数据做直接处理。
在使用ServletFileUpload对象解析请求时需要根据DiskFileItemFactory对象的属性 sizeThreshold(临

界值)和repository(临时目录) 来决定将解析得到的数据保存在内存还是临时文件中,如果是临时

文件,保存在哪个临时目录中?。所以,我们需要在进行解析工作前构造好DiskFileItemFactory对象,

通过ServletFileUpload对象的构造方法或setFileItemFactory()方法设置 ServletFileUpload对象的

fileItemFactory属性。

ServletFileUpload继承结构:

java.lang.Object
|—org.apache.commons.fileupload.FileUploadBase
|—org.apache.commons.fileupload.FileUpload
|—org.apache.commons.fileupload.servlet.ServletFileUpload

构造方法:

1) public ServletFileUpload():构造一个未初始化的实例,需要在解析请求之前先调用

setFileItemFactory()方法设置 fileItemFactory属性。

2) public ServletFileUpload(FileItemFactory fileItemFactory):构造一个实例,并根据参数

指定的FileItemFactory 对象,设置 fileItemFactory属性。

ServletFileUpload类常用方法:

1. public void setSizeMax(long sizeMax)方法
setSizeMax方法继承自FileUploadBase类,用于设置请求消息实体内容(即所有上传数据)的最大

尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的

long型数字。
在请求解析的过程中,如果请求消息体内容的大小超过了setSizeMax方法的设置值,将会抛出

FileUploadBase内部定义的SizeLimitExceededException异常(FileUploadException的子类)。如:
org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException:

the request was rejected because its size (1649104) exceeds the configured

maximum (153600)
该方法有一个对应的读方法:public long getSizeMax()方法。
2. public void setFileSizeMax(long fileSizeMax)方法
setFileSizeMax方法继承自FileUploadBase类,用于设置单个上传文件的最大尺寸限制,以防止客户

端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。该方法有一个

对应的读方法:public long geFileSizeMax()方法。
在请求解析的过程中,如果单个上传文件的大小超过了setFileSizeMax方法的设置值,将会抛出

FileUploadBase内部定义的FileSizeLimitExceededException异常(FileUploadException的子类)。如:
org.apache.commons.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file1 exceeds its

maximum permitted size of 51200 characters.
3. public List parseRequest(javax.servlet.http.HttpServletRequest req)
parseRequest 方法是ServletFileUpload类的重要方法,它是对HTTP请求消息体内容进行解析的入口

方法。它解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这

些FileItem对象加入进一个List类型的集合对象中返回。
该方法抛出FileUploadException异常来处理诸如文件尺寸过大、请求消息中的实体内容的类型不

是“multipart/form-data”、IO异常、请求消息体长度信息丢失等各种异常。每一种异常都是

FileUploadException的一个子类型。
4. public FileItemIterator getItemIterator(HttpServletRequest request)
getItemIterator方法和parseRequest 方法基本相同。但是getItemIterator方法返回的是一个迭代

器,该迭代器中保存的不是FileItem对象,而是FileItemStream 对象,如果你希望进一步提高新能,

你可以采用 getItemIterator 方法,直接获得每一个文件项的数据输入流,做底层处理;如果性能不

是问题,你希望代码简单,则采用parseRequest方法即可。
5. public stiatc boolean isMultipartContent(HttpServletRequest req)
isMultipartContent方法方法用于判断请求消息中的内容是否是“multipart/form-data”类型,是则返

回true,否则返回false。isMultipartContent方法是一个静态方法,不用创建ServletFileUpload类的实例对

象即可被调用。
6. getFileItemFactory()和setFileItemFactory(FileItemFactory)方法
方法继承自FileUpload类,用于设置和读取fileItemFactory属性。
7. public void setProgressListener(ProgressListener pListener)
设置文件上传进度监听器。关于监听器的具体内容,将在后面学习。该方法有一个对应的读取

方法:ProgressListener getProgressListener()。
8.public void setHeaderEncoding()方法
在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,文件上传字段中的文件路

径名也是文本,在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取

这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
setHeaderEncoding方法继承自FileUploadBase类,用于设置上面提到的字符编码。如果没有设置,

则对应的读方法getHeaderEncoding()方法返回null,将采用HttpServletRequest设置的字符编码,如果

HttpServletRequest的字符编码也为null,则采用系统默认字符编码。可以通过一下语句获得系统默认

字符编码:
System.getProperty("file.encoding"));

好,到这里我们学习了主要的一些API,足够我们来完成一个简单文件上传的功能了,下一章,

我们将一起来编写一个文件上传应用程序。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: