多文件上传,搞的头大了,受不了。
在实现文件上传的时候,form表单中添加或修改为:enctype="multipart/form-data",因为在默认情况下,enctype值为enctype=application/x-www-form-urlencoded,这种编码格式不能够上传文件,而采用multipart/form-data时,表单数据被已二进制的方式被传递出去, 无法正常用request接收,所以搞的令人很头疼。。。。
下面是我的用servlet实现多文件上传的代码,参考网站:http://blog.sina.com.cn/s/blog_81c2545a0100ybqz.html,他说是可以实现多文件上传,但我测试的结果却是只能上传一个,第二个或第三个择没有上传成功,最后我把一些东西给注释掉,下面会提示。然后就可以了。。。
浏览器传递出去的消息内容格式(请参考上面的参考网站):
POST / HTTP/1.1
Host: localhost:8091
Connection: keep-alive
Content-Length: 7741
Cache-Control: max-age=0
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Content-Disposition: form-data; name="FileID1"
你好
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Content-Disposition: form-data; name="FileID2"
发生大幅
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Content-Disposition: form-data; name="fileData1"; filename="a.txt"
Content-Type: text/plain
content of a.txt
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Content-Disposition: form-data; name="fileData2"; filename="b.txt"
Content-Type: text/plain
content of b.txt
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK
Content-Disposition: form-data; name="uploadfile"
upload
------WebKitFormBoundaryIl2Vzdy7PwfJVaWK--
UploadFileServlet.java
package com.xuezhi.control;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.FileNotFoundException;
import javax.servlet.RequestDispatcher;
/**
*
* @author WJC
*/
public class UploadFileServlet extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException ,FileNotFoundException{
doGet(request,response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException ,FileNotFoundException {
//processRequest(request, response);
PrintWriter out = response.getWriter();
final int NONE = 0;//状态码,表示没有特殊操作
final int DATAHEADER = 1;//表示下一行要读到报头信息
final int FILEDATA = 2;//表示下面要读到表单域的文本值
final int FIELDDATA = 3;//表示下面要独到表单域的是上传文件和二进制数据
//请求信息实体的总长度(请求信息中除表头之外的数据长度)
int totalbytes =request.getContentLength();
//容纳请求信息实体的字节数组
byte[] b = new byte[totalbytes];
byte[] srcb = new byte[totalbytes];
//请求信息类型
String contentType = request.getContentType();
String fieldname = "";//表单域的名称
String fieldvalue = "";//表单域的值
String filename = "";//上传文件名称
String boundary = "";//分界符字符串
String lastboundary = "";//结束分界符字符串
String fileFormName = "";
String fileRealName = "";
int fileSize = 0;//文件长度
//容纳表单域的名称/值的哈希表
HashMap formfields = new HashMap();
//在消息头类型中找到分界符的定义
int pos = contentType.indexOf("boundary=");
String fileID;//上传的文件ID
if(pos != -1)
{
pos += "boundary=".length();
boundary = "--"+ contentType.substring(pos);
lastboundary = boundary + "--";//得到结束分界符
}
int state = NONE;//起始状态为NONE
//得到请求信息的数据输入流
DataInputStream in = new DataInputStream(request.getInputStream());
in.readFully(srcb);//根据长度,将消息实体的内容读入字节数组b中
in.close();//关闭数据流
String reqcontent = new String(srcb);//这里如果你的页面编码用的是utf8的话, 可以修改为String reqcontent = new String(srcb,"utf-8");
//从字节数组中的到实体的字符串
//从字符串中得到输出缓冲流
BufferedReader reqbuf = new BufferedReader(new StringReader(reqcontent));
//设置循环标志
boolean flag = true;
int i = 0;
int rootNum = 0;
while(flag == true)
{
String s = reqbuf.readLine();
if( s == null || s.equals(lastboundary)) break;
switch(state)
{
case NONE :
if(s.startsWith(boundary))
{
//如果独到分界符,择表示下一行一个头信息
state = DATAHEADER;
i += 1;
}
break;
case DATAHEADER :
pos = s.indexOf("filename=");
//先判断出这是一个文本表单域的头信息,还是一个上传文件的头信息
if(pos == -1)
{
//如果是文本表单域的头信息,解析出表单域的名称
pos = s.indexOf("name=");
pos += "name=".length() + 1;//1表示后面的”的占位
s = s.substring(pos);
int L = s.length();
s = s.substring(0,L - 1);
fieldname = s;//表单域的名称放入fieldname
state = FIELDDATA;//设置状态码,准备读取表单域的值
}
else{
//如果是文件数据的头,先存储这一行,用于在字节数组中定位
String temp = s;
// 先解析出文件名
pos = s.indexOf("name=");
pos += "name=".length() + 1; // 1表示后面的"的占位
int pos2 = s.indexOf("filename=");
String s1 = s.substring(pos, pos2 - 3); // 3表示";加上一个空格
fileFormName = s1;
pos2 += "filename=".length() + 1; // 1表示后面的"的占位
s = s.substring(pos2);
int l = s.length();
s = s.substring(0, l - 1);
pos2 = s.lastIndexOf("\\"); // 对于IE浏览器的设置
s = s.substring(pos2 + 1);
fileRealName = s;
out.println("fileRealName = " + fileRealName + "<br>");
//下面这一部分从字节数组中取出文件的数据
if(fileRealName.length() > 0)
{
b = srcb;
pos = byteIndexOf(b,temp,0);
//定位下一行,2表示一个回车和一个换行占2个字节
b = subBytes(b,pos + temp.getBytes().length + 2,b.length);
//再读一行信息,是这一部分数据的Content-Type
s = reqbuf.readLine();
//设置文件输入流,准备写文件
//其中request.getSession(true).getServletContext().getRealPath("/")+"../../web/得到项目根目录下的web包,uploadFile是我自己创建的文件夹
String path = request.getSession(true).getServletContext().getRealPath("/")+"../../web/uploadFile/"+ fileRealName;
File f = new File(path);
DataOutputStream fileout = new
DataOutputStream(new FileOutputStream(f));
//字节数组再往下一行,4表示两个回车换行符占4个字节,本行的回车换行
//2个字节,Content-type的下一行是回车换行表示的空行,占2个字符
//得到文件数据的其实位置
b = subBytes(b,s.getBytes().length + 4 ,b.length);
pos = byteIndexOf(b,boundary,0);//定位文件数据的结尾
b = subBytes(b,0,pos -1);//获取文件数据
fileout.write(b,0,b.length - 1);//将文件数据存盘
fileout.close();
fileSize = b.length - 1;//文件长度存入fileSize
formfields.put(fileFormName,fileRealName);
rootNum ++;//文件路径编号root1....n
formfields.put("root"+rootNum,path);
state = NONE;//修改前为state=FILEDATA
}
}
break;
case FIELDDATA :
//读出表单域的值
s = reqbuf.readLine();
fieldvalue = s;//存入fieldvalue
formfields.put(fieldname,fieldvalue);
state = NONE;
break;
/*
*在注释掉这这部分和未修改上面红字部分之前,同时上传多个文件,只能接收到第一个文件,后面的都接收不到
case FILEDATA :
//如果是文件数据不进行分析,直接读过去
while((!s.startsWith(boundary)) && (!s.startsWith(lastboundary)))
{
s = reqbuf.readLine();
if(s.startsWith(boundary))
{
state = DATAHEADER;
}
else break;
break;
}
break;
*/
}
}
//指定内容类型,并且可显示为中文
out.println("<HTML>");
out.println("<HEAD><TITLE>上传文件结果</TITLE></HEAD>");
out.println("<BODY>");//HTML的头
out.println("<H1>文件上传结果</H1><hr>");
out.println(
"ID 为" + formfields.get("FileID1") + "的文件\n"
+"和ID 为" + formfields.get("FileID2") + "的文件\n"
+formfields.get("FileData1") + "已经上传\n"
+formfields.get("FileData2") + "已经上传"
);
out.println("<br>");
out.println("contentType="+contentType);
out.println("<br>");
out.println("totalBytes="+totalbytes);
out.println("</BODY>");
out.println("</HTML>");//HTML的结尾
}
/**
* Returns a short description of the servlet.
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
/**
* 字节数组中的indexof函数,与String类中的indexOf类似,
* b要搜索的字节数组
* s要找的字符串
* start搜索的起始位置
* 如果找到,返回s的第一个字节在buffer中的下标,没有就返回-1
*/
private static int byteIndexOf(byte[] b,String s,int start)
{
return byteIndexOf(b,s.getBytes(),start);
}
/**
* 字节数组中的indexof函数,与String类中的indexOf类似
* b源字节数组
* s目标自己数组
* start搜做的起始位置
* 如果找到,返回s的第一个字节在b中的下标,没有就返回-1
*/
private static int byteIndexOf(byte[] b,byte[] s,int start)
{
int i ;
if(s.length == 0)
{
return 0;
}
int max = b.length - s.length;
if(max < 0)
return - 1;
if( start > max )
return -1;
if(start < 0)
start = 0;
//在b中找到s的第一个元素
search :
for(i = start;i <= max;i++)
{
if(b[i] == s[0])
{
//找到了s中的第一个元素后,比较剩余的部分是否相等
int k = 1;
while(k < s.length)
{
if(b[k + i] != s[k])
{
continue search;
}
k++;
}
return i;
}
}
return -1;
}
/**
* 用于从一个字节数组中提取一个字节数组
* 类似于String类的苏北String()
*/
private static byte[] subBytes(byte[] b,int from,int end)
{
byte[] result = new byte[end - from];
System.arraycopy(b, from, result, 0, end - from);
return result;
}
/**
* 用于从一个字节数组中提取一个字符串
* 类似于String中的substring()
*/
private static String subBytesString(byte[] b,int from ,int end)
{
return new String(subBytes(b,from,end));
}
}
//end of UploadFileServelt.java
jsp页面如下:upload.jsp
<%@page contentType="text/html" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<form method="post" action="UploadFileServlet" enctype="multipart/form-data">
<p>flag:<input type="text" name="flag" size= "20"></p>
<p>ID:<input type="text" name="FileID1" size="20"></p>
<p>ID:<input type="text" name="FileID2" size="20"></p>
<p>文件名1:<input type="file" name="FileData1" size="20"></p>
<p>文件名2:<input type="file" name="FileData2" size="20"></p>
<input type="submit" name="upload" value="upload">
</form>
</body>
</html>
当然,你可以采用组件来做,那样简单方便。唉,我这是被逼得,组长不让用组件,结果我快被搞死了
采用组件的多文件上传参考网站:http://blog.youkuaiyun.com/tgm316750432/article/details/7952307
struts中文件上传也是相当的给力,可以找一下。。