HttpURLConnection和HttpClient
- 第一篇系列文章中我們說(shuō)過(guò),volley的底層是使用HttpURLConnection和HttpClient來(lái)實(shí)現(xiàn)的,所以在對(duì)HttpStack進(jìn)行分析時(shí),我們需要對(duì)HttpURLConnection和HttpClient進(jìn)行必要的了解。
HttpURLConnection
- HttpURLConnection是Java的標(biāo)準(zhǔn)類,它繼承自URLConnection,可用于向指定網(wǎng)站發(fā)送GET請(qǐng)求、POST請(qǐng)求。需要注意的是HttpURLConnection是一個(gè)抽象類,所以不能直接創(chuàng)建它的實(shí)例,所以一般我們通過(guò)URL類的openConnection()方法獲得它的實(shí)例。
- 其用法可歸納如下:
- 獲取HttpURLConnection的實(shí)例
URL url = new URL("http://www.baidu.com"); HttpURLConnection connection = (HttpURLConnection) url.openConnection();- 設(shè)置HTTP請(qǐng)求所使用的方法:GET和POST。GET標(biāo)識(shí)希望從服務(wù)器得到數(shù)據(jù),而POST表示希望提交數(shù)據(jù)到服務(wù)器
connection.setRequestMethod("GET"); connection.setRequestMethod("POST");- 設(shè)置連接超時(shí),讀取超時(shí)的毫秒數(shù)等
connection.setConnectTimeout(8000); connection.setReadTimeout(8000);- 獲取服務(wù)器返回的輸入流(若HTTP請(qǐng)求為GET)
InputStream in = connection.getInputStream();- 向服務(wù)器提交數(shù)據(jù)(若HTTP請(qǐng)求為POST)
DataOutputStream out = new DataOutputStream(connection.getOutputStresm()); out.writeBytes("username=admin&password=123456");- 將HTTP連接關(guān)閉
connection.disconnect(); - 注意事項(xiàng)
- HttpURLConnection的connect()函數(shù),實(shí)際上只是建立了一個(gè)與服務(wù)器的tcp連接,并沒(méi)有實(shí)際發(fā)送http請(qǐng)求。無(wú)論是post還是get,http請(qǐng)求實(shí)際上直到HttpURLConnection的getInputStream()這個(gè)函數(shù)里面才正式發(fā)送出去。
- 在用POST方式發(fā)送URL請(qǐng)求時(shí),URL請(qǐng)求參數(shù)的設(shè)定順序是重中之重,對(duì)connection對(duì)象的一切配置(那一堆set函數(shù)),都必須要在connect()函數(shù)執(zhí)行之前完成。而對(duì)outputStream的寫(xiě)操作,又必須要在inputStream的讀操作之前。 這些順序?qū)嶋H上是由http請(qǐng)求的格式?jīng)Q定的。 如果inputStream讀操作在outputStream的寫(xiě)操作之前,會(huì)拋出異常: java.net.ProtocolException: Cannot write output after reading input……
- http請(qǐng)求實(shí)際上由兩部分組成,一個(gè)是http頭,所有關(guān)于此次http請(qǐng)求的配置都在http頭里面定義,一個(gè)是正文content。 connect()函數(shù)會(huì)根據(jù)HttpURLConnection對(duì)象的配置值生成http頭部信息,因此在調(diào)用connect函數(shù)之前,就必須把所有的配置準(zhǔn)備好。
- 在http頭后面緊跟著的是http請(qǐng)求的正文,正文的內(nèi)容是通過(guò)outputStream流寫(xiě)入的, 實(shí)際上outputStream不是一個(gè)網(wǎng)絡(luò)流,充其量是個(gè)字符串流,往里面寫(xiě)入的東西不會(huì)立即發(fā)送到網(wǎng)絡(luò),而是存在于內(nèi)存緩沖區(qū)中,待outputStream流關(guān)閉時(shí),根據(jù)輸入的內(nèi)容生成http正文。 至此,http請(qǐng)求的東西已經(jīng)全部準(zhǔn)備就緒。在getInputStream()函數(shù)調(diào)用的時(shí)候,就會(huì)把準(zhǔn)備好的http請(qǐng)求 正式發(fā)送到服務(wù)器了,然后返回一個(gè)輸入流,用于讀取服務(wù)器對(duì)于此次http請(qǐng)求的返回信息。由于http請(qǐng)求在getInputStream的時(shí)候已經(jīng)發(fā)送出去了(包括http頭和正文),因此在getInputStream()函數(shù) 之后對(duì)connection對(duì)象進(jìn)行設(shè)置(對(duì)http頭的信息進(jìn)行修改)或者寫(xiě)入outputStream(對(duì)正文進(jìn)行修改) 都是沒(méi)有意義的了,執(zhí)行這些操作會(huì)導(dǎo)致異常的發(fā)生。
HttpClient
- 它是一個(gè)簡(jiǎn)單的HTTP客戶端(并不是瀏覽器),可以用于發(fā)送HTTP請(qǐng)求,接收HTTP響應(yīng)。但不會(huì)緩存服務(wù)器的響應(yīng),不能執(zhí)行HTML頁(yè)面中嵌入的Javascript代碼;也不會(huì)對(duì)頁(yè)面內(nèi)容進(jìn)行任何解析、處理。
- 簡(jiǎn)單來(lái)說(shuō),HttpClient就是一個(gè)增強(qiáng)版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection沒(méi)有提供的有些功能,HttpClient也提供了,但它只是關(guān)注于如何發(fā)送請(qǐng)求、接收響應(yīng),以及管理HTTP連接。
- 其用法可總結(jié)如下:
- 創(chuàng)建一個(gè)DefaultHttpClient實(shí)例
HttpClient httpClient = new DefaultHttpClient();- 如果是進(jìn)行GET請(qǐng)求
HttpGet httpGet = new HttpGet("http://www.baidu.com"); HttpResponse httpResponse = httpClient.execute(httpGet);- 如果是進(jìn)行POST請(qǐng)求
創(chuàng)建一個(gè)HttpPost對(duì)象
通過(guò)一個(gè)NameValuePair集合來(lái)存放待提交的參數(shù),并將這個(gè)參數(shù)集合傳入到一個(gè)UrlEncodedFormEntity中,然后調(diào)用HttpPost的setEntity()方法將構(gòu)建好的UrlEncodedEntity傳入,之后調(diào)用httpClient的execute()方法,并將httpPost對(duì)象傳入,并接受服務(wù)器返回的httpResponse對(duì)象。HttpPost httpPost = new HttpPost("http://www.baidu.com");List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("username", "admin")); params.add(new BasicNameValuePair("password", "123456")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "utf-8"); httpPost.setEntity(entity); HttpResponse httpResponse = httpClient.execute(httpPost);- 取出服務(wù)器返回的狀態(tài)碼,如果等于200就說(shuō)明請(qǐng)求和響應(yīng)都成功了
if(httpResponse.getStatusLine().getStatusCode() == 200) { HttpEntity entity = httpResponse.getEntity(); String response = EntityUtils.toString(entity, "utf-8"); }
HttpURLConnection和HttpClient的選擇
- 在Android 2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。
- 而在Android 2.3版本及以后,HttpURLConnection則是最佳的選擇。它的API簡(jiǎn)單,體積較小,因而非常適用于Android項(xiàng)目。壓縮和緩存機(jī)制可以有效地減少網(wǎng)絡(luò)訪問(wèn)的流量,在提升速度和省電方面也起到了較大的作用。對(duì)于新的應(yīng)用程序應(yīng)該更加偏向于使用HttpURLConnection,因?yàn)樵谝院蟮墓ぷ鳟?dāng)中我們也會(huì)將更多的時(shí)間放在優(yōu)化HttpURLConnection上面。
- 在前面的Volley解析中我們也提到,它遵從上面的規(guī)律使用的。
HttpStack
- 在對(duì)HttpURLConnection和HttpClient有了初步的了解后,我們就可以進(jìn)行HttpStack的解析了。
- HttpStack是一個(gè)接口,它有一個(gè)抽象方法performRequest,也就是進(jìn)行請(qǐng)求的方法。
HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
- 該接口分別有HurlStack和HttpClientStack兩實(shí)現(xiàn)類,也就是分別對(duì)應(yīng)HttpURLConnection和HttpClient,兩種不同的HTTP請(qǐng)求方式,接下來(lái)我們分別介紹這兩個(gè)不同的類。
HurlStack
- 首先看到HurlStack的成員變量
private static final String HEADER_CONTENT_TYPE = "Content-Type";
private final HurlStack.UrlRewriter mUrlRewriter;
private final SSLSocketFactory mSslSocketFactory;
HurlStack的成員變量只有三個(gè),一個(gè)是表示“Content-Type”字符串的靜態(tài)常量,一個(gè)URL轉(zhuǎn)換接口,還有一個(gè)是生成SSLSocket的工廠類。
至于SSLSocket,它是擴(kuò)展Socket并提供使用SSL或TLS協(xié)議的安全套接字。這種套接字是正常的流套接字,但是它們?cè)诨A(chǔ)網(wǎng)絡(luò)傳輸協(xié)議(如TCP)上添加了安全保護(hù)層。
- 既然HurlStack實(shí)現(xiàn)了HttpStack接口的,那么performRequest方法的實(shí)現(xiàn)肯定是重中之重。
這里先直接貼出performRequest的實(shí)現(xiàn),再對(duì)其進(jìn)行分析。
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if(this.mUrlRewriter != null) {
String rewritten = this.mUrlRewriter.rewriteUrl(url);
if(rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = this.openConnection(parsedUrl, request);
Iterator var8 = map.keySet().iterator();
while(var8.hasNext()) {
String headerName = (String)var8.next();
connection.addRequestProperty(headerName, (String)map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if(responseCode == -1) {
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
} else {
StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
Iterator var12 = connection.getHeaderFields().entrySet().iterator();
while(var12.hasNext()) {
Entry<String, List<String>> header = (Entry)var12.next();
if(header.getKey() != null) {
Header h = new BasicHeader((String)header.getKey(), (String)((List)header.getValue()).get(0));
response.addHeader(h);
}
}
return response;
}
}
首先這個(gè)方法將請(qǐng)求的參數(shù)信息全部整合放在一個(gè)map里,然后通過(guò)判斷mUrlRewriter是否為空來(lái)決定是否轉(zhuǎn)換URL,我們可以通過(guò)實(shí)現(xiàn)mUrlRewriter類去擴(kuò)展Volley從而實(shí)現(xiàn)過(guò)濾非法字符之類的操作。
接著,這個(gè)方法通過(guò)openConnection方法打開(kāi)了一個(gè)TCP連接,并配置連接的相關(guān)信息,如超時(shí)時(shí)間,是否采用緩存等,并且如果是采用HTTP協(xié)議,則會(huì)為其設(shè)置默認(rèn)的SSLSocketFactory(當(dāng)然是在SSLSocketFactory不為空的情況下),需要注意的是,現(xiàn)在只是打開(kāi)了一個(gè)TCP連接,并沒(méi)有實(shí)際發(fā)送http請(qǐng)求。
openConnection:
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = this.createConnection(url);
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
if("https".equals(url.getProtocol()) && this.mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(this.mSslSocketFactory);
}
return connection;
}
再接下來(lái),就是配置http的請(qǐng)求頭信息了,通過(guò)遍歷存放著所有參數(shù)信息的map,再調(diào)用addRequestProperty將信息加入http的請(qǐng)求頭。
而setConnectionParametersForRequest方法的作用則是根據(jù)request的請(qǐng)求類型來(lái)設(shè)置connection的請(qǐng)求類型,如果是POST或者PUT的話還需要將發(fā)送的內(nèi)容輸出到并且設(shè)置相應(yīng)的content-type。
setConnectionParametersForRequest:
static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
switch(request.getMethod()) {
case -1:
byte[] postBody = request.getPostBody();
if(postBody != null) {
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(postBody);
out.close();
}
break;
case 0:
connection.setRequestMethod("GET");
break;
.....
再接著就是設(shè)置協(xié)議的類型以及版本,然后構(gòu)造一個(gè)BasicHttpResponse封裝本次的響應(yīng)的信息。其中包含了響應(yīng)的頭部信息和內(nèi)容,返回給調(diào)用該方法的上級(jí)進(jìn)行進(jìn)一步的解析。(其真正解析成我們需要的數(shù)據(jù)是在Request的子類中,如StringRequest類中)。
HttpClientStack
- HttpClientStack類與HurlStack類的作用類似,結(jié)構(gòu)也差不多,只不過(guò)HttpClientStack的底層是使用HttpClient實(shí)現(xiàn)的。
- 看到HttpClientStack的成員變量:
protected final HttpClient mClient;
private static final String HEADER_CONTENT_TYPE = "Content-Type";
很簡(jiǎn)單,只有一個(gè)HttpClient對(duì)象和一個(gè)字符串常量。其中HttpClient對(duì)象在初始化時(shí)傳入,也就是在VOLLEY類的newRequestQueue方法中通過(guò)AndroidHttpClient.newInstance(userAgent)方法取得。而AndroidHttpClient則是實(shí)現(xiàn)了HttpClient接口的類,主要是幫我們做了一些缺省的配置,如連接超時(shí)和socket超時(shí)都是設(shè)置為20秒,連接管理器設(shè)置為T(mén)hreadSafeClientConnManager。
- 在看到它的performRequest()方法:
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
addHeaders(httpRequest, additionalHeaders);
addHeaders(httpRequest, request.getHeaders());
this.onPrepareRequest(httpRequest);
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
return this.mClient.execute(httpRequest);
}
首先通過(guò)createHttpRequest()方法,根據(jù)請(qǐng)求的類型產(chǎn)生一個(gè)網(wǎng)絡(luò)請(qǐng)求,這里跟HurlStack里的處理類似。接著配置頭部信息,加入請(qǐng)求的參數(shù),設(shè)置超時(shí)時(shí)長(zhǎng),最后再通過(guò)mClient執(zhí)行請(qǐng)求。
- 本質(zhì)上HttpClientStack和HurlStack都是進(jìn)行同樣的封裝行為,只不過(guò)HttpClientStack的底層是采用HttpClient實(shí)現(xiàn)的。而HttpClientStack和HurlStack最大的不同應(yīng)該是HttpClientStack還擁有一個(gè)onPrepareRequest方法,我們可以繼承HttpClientStack類重寫(xiě)該方法做一些執(zhí)行前的工作,類似相當(dāng)于AsyncTask的onPreExecute()。
總結(jié)
- 到這里,和HttpStack有關(guān)的就已經(jīng)解析完了,其實(shí)還是挺容易看懂的,但這是在對(duì)HttpURLConnection和HttpClient有一定了解的前提下。
- 下一篇計(jì)劃分析VOLLEY的緩存機(jī)制。