HttpURLConnection源码分析

  • 本文概述

    从源码角度理解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的部分会再起一篇文章总结。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android网络连接源码分析主要涉及到Android系统中网络连接的相关实现和机制。下面是Android网络连接的源码分析: 1. 网络连接管理类:Android中的网络连接管理由ConnectivityManager类负责。该类提供了获取网络状态、监听网络状态变化等功能。其源码位于frameworks/base/core/java/android/net/ConnectivityManager.java。 2. 网络请求类:Android中的网络请求由HttpClient或HttpURLConnection实现。在Android 6.0及以上版本中,Google推荐使用HttpURLConnectionHttpClient的源码位于frameworks/base/core/java/org/apache/http/impl/client/DefaultHttpClient.javaHttpURLConnection源码位于libcore/luni/src/main/java/java/net/HttpURLConnection.java。 3. 网络请求处理类:Android中的网络请求处理由AsyncTask或者Thread实现。AsyncTask是一个封装了线程池和Handler的异步任务类,用于在后台执行耗时操作,并在主线程更新UI。其源码位于frameworks/base/core/java/android/os/AsyncTask.java。 4. 网络请求结果处理类:Android中的网络请求结果处理由Handler或者Callback实现。Handler用于在主线程中处理异步任务的结果,Callback则是一种回调机制,用于在异步任务完成后执行相应的操作。 5. 网络缓存类:Android中的网络缓存由DiskLruCache或者LruCache实现。DiskLruCache用于将网络请求结果缓存到本地磁盘,LruCache则是一种内存缓存机制,用于缓存网络请求结果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值