首先感謝一下幾篇文章,讀了這幾篇文章,纔對如何構造http協議包傳輸混合數據有了個大體的瞭解。
使用Javascript XMLHttpRequest模擬表單(Form)提交上傳文件
上傳文件multipart form-data boundary 說明
如何使用multipart/form-data格式上傳文件php
原本幾乎沒有和協議大過什麼交道,習慣了用庫了。此次項目中須要上傳文件以及一些文本信息,使用 java.net.HttpURLConnection,原本可使用 org.apache.http.client.methods.HttpPost,很是簡單,網上也有不少示例代碼,但由於以前的代碼一直用的前者這個 API,沒有直接的上傳文件的方法,爲了改動較少,因此打算本身構造上傳文件的表單。java
在網上看了幾篇文章,找了幾段示例代碼,但都沒能成功實現功能,後來經過查協議,抓包的方式,終於搞通了。其實緣由也很簡單,就是本身構造的http數據的格式不對,協議對格式的要求很是嚴格,不少地方的換行都是很必要的,否則就會產生錯誤。node
上代碼,已運行測試。服務器端用php接收,正常。apache
public static final String HTTP_METHOD_GET = "GET"; public static final String HTTP_METHOD_POST = "POST"; public static final String BOUNDARYSTR = "aifudao7816510d1hq"; public static final String BOUNDARY = "--" + BOUNDARYSTR + "\r\n"; HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String cookie = bp_rfn_bp_session_id + "=" + Aifudao.globalBpSid; Log.d("cookie:" + cookie); conn.addRequestProperty("cookie", cookie); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod(mMethod); if (mMethod.equals(HTTP_METHOD_POST)) { conn.setDoOutput(true); conn.setUseCaches(false); if (mFiles.size() > 0) { conn.setRequestProperty("Content-type", "multipart/form-data;boundary=" + BOUNDARYSTR); } conn.connect(); BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream()); StringBuilder sb = new StringBuilder(); if (mFiles.size() > 0) { Iterator<String> it = mPostMap.keySet().iterator(); while (it.hasNext()) { String str = it.next(); sb.append(BOUNDARY); sb.append("Content-Disposition:form-data;name=\""); sb.append(str); sb.append("\"\r\n\r\n"); sb.append(mPostMap.get(str)); sb.append("\r\n"); } } else { Iterator<String> it = mPostMap.keySet().iterator(); while (it.hasNext()) { String str = it.next(); sb.append(";"); sb.append(str); sb.append("="); sb.append(mPostMap.get(str)); } } // post the string data. out.write(sb.toString().getBytes()); // post file data if (mFiles != null && mFiles.size() > 0) { for (int i = 0; i < mFiles.size(); i++) { File file = mFiles.get(i); if (!file.exists()) { Log.e("cant find post file."); continue; } Log.d("start upload file."); out.write(BOUNDARY.getBytes()); StringBuilder filenamesb = new StringBuilder(); filenamesb .append("Content-Disposition:form-data;Content-Type:application/octet-stream;name=\"uploadfile"); filenamesb.append(i); filenamesb.append("\";filename=\""); filenamesb.append(file.getName() + "\"\r\n\r\n"); out.write(filenamesb.toString().getBytes()); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[8192]; // 8k int count = 0; // 讀取文件 while ((count = fis.read(buffer)) != -1) { out.write(buffer, 0, count); } out.write("\r\n\r\n".getBytes()); fis.close(); } } out.write(("--" + BOUNDARYSTR + "--\r\n").getBytes()); out.flush(); out.close(); } 而後後面的接收相應的代碼就和普通的get方法沒有什麼區別了。 注意的幾個重要並且容易出錯的地方: 1.設置頭信息 conn.setRequestProperty(「Content-type」, 「multipart/form-data;boundary=」 + BOUNDARYSTR); 這裏須要在頭信息裏把類型設置爲multipart/form-data,boundary設爲一個通常不會和數據衝突的隨機字符串,這是用來區分你的多種數據的。 2.構造POST數組 在服務器端通常都有一個post數組來接收post數據,這段代碼就是用來構造post數據的。 while (it.hasNext()) { String str = it.next(); sb.append(BOUNDARY); sb.append("Content-Disposition:form-data;name=\""); sb.append(str); sb.append("\"\r\n\r\n"); sb.append(mPostMap.get(str)); sb.append("\r\n"); }
注意其中的那幾個換行,貌似都是頗有必要的。以前換行格式沒有正確,就總是拿不到post數據。數組
3.在合適的地方插入分界字符串和換行服務器
out.write(BOUNDARY.getBytes());cookie
協議默認使用 –BOUNDARYSTR 的格式來做爲分界字符串,而在結尾的時候使用 –BOUNDARYSTR– 的格式做爲結束標誌session
4.在某些數據後面加入必要換行app
- StringBuilder filenamesb = new StringBuilder();
- filenamesb
- .append("Content-Disposition:form-data;Content-Type:application/octet-stream;name=\"uploadfile");
- filenamesb.append(i);
- filenamesb.append("\";filename=\"");
- filenamesb.append(file.getName() + "\"\r\n\r\n");
- out.write(filenamesb.toString().getBytes());
- FileInputStream fis = new FileInputStream(file);
- byte[] buffer = new byte[8192]; // 8k
- int count = 0;
- // 讀取文件
- while ((count = fis.read(buffer)) != -1) {
- out.write(buffer, 0, count);
- }
- out.write("\r\n\r\n".getBytes());
fis.close();post
這裏注意的是,在文件名後的兩個換行很必要,不然可能出現文件名包含一部分文件數據的狀況。文件二進制數據寫入完成後,也有兩個換行(這個地方我記不清是不是必須的了,但這段代碼是能正常運行的)。
若是還不行,那確定是構造格式的問題,最好本身把本身發送的數據打印出來仔細對比和抓到的http包數據之間的區別,注意,不少換行和數據分界字符串都很嚴格。