背景:
3月,公司的實習生來了,每年都會招,招來之后人家看明白你公司環境后,畢業就和你拜拜了。不管怎么樣,部門要求培訓、要求每天寫日報,這下可好,我們組有5個實習生,每個5個Excel,天天如此,而且Excel每個1M,我就得天天刪來刪去,不僅僅我們小組的,別的小組也這樣,於是,我就想能不能部署個傻瓜應用,每天你們上傳上去,我去下載,然后我在上傳上去。呵呵,這個想法比較幼稚啊。。。。。。。。。。對於開發Web的各位來說。
我Web的功力實在太差,於是正好前一陣子寫了一個web小程序,一直處於開發進展中,我就加個小功能吧,先把原型搭出來。上網查了一下: 如果想要上傳的話必須把form的enctype的格式設置成“multipart/form-data”,注意默認是什么URLEncoded的,這個咱也就不關心了。設置完之后,還必須是Post請求,必須的嗎,上傳文件還能Get?另外Servlet和JSP規范中並沒有對這種格式進行解析,因此還得額外自己寫代碼。我shit,你下載文件只要設置一下響應頭,你規范就不能為上傳文件多做點東西么,還要我自己額外解析,雖說我對Web了解不多,可Apache我知道,common-fileupload這個組件我可知道,一查API,果真可以處理,等等,既然做了,就先自己造個輪子,畢竟是自己學習又不是做項目;再在網上搜,不錯,找到oreilly上的一個開源Servlet工具庫,看起來評價不錯,我先收藏,慢慢再看。
對付這種web流,我就兩種處理方式:
1、用HTTPWatch或者類似軟件截獲HTTP流,分析客戶端提交數據
2、調試階段在Web應用服務器上打出響應流信息,先別考慮神馬性能問題。。。那是后話。
1、2弄完了,好了發現格式了如下,最好對着RFC看,但是真心看RFC太爛費時間了,不過對於真正想要做好這種類型的事情不仔細研讀RFC以及各種瀏覽器的行為,總是會出現這樣或者那樣的問題。
Content-Disposition: form-data; name= " upfile "; filename= " C:\Documents and Settings\Administrator\桌面\小舅好啊.doc "
Content-Type: application/msword
通過分析流,發現其實郵件傳輸和這個大同小異,除了郵件使用了Base64編碼吧?印象中好象是。
在文件流結尾會有這個:
-------------------------------7dc1e411c05fe
因此,通過分析boundary確定邊界、通過分析Content-Disposition獲取文件元信息,當然了,也可以不傳文件,這樣僅僅什么也不做而已。
分析完了,奉上我第一版本的粗糙代碼,說實話,我這個就是為了研究原理,里面代碼存在很大問題,先說出來,別讓大家噴我:
1、編碼格式,編碼格式很重要,文件名可能是中文,因此需要設置UTF-8或者GBK,但是在解析流的時候,Encoding只能在解析元數據信息的時候的使用,其余時候
各位還是安心使用ASCII就是字節流吧,這樣原來文件是什么,存儲的就是什么,肯定不會出現問題,之前我用GBK了,就帶來Excel上傳每次都有格式問題。
2、流處理問題,緩沖流的操作,並且讀取流需要分階段來處理,先處理元數據信息,獲取完后在處理文件真實信息,避免過多用戶上傳導致程序繁忙。
JSP如下,一個簡單的JSP
pageEncoding="gb2312"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>SOC FileUpload</title>
</head>
<body>
<!-- <%=application.getServerInfo()%><br>-->
<form method="post" action="/soc/UploadFile"
enctype="multipart/form-data">
請選擇要上傳的文件<input type="file" name="upfile" size="40"><input
type="submit" value="提交">
</form>
</body>
</html>
Servlet:
private static final long serialVersionUID = 1L;
// 10M
// 這個版本的代碼性能問題,讀取大數據需要
// 逐步讀取
public static final long MAX_SIZE = 1024 * 1024 * 10;
static final String FILE_NAME = "filename=\"";
static final String BOUNDRAY = "boundary=";
/**
* @see HttpServlet#HttpServlet()
*/
public UploadFile() {
super();
LogUtil.debug("UploadFile load.");
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// request set encoding
LogUtil.debug("UploadFile called.");
LogUtil.debug(request.getServletPath());
LogUtil.debug(getServletContext().getRealPath(request.getServerName()));
String base = (getServletContext().getRealPath(request.getServerName()));
// platform
base = base.substring(0, base.lastIndexOf(File.separatorChar) + 1);
String rootPath = base + "upload";
String contentType = request.getContentType();
LogUtil.debug(contentType);
LogUtil.debug("donwload store: " + rootPath);
if (contentType.indexOf("multipart/form-data") >= 0) {
int length = request.getContentLength();
if (length > MAX_SIZE) {
response.getWriter().println(
"<P> File can't exceed " + MAX_SIZE + "<P><br>");
return;
}
byte[] total = new byte[length];
int totalRead = 0;
int byteRead = 0;
// not very good
while (totalRead < length) {
byteRead = request.getInputStream().read(total, totalRead,
length - totalRead);
totalRead += byteRead;
}
String raw = new String(total);
// LogUtil.debug("Raw: " + raw);
LogUtil.debug("total: " + length + " read: " + totalRead);
String saveFile = raw.substring(raw.indexOf(FILE_NAME)
+ FILE_NAME.length());
saveFile = saveFile.substring(0, saveFile.indexOf("\n"));
saveFile = saveFile.substring(0, saveFile.lastIndexOf("\""));
// 判斷瀏覽器
saveFile = saveFile.substring(saveFile.lastIndexOf("\\") + 1);
LogUtil.debug("File: " + saveFile);
String boudary = contentType
.substring(contentType.lastIndexOf("=") + 1);
LogUtil.debug("Bound: " + boudary);
String destFileName = rootPath + File.separatorChar + saveFile;
LogUtil.debug(destFileName);
int pos = raw.indexOf(FILE_NAME);
pos = raw.indexOf("\n", pos) + 1;
pos = raw.indexOf("\n", pos) + 1;
pos = raw.indexOf("\n", pos) + 1;
int startPosition = raw.substring(0, pos).getBytes().length;
// -4
LogUtil.debug("Start: " + raw.substring(0, pos));
raw = null;
raw = new String(total, "ascii");
int boundaryPosition = raw.indexOf(boudary, pos) - 4;
// LogUtil.debug("Bound: " + raw.substring(0, boundaryPosition));
int endPosition = raw.substring(0, boundaryPosition).getBytes(
"ascii").length;
// -----------------------------7dc251175d904aa
// -----------------------------7dc251175d904aa--
// ---------------------------7dc251175d904aa
File file = new File(destFileName);
if (!file.exists()) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
file.createNewFile();
}
FileOutputStream fout = new FileOutputStream(file);
int destNumber = endPosition - startPosition;
LogUtil.debug("Start: " + startPosition + " Boundary: "
+ boundaryPosition + " End:" + endPosition + " Number: "
+ destNumber);
// LogUtil.debug("DestLog: "
// + new String(total, startPosition, destNumber));
// 避免寫大文件
ByteArrayInputStream bin = new ByteArrayInputStream(total,
startPosition, destNumber);
byte[] cache = new byte[8 * 1024];
int totalWrite = 0;
while (totalWrite < destNumber) {
int cnt = bin.read(cache);
fout.write(cache, 0, cnt);
fout.flush();
totalWrite += cnt;
}
System.out.println("Write Total: " + totalWrite);
fout.close();
response.getWriter().println("<P>Upload File Successful<P><br>");
response.getWriter().flush();
}
}
}
弄完后,我就知道,我這個處理不了多文件,其實也能處理,但是各位看官也看見了,代碼寫的實在難看,我連重構的欲望都沒有,看來寫程序必須先思考一下啊。。。奉上一個Oreilly的例子,看看這個多簡潔:
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public UploadFileEx() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=692
int maxPostSize = 5 * 1024 * 1024;
MultipartRequest multi = new MultipartRequest(request, "d:\\",
maxPostSize, "GB2312");
Enumeration<?> filenames = multi.getFileNames();
while (filenames.hasMoreElements()) {
String filename = (String) filenames.nextElement();
System.out.println("Upload -> " + filename);
}
}
}
短短幾行,功能完成,當然Jar包里面也很多類處理了。核心是封裝一個MultipartRequest ,這個類里面調用MultipartParser生成不同的Part,不同的Part有不同的
處理方法。
附帶兩個URL鏈接:
http://commons.apache.org/fileupload/
希望對大家有所幫助。