之前寫過一個jsp的檔案上傳,把檔案上傳的動作製成一個class。而現在要提供的這個範例就比較亂一點了,重點會在增加監控的ProgressListener及Ajax取得上傳進度之資料。
程式需要一個bean放入session裡,專門來存放目前上傳的bytes數及其它資訊,選擇好檔案按下上傳時,會開始上傳動作(使用iframe),並啟動Ajax與Server要求目前上傳進度狀況,把資訊顯示在browser上。
這個專案需要使用二個額外的jar framework,分別為Apache Filupload及Apache common io,而Fileupload需要1.2以上版本才支援ProgressListener。
存入上傳資訊的bean
package com.yslifes.file.upload;
public class UploadStatus {
private long bytesRead;// 目前上傳byte數
private long totBytes;// 總共的byte數
private int item;// 目前上傳的item
private long startTime;// 啟始時間
public UploadStatus() {
startTime = System.currentTimeMillis();
}
public long getBytesRead() {
return bytesRead;
}
public void setBytesRead(long bytesRead) {
this.bytesRead = bytesRead;
}
public long getTotBytes() {
return totBytes;
}
public void setTotBytes(long totBytes) {
this.totBytes = totBytes;
}
public int getItem() {
return item;
}
public void setItem(int item) {
this.item = item;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
}
監控的ProgressListener
在上傳時,會一直呼叫ProgressListener的update函數。
package com.yslifes.file.upload;
import org.apache.commons.fileupload.ProgressListener;
public class UploadListener implements ProgressListener {
// 存放upload狀態的bean
private UploadStatus status;
public UploadListener(UploadStatus status) {
this.status = status;
}
// override,上傳時會被呼叫
@Override
public void update(long arg0, long arg1, int arg2) {
status.setBytesRead(arg0);// 目前已上傳的byte數
status.setTotBytes(arg1);// 總byte數
status.setItem(arg2);// 正在上傳的item編號
}
}
上傳畫面html
onsubmit時順便啟動Ajax動作來監控上傳進度,而ProgressBar則是由取得的資料利用css的width來設定目前上傳量。
<body>
<form action="UploadAction.jsp" method="post"
enctype="multipart/form-data" target="hiddenFrame"
onsubmit="showStatus()">
<div align="center">
<table>
<tr>
<td>上傳檔案1</td>
<td><input type="file" name="file1" /></td>
<td>描述1</td>
<td><input type="text" name="desc1" /></td>
</tr>
<tr>
<td>上傳檔案2</td>
<td><input type="file" name="file2" /></td>
<td>描述2</td>
<td><input type="text" name="desc2" /></td>
</tr>
<tr>
<td colspan="4" align="left"><input id="button" type="submit"
name="submit" value="確認" /></td>
</tr>
</table>
<div id="status" style="display: none" align="left">
<div id="progressBar"
style="width: 400px; height: 12px; background: #fff; border: 1px solid #00ff00; padding: 3px;">
<div id="barItem" style="width: 30%; height: 100%; background: #ff0000"></div>
</div>
<div id="msg"></div>
</div>
</div>
</form>
<iframe name="hiddenFrame" width="0" height="0" frameborder="0"></iframe>
</body>
Ajax的javascript內容
reuqest取得資料後,如果還沒上傳完成,則再利用setTimeout,一秒後重新再要求資料。
var success = true;//完成上傳與否
//取得上傳資訊
function showStatus() {
success = false;
//顯示ProgressBar
document.getElementById("status").style.display = "block";
//初始1%
document.getElementById("barItem").style.width = '1%';
//上傳按鈕使不能使用
document.getElementById("button").disabled = true;
//一秒檢查一次上傳狀態
setTimeout("requestStatus()", 1000);
}
function requestStatus() {
if (!success) {
var ajax;
if (window.XMLHttpRequest)//ajax request Object,非ie
ajax = new XMLHttpRequest();
else//ie
{
try {
ajax = new ActiveXObject("Mxsml2.XMLHTTP");
} catch (e) {
ajax = new ActiveXObject("Microsoft.XMLHTTP");
}
}
ajax.open("GET", "UploadStatus.jsp");
ajax.onreadystatechange = function() {
callback(ajax);
};
ajax.send(null);
}
}
function callback(ajax) {
if (ajax.readyState == 4) {
if (ajax.status != 200)//有錯誤產生
{
document.getElementById("msg").innerHTML = "回傳錯誤!";
} else {
var arr = ajax.responseText.split("/");
if (arr.length < 2) {
document.getElementById("msg").innerHTML = ajax.responseText;
} else {
//進度
document.getElementById("barItem").style.width = ""
+ arr[0] + "%";
document.getElementById("msg").innerHTML = '完成百分比(%):'
+ arr[0] + '</br>'+
'已完成數(M):' + arr[1] + '</br>'+
'檔案總長度(M):' + arr[2] + '</br>'+
'傳輸速率(K):' + arr[3] + '</br>'+
'已使用時間(s):' + arr[4] + '</br>'+
'估計總時間(s):' + arr[5] + '</br>'+
'估計剩餘時間(s):' + arr[6] + '</br>'+
'正在上傳文件:' + arr[7];
if (arr[1] == arr[2]) {
success = true;
document.getElementById("msg").innerHTML = "完成上傳!";
//把上傳按鈕設可使用
document.getElementById("button").disabled = false;
}else
setTimeout("requestStatus()", 1000);
}
}
}
}
上傳動作
因為在form表單時已經設定target為iframe,所以並不會把目前頁面帶離,只會在iframe裡進行上傳動作,而iframe本身設定為看不到。
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%><%
com.yslifes.file.upload.UploadStatus status = new com.yslifes.file.upload.UploadStatus();
com.yslifes.file.upload.UploadListener listener = new com.yslifes.file.upload.UploadListener(status);
//放到session裡
//再來listener就會依上傳狀況去更動status了
session.setAttribute("status",status);
org.apache.commons.fileupload.servlet.ServletFileUpload upload = new org.apache.commons.fileupload.servlet.ServletFileUpload(new org.apache.commons.fileupload.disk.DiskFileItemFactory());
upload.setProgressListener(listener);//設定監控
try
{
java.util.List<org.apache.commons.fileupload.FileItem> items = upload.parseRequest(request);//把request parse成FileItem List
java.util.Iterator<org.apache.commons.fileupload.FileItem> it = items.iterator();
while(it.hasNext())
{
org.apache.commons.fileupload.FileItem item = it.next();
if(item.isFormField())//一般的表單資料
{
System.out.println("表單資料名稱:"+item.getFieldName()+",表單資料內容:"+item.getString());
}else//檔案上傳
{
System.out.println("檔案名稱:"+item.getName());
String fileName = item.getName();
//要存放檔案的路徑及目錄
java.io.File saveFile = new java.io.File("./upload",fileName);
//如果目錄不存在.則會自動建立
saveFile.getParentFile().mkdirs();
//讀出輸入串流
java.io.InputStream in = item.getInputStream();
//寫入輸出串流
java.io.OutputStream writer = new java.io.FileOutputStream(saveFile);
//寫入檔案
byte[] tmp = new byte[1024];
int len ;
while((len=in.read(tmp))!=-1)
{
writer.write(tmp,0,len);
}
writer.close();
in.close();
out.write("上傳成功!");
}
}
}catch(org.apache.commons.fileupload.FileUploadException e)
{
e.printStackTrace();
out.println("上傳時發生錯誤:"+e);
}
%>
取得監控的上傳資料
利用ajax要求這個程式來取得目前上傳的進度
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="BIG5"%>
<%
//不讓browser使用cache資料
response.setHeader("Cache-Control", "on-store");
response.setHeader("Pagrma", "no-cache");
response.setDateHeader("Expires", 0);
//從session裡把資料取出來
com.yslifes.file.upload.UploadStatus status = (com.yslifes.file.upload.UploadStatus) session
.getAttribute("status");
if (status == null) {
out.println("未有上傳資料");
} else {
long startTime = status.getStartTime();
long nowTime = System.currentTimeMillis();
long time = (nowTime - startTime) / 1000 + 1;//上傳時間秒
//總共的上傳資料bytes
double totalLength = status.getTotBytes();
//上傳送率 byte/s
double velocity = status.getBytesRead() / (double) time;
//估計總時間秒
double totalTime = totalLength / velocity;
//估計剩飲時間秒
double guessTime = totalTime - time;
//完成百分比
int percent = (int) Math.round((100 * (double) status
.getBytesRead()) / totalLength);
//已讀取Mb數
double M_Readlength = (double) status.getBytesRead() / 1024 / 1024;
//總Mb數
double m_TotalLength = (double) totalLength / 1024 / 1024;
out.print(percent + "/");
out.print(M_Readlength + "/");
out.print(m_TotalLength + "/");
out.print(velocity + "/");
out.print(time + "/");
out.print(totalTime + "/");
out.print(guessTime + "/");
out.print(status.getItem());
}
%>
執行畫面,一開始狀況及按下確認後畫面
原始碼下載
写的真好
最近有个项目正好能用上了
但是,有个问题啊
session.setAttribute(“status”,status);
这里,resin报错了,说status 一定要序列化哦。
可能的话,加我q聊聊吧。。。二九601899
不好意思…不使用QQ
你可以把status implements java.io.Serializable
應該就可以
public class UploadStatus implements Serializable
已经可以了 ^_^
THX
public class UploadStatus implements Serializable
請問這行要在哪邊改呢?
@sam, 要改什麼?
另外 我執行時候有發生一個問題
第一次上傳 progress bar 不會動 但檔案會上傳
refresh一次後 就都沒有問題
請問這個要怎麼解決呢?
@Sam, 每次都這樣子嘛?
可能是javascript有error
@yku, 這個問題我也有看到, 好像傳40MB以上的檔案時, Ajax那邊的UploadStatus.jsp會變成另一個session, 以致於getAttribute回傳都為null, 所以status bean就沒有資料, progressbar就沒有資料可以進一步處理, 但檔案還是可上傳成功,
如果是較小Size的檔案, 就不會出現這個問題, session也會跟做上傳動作的jsp是同一個session.
還不知道為什麼@@….
@Pati, 抱歉現在才回你
以你的描述其實看不出來是什麼問題
基本上session的控制在web container上,不太可能發生這種狀況
是否您的browser有裝自動清除cookie或記憶體功能呢?
再check您的程式看看,是否有動作reset session to null
還是您可以提供您的程式碼呢?
@yku, 感謝回覆, 你用大檔案可以複製我看到的現象嗎?下面是我的囉哩囉嗦, 呵~
這個狀況,我用你的範例跟自己改的例子(不透過jsp調用自訂的Servlet Class, 直接利用HttpServlet的doGet/dePost),都會出現, 以下是用你提供的範例做的觀察,
環境: Windows7 + Apache Tomcat/7.0.25
Browser: IE 9/Chrome 22/Firefox 15
1. 在第一次使用傳檔時, 選檔案大一點,40MB以上, 現象就會出現,
在你的例子就會跑到out.println(“未有上傳資料”);
傳檔的部份是沒問題的,還是可以成功跑完, 但就Progress狀態有問題,是null,
這時到Tomcat的App Manager看, Application的session會出現兩個, 兩個創建時間幾乎同時,
但其中一個的被使用時間(Used Time)為0, 也就是未被使用,
這時再去使用傳檔, 一切看起來就都會正常, Progress狀態也有出現, 而且Progress狀態應該是用到剛剛那個沒有被用到的session, 因為Used Time有值了, 大概就是傳檔花費的時間, 而另一個第一次傳檔用到的session, 這次及之後也就沒有更新。
2. 而當上傳檔案大小40MB以下時, 不會有這個問題,
這時到Tomcat的App Manaer看, Application的session只有一個, 一切看來正常。
@yku, 感謝回覆, 你用大檔案可以複製我看到的現象嗎?下面是我的囉哩囉嗦, 呵~
這個狀況,我用你的範例跟自己改的例子(不透過jsp調用自訂的Servlet Class, 直接利用HttpServlet的doGet/dePost),都會出現,以下是用你提供的範例做的觀察,
環境: Windows7 + Apache Tomcat/7.0.25
Browser: IE 9/Chrome 22/Firefox 15
1. 在第一次使用傳檔時, 選檔案大一點,40MB以上, 現象就會出現,
在你的例子就會跑到out.println(“未有上傳資料”);
傳檔的部份是沒問題的,還是可以成功跑完, 但就Progress狀態有問題,是null,
這時到Tomcat的App Manager看, Application的session會出現兩個, 兩個創建時間幾乎同時,
但其中一個的被使用時間(Used Time)為0, 也就是未被使用,
這時再去使用傳檔, 一切看起來就都會正常, Progress狀態也有出現, 而且Progress狀態應該是用到剛剛那個沒有被用到的session, 因為Used Time有值了, 大概就是傳檔花費的時間, 而另一個第一次傳檔用到的session, 這次及之後也就沒有更新。
2. 而當上傳檔案大小40MB以下時, 不會有這個問題,
這時到Tomcat的App Manaer看, Application的session只有一個, 一切看來正常。
@Pati,
http://www.websina.com/bugzero/faq/exception-tomcat-connector.html
看看這篇能不能幫你解決