Volley框架解析(五)—–HttpStack接口及其默认实现类解析
1. 前言(可直接无视跳过= =
历经前面的四篇,终于涉及到网络请求核心的内容了,前面都在做一些准备工作,以及对request队列调度以及维护工作。之前一直说,httpResponse = mHttpStack.performRequest(request, headers);
这句话是网络请求的核心,因为这句话一出来,神不知鬼不觉的request就被发出去了,并且还带回来一个HttpResponse的实例= =,有没有感觉被忽悠了,今天就深入进去一探究竟0.0。
2. HttpStack.java
接口类,里面包含了一个方法,performRequest()
。
/**
* An HTTP stack abstraction.
*/
public interface HttpStack {
/**
* Performs an HTTP request with the given parameters.
* 用传入给定的参数来模拟Http请求
*
* 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().
* 如果传入的request.getPostBody()为空,则发送一个Get类型的请求,否则发送一个Post类型请求
*
* @param request the request to perform
* 即将发送的初始请求,也是volley自己写的= =,进去看看
* (还需要添加上额外的header
*
* @param additionalHeaders additional headers to be sent together with
* {@link Request#getHeaders()}
* 需要添加到该request上的header的信息
*
* @return the HTTP response
*
*/
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
3. HttpClientStack.java
当sdk版本小于2.3时,Volley会选择用HttpClient来实现请求的发送。
/**
* An HttpStack that performs request over an {@link HttpClient}.
* 在sdk小于2.3的时候
* 选用HttpClient来实现网络请求
*/
public class HttpClientStack implements HttpStack {
/**
* 官方文档
* Interface for an HTTP client.
* HTTP clients encapsulate a smorgasbord of objects required to execute HTTP requests while handling cookies,
* authentication, connection management, and other features.
* HTTP Clients将发送http请求需要需要做出的信息
* Thread safety of HTTP clients depends on the implementation and configuration of the specific client.
*
*/
protected final HttpClient mClient;
//Http请求头里面的固定格式
private final static String HEADER_CONTENT_TYPE = "Content-Type";
public HttpClientStack(HttpClient client) {
mClient = client;
}
//在组合出一个请求的过程中,向请求体中添加Header的方法,Header是以键值对的形式存在的
private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
for (String key : headers.keySet()) {
httpRequest.setHeader(key, headers.get(key));
}
}
/**
* NameValuePair 官方文档
* A simple class encapsulating an attribute/value pair.
*
* 该函数将传入的Map里面存放的值进一步转化成由NameValuePair子类组成的数组中
*/
@SuppressWarnings("unused")
private static List<NameValuePair> getPostParameterPairs(Map<String, String> postParams) {
List<NameValuePair> result = new ArrayList<NameValuePair>(postParams.size());
for (String key : postParams.keySet()) {
result.add(new BasicNameValuePair(key, postParams.get(key)));
}
return result;
}
//该函数也就是实现HttpStack接口需要实现的方法,用来执行Request的方法
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
/**
* 传入请求体和额外需要添加入的头部
* 生成并返回一个HttpUriRequest
*/
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
/**
* 这个方法在前面实现了,将这些传入的键值对全部添加到httpRequest里面去
*/
addHeaders(httpRequest, additionalHeaders);
addHeaders(httpRequest, request.getHeaders());
/**
* 一个protected方法,留给子类可以实现的方法(本类中并没有什么东西),在这里会调用。
*/
onPrepareRequest(httpRequest);
/**
* HttpParams 官方文档
* Represents a collection of HTTP protocol and framework parameters.
* 说白了就是Http协议和框架的相关参数
*/
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
/**
* HttpConnectionParams 官方文档
* An adaptor for accessing connection parameters in HttpParams.
* 一个用来访问请求参数的适配器
* Note that the implements relation to CoreConnectionPNames is for compatibility with existing application code only.
* References to the parameter names should use the interface, not this class.
*/
/* Sets the timeout until a connection is established.
* 该方法用来设置时间限制,
* A value of zero means the timeout is not used. The default value is zero.
* 如果timeout设置为0则表示该限时没有启用,默认为0
*/
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
/**
* Sets the default socket timeout (SO_TIMEOUT) in milliseconds which is the timeout for waiting for data.
* 设置请求发出后等待网络响应并返回数据的限时
* A timeout value of zero is interpreted as an infinite timeout.
* 如果timeout值为0则意味着无限等待,没有等待限时,同时也是默认的值
* This value is used when no socket timeout is set in the method parameters.
*/
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
/**
* 执行了HttpClient类中的execute方法
* 方法描述为 Executes a request using the default context.
* 方法结束后将返回一个HttpResponse,也就是请求的结果类
*/
return mClient.execute(httpRequest);
}
/**
* Creates the appropriate subclass of HttpUriRequest for passed in request.
* 根据传入的Request种类不同
* 创建不同的HttpUriRequest子类(也就是下面的HttpGet等等)
* 下面做的工作和HurlStack.java里面做的工作差不多
* 设置header,以及是否需要传入请求携带的参数
* 只是本类中用HttpClient实现,后者用的是HttpURLConnection实现的
*/
@SuppressWarnings("deprecation")
/* protected */ static HttpUriRequest createHttpRequest(Request<?> request,
Map<String, String> additionalHeaders) throws AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST: {
// This is the deprecated way that needs to be handled for backwards compatibility.
// If the request's post body is null, then the assumption is that the request is
// GET. Otherwise, it is assumed that the request is a POST.
byte[] postBody = request.getPostBody();
if (postBody != null) {
HttpPost postRequest = new HttpPost(request.getUrl());
postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
HttpEntity entity;
entity = new ByteArrayEntity(postBody);
postRequest.setEntity(entity);
return postRequest;
} else {
return new HttpGet(request.getUrl());
}
}
case Method.GET:
return new HttpGet(request.getUrl());
case Method.DELETE:
return new HttpDelete(request.getUrl());
case Method.POST: {
HttpPost postRequest = new HttpPost(request.getUrl());
postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(postRequest, request);
return postRequest;
}
case Method.PUT: {
HttpPut putRequest = new HttpPut(request.getUrl());
putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(putRequest, request);
return putRequest;
}
case Method.HEAD:
return new HttpHead(request.getUrl());
case Method.OPTIONS:
return new HttpOptions(request.getUrl());
case Method.TRACE:
return new HttpTrace(request.getUrl());
case Method.PATCH: {
HttpPatch patchRequest = new HttpPatch(request.getUrl());
patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(patchRequest, request);
return patchRequest;
}
default:
throw new IllegalStateException("Unknown request method.");
}
}
private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest,
Request<?> request) throws AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
HttpEntity entity = new ByteArrayEntity(body);
httpRequest.setEntity(entity);
}
}
/**
* Called before the request is executed using the underlying HttpClient.
*
* <p>Overwrite in subclasses to augment the request.</p>
*/
protected void onPrepareRequest(HttpUriRequest request) throws IOException {
// Nothing.
}
/**
* The HttpPatch class does not exist in the Android framework, so this has been defined here.
* = =在HttpUriClient的子类中没有支持Patch的请求方法
* 在这里volley实现了= =
*/
public static final class HttpPatch extends HttpEntityEnclosingRequestBase {
public final static String METHOD_NAME = "PATCH";
public HttpPatch() {
super();
}
public HttpPatch(final URI uri) {
super();
setURI(uri);
}
/**
* @throws IllegalArgumentException if the uri is invalid.
*/
public HttpPatch(final String uri) {
super();
setURI(URI.create(uri));
}
@Override
public String getMethod() {
return METHOD_NAME;
}
}
}
4.HurlStack.java
在sdk大于2.3的android手机上,Volley选择用HttpURLConnection来实现网络请求。
/**
* An {@link HttpStack} based on {@link HttpURLConnection}.
*/
/**
* 当os version 版本在2.3以上,也就是sdk >= 9 的时候
* 选用这个接口作为HttpStack, 用到了HttpURLConnection
* 关于HttpURLConnection,官方解释为:
* An URLConnection for HTTP (RFC 2616) used to send and receive data over the web.
* Data may be of any type and length.
* This class may be used to send and receive streaming data whose length is not known in advance.
* 用来发送和接受数据,数据可以为任意的形式及长度
* 这个类常用来发送和接受数据流里面长度不定的数据.
*/
public class HurlStack implements HttpStack {
/*
* 请求header中的一个关键字
* content-type代表着被发送的请求中主体内容
* 可以设置application/json等格式
*/
private static final String HEADER_CONTENT_TYPE = "Content-Type";
/**
* An interface for transforming URLs before use.
* 一个用来在使用url之前,将url处理的接口工具
* 可能是用来规范url格式的一个工具= =
*/
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.
*/
public String rewriteUrl(String originalUrl);
}
private final UrlRewriter mUrlRewriter;
/**
* The abstract factory implementation to create SSLSockets.
* 是一个抽象工厂类,用来创建SSLSockets(还是不懂是个什么鬼
*
* 对于SSLSocket,官方的解释是这样的:
* The extension of Socket providing secure protocols like SSL (Secure Sockets Layer) or TLS (Transport Layer Security).
* 是Socket的子类,并在之基础上新增了类似于SSL或者TLS等等的安全协议.
*/
private final SSLSocketFactory mSslSocketFactory;
public HurlStack() {
this(null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
*/
public HurlStack(UrlRewriter urlRewriter) {
this(urlRewriter, 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;
}
/**
* 该函数为HttpStack的接口
*/
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
/**
* 得到请求的url
*/
String url = request.getUrl();
/**
* 创建一个新的HashMap
* 用来存放请求的header的信息
*/
HashMap<String, String> map = new HashMap<String, String>();
/**
* 将原request(volley自己封装的一个request类)中的header
* 和另外需要添加入header的信息都整合起来
*/
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
/**
* 将url字符串形式规范成一个URL的类对象
*/
URL parsedUrl = new URL(url);
/**
* HurlStack类是在sdk>=2.3的android版本上使用的
* 这里面用到了HttpURLConnection类
* 在函数里面打开了并返回了一个HttpURLConnection
* 设置了HttpURLConnection的响应超时阀值
*/
HttpURLConnection connection = openConnection(parsedUrl, request);
/**
* 开始给HttpURLConnection添加header的信息
* 用addRequestProperty()函数将header以键值对的形式填入
*/
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
/**
* 根据request种类的不同
* 分别用不同的方式来处理其中的参数
*/
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
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.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}
/**
* Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
* @param connection
* @return an HttpEntity populated with data from <code>connection</code>.
*/
private static HttpEntity entityFromConnection(HttpURLConnection connection) {
BasicHttpEntity entity = new BasicHttpEntity();
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch (IOException ioe) {
inputStream = connection.getErrorStream();
}
entity.setContent(inputStream);
entity.setContentLength(connection.getContentLength());
entity.setContentEncoding(connection.getContentEncoding());
entity.setContentType(connection.getContentType());
return entity;
}
/**
* Create an {@link HttpURLConnection} for the specified {@code url}.
*/
protected HttpURLConnection createConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
/**
* Opens an {@link HttpURLConnection} with parameters.
* 通过给的url和参数,打开一个HttpURLConnection
* @param url
* @return an open connection
* @throws IOException
*/
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = createConnection(url);
/**
* 通过Request.java中的函数
* 获取到该request上所设置的服务器最大响应时间阀值
* 该阀值默认是2500ms,而且可能会随着retry的次数而增大
*/
int timeoutMs = request.getTimeoutMs();
/**
* 给connection设置上请求超时时间
*/
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
/**
* use caller-provided custom SslSocketFactory, if any, for HTTPS
* 请求方面的安全问题,暂时还不清清楚
*/
if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
}
return connection;
}
@SuppressWarnings("deprecation")
/* package */
/**
* switch不同的请求方法
* 来以不同的方式给HttpURLConnection添加请求参数
*/
static void setConnectionParametersForRequest(HttpURLConnection connection,
Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
/**
* 在构造Request的时候如果没有指明请求方式
* DEPRECATED_GET_OR_POST为其默认值
* 通过postBody是否为Null来区别POST和GET
* 这两种最常用的请求方式
*/
case Method.DEPRECATED_GET_OR_POST:
// This is the deprecated way that needs to be handled for backwards compatibility.
// If the request's post body is null, then the assumption is that the request is
// GET. Otherwise, it is assumed that the request is a POST.
/**
* 不要用这个参数了= =,因为不能处理什么DELETE之类的
* 该方法已经过时了。
*/
byte[] postBody = request.getPostBody();
if (postBody != null) {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
// output stream.
/**
* 设置是否输出
*/
connection.setDoOutput(true);
/**
* 给connection设置请求的方式
*/
connection.setRequestMethod("POST");
/**
* 设置http请求头中的content-type参数
*/
connection.addRequestProperty(HEADER_CONTENT_TYPE,
request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(0);
out.close();
}
break;
case Method.GET:
// Not necessary to set the request method because connection defaults to GET but
// being explicit here.
connection.setRequestMethod("GET");
break;
case Method.DELETE:
connection.setRequestMethod("DELETE");
break;
case Method.POST:
connection.setRequestMethod("POST");
addBodyIfExists(connection, request);
break;
case Method.PUT:
connection.setRequestMethod("PUT");
addBodyIfExists(connection, request);
break;
case Method.HEAD:
connection.setRequestMethod("HEAD");
break;
case Method.OPTIONS:
connection.setRequestMethod("OPTIONS");
break;
case Method.TRACE:
connection.setRequestMethod("TRACE");
break;
case Method.PATCH:
connection.setRequestMethod("PATCH");
addBodyIfExists(connection, request);
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
/**
* 如果存在请求参数的话
* 获取到connection的输出流对象
* 并创建一个DataOutputStream对象
* 用于向服务器写入需要传递的参数
*/
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
connection.setDoOutput(true);
connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
}
}
如果上面的注释有什么错误的地方,还望大家给与指正= =。