HTTP上傳文件探究

    一般狀況下,咱們想在網頁上上傳一個文件的時候,會採用<input type="file">標籤,可是你有沒有想過,爲何經過這樣一個標籤,服務器端就能獲取到文件數據呢?咱們先來寫這樣一個頁面:
html

 

 1 <html>
 2 <head>
 3 </head>
 4 <body>
 5    <form action="http://127.0.0.1:8899/upload.do" method="post" enctype="multipart/form-data" >
 6      <input type="file" name="myfile" />
 7           <input name="otherparm" value="aaaaa" />
 8      <input type="submit" value="aa" />
 9    </form>
10 </body>
11 </html>

 

    能夠看到這只是一個普通的form表單,裏邊有兩個字段,一個是file類型,另外一個是普通的text類型。同時咱們還要注意的是,在form的屬性裏咱們設置了提交方式爲post,且 enctype=multipart/form-data。那麼咱們提交後瀏覽器發送的請求格式是什麼樣的呢?下邊是chrome的開發者工具貼過來的數據:java

Request URL:http://127.0.0.1:8899/upload.do
Request Headers CAUTION: Provisional headers are shown.
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Content-Type:multipart/form-data; boundary=----WebKitFormBoundarykyKevNik4tKTFv0c
Origin:null
Referer:
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36
Request Payload
------WebKitFormBoundarykyKevNik4tKTFv0c(\n)
Content-Disposition: form-data; name="myfile"; filename="logo.png"(\n)
Content-Type: image/png(\n)
(\n)
(此處爲file數據,chrome下顯示爲空白)(\n\r)
------WebKitFormBoundarykyKevNik4tKTFv0c(\n)
Content-Disposition: form-data; name="otherparm"(\n)
(\n)
aaaaa(\n\r)
------WebKitFormBoundarykyKevNik4tKTFv0c--


     從中咱們能夠看出一些不一樣,一個就是在Content-Type的值爲
multipart/form-data,而通常狀況下咱們常常看到的爲text/html之類的(對照表), 此外就是表單的content內容比較怪異,仔細看下發現content被 ------WebKitFormBoundarykyKevNik4tKTFv0c劃分爲兩部分,而第一部分經過 Content-Disposition能夠看出這是一個文件,並且 Content-Type: image/png指定了文件類型,下邊跟着文件的數據;而第二部分則對應咱們在 表單中提交的otherparm參數。
    這樣一分析,input:file上傳文件的方式就很明白了,咱們後臺只要按照此格式對數據進行解析便可。其實這樣的格式是遵循 Request For Comments( RFC )1867對文件上傳的規定。
    那麼咱們後臺具體要怎麼處理呢?對於java web程序而言,咱們一般採用COS、apache common-upload組件等。那麼若是咱們想本身解析呢,下邊是一段copy來的代碼,僅供參考:git

 

  1 private byte[] parse(HttpServletRequest request) throws IOException {
  2 
  3         final int NONE = 0;
  4         final int DATAHEADER = 1;
  5         final int FILEDATA = 2;
  6         final int FIELDDATA = 3;
  7 
  8         final int MXA_SEGSIZE = 1000 * 1024 * 10;//每批最大的數據量 10M
  9 
 10         String contentType = request.getContentType();// 請求消息類型
 11         String fieldname = ""; // 表單域的名稱
 12         String fieldvalue = ""; // 表單域的值
 13         String filename = ""; // 文件名
 14         String boundary = ""; // 分界符
 15         String lastboundary = ""; // 結束符
 16         String filePath = "";
 17         Hashtable<String, String> formfields = new Hashtable<String, String>();
 18         int filesize = 0; // 文件長度
 19 
 20         int pos = contentType.indexOf("boundary=");
 21 
 22         if (pos != -1) { // 取得分界符和結束符
 23             pos += "boundary=".length();
 24             boundary = "--" + contentType.substring(pos);
 25             lastboundary = boundary + "--";
 26         }
 27         int state = NONE;
 28         // 獲得數據輸入流reqbuf
 29         DataInputStream in = new DataInputStream(request.getInputStream());
 30         // 將請求消息的實體送到b變量中
 31         int totalBytes = request.getContentLength();
 32         String message = "";
 33         if (totalBytes > MXA_SEGSIZE) {//每批大於10m時
 34             message = "Each batch of data can not be larger than " + MXA_SEGSIZE / (1000 * 1024)
 35                     + "M";
 36             return null;
 37         }
 38         byte[] b = new byte[totalBytes];
 39         in.readFully(b);
 40         in.close();
 41         String reqContent = new String(b, "UTF-8");//
 42         BufferedReader reqbuf = new BufferedReader(new StringReader(reqContent));
 43 
 44         boolean flag = true;
 45         int i = 0;
 46         while (flag == true) {
 47             String s = reqbuf.readLine();
 48             if ((s == null) || (s.equals(lastboundary)))
 49                 break;
 50 
 51             switch (state) {
 52                 case NONE:
 53                     if (s.startsWith(boundary)) {
 54                         state = DATAHEADER;
 55                         i += 1;
 56                     }
 57                     break;
 58                 case DATAHEADER:
 59                     pos = s.indexOf("filename=");
 60                     if (pos == -1) { // 將表單域的名字解析出來
 61                         pos = s.indexOf("name=");
 62                         pos += "name=".length() + 1;
 63                         s = s.substring(pos);
 64                         int l = s.length();
 65                         s = s.substring(0, l - 1);
 66                         fieldname = s;
 67                         state = FIELDDATA;
 68                     } else { // 將文件名解析出來
 69                         String temp = s;
 70                         pos = s.indexOf("filename=");
 71                         pos += "filename=".length() + 1;
 72                         s = s.substring(pos);
 73                         int l = s.length();
 74                         s = s.substring(0, l - 1);// 去掉最後那個引號」
 75                         filePath = s;
 76                         pos = s.lastIndexOf("\\");
 77                         s = s.substring(pos + 1);
 78                         filename = s;
 79                         // 從字節數組中取出文件數組
 80                         pos = byteIndexOf(b, temp, 0);
 81                         b = subBytes(b, pos + temp.getBytes().length + 2, b.length);// 去掉前面的部分
 82                         int n = 0;
 83                         /**
 84                          * 過濾boundary下形如 Content-Disposition: form-data; name="bin";
 85                          * filename="12.pdf" Content-Type: application/octet-stream
 86                          * Content-Transfer-Encoding: binary 的字符串
 87                          */
 88                         while ((s = reqbuf.readLine()) != null) {
 89                             if (n == 1)
 90                                 break;
 91                             if (s.equals(""))
 92                                 n++;
 93 
 94                             b = subBytes(b, s.getBytes().length + 2, b.length);
 95                         }
 96                         pos = byteIndexOf(b, boundary, 0);
 97                         if (pos != -1)
 98                             b = subBytes(b, 0, pos - 1);
 99 
100                         filesize = b.length - 1;
101                         formfields.put("filesize", String.valueOf(filesize));
102                         state = FILEDATA;
103                     }
104                     break;
105                 case FIELDDATA:
106                     s = reqbuf.readLine();
107                     fieldvalue = s;
108                     formfields.put(fieldname, fieldvalue);
109                     state = NONE;
110                     break;
111                 case FILEDATA:
112                     while ((!s.startsWith(boundary)) && (!s.startsWith(lastboundary))) {
113                         s = reqbuf.readLine();
114                         if (s.startsWith(boundary)) {
115                             state = DATAHEADER;
116                             break;
117                         }
118                     }
119                     break;
120             }
121         }
122         return b;
123 
124     }
125 
126     // 字節數組中的INDEXOF函數,與STRING類中的INDEXOF相似
127     public static int byteIndexOf(byte[] b, String s, int start) {
128         return byteIndexOf(b, s.getBytes(), start);
129     }
130 
131     // 字節數組中的INDEXOF函數,與STRING類中的INDEXOF相似
132     public static int byteIndexOf(byte[] b, byte[] s, int start) {
133         int i;
134         if (s.length == 0) {
135             return 0;
136         }
137         int max = b.length - s.length;
138         if (max < 0)
139             return -1;
140         if (start > max)
141             return -1;
142         if (start < 0)
143             start = 0;
144         search: for (i = start; i <= max; i++) {
145             if (b[i] == s[0]) {
146                 int k = 1;
147                 while (k < s.length) {
148                     if (b[k + i] != s[k]) {
149                         continue search;
150                     }
151                     k++;
152                 }
153                 return i;
154             }
155         }
156         return -1;
157     }
158 
159     // 用於從一個字節數組中提取一個字節數組
160     public static byte[] subBytes(byte[] b, int from, int end) {
161         byte[] result = new byte[end - from];
162         System.arraycopy(b, from, result, 0, end - from);
163         return result;
164     }
165 
166     // 用於從一個字節數組中提取一個字符串
167     public static String subBytesString(byte[] b, int from, int end) {
168         return new String(subBytes(b, from, end));
169     }
參考代碼

        最後附上本身寫的一個測試例子: DEMO github

相關文章
相關標籤/搜索