• 本文概述

    从源码角度理解HttpURLConnection是怎么一步步封装底层socket调用的。

  • HttpURLConnection使用回顾

    我们先来回顾一下它的用法:

    new Thread(() -> {HttpURLConnection connection = null;BufferedReader reader = null;try{URL url = new URL("https://www.baidu.com");//新建URLconnection = (HttpURLConnection)url.openConnection();//发起网络请求connection.setRequestMethod("GET");//请求方式connection.setConnectTimeout(8000);//连接最大时间connection.setReadTimeout(8000);//读取最大时间InputStream in = connection.getInputStream();reader = new BufferedReader(new InputStreamReader(in));//写入readerStringBuilder response = new StringBuilder();String line;while((line = reader.readLine()) != null){response.append(line);}//更新uishowResponse(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 handlerif (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 constructorif (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 RFC2396if (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 usedif (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 handlerif (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/25897689cls = 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则优先使用这两种方式的handlersynchronized (streamHandlerLock) {URLStreamHandler handler2 = null;// Check again with hashtable just in case another// thread created a handler since we last checkedhandler2 = handlers.get(protocol);if (handler2 != null) {return handler2;}// Check with factory if another thread set a// factory since our last checkif (!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 hashtableif (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);}@Overrideprotected 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 typesif (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 methodmethod = "POST";} else if (!HttpMethod.hasRequestBody(method)) {// If the request method is neither POST nor PUT nor PATCH, then you're not writingthrow 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();//看一下有没有缓存ResponseOkResponseCache responseCache = client.getOkResponseCache();Response cacheResponse = responseCache != null? responseCache.get(request): null;long now = System.currentTimeMillis();//追溯get方法最终会从getCandidate()中返回一个CacheStrategy对象,它的source是ResponseSource.NETWORKCacheStrategy 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.NETWORKif (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的部分会再起一篇文章总结。

HttpURLConnection源码分析相关推荐

  1. 【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  2. Spring源码分析——资源访问利器Resource之接口和抽象类分析

    从今天开始,一步步走上源码分析的路.刚开始肯定要从简单着手.我们先从Java发展史上最强大的框架--Spring...旗下的资源抽象接口Resource开始吧. 我看了好多分析Spring源码的,每每 ...

  3. 图片加载框架Picasso - 源码分析

    简书:图片加载框架Picasso - 源码分析 前一篇文章讲了Picasso的详细用法,Picasso 是一个强大的图片加载缓存框架,一个非常优秀的开源库,学习一个优秀的开源库,,我们不仅仅是学习它的 ...

  4. Volley 源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 图片分析 要说源码分析,我们得先看一下官方的配图: 从这张图中我们可以了解到 volley 工作流程: 1.请求加入优先队列 ...

  5. Android 图片框架原理——Glide源码分析

    目录 前言 一.With() 二.load() 三.into() 1. buildImageViewTarget() 2.关注1 :buildRequest 构建 3.关注2:runRequest 执 ...

  6. android源码分析

    01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 ...

  7. OkHttpClient 源码分析 1(基于3.9.0的源码)

    OkHttpClient是目前开发 android 应用使用最广泛的网络框架,最近看了阿里的 httpdns 里面对于 dns 的处理,我们团队就想调研一下在项目中有什么利弊,并且框架中是否对 soc ...

  8. Android Retrofit 2.0(三)从源码分析原理

    Retrofit·特点 性能最好,处理最快 使用REST API时非常方便: 传输层默认就使用OkHttp: 支持NIO: 拥有出色的API文档和社区支持 速度上比volley更快: 如果你的应用程序 ...

  9. mybatis高级操作及源码分析(一)

    Mybatis中使用注解 or xml 文件? 注解使用姿势 下面以Select注解为例. @Select 的本质还是 xml 文件的形式,有两种方式@Select注解和@SelectProvider ...

最新文章

  1. 不该被遗忘的nodeName、nodeValue和nodeType!
  2. 如果我们预先得知未来某一刻会因为此刻的决定而失败,那是否还会继续努力?
  3. VueRouter源码详细解读
  4. php命名空间更麻烦了,紧急求教PHP命名空间问题,12:10了我还没有吃饭呢,各位帮忙!!...
  5. vs 下如何调试js
  6. flutter android 和 ios 发布
  7. [转载] Python中的memoryview
  8. 页面布局让footer居页面底部_网站页面结构与关键词布局技巧
  9. python 对象和json互相转换
  10. 企业级PHP发卡网源码,金发卡企业级发卡平台源码
  11. 菜鸟python爬虫scrapy_Python爬虫(Scrapy)爬取秀人网
  12. HTML基础 - HTML文本格式
  13. 封神榜中的截教教徒是如何被杀的----开篇
  14. linux入侵检测工具之aide
  15. php导出excel失败原因,PHPExcel中导出Excel出错的一种可能原因
  16. Ndis网卡驱动是如何操控硬件的
  17. 对学校的希望和寄语_给学校的祝福语
  18. 人民币纸币采用防伪油墨
  19. 工业机器人实训实验平台
  20. 学习篇——了解OKR

热门文章

  1. 环球时报评出“影响新中国的60位外国人”
  2. 老中医开药,有需要的自己取
  3. Redy语法分析--抽象语法树简介
  4. 【C语言】关机小游戏(最好的朋友转瞬即逝)
  5. 从“Unknown database engine MySQL“说起
  6. 简单聊一聊 Android App Bundle 的话题
  7. mysql数据库表插入数据
  8. 异常值识别和缺失值处理
  9. java中数据类型的等级_Java 数据类型、变量
  10. 奇点临近:人类文明延续