Android网络编程(八) 之 HttpURLConnection原理分析

1 使用回顾

我们在前面博文《Android网络编程(四) 之 HttpClient与HttpURLConnection》中已经对HttpURLConnection的使用进行过介绍。今天我们接着往下来阅读HttpURLConnection的关键源码从而它进行更加深入的理解。开始前,先来回顾一下简单的使用,通过使用步骤来深入分析每行代码背后的原理,代码如:

InputStream inStream = null;
HttpURLConnection conn = null;
try {
    URL url = new URL("https://blog.csdn.net/lyz_zyx");
    conn = (HttpURLConnection)url.openConnection();
    if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
        inStream = conn.getInputStream();
        String result = inputStreamToString(inStream); // inputStreamToString方法用于解析转换,实现请见前面文章
        // ……
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (inStream != null) {
            inStream.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (conn != null) {
        conn.disconnect();
    }
}

这是一个最简单的使用例子,可见通过创建URL对象、创建HttpURLConnection对象、设置请求方法、获得响应状态代码并判断是否为HTTP_OK(200),最后解析结果。

2 原理分析

2.1 创建URL对象

来看看URL类,源码如下:

URL.java

public URL(String spec) throws MalformedURLException {
    this(null, spec);
}
public URL(URL context, String spec) throws MalformedURLException {
    this(context, spec, null);
}
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
    String original = spec;
    int i, limit, c;
    int start = 0;
    String newProtocol = null;
    boolean aRef=false;
    boolean isRelative = false;

    // Check for permission to specify a handler
    if (handler != null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkSpecifyHandler(sm);
        }
    }

    try {
        limit = spec.length();
        while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
            limit--;        //eliminate trailing whitespace
        }
        while ((start < limit) && (spec.charAt(start) <= ' ')) {
            start++;        // eliminate leading whitespace
        }

        if (spec.regionMatches(true, start, "url:", 0, 4)) {
            start += 4;
        }
        if (start < spec.length() && spec.charAt(start) == '#') {
            /* we're assuming this is a ref relative to the context URL.
             * This means protocols cannot start w/ '#', but we must parse
             * ref URL's like: "hello:there" w/ a ':' in them.
             */
            aRef=true;
        }
        for (i = start ; !aRef && (i < limit) &&
                 ((c = spec.charAt(i)) != '/') ; i++) {
            if (c == ':') {

                String s = spec.substring(start, i).toLowerCase();
                if (isValidProtocol(s)) {
                    newProtocol = s;
                    start = i + 1;
                }
                break;
            }
        }
        // 关键代码1,解析URL的协议,赋予类成员变量protocol
        // Only use our context if the protocols match.
        protocol = newProtocol;
        if ((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))) {
            // ……
        }

        if (protocol == null) {
            throw new MalformedURLException("no protocol: "+original);
        }

       // 关键代码2,获取URLStreamHandler对象赋予类成员变量handler
        if (handler == null && (handler = getURLStreamHandler(protocol)) == null) {
            throw new MalformedURLException("unknown protocol: "+protocol);
        }

        this.handler = handler;

        // ……

        // 关键代码3,调用URLStreamHandler对象的parseURL方法解析URL信息
        handler.parseURL(this, spec, start, limit);

    } catch(MalformedURLException e) {
        throw e;
    } catch(Exception e) {
        MalformedURLException exception = new MalformedURLException(e.getMessage());
        exception.initCause(e);
        throw exception;
    }
}

请看三个参数的构造函数,函数内部主要做了三个事情:

  1. 解析URL的协议,赋予类成员变量protocol,该逻辑在方法内完成,如上述代码;
  2. 通过方法getURLStreamHandler并传入protocol来获取URLStreamHandler对象赋予类成员变量handler;
  3. 调用变量handler(URLStreamHandler类对象)的parseURL方法解析URL信息。

2.1.1 getURLStreamHandler方法

来看看关键代码2getURLStreamHandler方法内部是如何获得URLStreamHandler对象的,代码见如下:

static URLStreamHandler getURLStreamHandler(String protocol) {

    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {
        // ……       

        // BEGIN Android-added: Custom built-in URLStreamHandlers for http, https.
        // Fallback to built-in stream handler.
        if (handler == null) {
            try {
                // 关键代码
                handler = createBuiltinHandler(protocol);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        // END Android-added: Custom built-in URLStreamHandlers for http, https.

        // ……
    }
    return handler;
}
private static URLStreamHandler createBuiltinHandler(String protocol)
        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    URLStreamHandler handler = null;
    if (protocol.equals("file")) {
        handler = new sun.net.www.protocol.file.Handler();
    } else if (protocol.equals("ftp")) {
        handler = new sun.net.www.protocol.ftp.Handler();
    } else if (protocol.equals("jar")) {
        handler = new sun.net.www.protocol.jar.Handler();
    } else if (protocol.equals("http")) {
        handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();
    } else if (protocol.equals("https")) {
        handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();
    }
    return handler;
}

URLStreamHandler方法接收第1步获得的协议变量protocol,然后内部又访问了一个关键的方法createBuiltinHandler,createBuiltinHandler方法内部通过判断协议类型来返回相对应的URLStreamHandler,这里我们只来讨论http和https协议情况,它们分别返回的URLStreamHandler对象是:com.android.okhttp.HttpHandler和com.android.okhttp.HttpsHandler。

com.android.okhttp.HttpHandler和com.android.okhttp.HttpsHandler的源码需要单独往https://android.googlesource.com/platform/external/okhttp/ 进行下载。HttpsHandler是继承自HttpHandler,区别在于添加了对TLS的支持,而且从源码可见,它们分别指定了默认端口是80和433。

HttpHandler.java

public class HttpHandler extends URLStreamHandler {
    // ……
}

HttpsHandler.java

public final class HttpsHandler extends HttpHandler {
    // ……
}

2.1.2 URLStreamHandler.parseURL方法

我们再回头看回URL类的构造函数关键代码3,它内部调用变量handler(URLStreamHandler类对象)的parseURL方法解析URL信息,代码见如下:

URLStreamHandler.java

protected void parseURL(URL u, String spec, int start, int limit) {
        // These fields may receive context content if this was relative URL
        String protocol = u.getProtocol();
        String authority = u.getAuthority();
        String userInfo = u.getUserInfo();
        String host = u.getHost();
        int port = u.getPort();
        String path = u.getPath();
        String query = u.getQuery();
        String ref = u.getRef();

        // ……
        setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
    }

   protected void setURL(URL u, String protocol, String host, int port,
                         String authority, String userInfo, String path,
                         String query, String ref) {
    if (this != u.handler) {
        throw new SecurityException("handler for url different from " + "this handler");
    }
    // ensure that no one can reset the protocol on a given URL.
    u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
}

parseURL方法中主要是解析出URL中的字符串信息,最后将这些字符串结果再回设置给URL类中去。以https://blog.csdn.net/lyz_zyx 为例,完成了URL对象的创建后,调用以下代码可见上述7个参数的值是:

URL url = new URL("https://blog.csdn.net/lyz_zyx");
String protocol = url.getProtocol();        // 代表协议,值是:https
String authority = url.getAuthority();      // 代表host和port两部分,值是:blog.csdn.net
String userInfo = url.getUserInfo();        // 代表用户信息,值是:null
String host = url.getHost();                 // 代表主机,值:blog.csdn.net
int port = url.getPort();                    // 代表端口,这里没有指定,所以值是:-1,如果使用的是getDefaultPort方法获取的话,值是:443
String path = url.getPath();                // 代表路径,值:/lyz_zyx
String query = url.getQuery();              // 代表参数,这里后面没跟有参数,值是:null
String ref = url.getRef();                  // 代表锚点,值是:null

2.2 创建HttpURLConnection对象

HttpURLConnection对象通过URL的openConnection方法返回,来看看代码:

URL.java

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

从上面创建URL对象得知,handler对象是HttpsHandler,而HttpsHandler又继承于HttpHandler,所以这里的实现代码在okhttp框架中的HttpHandler中,代码如下:

okhttp->HttpHandler.java

@Override 
protected URLConnection openConnection(URL url) throws IOException {
// 关键代码
    return newOkUrlFactory(null /* proxy */).open(url);
}
@Override 
protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
    if (url == null || proxy == null) {
        throw new IllegalArgumentException("url == null || proxy == null");
    }
    // 关键代码
    return newOkUrlFactory(proxy).open(url);
}

两个重载的方法都是指向于newOkUrlFactory方法,该方法虽然只有一行关键代码,但其实是做了两件事情:

  1. 通过newOkUrlFactory方法创建一个OkUrlFactory对象;
  2. 调用OkUrlFactory对象的open方法返回URLConnection对象。

2.2.1 newOkUrlFactory方法

来看看newOkUrlFactory方法的源代码做了什么:

okhttp->HttpHandler.java

protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
// 关键代码1,创建OkUrlFactory对象
    OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
    // For HttpURLConnections created through java.net.URL Android uses a connection pool that
    // is aware when the default network changes so that pooled connections are not re-used when
    // the default network changes.
// 关键代码2,设置连接池
    okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
    return okUrlFactory;
}

该方法最终是创建了一个newOkUrlFactory对象并返回,其中里面也做了两件事情:

1.    通过createHttpOkUrlFactory方法创建OkUrlFactory对象;
2.    给okUrlFactory对象的client方法返回的对象设置连接池。

2.2.1.1 createHttpOkUrlFactory方法

okhttp->HttpHandler.java

public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
    OkHttpClient client = new OkHttpClient();

    // Explicitly set the timeouts to infinity.
    client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
    client.setReadTimeout(0, TimeUnit.MILLISECONDS);
    client.setWriteTimeout(0, TimeUnit.MILLISECONDS);

    // Set the default (same protocol) redirect behavior. The default can be overridden for
    // each instance using HttpURLConnection.setInstanceFollowRedirects().
    client.setFollowRedirects(HttpURLConnection.getFollowRedirects());

    // Do not permit http -> https and https -> http redirects.
    client.setFollowSslRedirects(false);

    // Permit cleartext traffic only (this is a handler for HTTP, not for HTTPS).
    client.setConnectionSpecs(CLEARTEXT_ONLY);

    // When we do not set the Proxy explicitly OkHttp picks up a ProxySelector using
    // ProxySelector.getDefault().
    if (proxy != null) {
        client.setProxy(proxy);
    }

    // OkHttp requires that we explicitly set the response cache.
    OkUrlFactory okUrlFactory = new OkUrlFactory(client);

    // Use the installed NetworkSecurityPolicy to determine which requests are permitted over
    // http.
    OkUrlFactories.setUrlFilter(okUrlFactory, CLEARTEXT_FILTER);

    ResponseCache responseCache = ResponseCache.getDefault();
    if (responseCache != null) {
        AndroidInternal.setResponseCache(okUrlFactory, responseCache);
    }
    return okUrlFactory;
}

该方法中,首先new了一个OkHttpClient对象,然后给该对象设置了:连接超时时间、读超时时间、写超时时间、重定向相关、通信方式、代理等。

setConnectTimeout、setReadTimeout 和 setWriteTimeout,其值为0表示无超时。

setConnectionSpecs方法设置通信方式,CLEARTEXT_ONLY实际上是Collections.singletonList(ConnectionSpec.CLEARTEXT)的列表,表示不需要TLS,即明文传输。

接着通过OkHttpClient对象来创建一个OkUrlFactory对象,并对其设置指定host的服务器进行明文通信(setUrlFilter方法)和请求缓存信息(setResponseCache),最后就是返回对象。

HttpsHandler继承于HttpHandler,最大的区别在于OkUrlFactory对象的创建,我们也来看看它的源码:

okhttp->HttpsHandler.java

public static OkUrlFactory createHttpsOkUrlFactory(Proxy proxy) {
    // The HTTPS OkHttpClient is an HTTP OkHttpClient with extra configuration.
    OkUrlFactory okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);

    // All HTTPS requests are allowed.
    OkUrlFactories.setUrlFilter(okUrlFactory, null);

    OkHttpClient okHttpClient = okUrlFactory.client();

    // Only enable HTTP/1.1 (implies HTTP/1.0). Disable SPDY / HTTP/2.0.
    okHttpClient.setProtocols(HTTP_1_1_ONLY);

    okHttpClient.setConnectionSpecs(Collections.singletonList(TLS_CONNECTION_SPEC));

    // Android support certificate pinning via NetworkSecurityConfig so there is no need to
    // also expose OkHttp's mechanism. The OkHttpClient underlying https HttpsURLConnections
    // in Android should therefore always use the default certificate pinner, whose set of
    // {@code hostNamesToPin} is empty.
    okHttpClient.setCertificatePinner(CertificatePinner.DEFAULT);

    // OkHttp does not automatically honor the system-wide HostnameVerifier set with
    // HttpsURLConnection.setDefaultHostnameVerifier().
    okUrlFactory.client().setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
    // OkHttp does not automatically honor the system-wide SSLSocketFactory set with
    // HttpsURLConnection.setDefaultSSLSocketFactory().
    // See https://github.com/square/okhttp/issues/184 for details.
    okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());

    return okUrlFactory;
}

setProtocols:方法传入了HTTP_1_1_ONLY是一个列表,它对应着TLS握手ClientHello阶段中,支持最高的协议版本version。关于TLS握手过程,请参考《Android网络编程(三) 之 网络请求握手过程》

setConnectionSpecs:方法传入的是Collections.singletonList(TLS_CONNECTION_SPEC),表示支持TLS,即密文传输。

setCertificatePinner:方法设置证书锁定,在Android中始终使用默认的证书CertificatePinner.DEFAULT。

setHostnameVerifier:方法指定验证证书的HostnameVerifier。

sslSocketFactory:方法设置SSLSocket的工厂。

2.2.1.2 setConnectionPool方法

okhttp->OkHttpClient.java

public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
  this.connectionPool = connectionPool;
  return this;
}

通过setConnectionPool方法给OkUrlFactory内的OkHttpClient对象设置连接池configAwareConnectionPool.get(),来看看参数ConnectionPool是何方神圣代码:

okhttp->HttpHandler.java

private final ConfigAwareConnectionPool configAwareConnectionPool = ConfigAwareConnectionPool.getInstance();

configAwareConnectionPool事实上是一个ConfigAwareConnectionPool单例,再来看看ConfigAwareConnectionPool的代码:

okhttp->ConfigAwareConnectionPool.java

public synchronized ConnectionPool get() {
  if (connectionPool == null) {
    // Only register the listener once the first time a ConnectionPool is created.
    if (!networkEventListenerRegistered) {
      networkEventDispatcher.addListener(new NetworkEventListener() {
        @Override
        public void onNetworkConfigurationChanged() {
          synchronized (ConfigAwareConnectionPool.this) {
            // If the network config has changed then existing pooled connections should not be
            // re-used. By setting connectionPool to null it ensures that the next time
            // getConnectionPool() is called a new pool will be created.
            connectionPool = null;
          }
        }
      });
      networkEventListenerRegistered = true;
    }
    connectionPool = new ConnectionPool(
        CONNECTION_POOL_MAX_IDLE_CONNECTIONS, CONNECTION_POOL_KEEP_ALIVE_DURATION_MS);
  }
  return connectionPool;
}

代码中可见,get方法内做了监听网络变化事件,一旦网络发生变化,将会把connectionPool对象置为null,方法下面会执行创建出新的ConnectionPool对象,其构造函数参数:

CONNECTION_POOL_MAX_IDLE_CONNECTIONS:表示空闲TCP连接的最大数量,默认为5个。

CONNECTION_POOL_KEEP_ALIVE_DURATION_MS:表示TCP连接最长的空闲时长,默认为5分钟。

ConnectionPool类用于TCP连接池的封装,内部维护着一个双向的先进先出的ArrayDeque队列

2.2.2 open方法

看回上面,创建HttpURLConnection对象中的第2件事,调用OkUrlFactory对象的open方法,请看源代码:

okhttp->OkUrlFactory.java

public HttpURLConnection open(URL url) {
  return open(url, client.getProxy());
}
HttpURLConnection open(URL url, Proxy proxy) {
  String protocol = url.getProtocol();
  OkHttpClient copy = client.copyWithDefaults();
  copy.setProxy(proxy);

  if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
  if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
  throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}

可见,方法内根据协议创建出HttpURLConnectionImplHttpsURLConnectionImpl对象并返回。其中:

HttpURLConnectionImpl继承于HttpURLConnection;

HttpsURLConnectionImpl同样经过继承DelegatingHttpsURLConnection和HttpsURLConnection最终也是继承于HttpURLConnection。

2.3 执行网络请求

当创建了URL和HttpURLConnection对象后,接下来就是执行网络的请求。我们在使用上知道,无论是调用:conn.getResponseCode()、conn.connect() 还是conn.getInputStream() 都可以完成网络的请求操作。那么我们先来看看这三个方法到底内部做了些什么事情。请看代码:

HttpURLConnection.java

public int getResponseCode() throws IOException {
    if (responseCode != -1) {
        return responseCode;
    }
    Exception exc = null;
    try {
        getInputStream();
    } catch (Exception e) {
        exc = e;
    }

    // ……
}

getResponseCode方法很好理解,首先判断responseCode是否不为-1,代表如果已经执行过请求,就直接返回上次的请求码就可以。否则就执行getInputStream方法进行网络请求。可以理解成getResponseCode方法如果从未执行过网络请求的话,会调用到getInputStream方法中去完成网络请求。

我们再来看看connect方法代码:

okhttp->HttpURLConnectionImpl.java

@Override 
public final void connect() throws IOException {
  initHttpEngine();
  boolean success;
  do {
    success = execute(false);
  } while (!success);
}

connect方法很简单的几行代码,核心就是两件事情:initHttpEngine方法和execute方法。

我们再来看看getInputStream方法的代码:

okhttp->HttpURLConnectionImpl.java

@Override 
public final InputStream getInputStream() throws IOException {
  if (!doInput) {
    throw new ProtocolException("This protocol does not support input");
  }

  HttpEngine response = getResponse();

  // if the requested file does not exist, throw an exception formerly the
  // Error page from the server was returned if the requested file was
  // text/html this has changed to return FileNotFoundException for all
  // file types
  if (getResponseCode() >= HTTP_BAD_REQUEST) {
    throw new FileNotFoundException(url.toString());
  }

  return response.getResponse().body().byteStream();
}

getInputStream方法内里也是两件核心的事情:请求getResponse方法获得HttpEngine对象,然后返回HttpEngine对象的内容字节流。所以关键点就在于getResponse方法中,再继续看代码:

okhttp->HttpURLConnectionImpl.java

private HttpEngine getResponse() throws IOException {
  initHttpEngine();

  if (httpEngine.hasResponse()) {
    return httpEngine;
  }

  while (true) {
    if (!execute(true)) {
      continue;
    }

    Response response = httpEngine.getResponse();
    Request followUp = httpEngine.followUpRequest();

    if (followUp == null) {
      httpEngine.releaseStreamAllocation();
      return httpEngine;
    }

    if (++followUpCount > HttpEngine.MAX_FOLLOW_UPS) {
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    // The first request was insufficient. Prepare for another...
    url = followUp.url();
    requestHeaders = followUp.headers().newBuilder();

    // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM redirect
    // should keep the same method, Chrome, Firefox and the RI all issue GETs
    // when following any redirect.
    Sink requestBody = httpEngine.getRequestBody();
    if (!followUp.method().equals(method)) {
      requestBody = null;
    }

    if (requestBody != null && !(requestBody instanceof RetryableSink)) {
      throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
    }

    StreamAllocation streamAllocation = httpEngine.close();
    if (!httpEngine.sameConnection(followUp.httpUrl())) {
      streamAllocation.release();
      streamAllocation = null;
    }

    httpEngine = newHttpEngine(followUp.method(), streamAllocation, (RetryableSink) requestBody,
        response);
  }
}

从代码可见,getResponse方法内部同样也存在connect方法中要执行的initHttpEngine方法和execute方法。所以上述三个方法:getResponseCode、connect 和getInputStream,它们的核心原理就可以通过分析getResponse方法来得到答案了。先来了解一下getResponse方法内部有要关键就是调用了两个方法:

  1. initHttpEngine是初始化HttpEngine;
  2. execute是最终执行的发送网络请求。

2.3.1 initHttpEngine方法

来看看initHttpEngine方法源码。

okhttp->HttpURLConnectionImpl.java

private void initHttpEngine() throws IOException {
  if (httpEngineFailure != null) {
    throw httpEngineFailure;
  } else if (httpEngine != null) {
    return;
  }

  connected = true;
  try {
    if (doOutput) {
      if (method.equals("GET")) {
        // they are requesting a stream to write to. This implies a POST method
        method = "POST";
      } else if (!HttpMethod.permitsRequestBody(method)) {
        throw new ProtocolException(method + " does not support writing");
      }
    }
    // If the user set content length to zero, we know there will not be a request body.
    httpEngine = newHttpEngine(method, null, null, null);
  } catch (IOException e) {
    httpEngineFailure = e;
    throw e;
  }
}

上述方法中,会判断doOutput是否为true,如果是且请求方法是GET的话,会将其改为POST。先来聊聊doOutput和doInput是什么:

doOutput:表示是否通过请求体发送数据给服务端,默认为false。

doInput:表示是否读取服务端返回的响应体中的数据,默认为true。

因为GET请求方法是没有请求体的,所以如果doOutput为true的话,就要将其修改为POST的方式来请求。

接着,通过newHttpEngine方法来创建HttpEngine对象。往下看代码:

okhttp->HttpURLConnectionImpl.java

private HttpEngine newHttpEngine(String method, StreamAllocation streamAllocation, RetryableSink requestBody, Response priorResponse)
    throws MalformedURLException, UnknownHostException {
  // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
  RequestBody placeholderBody = HttpMethod.requiresRequestBody(method) ? EMPTY_REQUEST_BODY : null;
  URL url = getURL();
  HttpUrl httpUrl = Internal.instance.getHttpUrlChecked(url.toString());
 // 关键代码1,创建Request对象
  Request.Builder builder = new Request.Builder()
      .url(httpUrl)
      .method(method, placeholderBody);
  Headers headers = requestHeaders.build();
  for (int i = 0, size = headers.size(); i < size; i++) {
    builder.addHeader(headers.name(i), headers.value(i));
  }

  boolean bufferRequestBody = false;
  if (HttpMethod.permitsRequestBody(method)) {
    // Specify how the request body is terminated.
    if (fixedContentLength != -1) {
      builder.header("Content-Length", Long.toString(fixedContentLength));
    } else if (chunkLength > 0) {
      builder.header("Transfer-Encoding", "chunked");
    } else {
      bufferRequestBody = true;
    }

    // Add a content type for the request body, if one isn't already present.
    if (headers.get("Content-Type") == null) {
      builder.header("Content-Type", "application/x-www-form-urlencoded");
    }
  }

  if (headers.get("User-Agent") == null) {
    builder.header("User-Agent", defaultUserAgent());
  }

  Request request = builder.build();

  // If we're currently not using caches, make sure the engine's client doesn't have one.
  OkHttpClient engineClient = client;
  if (Internal.instance.internalCache(engineClient) != null && !getUseCaches()) {
    engineClient = client.clone().setCache(null);
  }
  // 关键代码2,创建 HttpEngine对象
  return new HttpEngine(engineClient, request, bufferRequestBody, true, false, streamAllocation, requestBody, priorResponse);
}

上述方法开始前,先通过HttpUrl和RequestBody来new了一个Request对象,然后给该对象添加header和设置:Content-Length、Transfer-Encoding、Content-Type、User-Agent等这些header值,更多header的介绍可见HTTP响应头和请求头信息对照表。最后就是创建HttpEngine对象,代码如下。

okhttp->HttpEngine.java

public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
    boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
    RetryableSink requestBodyOut, Response priorResponse) {
  this.client = client;
  this.userRequest = request;
  this.bufferRequestBody = bufferRequestBody;
  this.callerWritesRequestBody = callerWritesRequestBody;
  this.forWebSocket = forWebSocket;
  this.streamAllocation = streamAllocation != null ? streamAllocation : new StreamAllocation(client.getConnectionPool(), createAddress(client, request));
  this.requestBodyOut = requestBodyOut;
  this.priorResponse = priorResponse;
}

2.3.2 execute方法

完成了HttpEngine的初始化后,接着回到HttpURLConnectionImpl类中调用的execute方法做了什么事情。

okhttp->HttpURLConnectionImpl.java

private boolean execute(boolean readResponse) throws IOException {
  boolean releaseConnection = true;
  if (urlFilter != null) {
// 关键代码1,判断是否可以与指定host的服务器进行明文通信 
    urlFilter.checkURLPermitted(httpEngine.getRequest().url());
  }
  try {
//关键代码2,发起网络请求
    httpEngine.sendRequest();
    Connection connection = httpEngine.getConnection();
    if (connection != null) {
      route = connection.getRoute();
      handshake = connection.getHandshake();
    } else {
      route = null;
      handshake = null;
    }
//关键代码3,读取响应
    if (readResponse) {
      httpEngine.readResponse();
    }
    releaseConnection = false;

    return true;
  } catch (RequestException e) {
    // ……
  } catch (RouteException e) {
    // ……
  } catch (IOException e) {
    // ……
  } finally {
    // ……
  }
}

execute方法里做了3件事情:

  1. 判断指定host服务器进行明文通信
  2. 发起网络请求
  3. 读取响应

2.3.2.1判断指定host服务器进行明文通信

先看关键代码1处,方法先判断若urlFilter不为空,则调用checkURLPermitted来判断是否可以与指定host的服务器进行明文通信。urlFilter对象经构造方法传入,从OkUrlFactory.java中new HttpURLConnectionImpl或new HttpsURLConnectionImpl得知,urlFilter是在HttpHandler.java中通过setUrlFilter方法传入CLEARTEXT_FILTER,而在HttpsHandler.java中通过setUrlFilter方法传入null,所以具体实现代码在HttpHandler.java中。

okhttp->HttpHandler.java

private static final class CleartextURLFilter implements URLFilter {
    @Override
    public void checkURLPermitted(URL url) throws IOException {
        String host = url.getHost();
        if (!NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(host)) {
            throw new IOException("Cleartext HTTP traffic to " + host + " not permitted");
        }
    }
}

2.3.2.2 发起网络请求

看回execute中关键代码2,这里就是发起网络请求,我们看回HttpEngine类中的实现:

okhttp->HttpEngine.java

public void sendRequest() throws RequestException, RouteException, IOException {
  if (cacheStrategy != null) return; // Already sent.
  if (httpStream != null) throw new IllegalStateException();
 
// 关键代码1,通过newworkRequest方法处理Request对象,添加Host、Connection、Accept-Encoding、User-Agent请求头
  Request request = networkRequest(userRequest);

//关键代码2,获取缓存,默认情况下是开启缓存的,除非在HttpURLConnection中setUseCaches传入false
  InternalCache responseCache = Internal.instance.internalCache(client);
  Response cacheCandidate = responseCache != null ? responseCache.get(request) : null;

//关键代码2,通过CacheStrategy.Factory里缓存相关策略计算,获得networkRequest和cacheResponse两个对象,分别表示网络请求对象和缓存请求对象
  long now = System.currentTimeMillis();
  cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  networkRequest = cacheStrategy.networkRequest;
  cacheResponse = cacheStrategy.cacheResponse;

  if (responseCache != null) {
    responseCache.trackResponse(cacheStrategy);
  }

  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }

// 关键代码3,判断networkRequest不为null,表示无缓存或缓存过期,需求请求网络
  if (networkRequest != null) {
// 建立Socket连接
    httpStream = connect();
    httpStream.setHttpEngine(this);

    // If the caller's control flow writes the request body, we need to create that stream
    // immediately. And that means we need to immediately write the request headers, so we can
    // start streaming the request body. (We may already have a request body if we're retrying a failed POST.)
    if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
      long contentLength = OkHeaders.contentLength(request);
      if (bufferRequestBody) {
        if (contentLength > Integer.MAX_VALUE) {
          throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
              + "setChunkedStreamingMode() for requests larger than 2 GiB.");
        }

        if (contentLength != -1) {
          // 已知长度情况下,即设置过Content-Length情况下,往httpStream写入请求头信息和创建RetryableSink对象
          httpStream.writeRequestHeaders(networkRequest);
          requestBodyOut = new RetryableSink((int) contentLength);
        } else {
          // 未知长度情况下,只创建RetryableSink对象,待后面body准备完毕后再设置
          requestBodyOut = new RetryableSink();
        }
      } else {
        httpStream.writeRequestHeaders(networkRequest);
        requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
      }
    }
   } 
// 关键代码4,该请求的缓存有效,直接使用缓存请求
else {
    if (cacheResponse != null) {
      // We have a valid cached response. Promote it to the user response immediately.
      this.userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
    } else {
      // We're forbidden from using the network, and the cache is insufficient.
      this.userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
    }

    userResponse = unzip(userResponse);
  }
}

这方法很重要,我们将其分为做了4件事,如上述注释中的关键代码,分别有:

  1. 通过newworkRequest方法处理Request对象,添加Host、Connection、Accept-Encoding、User-Agent请求头;
  2. 通过计算缓存是否可用,然后获得网络请求对象networkRequest和缓存对象cacheResponse;
  3. 判断网络请求对象不为空,则执行网络相关请求操作;
  4. 否则执行缓存相关请求操作。

上述4件事中,我们重点来关注第2和第3件事情。

缓存策略

来来看下关键代码2中所做的第2件事,获得网络请求和缓存请求对象,关键看cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();这行代码所调用的逻辑。

okhttp->CacheStrategy.java

public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();
  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    return new CacheStrategy(null, null);
  }
  return candidate;
}

get方法调用了getCandidate方法获得返回的结果对象,判断如果禁止使用网络且缓存不足则返回一个新创建的networkRequest和cacheResponse都为空的CacheStrategy对象。我们重点来关注getCandidate方法的逻辑。

okhttp->CacheStrategy.java

private CacheStrategy getCandidate() {
  // 如果请求对应的缓存为空,则返回新创建的cacheResponse为空的CacheStrategy对象
  if (cacheResponse == null) {
    return new CacheStrategy(request, null);
  }

  // 如果缺少所需的握手,则返回新创建的cacheResponse为空的CacheStrategy对象
  if (request.isHttps() && cacheResponse.handshake() == null) {
    return new CacheStrategy(request, null);
  }

  // 如果不允许缓存,则返回新创建的cacheResponse为空的CacheStrategy对象
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }

  CacheControl requestCaching = request.cacheControl();
  if (requestCaching.noCache() || hasConditions(request)) {
    return new CacheStrategy(request, null);
  }

  long ageMillis = cacheResponseAge();
  long freshMillis = computeFreshnessLifetime();

  if (requestCaching.maxAgeSeconds() != -1) {
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

  long minFreshMillis = 0;
  if (requestCaching.minFreshSeconds() != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
  }

  long maxStaleMillis = 0;
  CacheControl responseCaching = cacheResponse.cacheControl();
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }

// 判断缓存是否过期,如果新鲜,则返回新创建的networkRequest为空的CacheStrategy对象
  if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
    Response.Builder builder = cacheResponse.newBuilder();
    if (ageMillis + minFreshMillis >= freshMillis) {
      builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
    }
    long oneDayMillis = 24 * 60 * 60 * 1000L;
    if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
      builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
    }
    return new CacheStrategy(null, builder.build());
  }

// 如果缓存已过期,需要进一步与服务端进行验证是否还可以使用
  Request.Builder conditionalRequestBuilder = request.newBuilder();

  if (etag != null) {
    conditionalRequestBuilder.header("If-None-Match", etag);
  } else if (lastModified != null) {
    conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
  } else if (servedDate != null) {
    conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
  }

  Request conditionalRequest = conditionalRequestBuilder.build();
  return hasConditions(conditionalRequest)
      ? new CacheStrategy(conditionalRequest, cacheResponse)
      : new CacheStrategy(conditionalRequest, null);
}

上述方法就是整个HttpURLConnection的缓存策略所关键的地方。缓存计算完毕后,接着就是来处理网络请求或者缓存请求了。

网络请求

再来来看下关键代码3中所做的第3件事,网络请求相关的逻辑,就要跳到connect方法去,请继续看代码。

okhttp->HttpEngine.java

private HttpStream connect() throws RouteException, RequestException, IOException {
  boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
  return streamAllocation.newStream(client.getConnectTimeout(),
      client.getReadTimeout(), client.getWriteTimeout(),
      client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
}

方法内主要是调用了streamAllocation对象的newStream方法,接着往下看

okhttp->StreamAllocation.java

public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws RouteException, IOException {
  try {
// 关键代码,寻找一个健康的连接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

    HttpStream resultStream;
    if (resultConnection.framedConnection != null) {
//使用HTTP/2协议
      resultStream = new Http2xStream(this, resultConnection.framedConnection);
    } else {
// 使用HTTP/1.x协议
      resultConnection.getSocket().setSoTimeout(readTimeout);
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
    }
    // ……
}
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException, RouteException {
  while (true) {
// 关键代码
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);

    // ……
  }
}
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException, RouteException {

  // ……
  Route route = routeSelector.next();
  RealConnection newConnection = new RealConnection(route);
  acquire(newConnection);

  synchronized (connectionPool) {
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }

// 关键代码,创建与服务端建立Socket连接
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.getRoute());

  return newConnection;
}

okhttp->RealConnection.java

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
    List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {

// ……

  while (protocol == null) {
    try {
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
          ? address.getSocketFactory().createSocket()
          : new Socket(proxy);
// 关键代码,发起与服务端的TCP连接
      connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
    } catch (IOException e) {
      // ……
    }
  }
}
private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  rawSocket.setSoTimeout(readTimeout);
  try {
// 关键代码,服务端建立TCP连接
    Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
  } catch (ConnectException e) {
    throw new ConnectException("Failed to connect to " + route.getSocketAddress());
  }
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));

  if (route.getAddress().getSslSocketFactory() != null) {
// 关键代码,TLS握手
    connectTls(readTimeout, writeTimeout, connectionSpecSelector);
  } else {
    protocol = Protocol.HTTP_1_1;
    socket = rawSocket;
  }
  // ……
}

由于底层代码过多和复杂的原因,这里省略了较多的过程,只保留一部分关键代码,我们对其进行一个大概的了解即可。能看见得是,这些方法的逻辑中最后就是跟Socket进行相应的处理。

2.3.2.3 读取响应

完成了请求后,最后就是读取响应了,继续看回execute中关键代码。

okhttp->HttpEngine.java

public void readResponse() throws IOException {
  if (userResponse != null) {
    return; // Already ready.
  }
  if (networkRequest == null && cacheResponse == null) {
    throw new IllegalStateException("call sendRequest() first!");
  }
  if (networkRequest == null) {
    return; // No network response to read.
  }

  Response networkResponse;

  if (forWebSocket) {
    httpStream.writeRequestHeaders(networkRequest);
    networkResponse = readNetworkResponse();

  } else if (!callerWritesRequestBody) {
    networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);

  } else {
    // ……
// 关键代码1,获得网络请求结果
    networkResponse = readNetworkResponse();
  }

  receiveHeaders(networkResponse.headers());

  // If we have a cache response too, then we're doing a conditional get.
  if (cacheResponse != null) {
    if (validate(cacheResponse, networkResponse)) {
//关键代码2,读取缓存请求结果
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();
      releaseStreamAllocation();

      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      InternalCache responseCache = Internal.instance.internalCache(client);
      responseCache.trackConditionalCacheHit();
      responseCache.update(cacheResponse, stripBody(userResponse));
      userResponse = unzip(userResponse);
      return;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }

//关键代码3,读取网络请求结果
  userResponse = networkResponse.newBuilder()
      .request(userRequest)
      .priorResponse(stripBody(priorResponse))
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();

  if (hasBody(userResponse)) {
//关键代码4,保存缓存
    maybeCache();
    userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
  }
}
private void maybeCache() throws IOException {
  InternalCache responseCache = Internal.instance.internalCache(client);
  if (responseCache == null) return;

  // Should we cache this response for this request?
  if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
    if (HttpMethod.invalidatesCache(networkRequest.method())) {
      try {
// 关键代码,移除无效的缓存
        responseCache.remove(networkRequest);
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
    }
    return;
  }

  // 关键代码,将本次请求结果缓存
  storeRequest = responseCache.put(stripBody(userResponse));
}

从上述注释可见,方法开头先判断readResponse是否为真,因为我们从前面得知,通过conn.connect()进行请求的话,传入的是false,而通过conn.getInputStream()进行请求会传入true。

过程中关键代码1处是获得网络请求的结果,然后去验证网络请求的结果和缓存请求的结果哪个是有效的,而进行相应的结果读取,最后就是更新缓存。

3 原理总结

到这里,通过源码分析原理就全部完成了,整个过程代码量还是非常多。我们平时在学习优秀框架时,并不一定全部代码完全读懂,其实能明白大概原理以及从中吸收框架设计的思想就已经足够了。那我们现在用简短的话来总结一下HttpURLConnection的原理:

第1  首先new了一个URL类的对象,内部就是解析URL的协议、主机名、端口、路径、参数等信息。

    1.1 其中在解析协议时创建对应的URLStreamHandler对象,若协议是http或https的话,其URLStreamHandler对象是google的okhttp框架中的com.android.okhttp.HttpHandler和com.android.okhttp.HttpsHandler。

   1.2 所以说,使用httpURLConnection框架进行网络请求,实际上就是使用okhttp进行的。

第2  通过URL类的openConnection方法创建HttpURLConnection类对象,现实过程也是在okhttp中进行,

   2.1 内部先创建一个指定的host服务端和请求缓存信息的OkUrlFactory对象,该对象内包含着一个OkHttpClient对象,过程中给OkHttpClient对象设置连接时间、读写时间、通信方式(是否需要TLS)、连接池(双向的先进先出的ArrayDeque队列,默认空闲TCP连接的最大数量为5个,默认TCP连接最长的空闲时长为5分钟)。

   2.2 创建OkHttpClient对象后便调用其open方法返回HttpURLConnection的实现类对象:HttpURLConnectionImpl或HttpsURLConnectionImpl。

第3  创建完了HttpURLConnection对象后,就是让其执行网络请求了。

   3.1 通过简单分析得知,无论是conn.getResponseCode()、conn.connect() 还是conn.getInputStream() 进行网络请求,最终会调用到initHttpEngineexecute方法

   3.2 initHttpEngine方法从名字便得知是初始化一个HttpEngine对象,过程中通过创建一个Request对象,并使Request对象添加header和设置:Content-Length、Transfer-Encoding、Content-Type、User-Agent等这些header值。

   3.3 execute方法就是核心网络请求的逻辑,其内部主要是先通过一个缓存策略计算缓存是否不为空、是否握手正常、是否允许缓存、是否新鲜等逻辑来返回一个缓存请求对象cacheResponse和网络请求对象。如果缓存方案有效,则网络请求对象是null,相反若缓存方案无效,该网络请求对象就是上面所创建的Request对象。网络请求过程内在就是通过Socket来进行与服务端的TCP握手和TLS握手连接的相关网络处理。

   3.4 最后同样判断缓存请求或网络请求对象是否有效再进行相应的结果读取,并更新缓存。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章