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








測試運行基本正常,總算解決了心中的一個很長時間的困惑。
注:以上的程序只是本人學習的代碼,有很多欠缺的地方,歡迎大家指正,不甚感激。
上面代碼只供參考,嘿嘿出了問題我就不負責了。
最近沒啥事,又出差在外,唉日子過得好慢!!!!!