HttpStack接口及其實(shí)現(xiàn)
HttpStack
HttpStack是一個(gè)接口鬼佣,定義了實(shí)現(xiàn)網(wǎng)絡(luò)請求的方法:performRequest。
根據(jù)傳入的請求信息以及其他二外的Header信息琳状,實(shí)現(xiàn)網(wǎng)絡(luò)請求燎字,返回一個(gè)org.apache.http.HttpResponse螺句。代碼比較簡單,關(guān)鍵是實(shí)現(xiàn)它的子類(這個(gè)類是將要已經(jīng)被廢棄了梧宫,但是接谨,我看Volley代碼中還是用到了,所以塘匣,還是分析下)脓豪。
/**
* An HTTP stack abstraction.
*
* @deprecated This interface should be avoided as it depends on the deprecated Apache HTTP library.
* Use {@link BaseHttpStack} to avoid this dependency. This class may be removed in a future
* release of Volley.
*/
@Deprecated
public interface HttpStack {
/**
* Performs an HTTP request with the given parameters.
*
* <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
* and the Content-Type header is set to request.getPostBodyContentType().
*
* @param request the request to perform
* @param additionalHeaders additional headers to be sent together with {@link
* Request#getHeaders()}
* @return the HTTP response
*/
HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
HttpStack一共有兩個(gè)實(shí)現(xiàn),一個(gè)是HttpClientStack忌卤,另一個(gè)是BaseHttpStack扫夜。這樣做的原因是因?yàn)椋沧?.0之后驰徊,廢棄了Apache的HttpClient笤闯,轉(zhuǎn)用HttpURLConnection。所以棍厂,為了兼容創(chuàng)建了BaseHttpStack颗味,在BaseHttpStack中另外定義了一個(gè)抽象方法excuteRequest,它重寫的performRequest方法中牺弹,調(diào)用excuteRequest方法獲取網(wǎng)絡(luò)數(shù)據(jù)浦马。
HttpClientStack
顯然,它實(shí)現(xiàn)了HttpStack接口中定義的最重要的方法:performRequest张漂。
它有一個(gè)內(nèi)部類晶默,我們先來看看這個(gè)內(nèi)部類到底是個(gè)啥。
HttpPatch
先看類注釋:The HttpPatch class does not exist in the Android framework, so this has been defined here.額鹃锈,意思就是這個(gè)類本來是HttpClient已有的荤胁,奈何Android架構(gòu)中不包含,所以屎债,自己有重新來定義了一個(gè)。Apache的HttpClient的注釋是這樣的:PATCH方法請求將請求實(shí)體中描述的一組更改應(yīng)用于Request-URI標(biāo)識的資源垢油。 與服務(wù)器處理封閉實(shí)體以修改Request-URI標(biāo)識的資源的方式不同盆驹。 在PUT請求中,封閉的實(shí)體源服務(wù)器和客戶端請求替換存儲的版本滩愁。但是躯喇,使用PATCH,隨附的實(shí)體包含一組指令,這些指令描述了如何修改當(dāng)前駐留在源服務(wù)器上的資源以生成新版本廉丽。簡單說倦微,這個(gè)類的作用就是當(dāng)請求類型為Patch時(shí),用這個(gè)類來封裝請求正压。
構(gòu)造函數(shù)
這個(gè)方法只一個(gè)構(gòu)造函數(shù)欣福,參數(shù)為HttpClient。后面需要根據(jù)這個(gè)來進(jìn)行網(wǎng)絡(luò)請求焦履。
protected final HttpClient mClient;
public HttpClientStack(HttpClient client) {
mClient = client;
}
實(shí)現(xiàn)performRequest
這個(gè)方法是網(wǎng)絡(luò)請求的關(guān)鍵拓劝。關(guān)鍵內(nèi)容不多,我們直接看源碼嘉裤。
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
setHeaders(httpRequest, additionalHeaders);
// Request.getHeaders() takes precedence over the given additional (cache) headers) and any
// headers set by createHttpRequest (like the Content-Type header).
setHeaders(httpRequest, request.getHeaders());
onPrepareRequest(httpRequest);
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
// TODO: Reevaluate this connection timeout based on more wide-scale
// data collection and possibly different for wifi vs. 3G.
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
return mClient.execute(httpRequest);
}
額郑临,感覺沒啥可講的。不熟悉的可以去看看Apache HttpClient如何使用屑宠,我就不當(dāng)搬用工了厢洞。
BaseHttpStack
這個(gè)是HttpStack的一個(gè)基礎(chǔ)實(shí)現(xiàn)。內(nèi)部的網(wǎng)絡(luò)請求使用的是自己抽象的方法executeRequest典奉。這在設(shè)計(jì)模式中躺翻,應(yīng)該叫做裝飾者模式。代碼不長秋柄,直接來分析获枝。
import com.android.volley.AuthFailureError;
import com.android.volley.Header;
import com.android.volley.Request;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
/** An HTTP stack abstraction. */
@SuppressWarnings("deprecation") // for HttpStack
public abstract class BaseHttpStack implements HttpStack {
/**
* Performs an HTTP request with the given parameters.
*
* <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
* and the Content-Type header is set to request.getPostBodyContentType().
*
* @param request the request to perform
* @param additionalHeaders additional headers to be sent together with {@link
* Request#getHeaders()}
* @return the {@link HttpResponse}
* @throws SocketTimeoutException if the request times out
* @throws IOException if another I/O error occurs during the request
* @throws AuthFailureError if an authentication failure occurs during the request
*/
public abstract HttpResponse executeRequest(
Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
/**
* @deprecated use {@link #executeRequest} instead to avoid a dependency on the deprecated
* Apache HTTP library. Nothing in Volley's own source calls this method. However, since
* {@link BasicNetwork#mHttpStack} is exposed to subclasses, we provide this implementation
* in case legacy client apps are dependent on that field. This method may be removed in a
* future release of Volley.
*/
@Deprecated
@Override
public final org.apache.http.HttpResponse performRequest(
Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
HttpResponse response = executeRequest(request, additionalHeaders);
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
StatusLine statusLine =
new BasicStatusLine(
protocolVersion, response.getStatusCode(), /* reasonPhrase= */ "");
BasicHttpResponse apacheResponse = new BasicHttpResponse(statusLine);
List<org.apache.http.Header> headers = new ArrayList<>();
for (Header header : response.getHeaders()) {
headers.add(new BasicHeader(header.getName(), header.getValue()));
}
apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[headers.size()]));
InputStream responseStream = response.getContent();
if (responseStream != null) {
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(responseStream);
entity.setContentLength(response.getContentLength());
apacheResponse.setEntity(entity);
}
return apacheResponse;
}
}
兩個(gè)方法,一個(gè)是HttpStack中定義的performRequest骇笔,返回了org.apache.http.HttpResponse省店,另一個(gè)是抽象的executeRequest,返回了com.android.volley.toolbox.HttpResponse笨触。先簡單講一下
HttpResponse
這個(gè)是Volley自帶的對網(wǎng)絡(luò)請求的一個(gè)封裝懦傍,注意與org.apache.http.HttpResponse是不同的。包括:狀態(tài)碼芦劣、Header列表粗俱、內(nèi)容長度和InputStream。
import com.android.volley.Header;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
/** A response from an HTTP server. */
public final class HttpResponse {
private final int mStatusCode;
private final List<Header> mHeaders;
private final int mContentLength;
private final InputStream mContent;
/**
* Construct a new HttpResponse for an empty response body.
*
* @param statusCode the HTTP status code of the response
* @param headers the response headers
*/
public HttpResponse(int statusCode, List<Header> headers) {
this(statusCode, headers, /* contentLength= */ -1, /* content= */ null);
}
/**
* Construct a new HttpResponse.
*
* @param statusCode the HTTP status code of the response
* @param headers the response headers
* @param contentLength the length of the response content. Ignored if there is no content.
* @param content an {@link InputStream} of the response content. May be null to indicate that
* the response has no content.
*/
public HttpResponse(
int statusCode, List<Header> headers, int contentLength, InputStream content) {
mStatusCode = statusCode;
mHeaders = headers;
mContentLength = contentLength;
mContent = content;
}
/** Returns the HTTP status code of the response. */
public final int getStatusCode() {
return mStatusCode;
}
/** Returns the response headers. Must not be mutated directly. */
public final List<Header> getHeaders() {
return Collections.unmodifiableList(mHeaders);
}
/** Returns the length of the content. Only valid if {@link #getContent} is non-null. */
public final int getContentLength() {
return mContentLength;
}
/**
* Returns an {@link InputStream} of the response content. May be null to indicate that the
* response has no content.
*/
public final InputStream getContent() {
return mContent;
}
}
代碼比較簡單虚吟,一看即懂寸认,不詳細(xì)講了。
- executeRequest
Performs an HTTP request with the given parameters.簡單說就是完成Http請求的方法串慰。由子類實(shí)現(xiàn)偏塞。
注意返回的HttpResponse是com.android.volley.toolbox.HttpResponse。/** * Performs an HTTP request with the given parameters. * * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, * and the Content-Type header is set to request.getPostBodyContentType(). * * @param request the request to perform * @param additionalHeaders additional headers to be sent together with {@link * Request#getHeaders()} * @return the {@link HttpResponse} * @throws SocketTimeoutException if the request times out * @throws IOException if another I/O error occurs during the request * @throws AuthFailureError if an authentication failure occurs during the request */ public abstract HttpResponse executeRequest( Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;
- performRequest
在這個(gè)方法中實(shí)際上是調(diào)用了executeRequest()方法邦鲫,然后將得到的com.android.volley.toolbox.HttpResponse轉(zhuǎn)換成org.apache.http.HttpResponse灸叼。額神汹,簡單,粗暴古今,有效屁魏。/** * @deprecated use {@link #executeRequest} instead to avoid a dependency on the deprecated * Apache HTTP library. Nothing in Volley's own source calls this method. However, since * {@link BasicNetwork#mHttpStack} is exposed to subclasses, we provide this implementation * in case legacy client apps are dependent on that field. This method may be removed in a * future release of Volley. */ @Deprecated @Override public final org.apache.http.HttpResponse performRequest( Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { HttpResponse response = executeRequest(request, additionalHeaders); ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); StatusLine statusLine = new BasicStatusLine( protocolVersion, response.getStatusCode(), /* reasonPhrase= */ ""); BasicHttpResponse apacheResponse = new BasicHttpResponse(statusLine); List<org.apache.http.Header> headers = new ArrayList<>(); for (Header header : response.getHeaders()) { headers.add(new BasicHeader(header.getName(), header.getValue())); } apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[headers.size()])); InputStream responseStream = response.getContent(); if (responseStream != null) { BasicHttpEntity entity = new BasicHttpEntity(); entity.setContent(responseStream); entity.setContentLength(response.getContentLength()); apacheResponse.setEntity(entity); } return apacheResponse; }
HurlStack
Hurl是HttpUrlConnection的縮寫。顧名思義捉腥,它是使用了HttpURLConnection實(shí)現(xiàn)網(wǎng)絡(luò)請求的氓拼。它是BaseHttpStack的子類。上面講過但狭,BaseHttpStack有一個(gè)抽象方法-executeRequest披诗。我們的HurlStack實(shí)現(xiàn)了這個(gè)方法。
我們先不急著看這個(gè)方法的代碼立磁,按照套路呈队,看完繼承之后,再看這個(gè)類是否有什么內(nèi)部類唱歧。果然宪摧,它有兩個(gè)內(nèi)部類。一個(gè)是:
- UrlRewriter接口
- UrlConnectionInputStream類
UrlRewriter
它是一個(gè)接口颅崩。
/** An interface for transforming URLs before use. */
public interface UrlRewriter {
/**
* Returns a URL to use instead of the provided one, or null to indicate this URL should not
* be used at all.
*/
String rewriteUrl(String originalUrl);
}
代碼注釋說几于,它是用于在使用前轉(zhuǎn)換URL的一個(gè)接口。有一個(gè)方法 rewriteUrl沿后,入?yún)riginalUrl沿彭,出參是轉(zhuǎn)換后的url。返回一個(gè)URL來使用而不是使用提供的URL尖滚,或返回null以指示不應(yīng)使用此URL喉刘。額,感覺不是很清楚啥意思漆弄。我們接著看代碼吧睦裳,說不定后面更清楚一些。
UrlConnectionInputStream
直接看代碼撼唾。
/**
* Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on
* stream close.
*/
static class UrlConnectionInputStream extends FilterInputStream {
private final HttpURLConnection mConnection;
UrlConnectionInputStream(HttpURLConnection connection) {
super(inputStreamFromConnection(connection));
mConnection = connection;
}
@Override
public void close() throws IOException {
super.close();
mConnection.disconnect();
}
}
/**
* Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
*
* @param connection
* @return an HttpEntity populated with data from <code>connection</code>.
*/
private static InputStream inputStreamFromConnection(HttpURLConnection connection) {
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch (IOException ioe) {
inputStream = connection.getErrorStream();
}
return inputStream;
}
從注釋我們知道廉邑,這個(gè)類的作用是:它是HttpURLConnection的InputStream的裝飾類,用于關(guān)閉連接倒谷。
構(gòu)造方法
現(xiàn)在我們看看構(gòu)造方法
private final UrlRewriter mUrlRewriter;
private final SSLSocketFactory mSslSocketFactory;
public HurlStack() {
this(/* urlRewriter = */ null);
}
/** @param urlRewriter Rewriter to use for request URLs */
public HurlStack(UrlRewriter urlRewriter) {
this(urlRewriter, /* sslSocketFactory = */ null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
* @param sslSocketFactory SSL factory to use for HTTPS connections
*/
public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
mUrlRewriter = urlRewriter;
mSslSocketFactory = sslSocketFactory;
}
它有三個(gè)構(gòu)造方法蛛蒙。用于設(shè)置UrlRewriter和SSLSocketFactory。UrlRewriter的作用是渤愁,對傳入的url進(jìn)行加工宇驾。SSLSocketFactory的作用是,當(dāng)請求協(xié)議是Https猴伶,可以使用這樣一個(gè)自定義的SSLSocketFactory。
executeRequest()方法
這個(gè)是重寫B(tài)aseHttpStack的抽象方法。
@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<>();
map.putAll(additionalHeaders);
// Request.getHeaders() takes precedence over the given additional (cache) headers).
map.putAll(request.getHeaders());
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
boolean keepConnectionOpen = false;
try {
for (String headerName : map.keySet()) {
connection.setRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
if (!hasResponseBody(request.getMethod(), responseCode)) {
return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
}
// Need to keep the connection open until the stream is consumed by the caller. Wrap the
// stream such that close() will disconnect the connection.
keepConnectionOpen = true;
return new HttpResponse(
responseCode,
convertHeaders(connection.getHeaderFields()),
connection.getContentLength(),
new UrlConnectionInputStream(connection));
} finally {
if (!keepConnectionOpen) {
connection.disconnect();
}
}
}
過程簡單他挎,首先判斷是否設(shè)置了個(gè)性化UrlRewriter筝尾,如果設(shè)置了,可以在這里調(diào)用自定義方法办桨,對Url進(jìn)行修飾筹淫。這個(gè)有啥作用呢?可能可以用來做Mocker吧呢撞。再創(chuàng)建HttpURLConnection损姜,調(diào)用了openConnection()方法。在這個(gè)方法中殊霞,我們判斷了Url的協(xié)議是不是https摧阅,如果是,就調(diào)用我們在構(gòu)造方法中設(shè)置的SSLSocketFactory绷蹲。然后調(diào)用connection.setRequestProperty()方法設(shè)置請求參數(shù)棒卷。然后就是更具Request來設(shè)置請求類型(POST、GET等)祝钢。
調(diào)用connection.getResponseCode()觸發(fā)請求比规,得到響應(yīng)碼。然后判斷響應(yīng)怕是否異常拦英,如果是蜒什,就拋出IO異常。接著調(diào)用hasResponseBody()方法疤估,來判斷是否得到了body灾常。根據(jù)請求結(jié)果創(chuàng)建了一個(gè)com.android.volley.toolbox.HttpResponse,并將它返回做裙。執(zhí)行結(jié)束岗憋。至此,這個(gè)類的分析完畢了锚贱。
AdaptedHttpStack
這個(gè)的作用的是為了兼容性仔戈,添加的一個(gè)接口。
import com.android.volley.AuthFailureError;
import com.android.volley.Header;
import com.android.volley.Request;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.conn.ConnectTimeoutException;
/**
* {@link BaseHttpStack} implementation wrapping a {@link HttpStack}.
*
* <p>{@link BasicNetwork} uses this if it is provided a {@link HttpStack} at construction time,
* allowing it to have one implementation based atop {@link BaseHttpStack}.
*/
@SuppressWarnings("deprecation")
class AdaptedHttpStack extends BaseHttpStack {
private final HttpStack mHttpStack;
AdaptedHttpStack(HttpStack httpStack) {
mHttpStack = httpStack;
}
@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
org.apache.http.HttpResponse apacheResp;
try {
apacheResp = mHttpStack.performRequest(request, additionalHeaders);
} catch (ConnectTimeoutException e) {
// BasicNetwork won't know that this exception should be retried like a timeout, since
// it's an Apache-specific error, so wrap it in a standard timeout exception.
throw new SocketTimeoutException(e.getMessage());
}
int statusCode = apacheResp.getStatusLine().getStatusCode();
org.apache.http.Header[] headers = apacheResp.getAllHeaders();
List<Header> headerList = new ArrayList<>(headers.length);
for (org.apache.http.Header header : headers) {
headerList.add(new Header(header.getName(), header.getValue()));
}
if (apacheResp.getEntity() == null) {
return new HttpResponse(statusCode, headerList);
}
long contentLength = apacheResp.getEntity().getContentLength();
if ((int) contentLength != contentLength) {
throw new IOException("Response too large: " + contentLength);
}
return new HttpResponse(
statusCode,
headerList,
(int) apacheResp.getEntity().getContentLength(),
apacheResp.getEntity().getContent());
}
}
MockHttpStack
這個(gè)是用來測試的拧廊。代碼比較簡單监徘,不分析了。
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.toolbox.BaseHttpStack;
import com.android.volley.toolbox.HttpResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class MockHttpStack extends BaseHttpStack {
private HttpResponse mResponseToReturn;
private IOException mExceptionToThrow;
private String mLastUrl;
private Map<String, String> mLastHeaders;
private byte[] mLastPostBody;
public String getLastUrl() {
return mLastUrl;
}
public Map<String, String> getLastHeaders() {
return mLastHeaders;
}
public byte[] getLastPostBody() {
return mLastPostBody;
}
public void setResponseToReturn(HttpResponse response) {
mResponseToReturn = response;
}
public void setExceptionToThrow(IOException exception) {
mExceptionToThrow = exception;
}
@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
if (mExceptionToThrow != null) {
throw mExceptionToThrow;
}
mLastUrl = request.getUrl();
mLastHeaders = new HashMap<>();
if (request.getHeaders() != null) {
mLastHeaders.putAll(request.getHeaders());
}
if (additionalHeaders != null) {
mLastHeaders.putAll(additionalHeaders);
}
try {
mLastPostBody = request.getBody();
} catch (AuthFailureError e) {
mLastPostBody = null;
}
return mResponseToReturn;
}
}