一、概述
在上一篇文章Android网络编程之HTTP协议中,我们分析了HTTP协议的协议规则,以及其传输方式;我们知道HTTP协议是属于应用层的协议,其主要规定了传输数据的格式,以便通信双方都能按照规定的格式来解读数据,而真正的数据传输是基于传输层的TCP协议。在平时的Android开发中,HTTP网络请求使用到的框架一般都离不开HttpClient和HttpURLConnection,无论是我们自己封装的网络请求类还是第三方网络请求框架,都离不开这两个库,一般来说,在Android2.3版本以前,用HttpClient是较好的选择,而在2.3版本以后,HttpURLConnection则是更好的选择,它的API简单,体积较小,更适用于Android开发。在这篇文章中,我们就来分析一下HttpURLConnetion的源码实现,来更进一步的了解HTTP协议在Android中的运用。
二、HttpURLConnection源码分析
首先我们看一下HttpURlConnection的基本用法:
new Thread() {
@Override
public void run() {
try {
URL url = new URL("https://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream is = connection.getInputStream();
String response = convertStreamToString(is);
Log.i("liunianprint:", response);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
上面的代码利用HttpURLConnection发起了一个GET请求,请求百度的首页并将结果打印出来。
废话不多说,直接上源码
1、创建URL对象
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) // spec是传入进来的URL字符串
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(); // 获得URL字符串长度
while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) { // 去除URL字符串尾部的无用字符
limit--; //eliminate trailing whitespace
}
while ((start < limit) && (spec.charAt(start) <= ' ')) { // 去除URL字符串首部的无用字符
start++; // eliminate leading whitespace
}
if (spec.regionMatches(true, start, "url:", 0, 4)) { // 如果URL字符串是以"url:"开头,则将其去除
start += 4;
}
if (start < spec.length() && spec.charAt(start) == '#') { // 如果URL字符串是以"#"开头,则将aRef设置为true
/* 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;
}
// 解析URL字符串中使用的协议,比如http或者https
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;
}
}
// Only use our context if the protocols match.
protocol = newProtocol; // 保存协议字符串
if ((context != null) && ((newProtocol == null) ||
newProtocol.equalsIgnoreCase(context.protocol))) { // 判断是否需要使用context里面的信息
// inherit the protocol handler from the context
// if not specified to the constructor
if (handler == null) {
handler = context.handler;
}
// If the context is a hierarchical URL scheme and the spec
// contains a matching scheme then maintain backwards
// compatibility and treat it as if the spec didn't contain
// the scheme; see 5.2.3 of RFC2396
if (context.path != null && context.path.startsWith("/"))
newProtocol = null;
if (newProtocol == null) {
protocol = context.protocol;
authority = context.authority;
userInfo = context.userInfo;
host = context.host;
port = context.port;
file = context.file;
path = context.path;
isRelative = true;
}
}
if (protocol == null) {
throw new MalformedURLException("no protocol: "+original);
}
// Get the protocol handler if not specified or the protocol
// of the context could not be used
if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) { // 获得协议对应的URLStreamHandler
throw new MalformedURLException("unknown protocol: "+protocol);
}
this.handler = handler;
// 获得URL字符串中“#”后面的字符串,“#”后面的字符串表示网页加载完成后要定向的位置,“#”后面的字符串不参与网络请求
i = spec.indexOf('#', start);
if (i >= 0) {
ref = spec.substring(i + 1, limit);
limit = i;
}
/*
* Handle special case inheritance of query and fragment
* implied by RFC2396 section 5.2.2.
*/
if (isRelative && start == limit) {
query = context.query;
if (ref == null) {
ref = context.ref;
}
}
// 解析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;
}
}
分析上面的代码,URL的构造函数主要做了三件事:
1、解析URL字符串中代表协议的字符串,比如http、https等
2、获得协议对应的URLStreamHandler
3、利用获得的URLStreamHandler解析URL字符串
getURLStreamHandler
if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) { // 获得协议对应的URLStreamHandler
throw new MalformedURLException("unknown protocol: "+protocol);
}
可以看到是通过调用getURLStreamHandler方法来获得URLStreamHandler的,代码如下:
/**
* Returns the Stream Handler.
* @param protocol the protocol to use
*/
static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = (URLStreamHandler)handlers.get(protocol); // handlers是Hashtable类型的缓存,用来缓存已经创建好的URLStreamHandler
if (handler == null) {
boolean checkedWithFactory = false;
// Use the factory (if any)
if (factory != null) { // 尝试用factory创建handler
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
// Try java protocol handler
if (handler == null) { // 尝试用java protocal handler
final String packagePrefixList = System.getProperty(protocolPathProp,"");
StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|");
while (handler == null &&
packagePrefixIter.hasMoreTokens()) {
String packagePrefix = packagePrefixIter.nextToken().trim();
try {
String clsName = packagePrefix + "." + protocol + ".Handler";
Class cls = null;
try {
ClassLoader cl = ClassLoader.getSystemClassLoader();
cls = Class.forName(clsName, true, cl);
} catch (ClassNotFoundException e) {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null) {
cls = Class.forName(clsName, true, contextLoader);
}
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (ReflectiveOperationException ignored) {
}
}
}
// Fallback to built-in stream handler.
// Makes okhttp the default http/https handler
if (handler == null) { // 判断协议类型,利用反射创建相应的URLStreamHandler
try {
if (protocol.equals("file")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.file.Handler").newInstance();
} else if (protocol.equals("ftp")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.ftp.Handler").newInstance();
} else if (protocol.equals("jar")) {
handler = (URLStreamHandler)Class.
forName("sun.net.www.protocol.jar.Handler").newInstance();
} 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();
}
} catch (Exception e) {
throw new AssertionError(e);
}
}
synchronized (streamHandlerLock) {
URLStreamHandler handler2 = null;
// 这里为了避免另外一个线程创建了对应协议的handler,再次做一遍检查
// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = (URLStreamHandler)handlers.get(protocol);
if (handler2 != null) {
return handler2;
}
// Check with factory if another thread set a
// factory since our last check
// 这里为了避免另外一个线程设置了factory,再次做一遍检查
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}
if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}
// Insert this handler into the hashtable
if (handler != null) {
handlers.put(protocol, handler); // 将创建的handler放入handlers中
}
}
}
return handler;
}
getURLStreamHandler主要通过反射获得对应协议的URLStreamHandler对象,并将其缓存在HashTable中,可以看到http协议对应的类是"com.android.okhttp.HttpHandler",https协议对应的类是"com.android.okhttp.HttpsHandler",接下来我们下载Android源码中内嵌的OkHttp代码,就可以找到HttpHandler和HttpsHandler的源码,但是发现其包名都是“com.squareup.okhttp”,并不是“com.android.okhttp”,这是怎么回事呢?我们打开okhttp项目下面的jarjar-rules.txt文件:
rule com.squareup.** com.android.@1
rule okio.** com.android.okhttp.okio.@1
第一行表示将包名“com.squareup”全部替换为“com.android”。
parseURL
paseURL的作用是解析URL字符串,并保存在URL对象中,它是URLStreamHandler的一个方法,源码如下:
protected void parseURL(URL u, String spec, int start, int limit) { // 这里的start已经是不包含协议的部分了,这个在URL的构造函数中有体现
// 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();
// This field has already been parsed
String ref = u.getRef();
boolean isRelPath = false;
boolean queryOnly = false;
// ----- BEGIN android -----
boolean querySet = false;
// ----- END android -----
// FIX: should not assume query if opaque
// Strip off the query part
// 解析查询参数字符串,“?”后面的内容表示查询参数
if (start < limit) {
int queryStart = spec.indexOf('?');
queryOnly = queryStart == start;
if ((queryStart != -1) && (queryStart < limit)) {
query = spec.substring(queryStart+1, limit);
if (limit > queryStart)
limit = queryStart;
spec = spec.substring(0, queryStart);
// ----- BEGIN android -----
querySet = true;
// ----- END android -----
}
}
int i = 0;
// Parse the authority part if any
// ----- BEGIN android -----
// boolean isUNCName = (start <= limit - 4) &&
// (spec.charAt(start) == '/') &&
// (spec.charAt(start + 1) == '/') &&
// (spec.charAt(start + 2) == '/') &&
// (spec.charAt(start + 3) == '/');
boolean isUNCName = false;
// ----- END android -----
if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
(spec.charAt(start + 1) == '/')) { // 如果前两个字符是“//”
// 获得host字符串
start += 2;
i = spec.indexOf('/', start);
if (i < 0) {
i = spec.indexOf('?', start);
if (i < 0)
i = limit;
}
host = authority = spec.substring(start, i);
// 获得userinfo信息
int ind = authority.indexOf('@');
if (ind != -1) {
userInfo = authority.substring(0, ind);
host = authority.substring(ind+1);
} else {
userInfo = null;
}
if (host != null) {
// If the host is surrounded by [ and ] then its an IPv6
// literal address as specified in RFC2732
// 如果host是以“[]”包括,则说明其是一个IPv6格式的地址
if (host.length()>0 && (host.charAt(0) == '[')) {
if ((ind = host.indexOf(']')) > 2) {
String nhost = host ;
host = nhost.substring(0,ind+1); // 解析出host
if (!IPAddressUtil.
isIPv6LiteralAddress(host.substring(1, ind))) {
throw new IllegalArgumentException(
"Invalid host: "+ host);
}
port = -1 ;
if (nhost.length() > ind+1) {
if (nhost.charAt(ind+1) == ':') { // “:”后面表示端口号
++ind ;
// port can be null according to RFC2396
if (nhost.length() > (ind + 1)) {
port = Integer.parseInt(nhost.substring(ind+1)); // 获得端口
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
} else {
ind = host.indexOf(':');
port = -1;
if (ind >= 0) {
// port can be null according to RFC2396
if (host.length() > (ind + 1)) {
// ----- BEGIN android -----
// port = Integer.parseInt(host.substring(ind + 1));
char firstPortChar = host.charAt(ind+1);
if (firstPortChar >= '0' && firstPortChar <= '9') {
port = Integer.parseInt(host.substring(ind + 1)); // 获得端口
} else {
throw new IllegalArgumentException("invalid port: " +
host.substring(ind + 1));
}
// ----- END android -----
}
host = host.substring(0, ind); // 获得host
}
}
} else {
host = "";
}
if (port < -1)
throw new IllegalArgumentException("Invalid port number :" +
port);
start = i;
// ----- BEGIN android -----
// If the authority is defined then the path is defined by the
// spec only; See RFC 2396 Section 5.2.4.
// if (authority != null && authority.length() > 0)
// path = "";
path = null;
if (!querySet) {
query = null;
}
// ----- END android -----
}
if (host == null) {
host = "";
}
// Parse the file path if any
// 解析path
if (start < limit) {
if (spec.charAt(start) == '/') {
path = spec.substring(start, limit);
} else if (path != null && path.length() > 0) {
isRelPath = true;
int ind = path.lastIndexOf('/');
String seperator = "";
if (ind == -1 && authority != null)
seperator = "/";
path = path.substring(0, ind + 1) + seperator +
spec.substring(start, limit);
} else {
String seperator = (authority != null) ? "/" : "";
path = seperator + spec.substring(start, limit);
}
}
// ----- BEGIN android -----
//else if (queryOnly && path != null) {
// int ind = path.lastIndexOf('/');
// if (ind < 0)
// ind = 0;
// path = path.substring(0, ind) + "/";
//}
// ----- END android -----
if (path == null)
path = "";
// ----- BEGIN android -----
//if (isRelPath) {
if (true) {
// ----- END android -----
// Remove embedded /./
while ((i = path.indexOf("/./")) >= 0) {
path = path.substring(0, i) + path.substring(i + 2);
}
// Remove embedded /../ if possible
i = 0;
while ((i = path.indexOf("/../", i)) >= 0) {
// ----- BEGIN android -----
/*
* Trailing /../
*/
if (i == 0) {
path = path.substring(i + 3);
i = 0;
// ----- END android -----
/*
* A "/../" will cancel the previous segment and itself,
* unless that segment is a "/../" itself
* i.e. "/a/b/../c" becomes "/a/c"
* but "/../../a" should stay unchanged
*/
} else if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
(path.indexOf("/../", limit) != 0)) {
path = path.substring(0, limit) + path.substring(i + 3);
i = 0;
} else {
i = i + 3;
}
}
// Remove trailing .. if possible
while (path.endsWith("/..")) {
i = path.indexOf("/..");
if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
path = path.substring(0, limit+1);
} else {
break;
}
}
// Remove starting .
if (path.startsWith("./") && path.length() > 2)
path = path.substring(2);
// Remove trailing .
if (path.endsWith("/."))
path = path.substring(0, path.length() -1);
// Remove trailing ?
if (path.endsWith("?"))
path = path.substring(0, path.length() -1);
}
setURL(u, protocol, host, port, authority, userInfo, path, query, ref); // 将解析的结果设置给URL对象
}
我们可以结合URL字符串的结构来理解parseURL的工作原理:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
scheme:传送协议。
//:层级URL标记符号(为[//],固定不变)
user[:password]@:访问资源需要的凭证信息(可省略)
host:服务器。(通常为域名,有时为IP地址)
port:端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
path:路径。(以“/”字符区别路径中的每一个目录名称)
query:查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
fragment:片段。以“#”字符为起点
parseURL方法就是按照上面的规则来解析URL字符串的,并将解析解绑保存在URL对象中。
2、创建HttpURLConnection实例
在创建URL对象后,我们需要调用URL的openConnection()方法获得一个HttpURLConnection实例:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
点开openConnection的源码:
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
这里的handler对象是在URL的构造方法中创建的,我们前面说过http协议对应的handler类型是“com.squareup.okhttp.HttpHandler”,https协议对应的handler类型是“com.squareup.okhttp.HttpsHandler”,我们先分析HttpHandler的源码:
public class HttpHandler extends URLStreamHandler {
private final static List<ConnectionSpec> CLEARTEXT_ONLY =
Collections.singletonList(ConnectionSpec.CLEARTEXT);
private static final CleartextURLFilter CLEARTEXT_FILTER = new CleartextURLFilter();
private final ConfigAwareConnectionPool configAwareConnectionPool =
ConfigAwareConnectionPool.getInstance();
@Override protected URLConnection openConnection(URL url) throws IOException {
return newOkUrlFactory(null /* proxy */).open(url); // 创建一个OkUrlFactory对象并调用其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);
}
@Override protected int getDefaultPort() {
return 80;
}
protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy); // 调用createHttpOkUrlFactory创建一个OkUrlFactory对象
// 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.
okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get()); // 设置连接池
return okUrlFactory;
}
/**
* Creates an OkHttpClient suitable for creating {@link java.net.HttpURLConnection} instances on
* Android.
*/
// Visible for android.net.Network.
public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
OkHttpClient client = new OkHttpClient(); // 创建一个OkHttpClient对象
// Explicitly set the timeouts to infinity.
client.setConnectTimeout(0, TimeUnit.MILLISECONDS); // 设置连接超时时间,0代表超时时间为无穷大,即没有超时;可以通过调用HttpURLConnection的setConnectTimeout方法设置该值
client.setReadTimeout(0, TimeUnit.MILLISECONDS); // 设置读的超时时间,0代表超时时间为无穷大,即没有超时;可以通过调用HttpURLConnection的setReadTimeout方法设置该值
client.setWriteTimeout(0, TimeUnit.MILLISECONDS); // 设置写的超时时间,0代表超时时间为无穷大,即没有超时;可以通过调用HttpURLConnection的setWriteTimeout方法设置该值
// Set the default (same protocol) redirect behavior. The default can be overridden for
// each instance using HttpURLConnection.setInstanceFollowRedirects().
client.setFollowRedirects(HttpURLConnection.getFollowRedirects()); // 设置默认的处理重定向的对象,可以通过HttpURLConnection.setInstanceFollowRedirects()来设置
// 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); // CLEARTEXT_ONLY,代表不需要TLS,即明文传输
// 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;
}
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");
}
}
}
}
HttpHandler的openConnection方法主要是创建了一个OkUrlFactory对象,并调用其open方法来处理url,我们打开OkUrlFactory的open方法:
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);
}
可以看到,如果协议是http协议,则返回一个HttpURLConnectionImpl对象,如果是https对象,则返回一个HttpsURLConnectionImpl对象,HttpURLConnectionImpl和HttpsURLConnectionImpl都是HttpURLConnection的子类。
3、调用HttpURLConnection的connect()方法打开连接
abstract public void connect() throws IOException;
可以看到,connect()是一个抽象方法,其具体实现是在父类中实现的,我们先看HttpURLConnectionImpl的connect()方法:
@Override public final void connect() throws IOException {
initHttpEngine();
boolean success;
do {
// execute方法用于发送请求,如果请求成功执行,则返回true,如果请求可以重试,则返回false,
// 如果请求永久失败,则抛出异常。
success = execute(false);
} while (!success);
}
private void initHttpEngine() throws IOException {
if (httpEngineFailure != null) {
throw httpEngineFailure;
} else if (httpEngine != null) {
return;
}
connected = true;
try {
if (doOutput) { // 如果doOutPut设置为ture,表示可以通过给请求体写入数据向服务端发送数据,这个时候如果请求方法设置为“GET”,会强制将其改为“post”,因为“GET”是不能像请求体写入数据的
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); // 创建一个HttpEngine对象
} catch (IOException e) {
httpEngineFailure = e;
throw e;
}
}
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.
// 根据请求的url、method、请求头(通过HttpURLConnection的setRequestProperty方法设置的)
// 创建Request实例
RequestBody placeholderBody = HttpMethod.requiresRequestBody(method) // 判断请求方法是否有请求体
? EMPTY_REQUEST_BODY
: null;
URL url = getURL();
HttpUrl httpUrl = Internal.instance.getHttpUrlChecked(url.toString());
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);
}
return new HttpEngine(engineClient, request, bufferRequestBody, true, false, streamAllocation,
requestBody, priorResponse);
}
private String defaultUserAgent() {
String agent = System.getProperty("http.agent");
return agent != null ? Util.toHumanReadableAscii(agent) : Version.userAgent();
}
/**
* Aggressively tries to get the final HTTP response, potentially making
* many HTTP requests in the process in order to cope with redirects and
* authentication.
*/
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);
}
}
connect()方法首先初始化一个HttpEngine对象,然后调用execute方法执行请求,我们再来看一下HttpEngine的构造方法:
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;
}
private static Address createAddress(OkHttpClient client, Request request) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (request.isHttps()) {
sslSocketFactory = client.getSslSocketFactory();
hostnameVerifier = client.getHostnameVerifier();
certificatePinner = client.getCertificatePinner();
}
return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
client.getAuthenticator(), client.getProxy(), client.getProtocols(),
client.getConnectionSpecs(), client.getProxySelector());
}
回到HttpURLConnectionImpl的execute方法:
private boolean execute(boolean readResponse) throws IOException {
boolean releaseConnection = true;
if (urlFilter != null) {
urlFilter.checkURLPermitted(httpEngine.getRequest().url());
}
try {
// 发起请求
httpEngine.sendRequest();
Connection connection = httpEngine.getConnection();
if (connection != null) {
route = connection.getRoute();
handshake = connection.getHandshake();
} else {
route = null;
handshake = null;
}
// 读取响应
if (readResponse) {
httpEngine.readResponse();
}
releaseConnection = false;
return true;
} catch (RequestException e) {
// An attempt to interpret a request failed.
IOException toThrow = e.getCause();
httpEngineFailure = toThrow;
throw toThrow;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
HttpEngine retryEngine = httpEngine.recover(e);
if (retryEngine != null) {
releaseConnection = false;
httpEngine = retryEngine;
return false;
}
// Give up; recovery is not possible.
IOException toThrow = e.getLastConnectException();
httpEngineFailure = toThrow;
throw toThrow;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
HttpEngine retryEngine = httpEngine.recover(e);
if (retryEngine != null) {
releaseConnection = false;
httpEngine = retryEngine;
return false;
}
// Give up; recovery is not possible.
httpEngineFailure = e;
throw e;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
StreamAllocation streamAllocation = httpEngine.close();
streamAllocation.release();
}
}
}
发送请求调用的是HttpEngine的sendRequest()方法:
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
Request request = networkRequest(userRequest);
// 尝试从缓存中获得响应内容
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
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.
}
// 从缓存中获取失败则需要请求服务端
if (networkRequest != null) {
httpStream = connect(); // 创建socket连接
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) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request
// headers until the entire body is ready; otherwise we can't set the
// Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
} 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);
}
}
connect()方法:
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);
}
在HttpEngine的构造方法中有对streamAllocation初始化:
public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
RetryableSink requestBodyOut, Response priorResponse) {
...
this.streamAllocation = streamAllocation != null
? streamAllocation
: new StreamAllocation(client.getConnectionPool(), createAddress(client, request));
...
}
当传入的streamAllocation为空时,会创建一个StreamAllocation,并且一个ConnectionPool和Address对象,ConnectionPool是一个连接池类,它用来管理HttpURLConnection中所用到的Socket连接,主要处理连接的复用以及回收工作。Address主要是用来存储请求的一些基本信息,比如host、port、protocols等:
private static Address createAddress(OkHttpClient client, Request request) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (request.isHttps()) {
sslSocketFactory = client.getSslSocketFactory();
hostnameVerifier = client.getHostnameVerifier();
certificatePinner = client.getCertificatePinner();
}
return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
client.getAuthenticator(), client.getProxy(), client.getProtocols(),
client.getConnectionSpecs(), client.getProxySelector());
}
}
OkHttpClient的ConnectionPool对象是在HttpHandler中设置的,代码如下:
private final ConfigAwareConnectionPool configAwareConnectionPool =
ConfigAwareConnectionPool.getInstance();
protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy); // 调用createHttpOkUrlFactory创建一个OkUrlFactory对象
// 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.
okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get()); // 设置连接池
return okUrlFactory;
}
在ConfigAwareConnectionPool中:
private static final ConfigAwareConnectionPool instance = new ConfigAwareConnectionPool();
public static ConfigAwareConnectionPool getInstance() {
return instance;
}
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;
}
我们继续看StreamAllocation的newStream方法:
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) { // 如果是http2的协议
resultStream = new Http2xStream(this, resultConnection.framedConnection);
} else {
resultConnection.getSocket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
resultConnection.streamCount++;
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
newStream主要是获得连接对象,通过连接对象构造一个HttpStream对象并返回。
我们来看一下获得连接的方法:
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
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);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) { // 如果是新建的连接,就不用检查连接是否有效
if (candidate.streamCount == 0) {
return candidate;
}
}
// Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
if (candidate.isHealthy(doExtensiveHealthChecks)) { // 检查从连接池获得的连接是否有效,如果无效则从连接池中移除这个连接,然后重新获得连接
return candidate;
}
connectionFailed();
}
}
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException, RouteException {
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (stream != null) throw new IllegalStateException("stream != null");
if (canceled) throw new IOException("Canceled");
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
// 尝试从连接池中获得一个连接
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}
// Attempt to create a connection.
if (routeSelector == null) {
routeSelector = new RouteSelector(address, routeDatabase());
}
}
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");
}
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
connectionRetryEnabled); // 调用Connetion的connect方法真正建立socket连接
routeDatabase().connected(newConnection.getRoute());
return newConnection;
}
首先,会尝试从连接池中获得合适的连接,如果连接池中有合适的连接,则直接使用连接池中的连接,如果连接池中没有合适的连接,则会先创建一个连接,并将其放入连接池中,在创建Connection对象后,对调用其connect方法真正建立socket连接。这里说明一下Internal.instace是一个全局的静态变量,其初始化是在OkHttpClient中:
public class OkHttpClient implements Cloneable {
...
static {
Internal.instance = new Internal() {
@Override public void addLenient(Headers.Builder builder, String line) {
builder.addLenient(line);
}
@Override public void addLenient(Headers.Builder builder, String name, String value) {
builder.addLenient(name, value);
}
@Override public void setCache(OkHttpClient client, InternalCache internalCache) {
client.setInternalCache(internalCache);
}
@Override public InternalCache internalCache(OkHttpClient client) {
return client.internalCache();
}
@Override public boolean connectionBecameIdle(
ConnectionPool pool, RealConnection connection) {
return pool.connectionBecameIdle(connection);
}
@Override public RealConnection get(
ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
return pool.get(address, streamAllocation);
}
@Override public void put(ConnectionPool pool, RealConnection connection) {
pool.put(connection);
}
@Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
return connectionPool.routeDatabase;
}
@Override
public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
call.enqueue(responseCallback, forWebSocket);
}
@Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
return call.engine.streamAllocation;
}
@Override
public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
tlsConfiguration.apply(sslSocket, isFallback);
}
@Override public HttpUrl getHttpUrlChecked(String url)
throws MalformedURLException, UnknownHostException {
return HttpUrl.getChecked(url);
}
};
}
RealConnection的connect()方法
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
Proxy proxy = route.getProxy();
Address address = route.getAddress();
if (route.getAddress().getSslSocketFactory() == null
&& !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not supported: " + connectionSpecs));
}
while (protocol == null) {
try {
// 创建socket对象并建立连接
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.getSocketFactory().createSocket()
: new Socket(proxy);
connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
} catch (IOException e) {
Util.closeQuietly(socket);
Util.closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
}
首先会创建一个socket对象,然后调用connectSocket方法建立socket连接
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout); // 建立socket连接
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.getSocketAddress());
}
source = Okio.buffer(Okio.source(rawSocket)); // 获得socket的InputStream
sink = Okio.buffer(Okio.sink(rawSocket)); // 获得socket的OutputStream
if (route.getAddress().getSslSocketFactory() != null) {
connectTls(readTimeout, writeTimeout, connectionSpecSelector);
} else {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
}
if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
FramedConnection framedConnection = new FramedConnection.Builder(true)
.socket(socket, route.getAddress().url().host(), source, sink)
.protocol(protocol)
.build();
framedConnection.sendConnectionPreface();
// Only assign the framed connection once the preface has been sent successfully.
this.framedConnection = framedConnection;
}
}
Platform.get()获得的是一个全局唯一的Platform对象:
private static final AtomicReference<Platform> INSTANCE_HOLDER
= new AtomicReference<>(new Platform());
public static Platform get() {
return INSTANCE_HOLDER.get();
}
这里看Platfrom的connectSocket()方法:
public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
socket.connect(address, connectTimeout); // 创建Socket连接
}
我们再来看一下下面的代码:
source = Okio.buffer(Okio.source(rawSocket)); // 获得socket的InputStream
sink = Okio.buffer(Okio.sink(rawSocket)); // 获得socket的OutputStream
在Okio中:
public static Source source(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout); // 封装了socket的InputStream,用于读取服务端返回的数据
return timeout.source(source);
}
public static Sink sink(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Sink sink = sink(socket.getOutputStream(), timeout); // 封装了socket的OutputStream,用于向服务端发送数据
return timeout.sink(sink);
}
接下来在回到StreamAllocation的newStream方法:
resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
newStream最终会创建一个Http1xStream对象并返回(基于Http1的协议),来看一下Http1xStream的构造方法:
public Http1xStream(StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) {
this.streamAllocation = streamAllocation; // 记录streamAllocation对象
this.source = source; // 记录socket的InputStream对象
this.sink = sink; //记录socket的OutputStream对象
}
这样HttpEngine的connect()方法就分析完了,其主要是完成Socket的连接工作,其中涉及到了连接池,利用连接池可以更好的复用连接,减少连接的创建,也能够更好的管理连接。接下来我们继续分析HttpEngine的sendRequest()方法:
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
Request request = networkRequest(userRequest);
// 尝试从缓存中获得响应内容
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
long now = System.currentTimeMillis();
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
networkRequest = cacheStrategy.networkRequest; // 只有请求设置为only-if-cached时,才使用缓存中的数据而不用发起请求
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.
}
// networkRequest不为空,表示不使用缓存中的数据,需要发起网络请求获得数据
if (networkRequest != null) {
httpStream = connect(); // 创建socket连接
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.)
// 注意,sendRequest方法只是向socket中写入了请求行和请求头的信息,并没有发送数据,要发送数据需要调用socket的outputstream的flush方法
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) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest); // 写入请求头数据到socket的OutputStream
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request
// headers until the entire body is ready; otherwise we can't set the
// Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
} 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);
}
}
首先尝试从缓存中获得相应,如果能够获取就不需要发送请求,如果不能获取,则向socket的OutputStream中写入请求行和请求头的数据,但是这个时候还没有发送数据,发送数据需要调用OutputStream的flush方法,我们看一下Http1xStream的writeRequestHeaders方法:
@Override public void writeRequestHeaders(Request request) throws IOException {
httpEngine.writingRequestHeaders();
String requestLine = RequestLine.get(
request, httpEngine.getConnection().getRoute().getProxy().type());
writeRequest(request.headers(), requestLine);
}
/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n"); // 写入请求行数据
for (int i = 0, size = headers.size(); i < size; i ++) { // 写入请求头数据
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n"); // 请求头和请求主体之间有空行隔开
state = STATE_OPEN_REQUEST_BODY;
}
上面写入请求行和请求头的数据格式是严格遵循http协议的,Http协议的格式可以参考:Android网络编程之Http协议
HTTP请求报文的格式:
到目前为止,我们还只是构建了Socket连接,并按照HTTP协议的格式将请求头和请求行的数据写入到Socket的OutputStream中,如果是POST请求,还需要写入请求体的数据(RequestBody),HttpURLConnection提供了getOutputStream()方法来给开发者获得Socket的OutputStream对象(其实不是真正的Socket的OutputStream对象,中间经过了封装,最终写入数据是写入到了Socket的OutputStream中),以HttpURLConnectionImpl为例:
@Override public final OutputStream getOutputStream() throws IOException {
connect(); // 调用了connect方法,所以我们可以不必主动调用connect方法
BufferedSink sink = httpEngine.getBufferedRequestBody();
if (sink == null) {
throw new ProtocolException("method does not support a request body: " + method);
} else if (httpEngine.hasResponse()) {
throw new ProtocolException("cannot write request body after response has been read");
}
return sink.outputStream(); // 返回Socket对应的OutputStream,其实不是真正的Socket的OutputStream对象,中间经过了封装,最终写入数据是写入到了Socket的OutputStream中
}
在getOutputStream()方法之中,首先会调用connect()方法,这是为了确保建立了Socket连接,所有我们也可以不用在代码中主动去调用connect()方法。我们可以给这个OutputStream中写入数据,写入的数据将会作为Http的请求体(RequestBody)。
在所有数据写入完成后,我们现在还没有真正的向服务端发送数据,这个时候我们可以主动调用OutputStream的flush()方法将数据发送给服务端,也可以直接调用HttpURLConnection的getResponseCode方法(getResponseCode方法会判断数据是否发送,如果没有发送则发送数据)来获得,看一下HttpURLConnectionImpl的getResponseCode方法:
@Override public final int getResponseCode() throws IOException {
return getResponse().getResponse().code();
}
private HttpEngine getResponse() throws IOException {
initHttpEngine(); // 这里和connect方法的流程一样,之所以在这里调用initHttpEngine,是为了防止没有主动调用connect方法
if (httpEngine.hasResponse()) {
return httpEngine;
}
while (true) {
if (!execute(true)) { // 执行execute方法,和connect方法不同,这里传入的是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);
}
}
这里调用execute方法时,和connect方法不同,传入的值是true,表示要读取响应,在execute方法中,有如下代码:
private boolean execute(boolean readResponse) throws IOException {
...
// 读取响应
if (readResponse) {
httpEngine.readResponse();
}
...
}
读取响应会调用HttpEngine的readResponse()方法:
public void readResponse() throws IOException {
...
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)) { // 校验缓存响应是否有效
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());
}
}
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build(); // 构造响应
if (hasBody(userResponse)) { // 如果响应有body
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse)); // 如果响应正文需要解压,则给响应Body对象设置解压用的Source GzipSource,GzipSource在读取响应正文后会先解压
}
}
private Response readNetworkResponse() throws IOException {
httpStream.finishRequest(); // 结束请求数据的输入,会调用Socket的OutputStream的flush方法,向服务端发送数据
// 读取响应数据
Response networkResponse = httpStream.readResponseHeaders()
.request(networkRequest)
.handshake(streamAllocation.connection().getHandshake())
.header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
.header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
.build();
if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
}
if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
|| "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
streamAllocation.noNewStreams();
}
return networkResponse;
}
@Override public void finishRequest() throws IOException {
sink.flush(); // 调用socket的OutputStream的flush()方法,向服务端发送数据
}
到这里,数据的发送整个流程就分析完了,HttpURLConnect利用socket建立TCP连接来发送数据,发送的数据格式严格参照HTTP协议,为了优化性能,HttpURLConnection加入了缓存的机制,避免多余的网络请求,在Socket连接这方面,引入了连接池,通过连接池可以更加方便的管理连接,并且能够达到连接的复用,这对性能的提示也有帮助。在请求方面,HttpURLConnect封装了底层的实现细节,只提供简单的API接口,开发者只需要通过简单的API调用即可实现网络请求。
上面我们分析了HttpURLConnection发送请求的部分,下面我们再来分析接收服务端返回数据的部分。
前面分析过,在调用HttpURLConnection的getResponseCode的方法时,HttpURLConnection会通过Socket连接向服务端发送请求数据,并且在execute中读取响应,读取响应的代码主要在HttpEngine的readResponse()方法中:
private Response readNetworkResponse() throws IOException {
httpStream.finishRequest(); // 结束请求数据的输入,会调用Socket的OutputStream的flush方法,向服务端发送数据
// 读取响应数据
Response networkResponse = httpStream.readResponseHeaders()
.request(networkRequest)
.handshake(streamAllocation.connection().getHandshake())
.header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
.header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
.build();
// 读取响应正文
if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
}
// 如果请求头里面的Connection配置的是"close",则直接关闭连接,这也是HTTP协议所规定的的
if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
|| "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
streamAllocation.noNewStreams();
}
return networkResponse;
}
在Http1xStream中:
@Override public Response.Builder readResponseHeaders() throws IOException {
return readResponse();
}
public Response.Builder readResponse() throws IOException {
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
throw new IllegalStateException("state: " + state);
}
try {
while (true) {
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict()); // 读取响应的状态行信息并解析成StatusLine对象
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol) // HTTP协议版本
.code(statusLine.code) // 状态码
.message(statusLine.message) // 状态码的文本描述
.headers(readHeaders()); // 读取响应报头
if (statusLine.code != HTTP_CONTINUE) { // HTTP_CONTINUE等于100,表示收到请求后,需要请求者继续执行操作,这种请求需要继续发送请求,等待服务端返回数据
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
}
}
} catch (EOFException e) {
// Provide more context if the server ends the stream before sending a response.
IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
exception.initCause(e);
throw exception;
}
}
/** Reads headers or trailers. */
public Headers readHeaders() throws IOException {
Headers.Builder headers = new Headers.Builder();
// parse the result headers until the first blank line
for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) { // 读取响应报头数据,响应报头和响应正文数据之间是有空行分隔开的,当读取到的数据为空行时表示响应报头读取完毕
Internal.instance.addLenient(headers, line);
}
return headers.build();
}
从代码上来看,readResponseHeaders()方法会读取响应报文的状态行、响应报头数据并存入一个Response.Builder对象中,这里读取的规则也是严格参照了HTTP协议的响应报文格式:
我们再来看一下读取响应正文的代码:
// 读取响应正文
if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
}
其主要是通过HttpStream的openResponseBody方法读取响应正文并购置一个Body对象设置给networkResponse,我们来看一下Http1xStream的openResponseBody方法:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}
private Source getTransferStream(Response response) throws IOException {
if (!HttpEngine.hasBody(response)) { // 判断响应是否有body,有些请求是没有body的,比如HEAD方法的请求
return newFixedLengthSource(0);
}
// 构造对应的封装好Socket的InputStream的Source对象
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return newChunkedSource(httpEngine);
}
long contentLength = OkHeaders.contentLength(response);
if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
// Wrap the input stream from the connection (rather than just returning
// "socketIn" directly here), so that we can control its use after the
// reference escapes.
return newUnknownLengthSource();
}
getTransferStream方法会构造对应的封装好Socket的InputStream的Source对象,以newFixedLengthSource()方法为例:
public Source newFixedLengthSource(long length) throws IOException {
if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READING_RESPONSE_BODY;
return new FixedLengthSource(length);
}
// FixedLengthSource 是Http1xStream的内部类,其可以直接使用Http1xStream的成员变量
/** An HTTP body with a fixed length specified in advance. */
private class FixedLengthSource extends AbstractSource {
private long bytesRemaining;
public FixedLengthSource(long length) throws IOException {
bytesRemaining = length;
if (bytesRemaining == 0) {
endOfInput();
}
}
@Override public long read(Buffer sink, long byteCount) throws IOException { // 重写read方法,读数据时使用的是Http1xStream的source,而之前我们分析过,Http1xStream的source封装了Socket的InputStream对象,source.read相当于是调用了Socket的InputStream的read方法读取服务端返回数据
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (bytesRemaining == 0) return -1;
long read = source.read(sink, Math.min(bytesRemaining, byteCount));
if (read == -1) {
unexpectedEndOfInput(); // The server didn't supply the promised content length.
throw new ProtocolException("unexpected end of stream");
}
bytesRemaining -= read;
if (bytesRemaining == 0) {
endOfInput();
}
return read;
}
@Override public void close() throws IOException {
if (closed) return;
if (bytesRemaining != 0
&& !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
unexpectedEndOfInput();
}
closed = true;
}
}
Response.Builder的设计是典型的Builder模式,使用它可以构造出一个Response对象,Builder模式可以参考Android设计模式之Builder模式
看一下Response.Builder的代码:
public static class Builder {
private Request request;
private Protocol protocol;
private int code = -1;
private String message;
private Handshake handshake;
private Headers.Builder headers;
private ResponseBody body;
private Response networkResponse;
private Response cacheResponse;
private Response priorResponse;
public Builder() {
headers = new Headers.Builder();
}
private Builder(Response response) {
this.request = response.request;
this.protocol = response.protocol;
this.code = response.code;
this.message = response.message;
this.handshake = response.handshake;
this.headers = response.headers.newBuilder();
this.body = response.body;
this.networkResponse = response.networkResponse;
this.cacheResponse = response.cacheResponse;
this.priorResponse = response.priorResponse;
}
public Builder request(Request request) {
this.request = request;
return this;
}
public Builder protocol(Protocol protocol) {
this.protocol = protocol;
return this;
}
public Builder code(int code) {
this.code = code;
return this;
}
public Builder message(String message) {
this.message = message;
return this;
}
public Builder handshake(Handshake handshake) {
this.handshake = handshake;
return this;
}
/**
* Sets the header named {@code name} to {@code value}. If this request
* already has any headers with that name, they are all replaced.
*/
public Builder header(String name, String value) {
headers.set(name, value);
return this;
}
/**
* Adds a header with {@code name} and {@code value}. Prefer this method for
* multiply-valued headers like "Set-Cookie".
*/
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}
public Builder removeHeader(String name) {
headers.removeAll(name);
return this;
}
/** Removes all headers on this builder and adds {@code headers}. */
public Builder headers(Headers headers) {
this.headers = headers.newBuilder();
return this;
}
public Builder body(ResponseBody body) {
this.body = body;
return this;
}
public Builder networkResponse(Response networkResponse) {
if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse);
this.networkResponse = networkResponse;
return this;
}
public Builder cacheResponse(Response cacheResponse) {
if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse);
this.cacheResponse = cacheResponse;
return this;
}
private void checkSupportResponse(String name, Response response) {
if (response.body != null) {
throw new IllegalArgumentException(name + ".body != null");
} else if (response.networkResponse != null) {
throw new IllegalArgumentException(name + ".networkResponse != null");
} else if (response.cacheResponse != null) {
throw new IllegalArgumentException(name + ".cacheResponse != null");
} else if (response.priorResponse != null) {
throw new IllegalArgumentException(name + ".priorResponse != null");
}
}
public Builder priorResponse(Response priorResponse) {
if (priorResponse != null) checkPriorResponse(priorResponse);
this.priorResponse = priorResponse;
return this;
}
private void checkPriorResponse(Response response) {
if (response.body != null) {
throw new IllegalArgumentException("priorResponse.body != null");
}
}
public Response build() {
if (request == null) throw new IllegalStateException("request == null");
if (protocol == null) throw new IllegalStateException("protocol == null");
if (code < 0) throw new IllegalStateException("code < 0: " + code);
return new Response(this); // 利用Builder构造一个Response对象
}
}
Response的构造方法:
private Response(Builder builder) {
this.request = builder.request;
this.protocol = builder.protocol;
this.code = builder.code;
this.message = builder.message;
this.handshake = builder.handshake;
this.headers = builder.headers.build();
this.body = builder.body;
this.networkResponse = builder.networkResponse;
this.cacheResponse = builder.cacheResponse;
this.priorResponse = builder.priorResponse;
}
分析到这里,我们知道,HttpEngine的readResponse()会发送请求数据,并读取响应正文的请求行和请求头,将其存入一个Response对象中:
public void readResponse() throws IOException {
...
networkResponse = readNetworkResponse(); // 发送请求并读取响应内容
...
}
我们知道,HttpURLConnection读取响应状态码的代码如下:
@Override public final int getResponseCode() throws IOException {
return getResponse().getResponse().code();
}
其中getResponse()方法返回的是HttpEngine实例:
private HttpEngine getResponse() throws IOException {
initHttpEngine(); // 这里和connect方法的流程一样,之所以在这里调用initHttpEngine,是为了防止没有主动调用connect方法
if (httpEngine.hasResponse()) { // 如果服务端已经发送响应数据回来了就不需要继续执行发送数据并读取响应的代码
return httpEngine;
}
while (true) { // 发送数据并读取响应
if (!execute(true)) { // 执行execute方法,和connect方法不同,这里传入的是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);
}
}
我们再来看一下HttpEngine的getResponse()方法:
public Response getResponse() {
if (userResponse == null) throw new IllegalStateException();
return userResponse;
}
userResponse是我们获得的响应对象,我们再来看一下readResponse()的代码:
public void readResponse() throws IOException {
...
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)) { // 校验缓存响应是否有效
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());
}
}
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build(); // 构造响应
if (hasBody(userResponse)) { // 如果响应有body
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse)); // 如果响应正文需要解压,则给响应Body对象设置解压用的Source GzipSource,GzipSource在读取响应正文后会先解压
}
}
在调用readNetworkResponse()方法发送请求并读取了响应的状态行和响应报头的数据,并且构造了ResponseBody对象,ResponseBody中封装了Socket的InputStream对象;然后readResponse()会处理响应的缓存,最后调用unzip方法判断是否需要解压响应正文,如果需要,则构造一个Source对象,重写其read()方法,在解读响应正文时会对其数据进行解压。
接下来我们来看一下数据的读取,开发者调用HttpURLConnection的getInputStream方法获得InputStream,并从其中读取数据:
InputStream is = connection.getInputStream();
String response = convertStreamToString(is);
public static String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "/n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
getInputStream()方法:
@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(); // 调用ResponseBody的byteStream()方法获得InputStream
}
ResponseBody的byteStream()方法
public final InputStream byteStream() throws IOException {
return source().inputStream();
}
public abstract BufferedSource source() throws IOException;
source()是一个抽象方法,其具体实现在父类中,通过前面的分析,我们知道,在Http1xStream的openResponseBody方法中,我们构造过真正的ResponseBody对象:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}
上面的source是对Socket的InputStream的封装,最终读取数据会调用InputStream的read()。Okio.buffer(source)会构造一个RealBufferedSource对象:
public static BufferedSource buffer(Source source) {
if (source == null) throw new IllegalArgumentException("source == null");
return new RealBufferedSource(source);
}
final class RealBufferedSource implements BufferedSource {
public final Buffer buffer;
public final Source source;
private boolean closed;
public RealBufferedSource(Source source, Buffer buffer) {
if (source == null) throw new IllegalArgumentException("source == null");
this.buffer = buffer;
this.source = source;
}
public RealBufferedSource(Source source) {
this(source, new Buffer());
}
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (buffer.size == 0) {
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
}
long toRead = Math.min(byteCount, buffer.size);
return buffer.read(sink, toRead);
}
...
}
ResponseBody其具体实现是RealResponseBody:
public final class RealResponseBody extends ResponseBody {
private final Headers headers;
private final BufferedSource source;
public RealResponseBody(Headers headers, BufferedSource source) {
this.headers = headers;
this.source = source;
}
@Override public MediaType contentType() {
String contentType = headers.get("Content-Type");
return contentType != null ? MediaType.parse(contentType) : null;
}
@Override public long contentLength() {
return OkHeaders.contentLength(headers);
}
@Override public BufferedSource source() {
return source;
}
}
到这里,读取服务端数据我们也分析完了,首先严格参照HTTP协议的格式读取响应状态行和响应报头的数据,读取完成后会构造一个RequestBody对象,这个RequestBody对象中封装了Socket的InputStream,并且如果响应正文数据需要解压,还会提供数据的解读方法,这些都是通过给RequestBody构造不同的Source对象来实现的(通过重新read方法)。最终开发者通过HttpURLConnection提供的getInputStream()方法来读取Socket中服务端返回的数据。
三、总结
HttpURLConnection是一个实现HTTP请求的客户端框架,其要实现的效果大概类似于一个没有界面的浏览器功能。对外只提供简单的API接口,开发者只需要调用API即可实现HTTP请求。HttpURLConnection核心的通信部分还是使用的Socket,Socket是系统提供的对网络传输层实现的接口,HttpURLConnection基于TCP协议传输数据。要想实现HTTP通信,我们发送数据和接收数据必须严格参考HTTP协议的格式,HttpURLConnection也不例外,其对传入的URL、请求方法、请求数据等都是严格按照HTTP协议的格式来解析的,通过解析传入的参数,构造HTTP协议的请求报文,然后再通过Socket连接将数据发送给服务端(向Socket的OutputStream写入数据并调用其flush方法),发送完成后,HttpURLConnection会等待服务端返回数据,当数据返回后,HttpURLConnection会通过Socket连接读取服务端返回的数据(调用Socket的InputStream的read方法)。当然,其中还有很多具体实现的细节,比如HttpURLConnection使用了连接池来保存Socket连接,能够提高连接的复用性;加入了缓存机制,对一些没有必要发起的网络请求直接使用缓存,减少网络请求的消耗。虽然HttpURLConnection是Android中最常用的HTTP网络请求框架,但是再项目中很少直接使用其进行开发;第一,是因为其使用比较麻烦;第二,是因为其请求是同步的,每次请求都必须手动开启一个子线程然后在子线程中发起网络请求,请求完毕后如果需要将结果更新到UI上,还必须通过Handler向主线程发送消息,通知主线程将结果更新到UI上。基于此,我们平时一般使用其他的网络请求框架,会对HttpURLConnection进行再度的封装,帮助我们简化简化网络请求步骤。后面我们会挑选一个平时开发经常用到的网络请求框架,来分析其实现过程。