一、 引言
基於瀏覽器的檔案上傳,特別是對於通過<input type="file">標籤包含到Web頁面來實現上傳的情況,還存在較嚴重的效能問題。我們知道,超過10MB的上傳檔案經常導致一種非常痛苦的使用者體驗。一旦使用者提交了檔案,在瀏覽器把檔案上傳到伺服器的過程中,介面看上去似乎處於靜止狀態。由於這一切發生在後台,所以許多沒有耐心的使用者開始認為伺服器"掛"了,因而再次提交檔案,這當然使得情況變得更糟糕。
為了儘可能使得檔案上感測覺更友好些,一旦使用者提交檔案,許多網站將顯示一個中間過程動畫(例如一旋轉表徵圖)。儘管這一技術在上傳提交到伺服器時起一些作用,但它還是提供了太少的有關檔案上傳狀態的資訊。解決這個問題的另外一種嘗試是實現一個applet——它通過FTP把檔案上傳到伺服器。這一方案的缺點是:限制了你的使用者,必須要有一個支援Java的瀏覽器。
在本文中,我們將實現一個具有AJAX能力的組件——它不僅實現把檔案上傳到伺服器,而且"即時地"監視檔案上傳的實際過程。這個組件工作的四個階段顯示於下面的圖1,2,3和4中:
圖1.階段1:選擇檔案上傳
圖2.階段2:上傳該檔案到伺服器
圖3.階段3:上傳完成
圖4.階段4:檔案上傳摘要 |
二、 實現該組件
首先,我們分析建立多部分過濾的過程,它將允許我們處理並且監視檔案上傳。然後,我們將繼續實現JavaServer Faces(JSF)組件-它將提供給使用者連續的回饋,以支援AJAX的進度條方式。
(一) 多部分過濾:UploadMultipartFilter
多部分過濾的任務是攔截到來的檔案上傳並且把該檔案寫到一個伺服器上的臨時目錄中。同時,它還將監視接收的位元組數並且確定已經上傳該檔案的程度。幸運的是,現在有一個優秀的Jakarta-Commons開源庫可以利用(FileUpload),可以由它來負責分析一個HTTP多部分請求並且把檔案上傳到伺服器。我們要做的是擴充該庫並且加入我們需要的"鉤子"來監視已經處理了多少位元組。
public class UploadMultipartFilter implements Filter{ public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException, ServletException { HttpServletRequest hRequest = (HttpServletRequest)request; //檢查是否我們在處理一個多部分請求 String contentHeader = hRequest.getHeader("content-type"); boolean isMultipart = ( contentHeader != null && contentHeader.indexOf("multipart/form-data") != -1); if(isMultipart == false){ chain.doFilter(request,response); }else{ UploadMultipartRequestWrapper wrapper = new UploadMultipartRequestWrapper(hRequest); chain.doFilter(wrapper,response); } ... } |
正如你所見,UploadMultipartFilter類簡單地檢查了當前的請求是否是一個多部分請求。如果該請求不包含檔案上傳,該請求將被傳遞到請求鏈中的下一個過濾,而不進行任何另外的處理。否則,該請求將被封裝在一個UploadMultipartRequestWrapper中。
(二) UploadMultipartRequestWrapper類
public class UploadMultipartRequestWrapper extends HttpServletRequestWrapper{ private Map<String,String> formParameters; private Map<String,FileItem> fileParameters; public UploadMultipartRequestWrapper(HttpServletRequest request) { super(request); try{ ServletFileUpload upload = new ServletFileUpload(); upload.setFileItemFactory(new ProgressMonitorFileItemFactory(request)); List fileItems = upload.parseRequest(request); formParameters = new HashMap<String,String>(); fileParameters = new HashMap<String,FileItem>(); for(int i=0;i<fileItems.size();i++){ FileItem item = (FileItem)fileItems.get(i); if(item.isFormField() == true){ formParameters.put(item.getFieldName(),item.getString()); }else{ fileParameters.put(item.getFieldName(),item); request.setAttribute(item.getFieldName(),item); } } }catch(FileUploadException fe){ //請求時間超過-使用者可能已經轉到另一個頁面。 //作一些記錄 //... } ... |
在UploadMultipartRequestWrapper類中,我們將初始化ServletFileUpload類,它負責分析我們的請求並且把檔案寫到伺服器上的預設臨時目錄。ServletFileUpload執行個體針對在該請求中遇到的每一個欄位建立一個FileItem執行個體(它們包含檔案上傳和正常的表單元素)。之後,一個FileItem執行個體用於檢索一個提交欄位的屬性,或者,在檔案上傳的情況下,檢索一個到底層的臨時檔案的InputStream。總之,UploadMultipartRequestWrapper負責分析該檔案並且設定任何FileItem-它在該請求中把檔案上傳描述為屬性。然後,這些屬性由JSF組件所進一步收集,而正常表單欄位的行為保持不變。
預設情況下,通用FileUpload庫將使用DiskFileItems類的執行個體來處理檔案上傳。儘管DiskFileItem在處理整個臨時檔案業務時是很有用的,但在準確監視該檔案已經處理程度方面存在很少支援。自版本1.1以來,通用FileUpload庫能夠使開發人員指定用於建立FileItem的工廠。我們將使用ProgressMonitorFileItemFactory和ProgressMonitorFileItem類來重載預設行為並監視檔案上傳過程。
(三) ProgressMonitorFileItemFactory類
public class ProgressMonitorFileItemFactory extends DiskFileItemFactory { private File temporaryDirectory; private HttpServletRequest requestRef; private long requestLength; public ProgressMonitorFileItemFactory(HttpServletRequest request) { super(); temporaryDirectory = (File)request.getSession().getServletContext().getAttribute("javax.servlet.context.tempdir"); requestRef = request; String contentLength = request.getHeader("content-length"); if(contentLength != null){requestLength = Long.parseLong(contentLength.trim());} } public FileItem createItem(String fieldName, String contentType,boolean isFormField, String fileName) { SessionUpdatingProgressObserver observer = null; if(isFormField == false) //這必須是一檔案上傳. observer = new SessionUpdatingProgressObserver(fieldName,fileName); ProgressMonitorFileItem item = new ProgressMonitorFileItem( fieldName,contentType,isFormField, fileName,2048,temporaryDirectory, observer,requestLength); return item; } ... public class SessionUpdatingProgressObserver implements ProgressObserver { private String fieldName; private String fileName; ... public void setProgress(double progress) { if(request != null){ request.getSession().setAttribute("FileUpload.Progress."+fieldName,progress); request.getSession().setAttribute("FileUpload.FileName."+fieldName,fileName); } } } } |
ProgressMonitorFileItemFactory Content-Length頭由瀏覽器設定並且假定它是被設定的上傳檔案的精確長度。這種確定檔案長度的方法確實限制了你在每次請求中上傳的檔案-如果有多個檔案在該請求中被編碼的話,不過這個值是不精確的。這是由於,瀏覽器僅僅發送一個Content-Length頭,而不考慮上傳的檔案數目。
除了建立ProgressMonitorFileItem執行個體之外,ProgressMonitorFileItemFactory還註冊了一個ProgressObserver執行個體,它將由ProgressMonitorFileItem來傳送檔案上傳過程中的更新。我們所使用的ProgressObserver的實現(SessionUpdatingProgressObserver)針對被提交欄位的id把進度百分數設定到使用者的會話中。然後,這個值可以由JSF組件存取以便把更新發送給使用者。
本文轉自http://90000cn.cn/Html/chengxusheji/Java/0233875407925.html