|
Posted on 2005-10-13 21:41 柳随风 阅读(7115) 评论(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 */ 8package study.http.upload; 9 10import java.io.BufferedInputStream; 11import java.io.File; 12import java.io.FileNotFoundException; 13import java.io.FileOutputStream; 14import java.io.IOException; 15import java.io.InputStream; 16import java.io.UnsupportedEncodingException; 17import java.util.ArrayList; 18import java.util.Hashtable; 19import java.util.Iterator; 20import java.util.List; 21import java.util.Map; 22import java.util.Set; 23 24import javax.servlet.ServletException; 25import javax.servlet.ServletInputStream; 26import javax.servlet.http.HttpServlet; 27import javax.servlet.http.HttpServletRequest; 28import 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 */ 36public 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
|