美文网首页
okhttp3-RealConnection

okhttp3-RealConnection

作者: miky_zheng | 来源:发表于2019-02-01 15:07 被阅读0次

    okhttp的连接过程是什么样的?

    由前章可知,RealConnection是在StreamAllocation中查找可用连接是生成的。连接过程主要分为两步,这个过程是个死循环,直到建立可用的连接为止。

      if (route.requiresTunnel()) {
              connectTunnel(connectTimeout, readTimeout, writeTimeout);
            } else {
              connectSocket(connectTimeout, readTimeout);
            }
            establishProtocol(connectionSpecSelector);
    
    

    1.如果路由需要通道,则连接通道,否则创建原始socket

    2.建立协议,如果涉及到tls,则包含tls握手连接等。
    关于tunnel:https://tools.ietf.org/html/rfc2817#section-5.2

     For example, proxy authentication might be used to establish the
       authority to create a tunnel:
    
          CONNECT server.example.com:80 HTTP/1.1
          Host: server.example.com:80
          Proxy-Authorization: basic aGVsbG86d29ybGQ=
    

    与任何其他流水线式HTTP/1.1请求一样,要传输的数据可以在空行之后立即发送。
    通常的警告也适用:如果最终响应为负数,数据可能会被丢弃;如果有多个TCP段未完成,连接可能会重置而不响应。

    先看下route
    是由RouteSelector生成的,即sun.net.spi.DefaultProxySelector
    .select(url.uri())
    尝试代理服务

      /** Prepares the proxy servers to try. */
      private void resetNextProxy(HttpUrl url, Proxy proxy) {
        if (proxy != null) {
          // If the user specifies a proxy, try that and only that.
          proxies = Collections.singletonList(proxy);
        } else {
          // Try each of the ProxySelector choices until one connection succeeds.
          List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
          proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
              ? Util.immutableList(proxiesOrNull)
              : Util.immutableList(Proxy.NO_PROXY);
        }
        nextProxyIndex = 0;
      }
    
     /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
      private Proxy nextProxy() throws IOException {
        if (!hasNextProxy()) {
          throw new SocketException("No route to " + address.url().host()
              + "; exhausted proxy configurations: " + proxies);
        }
        Proxy result = proxies.get(nextProxyIndex++);
        resetNextInetSocketAddress(result);
        return result;
      }
    
     /** Prepares the socket addresses to attempt for the current proxy or host. */
      private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
        // Clear the addresses. Necessary if getAllByName() below throws!
        inetSocketAddresses = new ArrayList<>();
    
        String socketHost;
        int socketPort;
        if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
          socketHost = address.url().host();
          socketPort = address.url().port();
        } else {
          SocketAddress proxyAddress = proxy.address();
          if (!(proxyAddress instanceof InetSocketAddress)) {
            throw new IllegalArgumentException(
                "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
          }
          InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
          socketHost = getHostString(proxySocketAddress);
          socketPort = proxySocketAddress.getPort();
        }
    
        if (socketPort < 1 || socketPort > 65535) {
          throw new SocketException("No route to " + socketHost + ":" + socketPort
              + "; port is out of range");
        }
    
        if (proxy.type() == Proxy.Type.SOCKS) {
          inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
        } else {
          // Try each address for best behavior in mixed IPv4/IPv6 environments.
          List<InetAddress> addresses = address.dns().lookup(socketHost);
          if (addresses.isEmpty()) {
            throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
          }
    
          for (int i = 0, size = addresses.size(); i < size; i++) {
            InetAddress inetAddress = addresses.get(i);
            inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
          }
        }
    
        nextInetSocketAddressIndex = 0;
      }
    

    RouteDatabase:失败的路由会被记录。
    ConnectionSpecSelector:
    处理连接规范回退策略:当安全套接字连接由于握手/协议问题而失败时,可以使用不同的协议重试连接。实例是有状态的,应该创建并用于单个连接尝试。

    private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
        if (route.address().sslSocketFactory() == null) {
          protocol = Protocol.HTTP_1_1;
          socket = rawSocket;
          return;
        }
    
        connectTls(connectionSpecSelector);
    
        if (protocol == Protocol.HTTP_2) {
          socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
          http2Connection = new Http2Connection.Builder(true)
              .socket(socket, route.address().url().host(), source, sink)
              .listener(this)
              .build();
          http2Connection.start();
        }
      }
    

    建立协议的过程,如果路由的address().sslSocketFactory为null,就跳过ssl层,直接使用原始socket,否则使用sslsocket。
    http2默认必须要使用ssl套接字。

    private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
        Address address = route.address();
        SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
        boolean success = false;
        SSLSocket sslSocket = null;
        try {
          // Create the wrapper over the connected socket.
          sslSocket = (SSLSocket) sslSocketFactory.createSocket(
              rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
    
          // Configure the socket's ciphers, TLS versions, and extensions.
          ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
          if (connectionSpec.supportsTlsExtensions()) {
            Platform.get().configureTlsExtensions(
                sslSocket, address.url().host(), address.protocols());
          }
    
          // Force handshake. This can throw!
          sslSocket.startHandshake();
          Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
    
          // Verify that the socket's certificates are acceptable for the target host.
          if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
            X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
            throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
                + "\n    certificate: " + CertificatePinner.pin(cert)
                + "\n    DN: " + cert.getSubjectDN().getName()
                + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
          }
    
          // Check that the certificate pinner is satisfied by the certificates presented.
          address.certificatePinner().check(address.url().host(),
              unverifiedHandshake.peerCertificates());
    
          // Success! Save the handshake and the ALPN protocol.
          String maybeProtocol = connectionSpec.supportsTlsExtensions()
              ? Platform.get().getSelectedProtocol(sslSocket)
              : null;
          socket = sslSocket;
          source = Okio.buffer(Okio.source(socket));
          sink = Okio.buffer(Okio.sink(socket));
          handshake = unverifiedHandshake;
          protocol = maybeProtocol != null
              ? Protocol.get(maybeProtocol)
              : Protocol.HTTP_1_1;
          success = true;
        } catch (AssertionError e) {
          if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
          throw e;
        } finally {
          if (sslSocket != null) {
            Platform.get().afterHandshake(sslSocket);
          }
          if (!success) {
            closeQuietly(sslSocket);
          }
        }
      }
    

    上面的额步骤,
    1.创建ssl套接字。
    2.配置密码套件,tls版本, extensions等,
    如果connectionSpec支持tls扩展,Android或java平台配置configureTlsExtensions
    NOTE: 安全套接字重要的API:
    socket.getEnabledProtocols,socket.getEnabledCipherSuites

    3.开始握手
    sslsocket.startHandshake()

    javax.net.ssl.SSLSocket::
       * @throws IOException on a network level error
         * @see #addHandshakeCompletedListener(HandshakeCompletedListener)
         */
        public abstract void startHandshake() throws IOException;
    

    在此连接上启动SSL握手。常见原因包括需要使用新的加密密钥、更改密码套件或启动新会话。若要强制完全重新验证,当前会话可能会在开始此握手之前失效。
    如果已经在连接上发送了数据,那么在这个握手过程中数据将继续流动。当握手完成时,将用一个事件发出信号。

    对于连接上的初始握手,此方法是同步的,并在协商握手完成时返回。某些协议可能不支持现有套接字上的多个握手,并可能引发IOException。

     public static Handshake get(SSLSession session) {
        String cipherSuiteString = session.getCipherSuite();
        if (cipherSuiteString == null) throw new IllegalStateException("cipherSuite == null");
        CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
    
        String tlsVersionString = session.getProtocol();
        if (tlsVersionString == null) throw new IllegalStateException("tlsVersion == null");
        TlsVersion tlsVersion = TlsVersion.forJavaName(tlsVersionString);
    
        Certificate[] peerCertificates;
        try {
          peerCertificates = session.getPeerCertificates();
        } catch (SSLPeerUnverifiedException ignored) {
          peerCertificates = null;
        }
        List<Certificate> peerCertificatesList = peerCertificates != null
            ? Util.immutableList(peerCertificates)
            : Collections.<Certificate>emptyList();
    
        Certificate[] localCertificates = session.getLocalCertificates();
        List<Certificate> localCertificatesList = localCertificates != null
            ? Util.immutableList(localCertificates)
            : Collections.<Certificate>emptyList();
    
        return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList);
      }
    

    4.通过sslSocket创建的SSLSession会话,生成握手
    // Verify that the socket's certificates are acceptable for the target host.
    5.校验certificate pinner
    6.支持的协议

      public static Protocol get(String protocol) throws IOException {
        // Unroll the loop over values() to save an allocation.
        if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
        if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
        if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
        if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
        throw new IOException("Unexpected protocol: " + protocol);
      }
    

    安卓平台的协议

      OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
        this.returnType = returnType;
        this.methodName = methodName;
        this.methodParams = methodParams;
      }
    
    getAlpnSelectedProtocol =new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
    
      @Override public String getSelectedProtocol(SSLSocket socket) {
        if (getAlpnSelectedProtocol == null) return null;
        if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
    
        byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
        return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
      }
    
    

    相关文章

      网友评论

          本文标题:okhttp3-RealConnection

          本文链接:https://www.haomeiwen.com/subject/sardsqtx.html