在開發中,我們使用的比較多的HTTP請求方式基本上就是GET、POST。其中GET用於從服務器獲取數據,POST主要用於向服務器提交一些表單數據,例如文件上傳等。而我們在使用HTTP請求時中遇到的比較麻煩的事情就是構造文件上傳的HTTP報文格式,這個格式雖說也比較簡單,但也比較容易出錯。今天我們就一起來學習HTTP POST的報文格式以及通過Java來模擬文件上傳的請求。
首先我們來看一個POST的報文請求,然后我們再來詳細的分析它。
POST報文格式
- POST /api/feed/ HTTP/1.1
- Accept-Encoding: gzip
- Content-Length: 225873
- Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Host: www.myhost.com
- Connection: Keep-Alive
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Content-Disposition: form-data; name="lng"
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- 116.361545
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Content-Disposition: form-data; name="lat"
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- 39.979006
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"
- Content-Type: application/octet-stream
- Content-Transfer-Encoding: binary
- 這里是圖片的二進制數據
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--
這里我們提交的是經度、緯度和一張圖片(圖片數據比較長,而且比較雜亂,這里省略掉了)。
格式分析
請求頭分析
- POST /api/feed/ HTTP/1.1
- Accept-Encoding: gzip
- Content-Length: 225873
- Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Host: www.myhost.com
- Connection: Keep-Alive
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
我們知道HTTP協議采用“請求-應答”模式,當使用普通模式,即非KeepAlive模式時,每個請求/應答客戶和服務器都要新建一個連接,完成之后立即斷開連接(HTTP協議為無連接的協議);當使用Keep-Alive模式(又稱持久連接、連接重用)時,Keep-Alive功能使客戶端到服務器端的連接持續有效,當出現對服務器的后續請求時,Keep-Alive功能避免了建立或者重新建立連接。
如上圖中,左邊的是關閉Keep-Alive的情況,每次請求都需要建立連接,然后關閉連接;右邊的則是Keep-Alive,在第一次建立請求之后保持連接,然后后續的就不需要每次都建立、關閉連接了,啟用Keep-Alive模式肯定更高效,性能更高,因為避免了建立/釋放連接的開銷。
http 1.0中默認是關閉的,需要在http頭加入"Connection: Keep-Alive",才能啟用Keep-Alive;http 1.1中默認啟用Keep-Alive,如果加入"Connection: close ",才關閉。目前大部分瀏覽器都是用http1.1協議,也就是說默認都會發起Keep-Alive的連接請求了,所以是否能完成一個完整的Keep- Alive連接就看服務器設置情況。
請求實體分析
- --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
- Content-Disposition: form-data; name="lng"
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- 116.361545
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- Content-Type: application/octet-stream
- Content-Transfer-Encoding: binary
模擬文件上傳請求
- public static void uploadFile(String fileName) {
- try {
- // 換行符
- final String newLine = "\r\n";
- final String boundaryPrefix = "--";
- // 定義數據分隔線
- String BOUNDARY = "========7d4a6d158c9";
- // 服務器的域名
- URL url = new URL("www.myhost.com");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- // 設置為POST情
- conn.setRequestMethod("POST");
- // 發送POST請求必須設置如下兩行
- conn.setDoOutput(true);
- conn.setDoInput(true);
- conn.setUseCaches(false);
- // 設置請求頭參數
- conn.setRequestProperty("connection", "Keep-Alive");
- conn.setRequestProperty("Charsert", "UTF-8");
- conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
- OutputStream out = new DataOutputStream(conn.getOutputStream());
- // 上傳文件
- File file = new File(fileName);
- StringBuilder sb = new StringBuilder();
- sb.append(boundaryPrefix);
- sb.append(BOUNDARY);
- sb.append(newLine);
- // 文件參數,photo參數名可以隨意修改
- sb.append("Content-Disposition: form-data;name=\"photo\";filename=\"" + fileName
- + "\"" + newLine);
- sb.append("Content-Type:application/octet-stream");
- // 參數頭設置完以后需要兩個換行,然后才是參數內容
- sb.append(newLine);
- sb.append(newLine);
- // 將參數頭的數據寫入到輸出流中
- out.write(sb.toString().getBytes());
- // 數據輸入流,用於讀取文件數據
- DataInputStream in = new DataInputStream(new FileInputStream(
- file));
- byte[] bufferOut = new byte[1024];
- int bytes = 0;
- // 每次讀1KB數據,並且將文件數據寫入到輸出流中
- while ((bytes = in.read(bufferOut)) != -1) {
- out.write(bufferOut, 0, bytes);
- }
- // 最后添加換行
- out.write(newLine.getBytes());
- in.close();
- // 定義最后數據分隔線,即--加上BOUNDARY再加上--。
- byte[] end_data = (newLine + boundaryPrefix + BOUNDARY + boundaryPrefix + newLine)
- .getBytes();
- // 寫上結尾標識
- out.write(end_data);
- out.flush();
- out.close();
- // 定義BufferedReader輸入流來讀取URL的響應
- // BufferedReader reader = new BufferedReader(new InputStreamReader(
- // conn.getInputStream()));
- // String line = null;
- // while ((line = reader.readLine()) != null) {
- // System.out.println(line);
- // }
- } catch (Exception e) {
- System.out.println("發送POST請求出現異常!" + e);
- e.printStackTrace();
- }
- }