Skip to content

Commit

Permalink
Avoid expensive init on main
Browse files Browse the repository at this point in the history
  • Loading branch information
yschimke committed Feb 10, 2024
1 parent c65a67a commit 0e94b25
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 26 deletions.
63 changes: 37 additions & 26 deletions okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,15 @@ open class OkHttpClient internal constructor(
@get:JvmName("socketFactory")
val socketFactory: SocketFactory = builder.socketFactory

private val sslSocketFactoryOrNull: SSLSocketFactory?
private val sslInitializedFields: Lazy<SSLInitializedFields>?

@get:JvmName("sslSocketFactory")
val sslSocketFactory: SSLSocketFactory
get() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client")
get() = sslInitializedFields?.value?.sslSocketFactory ?: throw IllegalStateException("CLEARTEXT-only client")

@get:JvmName("x509TrustManager")
val x509TrustManager: X509TrustManager?
get() = sslInitializedFields?.value?.x509TrustManager

@get:JvmName("connectionSpecs")
val connectionSpecs: List<ConnectionSpec> =
Expand All @@ -226,9 +227,11 @@ open class OkHttpClient internal constructor(

@get:JvmName("certificatePinner")
val certificatePinner: CertificatePinner
get() = sslInitializedFields?.value?.certificatePinner ?: CertificatePinner.DEFAULT

@get:JvmName("certificateChainCleaner")
val certificateChainCleaner: CertificateChainCleaner?
get() = sslInitializedFields?.value?.certificateChainCleaner

/**
* Default call timeout (in milliseconds). By default there is no timeout for complete calls, but
Expand Down Expand Up @@ -269,29 +272,42 @@ open class OkHttpClient internal constructor(

init {
if (connectionSpecs.none { it.isTls }) {
this.sslSocketFactoryOrNull = null
this.certificateChainCleaner = null
this.x509TrustManager = null
this.certificatePinner = CertificatePinner.DEFAULT
this.sslInitializedFields = null
} else if (builder.sslSocketFactoryOrNull != null) {
this.sslSocketFactoryOrNull = builder.sslSocketFactoryOrNull
this.certificateChainCleaner = builder.certificateChainCleaner!!
this.x509TrustManager = builder.x509TrustManagerOrNull!!
this.certificatePinner =
builder.certificatePinner
.withCertificateChainCleaner(certificateChainCleaner!!)
this.sslInitializedFields =
lazyOf(
SSLInitializedFields(
builder.x509TrustManagerOrNull!!,
builder.sslSocketFactoryOrNull!!,
builder.certificateChainCleaner!!,
builder.certificatePinner.withCertificateChainCleaner(builder.certificateChainCleaner!!),
),
)
} else {
this.x509TrustManager = Platform.get().platformTrustManager()
this.sslSocketFactoryOrNull = Platform.get().newSslSocketFactory(x509TrustManager!!)
this.certificateChainCleaner = CertificateChainCleaner.get(x509TrustManager!!)
this.certificatePinner =
builder.certificatePinner
.withCertificateChainCleaner(certificateChainCleaner!!)
this.sslInitializedFields =
lazy {
val platform = Platform.get()
val trustManager = platform.platformTrustManager()
val certificateChainCleaner = CertificateChainCleaner.get(trustManager)
SSLInitializedFields(
trustManager,
platform.newSslSocketFactory(trustManager),
certificateChainCleaner,
builder.certificatePinner.withCertificateChainCleaner(certificateChainCleaner),
)
}
}

verifyClientState()
}

private data class SSLInitializedFields(
val x509TrustManager: X509TrustManager,
val sslSocketFactory: SSLSocketFactory,
val certificateChainCleaner: CertificateChainCleaner,
val certificatePinner: CertificatePinner,
)

private fun verifyClientState() {
check(null !in (interceptors as List<Interceptor?>)) {
"Null interceptor: $interceptors"
Expand All @@ -301,14 +317,9 @@ open class OkHttpClient internal constructor(
}

if (connectionSpecs.none { it.isTls }) {
check(sslSocketFactoryOrNull == null)
check(certificateChainCleaner == null)
check(x509TrustManager == null)
check(certificatePinner == CertificatePinner.DEFAULT)
check(sslInitializedFields == null) { "ssl initialized for plaintext client" }
} else {
checkNotNull(sslSocketFactoryOrNull) { "sslSocketFactory == null" }
checkNotNull(certificateChainCleaner) { "certificateChainCleaner == null" }
checkNotNull(x509TrustManager) { "x509TrustManager == null" }
checkNotNull(sslInitializedFields) { "ssl not initialized for client" }
}
}

Expand Down Expand Up @@ -597,7 +608,7 @@ open class OkHttpClient internal constructor(
this.proxySelector = okHttpClient.proxySelector
this.proxyAuthenticator = okHttpClient.proxyAuthenticator
this.socketFactory = okHttpClient.socketFactory
this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactory
this.x509TrustManagerOrNull = okHttpClient.x509TrustManager
this.connectionSpecs = okHttpClient.connectionSpecs
this.protocols = okHttpClient.protocols
Expand Down
55 changes: 55 additions & 0 deletions okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3

import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.internal.platform.Platform
import okhttp3.testing.PlatformRule
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

class OkHttpClientConstructionTest {
@RegisterExtension
var platform = PlatformRule()

@Test fun constructionDoesntTriggerPlatformOrSSL() {
Platform.resetForTests(platform = ExplosivePlatform())

val client = OkHttpClient()

assertNotNull(client.toString())

client.newCall(Request("https://example.org/robots.txt".toHttpUrl()))
}

class ExplosivePlatform : Platform() {
override fun newSSLContext(): SSLContext {
TODO("Avoid call")
}

override fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory {
TODO("Avoid call")
}

override fun platformTrustManager(): X509TrustManager {
TODO("Avoid call")
}
}
}

0 comments on commit 0e94b25

Please sign in to comment.