JAVA模擬HTTP post請求上傳文件


在開發中,我們使用的比較多的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
這一行就說明了這個請求的請求方式,即為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壓縮、請求的內容長度為225873、內容的類型為"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功能避免了建立或者重新建立連接。

如上圖中,左邊的是關閉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-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();  
        }  
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM