HttpURLConnection 使用總結(jié)

要使用 HttpURLConnection,最好對一些基礎(chǔ)概念有所認(rèn)識,比如 TCP/IP 協(xié)議,HTTP 報文, Socket 等。
先談一些我的認(rèn)識,有可能不完全正確:

  • Socket 應(yīng)該是 TCP 協(xié)議層的概念,如果要使用 Socket 直接通信,需要使用遠(yuǎn)程地址和端口號。其中,端口號根據(jù)具體的協(xié)議而不同,比如 HTTP 協(xié)議默認(rèn)使用的端口號為 80/tcp。
  • HttpURLConnection 是在底層連接上的一個請求,最終也是通過 Socket 連接網(wǎng)絡(luò),所謂的 underlaying Socket。本文結(jié)尾我也會附上相關(guān)帖子連接。但是使用 HttpURLConnection 不需要我們專門去處理遠(yuǎn)程地址和端口號。
  • HttpURLConnection 只是一個抽象類,只能通過 url.openConection() 方法創(chuàng)建具體的實例。嚴(yán)格來說,openConection() 方法返回的是 URLConnection 的子類。根據(jù) url 對象的不同,如可能不是 http:// 開頭的,那么 openConection() 返回的可能就不是 HttpURLConnection。
  • HttpURLConnection 的 connect() 和 disconnect() 方法有必要特別強調(diào)一下,我會在下文使用到的地方詳細(xì)說明。

我在測試 HttpURLConnection 的時候,是分別使用 HTTP 的 GET 和 POST 方法發(fā)送消息到 http://ip.taobao.com//service/getIpInfo.php 查詢 IP 地址歸屬地。http://ip.taobao.com/instructions.php 是 GET 方法接口說明。

下面來具體說一下 HttpURLConnection 的使用步驟。

  1. 獲得 HttpURLConnection 對象

    // 如果使用 POST 方法
    URL url = new 
    URL("http://ip.taobao.com//service/getIpInfo.php");
    
    // 如果打算使用 GET 方法
    //URL url = new URL("http://ip.taobao.com/service/getIpInfo.php?ip=xxx.xxx.xxx.xxx");
    
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    
  2. 設(shè)置請求屬性
    在連接到遠(yuǎn)程資源(可以簡單理解為遠(yuǎn)端服務(wù)器,但是這么說不準(zhǔn)確)之前,可以設(shè)置一些 HttpURLConnection 的屬性。

    // 設(shè)置連接超時時間
    connection.setConnectTimeOut(15000);
    
    // 設(shè)置讀取超時時間
    connection.setReadTimeOut(15000);
    
    // 設(shè)置請求參數(shù),即具體的 HTTP 方法
    connection.setRequestMethod("POST");
    
    // 添加 HTTP HEAD 中的一些參數(shù),可參考《Java 核心技術(shù) 卷II》
    connection.setRequestProperty("Connection", "Keep-Alive");
    
    // 設(shè)置是否向 httpUrlConnection 輸出,
    // 對于post請求,參數(shù)要放在 http 正文內(nèi),因此需要設(shè)為true。
    // 默認(rèn)情況下是false;
    connection.setDoOutput(true);
    
    // 設(shè)置是否從 httpUrlConnection 讀入,默認(rèn)情況下是true;
    connection.setDoInput(true);
    

    這些屬性的設(shè)置要在 connect() 之前完成。如果對 HTTP 包信息的結(jié)構(gòu)有很好的理解,有助于理解這些方法。
    setDoOutput() 方法是為了下面 getOutputStream();
    setDoInput() 方法是為了下面 getInputStream()。
    按照我在手機上測試,getOutputStream 和 getInputStream 內(nèi)部都會隱式的調(diào)用 connect()。不過這只是我手機上的環(huán)境,嚴(yán)謹(jǐn)?shù)膩碇v,我覺得還是應(yīng)該自己顯示的調(diào)用 conect()。(多次調(diào)用 connect(),后面的調(diào)用自動忽略)

  3. 調(diào)用 connect() 連接遠(yuǎn)程資源

    connection.connect();
    

    這會與服務(wù)器建立 Socket 連接,而連接以后,連接屬性就不可以再修改;但是可以查詢服務(wù)器返回的頭信息了(header information)。
    connect 成功手機上 logcat 會打印相關(guān)信息,包括目標(biāo) IP 地址。我是用魅族做的測試,其他品牌理論上也應(yīng)該會打印。

  4. 利用 getOutputStream() 傳輸 POST 消息
    說明一下,POST 消息才需要寫數(shù)據(jù),GET 不需要。

    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
    writer.write("ip=xxx.xxx.xxx.xxx");
    writer.flush();
    writer.close();
    

    上面提到過,getOutputStream 會隱式的調(diào)用 connect()。
    這里要注意的,主要是 HTTP 傳輸?shù)南⒁褂?URL UTF-8 編碼,英文字母、數(shù)字和部分符號保持不變,空格編碼成'+'。其他字符編碼成 "%XY" 形式的字節(jié)序列,特別是中文字符,不能直接傳輸??梢钥紤]使用
    URLEncoder.encode(string, "UTF-8") 方法。

  5. 查詢服務(wù)器頭信息
    理論上,connect() 以后就可以查詢服務(wù)器返回的頭信息了。并且,getOutputStream 里面會隱式調(diào)用 connect()。
    但是,查詢服務(wù)器消息要在寫完所有要傳輸?shù)臄?shù)據(jù)以后。
    如果 getResponseCode 或者 getResponseMessage 以后,是不能向 outputStream 寫消息的,報錯為:

    cannot write request body after response has been read

    這兩個方法內(nèi)部都調(diào)用了 getInputStream()。

    因為有資料說,getInputStream() 的時候才會真正把 outputStream 里面的消息發(fā)出去。想想,這么做是有道理的:這樣就允許我們關(guān)閉 outputStream 后重新打開,并且補充數(shù)據(jù)。這么理解的話,getResponseCode 內(nèi)部調(diào)用了 getInputStream,導(dǎo)致 outputStream 已經(jīng)發(fā)送;而一個 HttpURLConnection 只能發(fā)送一個請求,所以就不能再向 outputStream 寫數(shù)據(jù),否則就等于傳輸了兩個消息。
    我沒有在手機上安裝抓手機報文的工具,所以沒有直接驗證。
    實際使用時,肯定是先通過 outputStream 傳輸數(shù)據(jù),然后查詢服務(wù)器的返回信息,所以 outputStream 消息到底是什么時候發(fā)送出去的,我們不需要太關(guān)心。
    查詢頭信息的方法有一下幾個:
    // 這兩個方法結(jié)合,可以查詢所有消息頭字段
    public String getHeaderFieldKey (int n)
    public String getHeaderField(int n)

    // 返回一個包含消息頭所有字段的標(biāo)準(zhǔn) map 對象
    public Map<String,List<String>> getHeaderFields()

    // 為了方便使用,以下方法可以查詢各標(biāo)準(zhǔn)字段
    public String getContentType()
    public int getContentLength()
    public String getContentEncoding()
    public long getDate()
    public long getExpiration()
    public long getLastModified()

  6. 利用 getInputStream() 訪問資源數(shù)據(jù)

    使用 getInputStream() 方法獲取一個輸入流用以讀取信息(這個輸入流與 URL 類中的 openStream 方法所返回的流相同)。另一個方法 getContent 在實際操作中并不是很有用。由標(biāo)準(zhǔn)內(nèi)容類型(比如 text/plain 和 image/gif)所返回的對象需要使用 com.sun 層次結(jié)構(gòu)中的類來進(jìn)行處理。也可以注冊自己的內(nèi)容處理器。
    ---《Java 核心技術(shù) 卷II》,CH3 網(wǎng)絡(luò),使用 URLConnection 獲取信息

    private String convertStreamToString() {
        InputStream inputStream = connection.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream ));
        StringBuffer sb = new StringBuffer();
        String line = null;
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    
        String reponse = sb.toString();
        return reponse;
    }
    
  7. 關(guān)閉 HttpURLConnection
    本身要 HttpURLConnection 是很簡單的,調(diào)用 connection.disconnect() 就可以了。
    這里是想說明一下,是否需要關(guān)閉,應(yīng)該根據(jù)實際需要來。
    當(dāng) HttpURLConnection 是 "Connection: close " 模式,那么關(guān)閉 inputStream 后就會自動斷開連接。
    當(dāng) HttpURLConnection 是 "Connection: Keep-Alive" 模式,那么關(guān)閉 inputStream 后,并不會斷開底層的 Socket 連接。這樣的好處,是當(dāng)需要連接到同一服務(wù)器地址時,可以復(fù)用該 Socket。這時如果要求斷開連接,就可以調(diào)用 connection.disconnect() 了。
    當(dāng)然,HttpURLConnection 連接到底是不是 Keep-Alive 模式,除了 HttpURLConnection 請求設(shè)置為 Keep-Alive 外 (http 1.0中默認(rèn)是關(guān)閉的,http 1.1中默認(rèn)啟用Keep-Alive),也需要服務(wù)器支持 Keep-Alive,才可以真正建立 Keep-Alive 連接。

    // 連接 和 斷開連接 的 log,IP 地址為手機 IP
    I/System.out: [socket][/192.168.1.101:60330] connected
    I/System.out: close [socket][/192.168.1.101:60330]
    
  8. 補充一點
    在我測試http://ip.taobao.com//service/getIpInfo.php 的時候,服務(wù)器一直不能正常返回 IP 地址對應(yīng)的信息。最后發(fā)現(xiàn),是淘寶服務(wù)器故意不響應(yīng)我們這樣非瀏覽器發(fā)起的 IP 查詢請求。所以我還設(shè)置了 HttpURLConnection 的如下屬性,偽裝成瀏覽器,當(dāng)然,是在 connect() 之前。

    connection.setRequestProperty("user-agent",
                     "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.7 Safari/537.36");
    

    調(diào)試聯(lián)網(wǎng)程序的時候,出錯有時候很難說是哪里的問題,用抓包軟件分析是很有必要的;檢查服務(wù)器的 ResponseCode 也是有必要的。


關(guān)于 HttpURLConnection 的學(xué)習(xí),我覺得《Java 核心技術(shù) 卷II》寫的不錯。
我也參考了《Android 進(jìn)階之光》和下面兩個鏈接。

JDK中的URLConnection參數(shù)詳解

詳解HttpURLConnection

關(guān)于 HTTP 的 GET 方法和 POST 方法,剛開始有些疑惑,也是看了《Java 核心技術(shù) 卷II》,以及下面兩個鏈接。

99%的人都理解錯了HTTP中GET與POST的區(qū)別

GET和POST有什么區(qū)別?及為什么網(wǎng)上的多數(shù)答案都是錯的。

工作中經(jīng)常用到的話,有必要專門學(xué)習(xí)一下 HTTP 協(xié)議和報文。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容