OkHttp
okhttp是Android 平台的网络请求框架,已经被Android吸收了。并且OkHttp很好的支持了Cookie,Cache,连接复用,HTTP2 Https...使用拦截链模式,能很方便的扩展自己的功能。从Android4.4开始已经引入了OkHttp
ConnectionSpec
ConnectionSpec是用来指定Http传输时的Socket连接。对于Https连接,ConnectionSpec是在构建TLS连接时向服务端说明客户端支持的TLS版本,密码套件等的一个类。
下面介绍一下OkHttp默认的ConnectionSpec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | // Most secure but generally supported list. 支持的密码套件的列表 private static final CipherSuite[] RESTRICTED_CIPHER_SUITES = new CipherSuite[] { // TLSv1.3 CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_AES_128_CCM_SHA256, CipherSuite.TLS_AES_256_CCM_8_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 }; //支持的密码套件的列表 // This is nearly equal to the cipher suites supported in Chrome 51, current as of 2016-05-25. // All of these suites are available on Android 7.0; earlier releases support a subset of these // suites. https://github.com/square/okhttp/issues/1972 private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] { // TLSv1.3 CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_AES_128_CCM_SHA256, CipherSuite.TLS_AES_256_CCM_8_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll // continue to include them until better suites are commonly available. For example, none // of the better cipher suites listed above shipped with Android 4.4 or Java 7. CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, }; //安全的TLS连接需要最近的客户端和最近的服务器 public static final ConnectionSpec RESTRICTED_TLS = new Builder(true) .cipherSuites(RESTRICTED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); //现代的TLS的配置,适用于大多数客户端和可以连接到的服务器,是OkHttp的默认配置 public static final ConnectionSpec MODERN_TLS = new Builder(true) .cipherSuites(APPROVED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .supportsTlsExtensions(true) .build(); //向后兼容的配置,相比于MODERN_TLS,支持的TLS的版本变少了,只支持TLS_1_0版本 public static final ConnectionSpec COMPATIBLE_TLS = new Builder(true) .cipherSuites(APPROVED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_0) .supportsTlsExtensions(true) .build(); //未加密未认证的连接,就是HTTP连接 public static final ConnectionSpec CLEARTEXT = new Builder(false).build(); |
上面是OkHttp提供的一些默认的ConnectionSpec,再找下OKHttpClient中默认用的是哪些ConnectionSpec
1 2 3 4 5 6 7 8 9 10 | static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList( ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT); public Builder() { ...... protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; ... } |
从代码中看的出来,默认ConnectionSpec.MODERN_TLS(支持较多的TLS版本和较多的密码套件), ConnectionSpec.CLEARTEXT(支持HTTP连接)
下面再拆解ConnectionSpec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | final boolean tls;//是否是TLS连接 final boolean supportsTlsExtensions;//是否支持Tls扩展 final @Nullable String[] cipherSuites;//支持的密码套件类型 final @Nullable String[] tlsVersions;//支持的Tls版本的类型 //把相应的ConnectionSpec设置到SSLSocket上面,使其具有相应的密码套件和TLS版本的支持 //isFallback 是否支持回退策略,就是是否还支持其他的密码套件 void apply(SSLSocket sslSocket, boolean isFallback) { ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);//返回这个socket支持的ConnectionSpec,并在设置到SSLSocket上 if (specToApply.tlsVersions != null) { sslSocket.setEnabledProtocols(specToApply.tlsVersions); } if (specToApply.cipherSuites != null) { sslSocket.setEnabledCipherSuites(specToApply.cipherSuites); } } //返回sslSocket支持的密码套件和TLS版本 private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) { String[] cipherSuitesIntersection = cipherSuites != null ? intersect(CipherSuite.ORDER_BY_NAME, sslSocket.getEnabledCipherSuites(), cipherSuites) : sslSocket.getEnabledCipherSuites(); String[] tlsVersionsIntersection = tlsVersions != null ? intersect(Util.NATURAL_ORDER, sslSocket.getEnabledProtocols(), tlsVersions) : sslSocket.getEnabledProtocols(); // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 // the SCSV cipher is added to signal that a protocol fallback has taken place. String[] supportedCipherSuites = sslSocket.getSupportedCipherSuites(); int indexOfFallbackScsv = indexOf( CipherSuite.ORDER_BY_NAME, supportedCipherSuites, "TLS_FALLBACK_SCSV"); if (isFallback && indexOfFallbackScsv != -1) { cipherSuitesIntersection = concat( cipherSuitesIntersection, supportedCipherSuites[indexOfFallbackScsv]); } return new Builder(this) .cipherSuites(cipherSuitesIntersection) .tlsVersions(tlsVersionsIntersection) .build();//构造出ConnectionSpec } //是否与传入的SSLSocket具有匹配的密码套件和TLS版本 public boolean isCompatible(SSLSocket socket) { if (!tls) { return false; } if (tlsVersions != null && !nonEmptyIntersection( Util.NATURAL_ORDER, tlsVersions, socket.getEnabledProtocols())) { return false; } if (cipherSuites != null && !nonEmptyIntersection( CipherSuite.ORDER_BY_NAME, cipherSuites, socket.getEnabledCipherSuites())) { return false; } return true; } |
调用Apply方法的入口地址是在RealConnection的connectTls()方法中
1 2 3 | 确定这个socket简历TLS连接时的对应的密码套件和TLS版本 // Configure the socket's ciphers, TLS versions, and extensions. ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); |
ConnectionSpecSelector
ConnectionSpecSelector是处理ConnectionSpec的后备策略,当Https由于握手或者TLS版本,密码套件问题连接失败时,可以尝试使用不同的协议。ConnectionSpecSelector是有状态的,所以对于每一个Connection都应该创建一个新的ConnectionSpecSelector
1 2 3 4 | private final List<ConnectionSpec> connectionSpecs;//支持的ConnectionSpec列表 private int nextModeIndex;//当前可用的协议在列表中的位置的下一个索引(便于查找是否支持后备策略) private boolean isFallbackPossible;//是否支持后备策略 private boolean isFallback;//是否处于后备策略 |
ConnectionSpecSelector比较简单里面的方法有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs) { this.nextModeIndex = 0; this.connectionSpecs = connectionSpecs; } public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException { ConnectionSpec tlsConfiguration = null; for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) { ConnectionSpec connectionSpec = connectionSpecs.get(i); if (connectionSpec.isCompatible(sslSocket)) { tlsConfiguration = connectionSpec; nextModeIndex = i + 1; break; } } if (tlsConfiguration == null) { // This may be the first time a connection has been attempted and the socket does not support // any the required protocols, or it may be a retry (but this socket supports fewer // protocols than was suggested by a prior socket). throw new UnknownServiceException( "Unable to find acceptable protocols. isFallback=" + isFallback + ", modes=" + connectionSpecs + ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols())); } isFallbackPossible = isFallbackPossible(sslSocket); Internal.instance.apply(tlsConfiguration, sslSocket, isFallback); return tlsConfiguration; } //是否支持后备策略,从nextModeIndex处开始遍历,判断后面的ConnectionSpec是否匹配SSLSocket private boolean isFallbackPossible(SSLSocket socket) { for (int i = nextModeIndex; i < connectionSpecs.size(); i++) { if (connectionSpecs.get(i).isCompatible(socket)) { return true; } } return false; } |
- 构造方法中nextModeIndex为0,遍历connectionSpec列表,找到适合SSLSocket的ConnectionSpec,如果未找到直接抛出异常
- 判断在ConnectionSpec列表的后面是否还有适合SSLSocket的connectionSpec,如果有的话,那么就代表支持支持后备策略isFallbackPossible为true
- 将支持的ConnectionSpec设置给SSLSocket对象,并且此时isFallback还是为isFallback,表明目前还未处于后备策略中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public boolean connectionFailed(IOException e) { // Any future attempt to connect using this strategy will be a fallback attempt. isFallback = true; if (!isFallbackPossible) { return false; } // If there was a protocol problem, don't recover. if (e instanceof ProtocolException) { return false; } // If there was an interruption or timeout (SocketTimeoutException), don't recover. // For the socket connect timeout case we do not try the same host with a different // ConnectionSpec: we assume it is unreachable. if (e instanceof InterruptedIOException) { return false; } // Look for known client-side or negotiation errors that are unlikely to be fixed by trying // again with a different connection spec. if (e instanceof SSLHandshakeException) { // If the problem was a CertificateException from the X509TrustManager, // do not retry. if (e.getCause() instanceof CertificateException) { return false; } } if (e instanceof SSLPeerUnverifiedException) { // e.g. a certificate pinning error. return false; } // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we // retry those when we probably should not. return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException || e instanceof SSLException); } |
- 如果之前的SSLSocket连接失败了,那么之后连接尝试都属于后备策略,所以isFallBack置为true
上面是分别介绍了ConnectionSpec和ConnectionSpecSelector,下面把这两个类直接串联起来,看看OkHttp里面是怎么使用的
在RealConnection中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) { ... List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs(); ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);// if (route.address().sslSocketFactory() == null) { if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication not enabled for client")); } String host = route.address().url().host(); if (!Platform.get().isCleartextTrafficPermitted(host)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication to " + host + " not permitted by network security policy")); } } else { if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) { throw new RouteException(new UnknownServiceException( "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS")); } } while (true) { try { if (route.requiresTunnel()) { connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener); if (rawSocket == null) { // We were unable to connect the tunnel but properly closed down our resources. break; } } else { connectSocket(connectTimeout, readTimeout, call, eventListener); } establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener); eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol); break; } catch (IOException e) { ... //这里的connectionRetryEnabled默认值为true,是从OKHttpClient中传过来的,默认值为true //所以这里会执行ConnectionSpecSelector的connectionFailed()方法,如果是SSLHandshakeException,SSLProtocolException,SSLException那么就会执行后备策略,继续尝试TLS连接 if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) { throw routeException; } } } ... } |
- Address里面的ConnectionSpec就是OkHttpClient传入的ConnectionSpec,也就是之前说到的默认值
- 通过ConnectionSpecs构造出ConnectionSpecSelector
- 对于HTTP连接,如果OkHttp和Platform不支持Http的话,那么就抛出异常,Platform在Android
上是Android Platform - 开始尝试连接了,connectSocket主要是构建TCP连接,establishProtocol方法里面会构建TLS连接,最终会走到connectTls方法
- 如果连接过程失败了,那么会调用connectionSpecSelector的connectionFailed(),如果异常是SSLHandshakeException,SSLProtocolException,SSLException那么就会执行后备策略,继续尝试TLS连接,走回while(true)
下面再具体分析connectTls()方法,开始Tls连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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(); // block for session establishment SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); // Verify that the socket's certificates are acceptable for the target host. if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) { X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:" + " certificate: " + CertificatePinner.pin(cert) + " DN: " + cert.getSubjectDN().getName() + " 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); } } } |
- connectionSpecSelector.configureSecureSocket(sslSocket);开始确认密码套件,Tls版本和tls扩展等,这里就会调用ConnectionSpecSelector的方法,这里就是入口,这样就串联起来了,可以从这里进入,再结合上面对两个类的分析,再梳理下流程