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内容如下:








本文转载自:http://www.360doc.com/content/05/1014/08/73_19269.shtml