一般狀況下,咱們想在網頁上上傳一個文件的時候,會採用<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