一. 需求
1.用JAVA实现HTTP的文件上传。
2.显示上传进度条。
二. 问题分析
1.如果使用apache的上传组件common-fileupload.jar进行文件上传,当上传的文件大于服务器的内存SIZE的时候,会生成一个临时文件,文件先保存到临时文件中,然后再从临时文件COPY到到指定的路径,这个时候就会有问题了。
A.上传的进度条是根据上传文件的大小,和已经上传到指定文件的大小来生成的,如果生产临时文件,那么在文件从本地上传到服务器端的这段时间我们是看不到进度条的,看到的只是从服务端的临时文件COPY到目的文件的进度条,理论上在网络上从本地上传到服务器的时间大于从临时文件COPY到目的文件的时间,这样在界面上就会有个很长时间的DELAY,用户根本相应不到这个状态。
B.上传完成以后在临时文件夹中会有个上传文件的临时文件,common-fileupload.jar没有方法去DELETE他(我们也不能手动的去DELETE,因为根本不知道临时文件的名称)。
2.如果使用JAVA的无组件上传,复杂的HTTP头将是很头疼的。
A.实现个demo来分析HTTP上传的格式。
这段是上传的jsp code,
view plaincopy to clipboardprint?
上传然后得到上传头的格式为:
-----------------------------7d91463b90688
Content-Disposition: form-data; name="p1"
li
-----------------------------7d91463b90688
Content-Disposition: form-data; name="p2"
heng
-----------------------------7d91463b90688
Content-Disposition: form-data; name="f1"; filename="D:\HelloWorld.java"
Content-Type: text/plain
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class HelloWorld extends MIDlet
{
}
-----------------------------7d91463b90688--
FORM表单的各个字段是由一连串的减号(-)字符后跟上一个随机产生的十六进制数字序列组成,这种分割线就称为MIME分割线。
字段名称和值之间用空行分割,最后一行是在组成分隔符的字符序列再加上两个减号(-)组成。所以如果得到的字符串序列为这个结束符时,就表示上传文件结束。
在分割线之间的内容就是一个表单字段元素的信息,但是除了第一个分割线外,所有分割线前面都有一个回车换行符;除了最后一个分割线以外,每个分割线后面都有个回车换行符,使用二进制工具可以看到回车换行符是"0D 0A"
这些回车换行符不是表单字段的元素的信息,所以在提取这些表单元素的信息的时候要考虑这些回车换行符。
非文件类型的字段元素的描述头部分包括字段元素的名称,格式如下:
Content-Disposition: form-data; name="p1"
文件类型的字段元素的描述头部分包括除了含有字段的名称外,还含有上传文件的文件名称和文件类型,格式如下:
Content-Disposition: form-data; name="f1"; filename="D:\HelloWorld.java"
Content-Type: text/plain
我们可以分析这些字符串来得到文件名称等格式信息,但是注意在IE下和FF下得到的文件名称是不同的,IE下我们可以得到完整的文件路径名,而在FF下我们只能得到文件的名称,这个在编码的时候也需要考虑。
在上传的时候我们一定要用byte[] 数组的形式来传递上传数据,这样就不会有文件的编码问题。这样就产生了一个问题,就是我们既要得到一行数据的String,又想得到他的byte[]的形式。所以我们定义了如下的方法
byte[] bytes = new byte[4096]; //定义一个buffer来存放一行数据的字节数组
int[] count = new int[1]; //定义个长度为1的int[],用这个数组来返回buffer中byte的长度
这里使用int[] 而不使用int是因为,java是传值调用,使用int的时候没有办法去得到这个int的返回,但是用数组,相当是C中的指针的调用。这样一个方法就可以有几个东西来返回了。
我们可以使用encoding的编码格式来为得到的byte[]生成String,这些String就是用来是分隔符比较,判断是否已经结束。
public String readLine(ServletInputStream sis, byte[] bytes, int[] count, String encoding)
在SAVE上传数据的时候有个DELAY,就是当下一行得到的String不等于结束分隔符的时候,就SAVE上一行得到的所有byte[]。
当等于结束分隔符的时候,保存在上一行得到的byte[]的时候,要rm最后两个byte,因为这个是由于分割线产生的回车换行符,这个不是我们需要的。
然后跳出循环,这个时候上传的文件就完全保存成功了。
UploadServlet.java code:
view plaincopy to clipboardprint?
package org.eimhe.upload;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UploadServlet extends HttpServlet{
protected String encoding = "UTF-8";
protected String decoding = null;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
ServletInputStream sis = request.getInputStream();
int len = request.getContentLength();
int index = 0;
String tmp = null;
boolean isFirst = true;
String firstLine = null;
System.out.println("the content lenth is: "+len);
//the iindex can save the length that read ServetInputStream byte's count
int[] iindex = new int[1];
//the bytes is a byte array ,the array swap byte's read from ServletInputStream
byte[] bytes = new byte[4096];
String filename = null;
while((tmp = readLine(bytes, iindex, sis, encoding)) != null)
{
if(isFirst)
{
firstLine = tmp;
isFirst = false;
}
index = tmp.indexOf("filename=");
if(index != -1) //get the upload file name
{
String tailString = tmp.substring(index+10);
if(tailString != null)
{
int ii = tailString.indexOf("\"");
filename = tailString.substring(0,ii);
}
System.out.println(tmp);
break;
}
}
filename = getName(filename);
if(filename == null)
{
filename = "file.out1";
}
String filepath = "d:/"+filename;
FileOutputStream fos = new FileOutputStream(filepath);
//the endFlag is end separate flag
String endFlag = firstLine.substring(0, firstLine.length() -2)+"--"+firstLine.substring(firstLine.length() -2);
String contentType = readLine(bytes, iindex, sis, encoding);
System.out.println("Content-Type is : "+contentType);
if(contentType != null)
{
if(contentType.indexOf("Content-Type") == -1)
{
System.out.println(contentType);
}
else
{
System.out.println("the head of file: "+readLine(bytes, iindex, sis, encoding));
}
}
boolean tt = false;
int mark = 0;
byte[] backups = new byte[4096];
while((tmp = readLine(bytes, iindex, sis, encoding)) != null)
{
//if read the endFlag,the break;and save the last line rm \n\r
if(endFlag.equals(tmp))
{
if(mark >2)
{
fos.write(backups, 0, mark-2);
fos.flush();
}
break;
}
else //delay save the reading byte[]
{
if(tt)
{
fos.write(backups, 0, mark);
fos.flush();
}
mark = iindex[0];
for(int i=0;i<iindex[0];i++)
{
backups[i] = bytes[i];
}
tt = true;
}
}
fos.close();
sis.close();
}
//get the file name from the upload head file
protected String getName(String name)
{
String rtn = null;
if(name != null)
{
int index = name.lastIndexOf("/");
if(index != -1)
{
rtn = name.substring(index +1);
}
else
{
index = name.lastIndexOf("\\");
if(index != -1)
{
rtn = name.substring(index +1);
}
else
{
rtn = name;
}
}
}
return rtn;
}
//the readLine Method,return encoding string,and byte array bytes save the line data, the byte array index save the count of data
protected String readLine(byte[] bytes, int[] index, ServletInputStream sis, String enconding)
{
try {
index[0] = sis.readLine(bytes, 0, bytes.length);
if(index[0] < 0)
return null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
try{
if(encoding == null)
{
return new String(bytes, 0, index[0]);
}
else
{
return new String(bytes, 0, index[0], encoding);
}
}catch(Exception e)
{
return null;
}
}
}