Volley源码阅读

HttpStack接口及其实现

2018-07-17  本文已影响135人  Jaesoon

HttpStack接口及其实现

HttpStack

HttpStack是一个接口,定义了实现网络请求的方法:performRequest。
根据传入的请求信息以及其他二外的Header信息,实现网络请求,返回一个org.apache.http.HttpResponse。代码比较简单,关键是实现它的子类(这个类是将要已经被废弃了,但是,我看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一共有两个实现,一个是HttpClientStack,另一个是BaseHttpStack。这样做的原因是因为,安卓5.0之后,废弃了Apache的HttpClient,转用HttpURLConnection。所以,为了兼容创建了BaseHttpStack,在BaseHttpStack中另外定义了一个抽象方法excuteRequest,它重写的performRequest方法中,调用excuteRequest方法获取网络数据。

HttpClientStack

显然,它实现了HttpStack接口中定义的最重要的方法:performRequest。
它有一个内部类,我们先来看看这个内部类到底是个啥。

HttpPatch

先看类注释:The HttpPatch class does not exist in the Android framework, so this has been defined here.额,意思就是这个类本来是HttpClient已有的,奈何Android架构中不包含,所以,自己有重新来定义了一个。Apache的HttpClient的注释是这样的:PATCH方法请求将请求实体中描述的一组更改应用于Request-URI标识的资源。 与服务器处理封闭实体以修改Request-URI标识的资源的方式不同。 在PUT请求中,封闭的实体源服务器和客户端请求替换存储的版本。但是,使用PATCH,随附的实体包含一组指令,这些指令描述了如何修改当前驻留在源服务器上的资源以生成新版本。简单说,这个类的作用就是当请求类型为Patch时,用这个类来封装请求。

构造函数

这个方法只一个构造函数,参数为HttpClient。后面需要根据这个来进行网络请求。

    protected final HttpClient mClient;

    public HttpClientStack(HttpClient client) {
        mClient = client;
    }

实现performRequest

这个方法是网络请求的关键。关键内容不多,我们直接看源码。

    @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如何使用,我就不当搬用工了。

BaseHttpStack

这个是HttpStack的一个基础实现。内部的网络请求使用的是自己抽象的方法executeRequest。这在设计模式中,应该叫做装饰者模式。代码不长,直接来分析。

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;
    }
}

两个方法,一个是HttpStack中定义的performRequest,返回了org.apache.http.HttpResponse,另一个是抽象的executeRequest,返回了com.android.volley.toolbox.HttpResponse。先简单讲一下

HttpResponse

这个是Volley自带的对网络请求的一个封装,注意与org.apache.http.HttpResponse是不同的。包括:状态码、Header列表、内容长度和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;
    }
}

代码比较简单,一看即懂,不详细讲了。

HurlStack

Hurl是HttpUrlConnection的缩写。顾名思义,它是使用了HttpURLConnection实现网络请求的。它是BaseHttpStack的子类。上面讲过,BaseHttpStack有一个抽象方法-executeRequest。我们的HurlStack实现了这个方法。
我们先不急着看这个方法的代码,按照套路,看完继承之后,再看这个类是否有什么内部类。果然,它有两个内部类。一个是:

UrlRewriter

它是一个接口。

    /** 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);
    }

代码注释说,它是用于在使用前转换URL的一个接口。有一个方法 rewriteUrl,入参originalUrl,出参是转换后的url。返回一个URL来使用而不是使用提供的URL,或返回null以指示不应使用此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;
    }

从注释我们知道,这个类的作用是:它是HttpURLConnection的InputStream的装饰类,用于关闭连接。

构造方法

现在我们看看构造方法

    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;
    }

它有三个构造方法。用于设置UrlRewriter和SSLSocketFactory。UrlRewriter的作用是,对传入的url进行加工。SSLSocketFactory的作用是,当请求协议是Https,可以使用这样一个自定义的SSLSocketFactory。

executeRequest()方法

这个是重写BaseHttpStack的抽象方法。

    @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();
            }
        }
    }

过程简单,首先判断是否设置了个性化UrlRewriter,如果设置了,可以在这里调用自定义方法,对Url进行修饰。这个有啥作用呢?可能可以用来做Mocker吧。再创建HttpURLConnection,调用了openConnection()方法。在这个方法中,我们判断了Url的协议是不是https,如果是,就调用我们在构造方法中设置的SSLSocketFactory。然后调用connection.setRequestProperty()方法设置请求参数。然后就是更具Request来设置请求类型(POST、GET等)。
调用connection.getResponseCode()触发请求,得到响应码。然后判断响应怕是否异常,如果是,就抛出IO异常。接着调用hasResponseBody()方法,来判断是否得到了body。根据请求结果创建了一个com.android.volley.toolbox.HttpResponse,并将它返回。执行结束。至此,这个类的分析完毕了。

AdaptedHttpStack

这个的作用的是为了兼容性,添加的一个接口。

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

这个是用来测试的。代码比较简单,不分析了。

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;
    }
}
上一篇下一篇

猜你喜欢

热点阅读