HTTP POST請求報文格式分析與Java實現檔案上傳
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
在開發中,我們使用的比較多的HTTP請求方式基本上就是GET、POST。其中GET用於從伺服器獲取資料,POST主要用於向伺服器提交一些表單資料,例如檔案上傳等。而我們在使用HTTP請求時中遇到的比較麻煩的事情就是構造檔案上傳的HTTP報文格式,這個格式雖說也比較簡單,但也比較容易出錯。今天我們就一起來學習HTTP POST的報文格式以及通過Java來模擬檔案上傳的請求。
首先我們來看一個POST的報文請求,然後我們再來詳細的分析它。
POST報文格式
這裡我們提交的是經度、緯度和一張圖片(圖片資料比較長,而且比較雜亂,這裡省略掉了)。POST /api/feed/ HTTP/1.1Accept-Encoding: gzipContent-Length: 225873Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpHost: www.myhost.comConnection: Keep-Alive--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpContent-Disposition: form-data; name="lng"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bit116.361545--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpContent-Disposition: form-data; name="lat"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bit39.979006--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpContent-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"Content-Type: application/octet-streamContent-Transfer-Encoding: binary這裡是圖片的二進位制資料--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--
格式分析
請求頭分析
我們先看報文格式中的第一行:POST /api/feed/ HTTP/1.1
這一行就說明了這個請求的請求方式,即為POST方式,要請求的子路徑為/api/feed/,例如我們的伺服器地址為www.myhost.com,然後我們的這個請求的完整路徑就是
www.myhost.com/api/feed/,最後說明了HTTP協議的版本號為1.1。
這幾個header的意思分別為伺服器返回的資料需要使用gzip壓縮、請求的內容長度為225873、內容的型別為"multipart/form-data"、請求引數分隔符(boundary)為OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp、請求的根域名為www.myhost.com、HTTP連線方式為持久連線( Keep-Alive)。Accept-Encoding: gzipContent-Length: 225873Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpHost: www.myhost.comConnection: 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功能避免了建立或者重新建立連線。
如上圖中,左邊的是關閉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連線就看伺服器設定情況。
請求實體分析
請求實體其實就是HTTP POST請求的引數列表,每個引數以請求分隔符開始,即-- + boundary。例如下面這個引數。--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBpContent-Disposition: form-data; name="lng"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bit116.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-8Content-Transfer-Encoding: 8bit
檔案引數:
Content-Type: application/octet-streamContent-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包含在其中。