|
Posted on 2005-10-13 21:41 柳隨風 閱讀(7119) 評論(1) 編輯 收藏
j2ee開發也好幾年,文件上傳功能基本都是用的第三方的組件,雖然知道其原理,但一直不知道具體是如何實現的,最近有時間,正好同事開發遇到這方面的問題,查了點資料,基本明白了具體實現,為了備忘,就寫下這篇隨筆。 首先說說同事遇到的問題,最近的項目是使用webwork開發的,同事需要實現多文件上傳的功能,但是
webwork原則上支持三種上傳解析 pell,cos,jakarta,三種都有自身的優勢和不足,使用發現:
在webwork中只有pell支持中文文件路徑,但是使用該方式只能處理一個上傳文件,
而其他兩種雖然支持多文件上傳,但中文支持不好。
上述的見解都是本人的個人認識,可能支持只是我不知道,如果有誰知道不妨指導一下,不勝感激。 上傳解析的實現簡單說一下: 通過ServletRequest類的getInputStream()方法獲得一個客戶端向服務器發出的數據流、分析上傳的文件格式,根據分析結果將多個文件依次輸出服務器端的目標文件中。 格式類似下面:
//文件分隔符
-----------------------------7d226137250336
//文件信息頭
Content-Disposition: form-data; name="FILE1"; filename="C:\Documents and Settings\Administrator.TIMBER-4O6B0ZZ0\My Documents\tt.sql"
Content-Type: text/plain
//源文件內容
create table info(
content image null);
//下一個文件的分隔符
-----------------------------7d226137250336
Content-Disposition: form-data; name="FILE2"; filename=""
Content-Type: application/octet-stream
-----------------------------7d226137250336 每個表單提交的元素都有分隔符將其分隔,其提交的表單元素的名稱和對應的輸入值之間也有特殊的字符將其分隔開。
都知道格式了,呵呵就嘗試了一下,參照了pell中的MultipartRequest類寫了一個上傳組件(本來不想自己寫的,想改造改造就完事的,可惜反編譯出來的代碼比較難讀),代碼如下:
1 /**//* 2 * 只支持在windows下上傳文件 3 * Created on 2005-10-10 4 * 5 * TODO To change the template for this generated file go to 6 * Window - Preferences - Java - Code Style - Code Templates 7 */ 8 package study.http.upload; 9 10 import java.io.BufferedInputStream; 11 import java.io.File; 12 import java.io.FileNotFoundException; 13 import java.io.FileOutputStream; 14 import java.io.IOException; 15 import java.io.InputStream; 16 import java.io.UnsupportedEncodingException; 17 import java.util.ArrayList; 18 import java.util.Hashtable; 19 import java.util.Iterator; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Set; 23 24 import javax.servlet.ServletException; 25 import javax.servlet.ServletInputStream; 26 import javax.servlet.http.HttpServlet; 27 import javax.servlet.http.HttpServletRequest; 28 import javax.servlet.http.HttpServletResponse; 29 30 /** *//** 31 * @author liusuifeng 32 * 33 * TODO To change the template for this generated type comment go to Window - 34 * Preferences - Java - Code Style - Code Templates 35 */ 36 public class TestServlet extends HttpServlet { 37 38 public final static String DEFAULT_ENCODING = "ISO8859_1"; 39 40 public final static String CHINESE_ENCODING = "GBK"; 41 42 public final static String SIGN_BOUNDARY = "boundary="; 43 44 public final static String SIGN_FORMELEMENT = "name="; 45 46 public final static String SIGN_FORMFILE = "filename="; 47 48 public final static String SIGN_NOTFILE = "application/octet-stream"; 49 50 public final static String SIGN_MULTIDATA = "multipart/form-data"; 51 52 public final static String CHINESE_CONTENTTYPE = "text/html; charset=GBK"; 53 54 private Hashtable paratable = new Hashtable(); 55 56 private Hashtable filetable = new Hashtable(); 57 58 private String strBoundary = ""; 59 60 private String strSavePath=""; 61 62 63 private static void println(String s) { 64 System.out.println(s); 65 } 66 67 68 69 70 /** *//** 71 * 增加數據到對應的Hashtable中 72 * 說明:如果Hashtable中已存在該鍵值,則將新增加的和原來的都封裝到列表中。 73 * @param table 74 * @param paraName 75 * @param paraValue 76 */ 77 private static void addElement(Hashtable table, String paraName, 78 Object paraValue) { 79 ArrayList list = new ArrayList(); 80 if (table.containsKey(paraName)) { 81 Object o = table.get(paraName); 82 if (o instanceof List) { 83 ((List) o).add(paraValue); 84 } else { 85 list.add(o); 86 list.add(paraValue); 87 o = list; 88 } 89 table.put(paraName, o); 90 } else { 91 table.put(paraName, paraValue); 92 } 93 } 94 95 public static String getHashInfo(Hashtable paratable) { 96 StringBuffer sb=new StringBuffer(); 97 Set keySet=paratable.keySet(); 98 Iterator it=keySet.iterator(); 99 while(it.hasNext()) { 100 101 Object keyobj=it.next(); 102 Object valueobj=paratable.get(keyobj); 103 104 sb.append("<tr>"); 105 sb.append("<td>"+keyobj.toString()+"</td>"); 106 if(valueobj instanceof List) { 107 sb.append("<td>"); 108 int isize=((List)valueobj).size(); 109 for(int i=0;i<isize;i++) { 110 Object tempobj=((List)valueobj).get(i); 111 if(i<isize-1) { 112 sb.append(tempobj.toString()+","); 113 } 114 else { 115 sb.append(tempobj.toString()); 116 } 117 } 118 119 sb.append("</td>"); 120 } 121 else { 122 sb.append("<td>"+valueobj.toString()+"</td>"); 123 } 124 sb.append("</tr>"); 125 } 126 return sb.toString(); 127 } 128 129 130 private static byte[] getfileBytes(InputStream is) { 131 List byteList = new ArrayList(); 132 byte[] filebyte = null; 133 int readbyte = 0; 134 try { 135 while ((readbyte = is.read()) != -1) { 136 byteList.add(new Byte((byte) readbyte)); 137 } 138 } catch (FileNotFoundException e) { 139 e.printStackTrace(); 140 } catch (IOException e) { 141 e.printStackTrace(); 142 } 143 filebyte = new byte[byteList.size()]; 144 for (int i = 0; i < byteList.size(); i++) { 145 filebyte[i] = ((Byte) byteList.get(i)).byteValue(); 146 } 147 return filebyte; 148 149 } 150 151 152 153 154 protected void doGet(HttpServletRequest request, 155 HttpServletResponse response) throws ServletException, IOException { 156 doPost(request, response); 157 } 158 159 protected void doPost(HttpServletRequest request, 160 HttpServletResponse response) throws ServletException, IOException { 161 paratable = new Hashtable(); 162 filetable = new Hashtable(); 163 strSavePath=this.getInitParameter("savepath"); 164 File file=new File(strSavePath); 165 if(!file.exists()) { 166 file.mkdirs(); 167 } 168 String contentType = request.getContentType(); 169 strBoundary = getBoundary(contentType); 170 ServletInputStream sis = request.getInputStream(); 171 BufferedInputStream bis = new BufferedInputStream(sis); 172 parseInputStream(bis); 173 appendPara(request.getParameterMap()); /**//*追加url對應傳遞的參數*/ 174 response.setContentType(CHINESE_CONTENTTYPE); 175 176 // response.getWriter().write(getOutPutInfo()); 177 // response.getWriter().write(new String(getfileBytes(sis),"GBK")); 178 bis.close(); 179 sis.close(); 180 request.setAttribute("para",paratable); 181 request.setAttribute("file",filetable); 182 183 this.getServletContext().getRequestDispatcher("/result.jsp"). 184 forward(request,response); 185 186 } 187 188 189 /** *//** 190 * 不用Hashtable對應的put方法,目的避免覆蓋重復的鍵值 191 * @return 192 */ 193 private void appendPara(Map map) { 194 195 if(map!=null) { 196 Set keySet=map.keySet(); 197 Iterator it=keySet.iterator(); 198 while(it.hasNext()) { 199 Object keyobj=it.next(); 200 String[] valueobj=(String[])map.get(keyobj); 201 println("keyobj===="+keyobj); 202 println("valueobj===="+valueobj); 203 for(int i=0;i<valueobj.length;i++) { 204 addElement(paratable,(String)keyobj,valueobj[i]); 205 } 206 } 207 } 208 } 209 210 211 212 /** *//** 213 * 輸出上傳表單信息 214 * 215 * @param pw 216 */ 217 protected String getOutPutInfo() { 218 StringBuffer sb = new StringBuffer(); 219 sb.append("<table width=100% border=1>"); 220 sb.append("<tr><td>參數名</td><td>參數值</td></tr>"); 221 sb.append(getHashInfo(paratable)); 222 sb.append(getHashInfo(filetable)); 223 sb.append("</table>"); 224 return sb.toString(); 225 } 226 227 /** *//** 228 * 解析字節流 229 * @param is 230 */ 231 private void parseInputStream(InputStream is) { 232 byte[] sizes = getfileBytes(is); 233 int icount = 0; 234 String s = ""; 235 int readbyte = 0; 236 String reals; 237 try { 238 reals = new String(sizes, DEFAULT_ENCODING); 239 String realsvalue = new String(sizes, CHINESE_ENCODING); 240 String[] arrs = reals.split(strBoundary); 241 String[] arrsvalue = realsvalue.split(strBoundary); 242 for (int i = 0; i < arrs.length; i++) { 243 String tempStr = arrs[i]; 244 String tempStr2 = arrsvalue[i]; 245 if (tempStr.indexOf(SIGN_FORMFILE) >= 0) { 246 readFile(tempStr, tempStr2); 247 } else { 248 readParameter(tempStr2); 249 } 250 } 251 } catch (UnsupportedEncodingException e) { 252 e.printStackTrace(); 253 } 254 255 } 256 257 /** *//** 258 * 獲取本次上傳對應的表單元素間的分隔符,注意該分隔符是隨機生成的 259 * @param contentType 260 * @return 261 */ 262 private String getBoundary(String contentType) { 263 String tempStr = ""; 264 if (contentType != null && contentType.startsWith(SIGN_MULTIDATA) 265 && contentType.indexOf(SIGN_BOUNDARY) != -1) { 266 //獲取表單每個元素的分隔符 267 tempStr = contentType 268 .substring( 269 contentType.indexOf(SIGN_BOUNDARY) 270 + SIGN_BOUNDARY.length()).trim(); 271 } 272 return tempStr; 273 } 274 275 /** *//** 276 * 解析文件上傳對應的字節流。實現算法<br> 277 * 通過解析ISO8859_1編碼方式的字符串后轉換成對應上傳文件的字節。 278 * 通過解析GBK編碼方式的字符串后轉換成對應上傳文件的文件名。 279 * 說明:因不清楚字節在不同編碼方式下的關系,只好使用兩個字符串(比較影響性能,以后優化) 280 * @param s 以ISO8859_1編碼方式組成的字符串 281 * @param s2 以GBK編碼方式組成的字符串 282 */ 283 private void readFile(String s, String s2) { 284 int filepos = -1; 285 if ((filepos = s.indexOf(SIGN_FORMFILE)) >= 0) { 286 String realName = readFileName(s2); 287 //部分確定上傳的是文件而不是任意輸入的字符串 288 if(!realName.equals("")&& realName.length()>0 && (realName.indexOf(".")>=0)) { 289 String filepath = readWriteFile(s, realName); 290 addElement(filetable, realName, filepath); 291 } 292 } 293 else { 294 /**//*上傳的不是文件*/ 295 if (s.indexOf(SIGN_NOTFILE) >= 0) { 296 return; 297 } 298 } 299 300 } 301 302 /** *//** 303 * 解析文件上傳對應的名稱 304 * 實現說明:如果上傳的是文件對應格式為:<br>filename="文件名"</br> 格式 305 * 通過處理可以拆分出對應的文件名 306 * @param s 以GBK編碼方式組成的包含文件名的字符串 307 * @return 對應上傳文件的文件名(不包括文件路徑) 308 */ 309 private String readFileName(String s) { 310 int filepos = s.indexOf(SIGN_FORMFILE); 311 String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1); 312 int iendpos = tempstr.indexOf("\""); 313 String fileName = tempstr.substring(0, iendpos); 314 int ifilenamepos = fileName.lastIndexOf("\\"); 315 String realName = fileName.substring(ifilenamepos + 1); 316 return realName; 317 318 } 319 320 /** *//** 321 * 通過解析ISO8859_1編碼方式的字符串后轉換成對應上傳文件的字節。 322 * 實現算法說明:文件名轉化后的字節和具體的文件字節中間是以兩個重復的兩個字符隔開, 323 * 對應char值為13,10,轉換后的字符對應的最后四個字符也是格式字符,獲取對應中間的字節即為 324 * 上傳文件的真正的字節數 325 * @param s 以ISO8859_1編碼方式組成的包含文件名和具體文件字節的字符串 326 * @param realName 對應的文件名 327 * @return 對應生成的文件名包括全路徑 328 */ 329 private String readWriteFile(String s, String realName) { 330 int filepos = s.indexOf(SIGN_FORMFILE); 331 String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1); 332 int icount = 0; 333 while (true) { 334 int charnum = tempstr.charAt(icount); 335 int charnum2 = tempstr.charAt(icount + 1); 336 int charnum3 = tempstr.charAt(icount + 2); 337 int charnum4 = tempstr.charAt(icount + 3); 338 if (charnum == 13 && charnum2 == 10 && charnum3 == 13 339 && charnum4 == 10) { 340 break; 341 } 342 icount++; 343 } 344 String filevalue = tempstr.substring(icount + 4, tempstr.length() - 4); 345 FileOutputStream fos = null; 346 String createName=strSavePath + realName; 347 File uploadfile = new File(createName); 348 String shortname=realName.substring(0,realName.lastIndexOf(".")); 349 String filetype=realName.substring(realName.lastIndexOf(".")+1); 350 int namecount=1; 351 while(uploadfile.exists()) { 352 createName=strSavePath+shortname+"["+namecount+"]"+"."+filetype; 353 uploadfile=new File(createName); 354 namecount++; 355 356 } 357 try { 358 byte[] filebytes = filevalue.getBytes(DEFAULT_ENCODING); 359 fos = new FileOutputStream(uploadfile); 360 fos.write(filebytes); 361 } catch (FileNotFoundException e) { 362 e.printStackTrace(); 363 } catch (IOException e1) { 364 365 e1.printStackTrace(); 366 } finally { 367 try { 368 fos.close(); 369 } catch (IOException e2) { 370 371 e2.printStackTrace(); 372 } 373 } 374 375 return createName; 376 } 377 378 379 /** *//** 380 * 解析提交過來的表單元素對應的名稱以及值<br> 381 * 實現說明:如果表單元素的是對應格式為:<br>name="表單元素名"</br> 格式 382 * 表單元素名和具體的輸入值中間是以兩個重復的兩個字符隔開, 383 * 對應char值為13,10,轉換后的字符對應的最后四個字符也是格式字符,獲取對應中間的字符即為 384 * 表單元素的輸入值 385 * 通過處理可以拆分出對應的表單元素名以及輸入值 386 * @param s 以GBK編碼方式組成的包含表單元素名和值的字符串 387 */ 388 private void readParameter(String s) { 389 String paraName = ""; 390 String paraValue = ""; 391 int istartlen = -1; 392 int iendlen = -1; 393 394 if ((istartlen = s.indexOf(SIGN_FORMELEMENT)) >= 0) { 395 String tempstr = s.substring(istartlen + SIGN_FORMELEMENT.length() 396 + 1); 397 int nameindex = tempstr.indexOf("\""); 398 paraName = tempstr.substring(0, nameindex); 399 paraValue = tempstr.substring(nameindex + 5, tempstr.length() - 4); 400 addElement(paratable, paraName, paraValue); 401 } 402 } 403 404 } 組件簡單說明: 上傳路徑在servlet初始參數中設定。
上傳的表單元素、文件數據分別封裝在Hashtable中。
做了測試,測試環境說明: AppServer: WeblogicSP4 OS: WindowXP/ Soloaris 9.0 測試程序: index.jsp(文件上傳頁面):
<html>
<head><title>File Upload</title></head>
<body>

<form name="kkkkkk" action="test.upload?ssss=bbbbbbbbb&ccccc=eeeeeeee" enctype="multipart/form-data" method="post" >
<input type=text name="ssss" ><br>
<input type=text name="ssss" ><br>
<input type=text name="ssss3" ><br>
<textarea name="araea"></textarea><br>
<input type=file name="cccc" ><br>
<input type=file name="ddddd" ><br>
<input type=submit value="submit" name="bbbbbbbbb">
</form>

</body>
</html> result.jsp(查看提交表單數據)
 <% @ page contentType="text/html;charset=GBK"%>
 <% @ page import="java.util.*"%>
 <% @ page import="study.http.upload.*"%>



 <%
Hashtable paratable=(Hashtable)request.getAttribute("para");
Hashtable filetable=(Hashtable)request.getAttribute("file");
String parastr=TestServlet.getHashInfo(paratable);
out.println("<table width=100% border=1>");
out.println(parastr);
out.println(TestServlet.getHashInfo(filetable));
out.println("</table>");
%>
<html>
<head><title>File Upload</title></head>
<body>


</body>
</html>
測試時對應的web應用已經指定相關字符集,weblogic.xml內容如下:
<weblogic-web-app>
<charset-params>
<input-charset>
<resource-path>/*</resource-path>
<java-charset-name>GBK</java-charset-name>
</input-charset>
</charset-params>
</weblogic-web-app>
測試運行基本正常,總算解決了心中的一個很長時間的困惑。
注:以上的程序只是本人學習的代碼,有很多欠缺的地方,歡迎大家指正,不甚感激。 上面代碼只供參考,嘿嘿出了問題我就不負責了。
最近沒啥事,又出差在外,唉日子過得好慢!!!!!
Feedback
# re: 采用HTTP協議上傳文件實現(java) 回復 更多評論
2006-09-27 14:28 by
good
|