j2ee开发也好几年,文件上传功能基本都是用的第三方的组件,虽然知道其原理,但一直不知道具体是如何实现的,最近有时间,正好同事开发遇到这方面的问题,查了点资料,基本明白了具体实现,为了备忘,就写下这篇随笔。
首先说说同事遇到的问题,最近的项目是使用webwork开发的,同事需要实现多文件上传的功能,但是
上传解析的实现简单说一下:
通过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类写了一个上传组件(本来不想自己写的,想改造改造就完事的,可惜反编译出来的代码比较难读),代码如下:
组件简单说明:
上传路径在servlet初始参数中设定。
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
>
测试运行基本正常,总算解决了心中的一个很长时间的困惑。
注:以上的程序只是本人学习的代码,有很多欠缺的地方,欢迎大家指正,不甚感激。
上面代码只供参考,嘿嘿出了问题我就不负责了。
最近没啥事,又出差在外,唉日子过得好慢!!!!!
首先说说同事遇到的问题,最近的项目是使用webwork开发的,同事需要实现多文件上传的功能,但是
webwork原则上支持三种上传解析 pell,cos,jakarta,三种都有自身的优势和不足,使用发现:
在webwork中只有pell支持中文文件路径,但是使用该方式只能处理一个上传文件,
而其他两种虽然支持多文件上传,但中文支持不好。
上述的见解都是本人的个人认识,可能支持只是我不知道,如果有谁知道不妨指导一下,不胜感激。
上传解析的实现简单说一下:
通过ServletRequest类的getInputStream()方法获得一个客户端向服务器发出的数据流、分析上传的文件格式,根据分析结果将多个文件依次输出服务器端的目标文件中。
格式类似下面:
每个表单提交的元素都有分隔符将其分隔,其提交的表单元素的名称和对应的输入值之间也有特殊的字符将其分隔开。
都知道格式了,呵呵就尝试了一下,参照了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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
组件简单说明:
上传路径在servlet初始参数中设定。
上传的表单元素、文件数据分别封装在Hashtable中。
做了测试,测试环境说明:
AppServer: WeblogicSP4
OS: WindowXP/ Soloaris 9.0
测试程序:
index.jsp(文件上传页面):
result.jsp(查看提交表单数据)
测试时对应的web应用已经指定相关字符集,weblogic.xml内容如下:
测试运行基本正常,总算解决了心中的一个很长时间的困惑。
注:以上的程序只是本人学习的代码,有很多欠缺的地方,欢迎大家指正,不甚感激。
上面代码只供参考,嘿嘿出了问题我就不负责了。
最近没啥事,又出差在外,唉日子过得好慢!!!!!