-
本文概述
从源码角度理解HttpURLConnection是怎么一步步封装底层socket调用的。
-
HttpURLConnection使用回顾
我们先来回顾一下它的用法:
new Thread(() -> { HttpURLConnection connection = null; BufferedReader reader = null; try{ URL url = new URL("https://www.baidu.com");//新建URL connection = (HttpURLConnection)url.openConnection();//发起网络请求 connection.setRequestMethod("GET");//请求方式 connection.setConnectTimeout(8000);//连接最大时间 connection.setReadTimeout(8000);//读取最大时间 InputStream in = connection.getInputStream(); reader = new BufferedReader(new InputStreamReader(in));//写入reader StringBuilder response = new StringBuilder(); String line; while((line = reader.readLine()) != null){ response.append(line); } //更新ui showResponse(response.toString()); }catch (Exception e){ e.printStackTrace(); }finally { if(reader != null){ try{ reader.close(); }catch (IOException e){ e.printStackTrace(); } } if(connection != null){ connection.disconnect(); } } }).start();
接下来从源码角度研究一下它的原理。
-
源码分析
首先构造一个URL对象,然后调用它的openConnection(),进去发现调用的是handler.openConnection(URL url),handler是怎么创建的呢?看一下URL对象的构造方法就会发现一些线索,构造方法最终会走到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; } } // Only use our context if the protocols match. protocol = newProtocol; if ((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))) { // 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) { throw new MalformedURLException("unknown protocol: "+protocol); } this.handler = handler; 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; } } 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解析成对应的字段,然后把构造的handler和URL联系在一起,至于handler的创建,首先这里因为context(URL)是null,所以handler = context.handler不会执行,所以getURLStreamHandler(protocol)成为了关键:
static URLStreamHandler getURLStreamHandler(String protocol) { //首先从一个叫handlers的Hashtable中以protocol为key去取 URLStreamHandler handler = handlers.get(protocol); if (handler == null) { boolean checkedWithFactory = false; //如果没取到则尝试用factory去构造一个,这里的factory没有赋值所以是null // Use the factory (if any) if (factory != null) { handler = factory.createURLStreamHandler(protocol); checkedWithFactory = true; } //此时handler还是没有创建 // Try java protocol handler if (handler == null) { // Android-changed: Android doesn't need AccessController. // Remove unnecessary use of reflection for sun classes /* packagePrefixList = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( protocolPathProp,"")); if (packagePrefixList != "") { packagePrefixList += "|"; } // REMIND: decide whether to allow the "null" class prefix // or not. packagePrefixList += "sun.net.www.protocol"; */ //这里会以propotolPathProp(java.protocol.handler.pkgs)为key去取一个系统属性,看样子是一个包名前缀集合字符串的东西 final String packagePrefixList = System.getProperty(protocolPathProp,""); //构造一个分词器,以“|”分割 StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|"); //如果找到这么一个类可以创建handler则跳出循环 while (handler == null && packagePrefixIter.hasMoreTokens()) { String packagePrefix = packagePrefixIter.nextToken().trim(); try { String clsName = packagePrefix + "." + protocol + ".Handler"; Class<?> cls = null; try { ClassLoader cl = ClassLoader.getSystemClassLoader(); // BEGIN Android-changed: Fall back to thread's contextClassLoader. // http://b/25897689 cls = Class.forName(clsName, true, cl); } catch (ClassNotFoundException e) { ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); if (contextLoader != null) { cls = Class.forName(clsName, true, contextLoader); } // END Android-changed: Fall back to thread's contextClassLoader. } if (cls != null) { handler = (URLStreamHandler)cls.newInstance(); } } catch (ReflectiveOperationException ignored) { } } } // BEGIN Android-added: Custom built-in URLStreamHandlers for http, https. // Fallback to built-in stream handler. //如果此时handler还没创建,则调用createBuiltinHandler方法 if (handler == null) { try { handler = createBuiltinHandler(protocol); } catch (Exception e) { throw new AssertionError(e); } } // END Android-added: Custom built-in URLStreamHandlers for http, https. //注意最后再走一下前面的逻辑,如果在试图创建的过程中其他线程创建了handler或者设置了factory则优先使用这两种方式的handler synchronized (streamHandlerLock) { URLStreamHandler handler2 = null; // Check again with hashtable just in case another // thread created a handler since we last checked handler2 = handlers.get(protocol); if (handler2 != null) { return handler2; } // Check with factory if another thread set a // factory since our last check 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); } } } return handler; }
看一下createBuiltinHandler方法:
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类型的子类对象当作handler,所以以com.android.okhttp.HttpHandler为例来看看handler.openConnection()方法:
public final class HttpHandler extends URLStreamHandler { @Override protected URLConnection openConnection(URL url) throws IOException { // 调用了OKHttpClient()的方法 return new OkHttpClient().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 new OkHttpClient().setProxy(proxy).open(url); } @Override protected int getDefaultPort() { return 80; } }
可见也是调用了调用了OKHttpClient()的open方法:
public HttpURLConnection open(URL url) { return open(url, proxy); } HttpURLConnection open(URL url, Proxy proxy) { String protocol = url.getProtocol(); OkHttpClient copy = copyWithDefaults(); copy.proxy = proxy; if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy); if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy); throw new IllegalArgumentException("Unexpected protocol: " + protocol); }
以Http为例则返回了HttpURLConnectionImpl对象。
所以openConnection()并没有开始去请求网络,它做的工作只是封装好请求。
回到调用流程,接着是调用connect:
@Override public final void connect() throws IOException { initHttpEngine(); boolean success; do { success = execute(false); } while (!success); }
这里的do-while循环相当于失败重连,因为这只是连接,所以execute方法的参数是false,表示不读取response。
接下来都是给HttpURLConnectionImpl对象继续设置参数,然后调用它的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()); } InputStream result = response.getResponseBodyBytes(); if (result == null) { throw new ProtocolException("No response body exists; responseCode=" + getResponseCode()); } return result; }
可以看出getResponse()方法之后的代码都是读取请求返回值了,所以这个getResonse方法就是调用请求的地方:
private HttpEngine getResponse() throws IOException { initHttpEngine(); if (httpEngine.hasResponse()) { return httpEngine; } while (true) { if (!execute(true)) { continue; } Retry retry = processResponseHeaders(); if (retry == Retry.NONE) { httpEngine.releaseConnection(); return httpEngine; } // The first request was insufficient. Prepare for another... String retryMethod = method; Sink requestBody = httpEngine.getRequestBody(); // 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. int responseCode = httpEngine.getResponse().code(); if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) { retryMethod = "GET"; requestHeaders.removeAll("Content-Length"); requestBody = null; } if (requestBody != null && !(requestBody instanceof RetryableSink)) { throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode); } if (retry == Retry.DIFFERENT_CONNECTION) { httpEngine.releaseConnection(); } Connection connection = httpEngine.close(); httpEngine = newHttpEngine(retryMethod, connection, (RetryableSink) requestBody); } }
先看一下initHttpEngine:
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.hasRequestBody(method)) { // If the request method is neither POST nor PUT nor PATCH, then you're not writing throw new ProtocolException(method + " does not support writing"); } } httpEngine = newHttpEngine(method, null, null); } catch (IOException e) { httpEngineFailure = e; throw e; } }
这里把connected设置成true也可以看出来是要进行请求了,因为没有设置doOutput(如果是GET方法设置了doOutput为true则在这里改为POST方法,其他的则判断此请求方法是否允许携带requestBody,不支持会抛出异常),所以默认false,最后走到newHttpEngine方法里:
private HttpEngine newHttpEngine(String method, Connection connection, RetryableSink requestBody) { Request.Builder builder = new Request.Builder() .url(getURL()) .method(method, null /* No body; that's passed separately. */); Headers headers = requestHeaders.build(); for (int i = 0; i < headers.size(); i++) { builder.addHeader(headers.name(i), headers.value(i)); } boolean bufferRequestBody = false; if (HttpMethod.hasRequestBody(method)) { if (fixedContentLength != -1) { builder.header("Content-Length", Long.toString(fixedContentLength)); } else if (chunkLength > 0) { builder.header("Transfer-Encoding", "chunked"); } else { bufferRequestBody = true; } } 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 (engineClient.getOkResponseCache() != null && !getUseCaches()) { engineClient = client.clone().setOkResponseCache(null); } return new HttpEngine(engineClient, request, bufferRequestBody, connection, null, requestBody); }
再回到getResponse方法,while(true)里面首先有一个execute的判断:
private boolean execute(boolean readResponse) throws IOException { try { httpEngine.sendRequest(); route = httpEngine.getRoute(); handshake = httpEngine.getConnection() != null ? httpEngine.getConnection().getHandshake() : null; if (readResponse) { httpEngine.readResponse(); } return true; } catch (IOException e) { HttpEngine retryEngine = httpEngine.recover(e); if (retryEngine != null) { httpEngine = retryEngine; return false; } // Give up; recovery is not possible. httpEngineFailure = e; throw e; } }
我们看到在失败产生异常的时候会被捕获,recover就是根据之前的httpEngine的属性重新创建HttpEngine对象,返回false就会重走while循环,又会执行到execute,又会sendRequest,所以这部分就是失败重新请求操作。看一下sendRequest操作:
public final void sendRequest() throws IOException { if (responseSource != null) return; // Already sent. if (transport != null) throw new IllegalStateException(); //设置header信息 prepareRawRequestHeaders(); //看一下有没有缓存Response OkResponseCache responseCache = client.getOkResponseCache(); Response cacheResponse = responseCache != null ? responseCache.get(request) : null; long now = System.currentTimeMillis(); //追溯get方法最终会从getCandidate()中返回一个CacheStrategy对象,它的source是ResponseSource.NETWORK CacheStrategy cacheStrategy = new CacheStrategy.Factory(now, request, cacheResponse).get(); responseSource = cacheStrategy.source; request = cacheStrategy.request; if (responseCache != null) { responseCache.trackResponse(responseSource); } if (responseSource != ResponseSource.NETWORK) { validatingResponse = cacheStrategy.response; } if (cacheResponse != null && !responseSource.usesCache()) { closeQuietly(cacheResponse.body()); // We don't need this cached response. Close it. } //responseSource.requiresConnection()会判断source是否是ResponseSource.CONDITIONAL_CACHE或者是ResponseSource.NETWORK if (responseSource.requiresConnection()) { // Open a connection unless we inherited one from a redirect. if (connection == null) { //关键代码,进行连接请求 connect(); } // Blow up if we aren't the current owner of the connection. if (connection.getOwner() != this && !connection.isSpdy()) throw new AssertionError(); transport = (Transport) connection.newTransport(this); // Create a request body if we don't have one already. We'll already have // one if we're retrying a failed POST. if (hasRequestBody() && requestBodyOut == null) { requestBodyOut = transport.createRequestBody(request); } } else { // We're using a cached response. Recycle a connection we may have inherited from a redirect. if (connection != null) { client.getConnectionPool().recycle(connection); connection = null; } // No need for the network! Promote the cached response immediately. this.response = validatingResponse; if (validatingResponse.body() != null) { initContentStream(validatingResponse.body().source()); } } }
执行到connect进行请求连接:
private void connect() throws IOException { if (connection != null) throw new IllegalStateException(); if (routeSelector == null) { //主机地址 String uriHost = request.url().getHost(); if (uriHost == null || uriHost.length() == 0) { throw new UnknownHostException(request.url().toString()); } //Https的安全验证信息 SSLSocketFactory sslSocketFactory = null; HostnameVerifier hostnameVerifier = null; if (request.isHttps()) { sslSocketFactory = client.getSslSocketFactory(); hostnameVerifier = client.getHostnameVerifier(); } //封装Address信息 Address address = new Address(uriHost, getEffectivePort(request.url()), sslSocketFactory, hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getProtocols()); //封装RouterSelector信息 routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(), client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase()); } //next方法构造一个Connection对象 connection = routeSelector.next(request.method()); connection.setOwner(this); if (!connection.isConnected()) { //连接 connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig()); if (connection.isSpdy()) client.getConnectionPool().share(connection); client.getRoutesDatabase().connected(connection.getRoute()); } else if (!connection.isSpdy()) { connection.updateReadTimeout(client.getReadTimeout()); } route = connection.getRoute(); }
connection.connect方法如下:
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest) throws IOException { if (connected) throw new IllegalStateException("already connected"); socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket(); Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout); socket.setSoTimeout(readTimeout); in = socket.getInputStream(); out = socket.getOutputStream(); if (route.address.sslSocketFactory != null) { upgradeToTls(tunnelRequest); } else { initSourceAndSink(); httpConnection = new HttpConnection(pool, this, source, sink); } connected = true; }
终于看到socket了,这是java级别的网络请求了,Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout)进行socket连接:
public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException { socket.connect(address, connectTimeout); }
到了这里就是HttpURLConnection和socket联系起来了,至于socket的部分会再起一篇文章总结。
网友评论