HttpClient入門(1) 傳送請求,處理響應及響應重複讀取

果林椰子發表於2018-03-28

HttpClient是Apache旗下的專案,是一個負責建立和維護HTTP和相關協議的工具集。

以下分析使用版本為: httpclient-4.5.3.jar, httpcore-4.4.6.jar, jdk1.8.0_131 所有示例程式碼均經過執行測試

傳送請求

httpclient最重要的功能就是傳送http請求,下面介紹如何執行一個get請求:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {			
    HttpEntity entity = response.getEntity();
    System.out.println(EntityUtils.toString(entity, "UTF-8"));
} catch (Exception e) {
    e.printStackTrace();
}
複製程式碼

httpclient支援Http/1.1規範中定義的所有方法:GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,對應的類是:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace,HttpOptions。

請求URI

URI(統一資源識別符號),用於標識應用請求的資源,通常由協議版本,主機名,埠(可選),資源路徑,引數名(可選),引數值(可選)組成。 請求時,可以通過拼接字串的形式訪問:

HttpGet get = new HttpGet("http://www.jianshu.com/p/7021031d6e49?utm_medium=index-banner&utm_source=desktop");
複製程式碼

httpclient也提供了URIBuilder這個類簡化請求URI的建立和修改。

URI uri = new URIBuilder()
		        .setScheme("http")
		        .setHost("www.jianshu.com")
		        .setPath("/p/7021031d6e49")
		        .setParameter("utm_medium", "index-banner")
		        .setParameter("utm_source", "desktop")
		        .build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
複製程式碼

輸出

http://www.jianshu.com/p/7021031d6e49?utm_medium=index-banner&utm_source=desktop
複製程式碼

http請求是客戶端發給服務端的一個訊息,訊息的第一行包括了請求方法,URI以及使用的協議版本。

HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
RequestLine requestLine = get.getRequestLine();
System.out.println(requestLine.getMethod());
System.out.println(requestLine.getUri());
System.out.println(requestLine.getProtocolVersion());
System.out.println(requestLine);
複製程式碼

輸出

GET
http://www.jianshu.com/u/8a3115bb299c
HTTP/1.1
GET http://www.jianshu.com/u/8a3115bb299c HTTP/1.1
複製程式碼

處理響應

服務端接收並處理請求後,會返回訊息給到客戶端,該訊息的第一行包括協議版本,狀態碼以及描述文字。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {   
    System.out.println(response.getStatusLine());
    System.out.println(response.getProtocolVersion());
    System.out.println(response.getStatusLine().getStatusCode());
    System.out.println(response.getStatusLine().getReasonPhrase());
    System.out.println(response.getStatusLine().toString());
} catch (Exception e) {
    e.printStackTrace();
}
複製程式碼

輸出

HTTP/1.1 200 OK
HTTP/1.1
200
OK
HTTP/1.1 200 OK
複製程式碼

服務端返回的響應被封裝在HttpEntity,通過HttpEntity可以獲取請求響應的一些元資訊,比如Content-Type,Content-Length以及Content-Encoding,元資訊需由服務端提供,否則將是空值。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {            
    HttpEntity entity = response.getEntity();
    System.out.println(entity.getContentType());
    System.out.println(entity.getContentLength());
    System.out.println(entity.getContentEncoding());
} catch (Exception e) {
    e.printStackTrace();
}
複製程式碼

輸出

Content-Type: text/html; charset=utf-8
-1
null
複製程式碼

HttpEntity提供了多種方法來讀取正文,官方文件推薦使用以下方法進行讀取:HttpEntity.getContent()HttpEntity.writeTo(outputStream)

HttpEntity.getContent()方式
CloseableHttpClient httpclient = null;
CloseableHttpResponse response = null;
BufferedReader reader = null;
try {
    httpclient = HttpClients.createDefault();
    HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
    response = httpclient.execute(get);
    HttpEntity entity = response.getEntity();
    reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
    String str = "";
    StringBuilder sb = new StringBuilder();  
    while ((str = reader.readLine()) != null) {  
        sb.append(str).append("\n");  
    }
    System.out.println(sb);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeReader(reader);
    CommonUtils.closeResponse(response);
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼
CommonUtils
public void closeReader(BufferedReader reader) {
    try {
        if (reader != null) {
            reader.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
	
public void closeResponse(CloseableHttpResponse response) {
    try {
        if (response != null) {
            response.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void closeHttpClient(CloseableHttpClient httpclient) {
    try {
        if (httpclient != null) {
            httpclient.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
複製程式碼
HttpEntity.writeTo方式
CloseableHttpClient httpclient = null;
CloseableHttpResponse response = null;
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
try {
    httpclient = HttpClients.createDefault();
    HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
    response = httpclient.execute(get);
    HttpEntity entity = response.getEntity();
    entity.writeTo(outstream);
    System.out.println(outstream.toString("UTF-8"));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeOutputStream(outstream);
    CommonUtils.closeResponse(response);
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼
CommonUtils
public void closeOutputStream(ByteArrayOutputStream outstream) {
    try {
        if (outstream != null) {
	    outstream.close();
	}
    } catch (Exception e) {
        e.printStackTrace();
    }
}
複製程式碼

為了確保資源的正確釋放,採用上述兩種方式讀取正文時,都需對流進行關閉。除此之外,HttpClient還提供了EntityUtils工具類,方便對正文的讀取操作,不過官方文件中建議,只有當響應實體來自受信任的HTTP伺服器,並且已知其長度有限,才可以採用這種方法。

EntityUtils.toString方式
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {			
    HttpEntity entity = response.getEntity();
    System.out.println(EntityUtils.toString(entity, "UTF-8"));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼

EntityUtils.toString內部會對HttpEntity中的輸入流進行關閉。 以上三種讀取方式,當CloseableHttpClient的例項不再使用時,都需呼叫close方法進行關閉。

通過BufferedHttpEntity實現響應多次讀取

對於流型別的HttpEntity而言,是不可重複讀取的,若想多次讀取,則需要在某個地方將HttpEntity快取起來,最簡單的方式,可以使用HttpClient提供的BufferedHttpEntity類來實現。

HttpEntity.getContent()+BufferedHttpEntity實現多次讀取
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {
    HttpEntity entity = response.getEntity();
    BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
    System.out.println(getEntityContent(bufferedEntity));
    System.out.println(getEntityContent(bufferedEntity));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼
public String getEntityContent(HttpEntity entity) {
    try(BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"))) {
        String str = "";
        StringBuilder sb = new StringBuilder();  
        while ((str = reader.readLine()) != null) {  
            sb.append(str).append("\n");  
        }
        return sb.toString();
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}
複製程式碼
HttpEntity.writeTo+BufferedHttpEntity實現多次讀取
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {
    HttpEntity entity = response.getEntity();
    BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
    System.out.println(getContentByWriteTo(bufferedEntity));
    System.out.println(getContentByWriteTo(bufferedEntity));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼
public String getContentByWriteTo(HttpEntity entity) {
    try(ByteArrayOutputStream outstream = new ByteArrayOutputStream()) {
        entity.writeTo(outstream);
        return outstream.toString("UTF-8");
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}
複製程式碼
EntityUtils.toString+BufferedHttpEntity實現多次讀取
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://www.jianshu.com/u/8a3115bb299c");
try(CloseableHttpResponse response = httpclient.execute(get)) {            
    HttpEntity entity = response.getEntity();
    BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
    System.out.println(EntityUtils.toString(bufferedEntity, "UTF-8"));
    System.out.println(EntityUtils.toString(bufferedEntity, "UTF-8"));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    CommonUtils.closeHttpClient(httpclient);
}
複製程式碼

至此,通過HttpClient,簡單實現了http get請求的傳送和響應處理。

相關文章