問題描述
最近遇到需要用到上傳圖片到服務器上,學習了一下原生servlet中的form上傳圖片保存到指定目錄的情況
思路:前端提交–servlet獲取inputstream–輸出到本地
獲取輸入流后輸出到本地一直打不開提示損壞/0kb.從網上看到有說需要apache的兩個包io和fileupload包.我想的是不借助第三方工具包處理(tomcat也是第三方呵呵,純的應該是利用socket吧)
項目結構
如圖所示:并未使用其余組件,創建了一個動態java項目即可
問題原因
網上查到一片文章,大概意思是,上傳文件不是單純的文件流,其與本地io不同其中多了些東西.按照本地上傳下載的方式無法解析出來.包括些分隔符\和表單的一些信息,需要重新處理
解決方法
手動解析出圖片的流,并把其中的多余東西去掉,然后將得到的純文件流輸出到指定位置
總結回顧
該問題居然查不到當前時間的帖子,一般都是2-3年前的讓我有點意外.知其然而不知其所以然早晚被人家掣肘.在框架琳瑯滿目的當下抄抄寫寫確實能解決問題而且真的是事半功倍.
另寫代碼要多查多看api文檔
多動手敲代碼,不然不知道所以然,這么點破問題弄了個周末
1
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
|
package server; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings ( "serial" ) @WebServlet (name = "streams" ,urlPatterns = "/UploadServlet.do" ) public class CsvTest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType( "text/html;charset=UTF-8" ); PrintWriter out = response.getWriter(); final int NONE = 0 ; // 狀態碼,表示沒有特殊操作 final int DATAHEADER = 1 ; // 表示下一行要讀到報頭信息 final int FILEDATA = 2 ; // 表示下面要讀的是上傳文件和二進制數據 final int FIELDDATA = 3 ; // 表示下面要讀到表單域的文本值 // 請求消息實體的總長度(請求消息中除消息頭之外的數據長度) int totalbytes = request.getContentLength(); File f; // 上傳文件儲存在服務器上 // 容納請求消息實體的字節數組 byte [] dataOrigin = new byte [totalbytes]; // 對于post多個文件的表單,b作為原始數據的副本提供提取文件數據的操作 byte [] b = new byte [totalbytes]; // 請求消息類型 String contentType = request.getContentType(); String fieldname = "" ; // 表單域的名稱 String fieldvalue = "" ; // 表單域的值 String fileFormName = "" ; // 上傳的文件再表單中的名稱 String fileRealName = "" ; // 上傳文件的真實名字 String boundary = "" ; // 分界符字符串 String lastboundary = "" ; // 結束分界符字符串 int fileSize = 0 ; // 文件長度 // 容納表單域的名稱/值的哈希表 Map<String, String> formfieldsTable = new HashMap<String, String>(); // 容納文件域的名稱/文件名的哈希表 Map<String, String> filenameTable = new HashMap<String, String>(); // 在消息頭類型中找到分界符的定義 int pos = contentType.indexOf( "boundary=" ); int pos2; // position2 if (pos != - 1 ) { pos += "boundary=" .length(); boundary = "--" + contentType.substring(pos); // 解析出分界符 lastboundary = boundary + "--" ; // 得到結束分界符 } int state = NONE; // 起始狀態為NONE // 得到請求消息的數據輸入流 DataInputStream in = new DataInputStream(request.getInputStream()); in.readFully(dataOrigin); // 根據長度,將消息實體的內容讀入字節數組dataOrigin中 in.close(); // 關閉數據流 String reqcontent = new String(dataOrigin); // 從字節數組中得到表示實體的字符串 // 從字符串中得到輸出緩沖流 BufferedReader reqbuf = new BufferedReader( new StringReader(reqcontent)); // 設置循環標志 boolean flag = true ; // int i = 0; while (flag == true ) { String s = reqbuf.readLine(); if (s == lastboundary || s == null ) break ; switch (state) { case NONE: if (s.startsWith(boundary)) { // 如果讀到分界符,則表示下一行一個頭信息 state = DATAHEADER; // i += 1; } break ; case DATAHEADER: pos = s.indexOf( "filename=" ); // 先判斷出這是一個文本表單域的頭信息,還是一個上傳文件的頭信息 if (pos == - 1 ) { // 如果是文本表單域的頭信息,解析出表單域的名稱 pos = s.indexOf( "name=" ); pos += "name=" .length() + 1 ; // 1表示后面的"的占位 s = s.substring(pos); int l = s.length(); s = s.substring( 0 , l - 1 ); // 應該是" fieldname = s; // 表單域的名稱放入fieldname out.print( "fieldname=" + fieldname); state = FIELDDATA; // 設置狀態碼,準備讀取表單域的值 } else { // 如果是文件數據的頭,先存儲這一行,用于在字節數組中定位 String temp = s; // 先解析出文件名 pos = s.indexOf( "name=" ); pos += "name=" .length() + 1 ; // 1表示后面的"的占位 pos2 = s.indexOf( "filename=" ); String s1 = s.substring(pos, pos2 - 3 ); // 3表示";加上一個空格 fileFormName = s1; pos2 += "filename=" .length() + 1 ; // 1表示后面的"的占位 s = s.substring(pos2); int l = s.length(); s = s.substring( 0 , l - 1 ); pos2 = s.lastIndexOf( "\\" ); // 對于IE瀏覽器的設置 s = s.substring(pos2 + 1 ); fileRealName = s; out.print( "fileRealName=" + fileRealName + "<br>" ); out.print( "fileRealName.length()=" + fileRealName.length() + "<br>" ); if (fileRealName.length() != 0 ) { // 確定有文件被上傳 // 下面這一部分從字節數組中取出文件的數據 b = dataOrigin; // 復制原始數據以便提取文件 pos = byteIndexOf(b, temp, 0 ); // 定位行 // 定位下一行,2 表示一個回車和一個換行占兩個字節 b = subBytes(b, pos + temp.getBytes().length + 2 , b.length); // 再讀一行信息,是這一部分數據的Content-type s = reqbuf.readLine(); // 設置文件輸入流,準備寫文件 f = new File( "C:" + File.separator + "Users" + File.separator + "Administrator" + File.separator + "Desktop" + File.separator +fileRealName); DataOutputStream fileout = new DataOutputStream( new FileOutputStream(f)); // 字節數組再往下一行,4表示兩回車換行占4個字節,本行的回車換行2個字節,Content-type的下 // 一行是回車換行表示的空行,占2個字節 // 得到文件數據的起始位置 b = subBytes(b, s.getBytes().length + 4 , b.length); pos = byteIndexOf(b, boundary, 0 ); // 定位文件數據的結尾 b = subBytes(b, 0 , pos - 1 ); // 取得文件數據 fileout.write(b, 0 , b.length - 1 ); // 將文件數據存盤 fileout.close(); fileSize = b.length - 1 ; // 文件長度存入fileSize out.print( "fileFormName=" + fileFormName + " filename=" + fileRealName + " fileSize=" + fileSize + "<br>" ); filenameTable.put(fileFormName, fileRealName); state = FILEDATA; } } break ; case FIELDDATA: // 讀取表單域的值 s = reqbuf.readLine(); fieldvalue = s; // 存入fieldvalue out.print( " fieldvalue=" + fieldvalue + "<br>" ); formfieldsTable.put(fieldname, fieldvalue); state = NONE; break ; case FILEDATA: // 如果是文件數據不進行分析,直接讀過去 while ((!s.startsWith(boundary)) && (!s.startsWith(lastboundary))) { s = reqbuf.readLine(); if (s.startsWith(boundary)) { state = DATAHEADER; } else { break ; } } break ; } } // 指定內容類型,并且可以顯示中文 out.println( "<HTML" ); out.println( "<HEAD><TITLE>文件上傳結果</TITLE></HEAD>" ); out.println( "<BODY>" ); out.println( "<H1>文件上傳結果</H1><hr>" ); out.println( "ID為" + formfieldsTable.get( "FileID1" ) + "的文件" + filenameTable.get( "FileData1" ) + "已經上傳!<br>" ); out.println( "ID為" + formfieldsTable.get( "FileID2" ) + "的文件" + filenameTable.get( "FileData2" ) + "已經上傳!<br>" ); // out.println("i = " + i + "<br>"); out.println( "</BODY>" ); out.println( "</HTML>" ); } private static int byteIndexOf( byte [] b, String s, int start) { return byteIndexOf(b, s.getBytes(), start); } private static int byteIndexOf( byte [] b, byte [] s, int start) { int i; if (s.length == 0 ) { return 0 ; } int max = b.length - s.length; if (max < 0 ) { return - 1 ; } if (start > max) { return - 1 ; } if (start < 0 ) { start = 0 ; } // 在b中找到s的第一個元素 search: for (i = start; i <= max; i++) { if (b[i] == s[ 0 ]) { // 找到了s中的第一個元素后,比較剩余的部分是否相等 int k = 1 ; while (k < s.length) { if (b[k + i] != s[k]) { continue search; } k++; } return i; } } return - 1 ; } private static byte [] subBytes( byte [] b, int from, int end) { byte [] result = new byte [end - from]; System.arraycopy(b, from, result, 0 , end - from); return result; } private static String subBytesString( byte [] b, int from, int end) { return new String(subBytes(b, from, end)); } } |
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/weixin_43671743/article/details/106616327