HTTP POST請求報文格式分析與Java實現文件上傳 HTTP POST請求報文格式分析與Java實現文件上傳

HTTP POST請求報文格式分析與Java實現文件上傳

轉載:https://www.cnblogs.com/stsinghua/p/6413087.htmlhtml

 

在開發中,咱們使用的比較多的HTTP請求方式基本上就是GET、POST。其中GET用於從服務器獲取數據,POST主要用於向服務器提交一些表單數據,例如文件上傳等。而咱們在使用HTTP請求時中遇到的比較麻煩的事情就是構造文件上傳的HTTP報文格式,這個格式雖然說也比較簡單,但也比較容易出錯。今天咱們就一塊兒來學習HTTP POST的報文格式以及經過Java來模擬文件上傳的請求。apache

首先咱們來看一個POST的報文請求,而後咱們再來詳細的分析它。json

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--

這裏咱們提交的是經度、緯度和一張圖片(圖片數據比較長,並且比較雜亂,這裏省略掉了)。api

格式分析

請求頭分析

咱們先看 報文格式中的第一行:瀏覽器

POST /api/feed/ HTTP/1.1
這一行就說明了這個請求的請求方式,即爲POST方式,要請求的子路徑爲/api/feed/,例如咱們的服務器地址爲www.myhost.com,而後咱們的這個請求的完整路徑就是 www.myhost.com/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
這幾個header的意思分別爲服務器返回的數據須要使用gzip壓縮、請求的內容長度爲22587三、內容的類型爲"multipart/form-data"、請求參數分隔符(boundary)爲OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp、請求的根域名爲www.myhost.com、HTTP鏈接方式爲持久鏈接( Keep-Alive)。

其中這裏須要注意的一點是分隔符,即boundary。 boundary用於做爲請求參數之間的界限標識,例如參數1和參數2之間須要有一個明確的界限,這樣服務器才能正確的解析到參數1和參數2。可是分隔符並不只僅是boundary,而是下面這樣的格式:-- + boundary。例如這裏的boundary爲 OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,那麼參數分隔符則爲:服務器

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
無論boundary自己有沒有這個"--",這個"--"都是不能省略的。

咱們知道HTTP協議採用「請求-應答」模式,當使用普通模式,即非KeepAlive模式時,每一個請求/應答客戶和服務器都要新建一個鏈接,完成以後當即斷開鏈接(HTTP協議爲無鏈接的協議);當使用Keep-Alive模式(又稱持久鏈接、鏈接重用)時,Keep-Alive功能使客戶端到服務器端的鏈接持續有效,當出現對服務器的後續請求時,Keep-Alive功能避免了創建或者從新創建鏈接。app

如上圖中,左邊的是關閉Keep-Alive的狀況,每次請求都須要創建鏈接,而後關閉鏈接;右邊的則是Keep-Alive,在第一次創建請求以後保持鏈接,而後後續的就不須要每次都創建、關閉鏈接了, 啓用Keep-Alive模式確定更高效,性能更高,由於避免了創建/釋放鏈接的開銷 。post

http 1.0中默認是關閉的,須要在http頭加入"Connection: Keep-Alive",才能啓用Keep-Alive;http 1.1中默認啓用Keep-Alive,若是加入"Connection: close ",才關閉。目前大部分瀏覽器都是用http1.1協議,也就是說默認都會發起Keep-Alive的鏈接請求了,因此是否能完成一個完整的Keep- Alive鏈接就看服務器設置狀況。性能

請求實體分析

請求實體其實就是HTTP POST請求的參數列表,每一個參數以請求分隔符開始,即-- + boundary。例以下面這個參數。學習

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="lng" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 116.361545
上面第一行爲--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,也就是--加上boundary內容, 最後加上一個換行 (這個換行不能省略),換行的字符串表示爲"\r\n" 。第二行爲Content-Disposition和參數名,這裏的參數名爲lng,即經度。 Content-Disposition就是當用戶想把請求所得的內容存爲一個文件的時候提供一個默認的文件名,這裏咱們不過多關注。第三行爲 Content-Type,即 WEB 服務器告訴瀏覽器本身響應的對象的類型 ,還有指定字符編碼爲UTF-8。 第四行是 描述的是消息請求(request)和響應(response)所附帶的實體對象(entity)的傳輸形式, 簡單文本數據咱們設置爲8bit,文件參數咱們設置爲binary就行 。而後添加兩個換行以後纔是參數的具體內容。例如這裏的參數內容爲116.361545。

注意這裏的每行之間都是使用「\r\n」來換行的,最後一行和參數內容之間是兩個換行。文件參數也是同樣的格式,只是文件參數的內容是字節流。

這裏要注意一下,普通文本參數和文件參數有以下兩個地方的不一樣,由於其內容自己的格式是不同的。

普通參數:

Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

文件參數:

Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

參數實體的最後一行是: --加上boundary加上--,最後換行,這裏的 格式即爲: --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--。

模擬文件上傳請求

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(); } } 

使用Apache Httpmime上傳文件

/**
     * @param fileName 圖片路徑
     */
    public static void uploadFileWithHttpMime(String fileName) { // 定義請求url String uri = "www.myhost.com"; // 實例化http客戶端 HttpClient httpClient = new DefaultHttpClient(); // 實例化post提交方式 HttpPost post = new HttpPost(uri); // 添加json參數 try { // 實例化參數對象 MultipartEntity params = new MultipartEntity(); // 圖片文本參數 params.addPart("textParams", new StringBody( "{'user_name':'個人用戶名','channel_name':'卻道明','channel_address':'(123.4,30.6)'}", Charset.forName("UTF-8"))); // 設置上傳文件 File file = new File(fileName); // 文件參數內容 FileBody fileBody = new FileBody(file); // 添加文件參數 params.addPart("photo", fileBody); params.addPart("photoName", new StringBody(file.getName())); // 將參數加入post請求體中 post.setEntity(params); // 執行post請求並獲得返回對象 [ 到這一步咱們的請求就開始了 ] HttpResponse resp = httpClient.execute(post); // 解析返回請求結果 HttpEntity entity = resp.getEntity(); InputStream is = entity.getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuffer buffer = new StringBuffer(); String temp; while ((temp = reader.readLine()) != null) { buffer.append(temp); } System.out.println(buffer); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } }

HttpMime.jar下載地址  HttpClient的壓縮包便可,httpmime.jar包含在其中。

相關文章
相關標籤/搜索