diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d22720800..af19d4968 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,6 +2,8 @@ name: Tests on: push: + branches-ignore: + - master workflow_dispatch: jobs: diff --git a/.github/workflows/run-validations.yml b/.github/workflows/run-validations.yml index 1969875e8..c125fcfc4 100644 --- a/.github/workflows/run-validations.yml +++ b/.github/workflows/run-validations.yml @@ -2,6 +2,8 @@ name: Validations on: push: + branches-ignore: + - master workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/v2/PNConfigurationImpl.kt b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/v2/PNConfigurationImpl.kt index c75f9bf0c..3be559841 100644 --- a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/v2/PNConfigurationImpl.kt +++ b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/v2/PNConfigurationImpl.kt @@ -11,6 +11,7 @@ import com.pubnub.api.retry.RetryableEndpointGroup import okhttp3.Authenticator import okhttp3.CertificatePinner import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.slf4j.LoggerFactory import java.net.Proxy @@ -71,6 +72,10 @@ class PNConfigurationImpl( ) ), override val managePresenceListManually: Boolean = false, + override val baseOkHttpClient: OkHttpClient? = null, + override val subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, + override val nonSubscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, + override val filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, ) : PNConfiguration { companion object { const val DEFAULT_DEDUPE_SIZE = 100 diff --git a/pubnub-kotlin/pubnub-kotlin-core-api/src/jvmMain/kotlin/com/pubnub/api/v2/PNConfiguration.kt b/pubnub-kotlin/pubnub-kotlin-core-api/src/jvmMain/kotlin/com/pubnub/api/v2/PNConfiguration.kt index af2d5c353..bf4a9ab41 100644 --- a/pubnub-kotlin/pubnub-kotlin-core-api/src/jvmMain/kotlin/com/pubnub/api/v2/PNConfiguration.kt +++ b/pubnub-kotlin/pubnub-kotlin-core-api/src/jvmMain/kotlin/com/pubnub/api/v2/PNConfiguration.kt @@ -8,6 +8,7 @@ import com.pubnub.api.retry.RetryConfiguration import okhttp3.Authenticator import okhttp3.CertificatePinner import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import java.net.Proxy import java.net.ProxySelector @@ -16,6 +17,47 @@ import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509ExtendedTrustManager actual interface PNConfiguration { + /** + * The base [OkHttpClient] instance that will be used to preconfigure PubNub SDK's internal clients. + * + * Please note that the internal clients will share the dispatcher and connection pool with the provided client. + * + * @see [subscribeOkHttpConfigureAction] + * @see [nonSubscribeOkHttpConfigureAction] + * @see [filesOkHttpConfigureAction] + */ + val baseOkHttpClient: OkHttpClient? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for Subscribe requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing Subscribe requests. Those options should be set using the + * [subscribeOkHttpConfigureAction] instead. + */ + val subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for requests other than Subscribe or File upload/download. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing requests other than Subscribe and File upload/download. Those options + * should be set using the [nonSubscribeOkHttpConfigureAction] instead. + */ + val nonSubscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for File upload/download requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing File upload/download requests. Those options + * should be set using the [filesOkHttpConfigureAction] instead. + */ + val filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + /** * The user ID that the PubNub client will use. */ @@ -77,6 +119,9 @@ actual interface PNConfiguration { /** * Set to [PNLogVerbosity.BODY] to enable logging of network traffic, otherwise se to [PNLogVerbosity.NONE]. */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) actual val logVerbosity: PNLogVerbosity /** @@ -103,7 +148,7 @@ actual interface PNConfiguration { val heartbeatInterval: Int /** - * The subscribe request timeout. + * The subscribe read timeout. * * The value is in seconds. * @@ -147,6 +192,9 @@ actual interface PNConfiguration { * * Defaults to 10. */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val nonSubscribeReadTimeout: Int /** @@ -212,21 +260,33 @@ actual interface PNConfiguration { * * @see [Proxy] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val proxy: Proxy? /** * @see [ProxySelector] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val proxySelector: ProxySelector? /** * @see [Authenticator] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val proxyAuthenticator: Authenticator? /** * @see [CertificatePinner] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val certificatePinner: CertificatePinner? /** @@ -234,26 +294,41 @@ actual interface PNConfiguration { * * @see [HttpLoggingInterceptor] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val httpLoggingInterceptor: HttpLoggingInterceptor? /** * @see [SSLSocketFactory] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val sslSocketFactory: SSLSocketFactory? /** * @see [X509ExtendedTrustManager] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val x509ExtendedTrustManager: X509ExtendedTrustManager? /** * @see [okhttp3.ConnectionSpec] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val connectionSpec: ConnectionSpec? /** * @see [javax.net.ssl.HostnameVerifier] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) val hostnameVerifier: HostnameVerifier? /** @@ -505,6 +580,9 @@ actual interface PNConfiguration { /** * @see [okhttp3.Dispatcher.setMaxRequestsPerHost] */ + @Deprecated( + message = "Use `subscribeOkHttpConfigureAction`, `nonSubscribeOkHttpConfigureAction`, or `filesOkHttpConfigureAction` to configure the OkHttp client." + ) var maximumConnections: Int? /** @@ -594,6 +672,41 @@ actual interface PNConfiguration { */ var managePresenceListManually: Boolean + /** + * The base [OkHttpClient] instance that will be used to preconfigure PubNub SDK's internal `OKHttpClients`. + */ + var baseOkHttpClient: OkHttpClient? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for Subscribe requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing Subscribe requests. Those options should be set using the + * [subscribeOkHttpConfigureAction] instead. + */ + var subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for requests other than Subscribe or File upload/download. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing requests other than Subscribe and File upload/download. Those options + * should be set using the [nonSubscribeOkHttpConfigureAction] instead. + */ + var nonSubscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for File upload/download requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing File upload/download requests. Those options + * should be set using the [filesOkHttpConfigureAction] instead. + */ + var filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + /** * Create a [PNConfiguration] object with values from this builder. */ @@ -701,6 +814,36 @@ interface PNConfigurationOverride { */ var nonSubscribeReadTimeout: Int + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for Subscribe requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing Subscribe requests. Those options should be set using the + * [subscribeOkHttpConfigureAction] instead. + */ + var subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for requests other than Subscribe or File upload/download. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing requests other than Subscribe and File upload/download. Those options + * should be set using the [nonSubscribeOkHttpConfigureAction] instead. + */ + var nonSubscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + + /** + * Use this callback to configure any options on the [OkHttpClient] instance that will be used for File upload/download requests. + * + * Setting this option disables reading values from [logVerbosity], [httpLoggingInterceptor], [sslSocketFactory], + * [connectionSpec], [hostnameVerifier], [proxy], [proxySelector], [proxyAuthenticator] and [certificatePinner] for + * the instance of `OkHttpClient` servicing File upload/download requests. Those options + * should be set using the [filesOkHttpConfigureAction] instead. + */ + var filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? + /** * Create a [PNConfiguration] object with values from this builder. */ diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/managers/RetrofitManager.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/managers/RetrofitManager.kt index 93a78e190..c744494e6 100644 --- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/managers/RetrofitManager.kt +++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/managers/RetrofitManager.kt @@ -20,6 +20,7 @@ import com.pubnub.internal.services.TimeService import com.pubnub.internal.vendor.AppEngineFactory.Factory import okhttp3.Call import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor import org.jetbrains.annotations.TestOnly import retrofit2.Retrofit @@ -63,10 +64,22 @@ class RetrofitManager( init { if (!configuration.googleAppEngineNetworking) { - transactionClientInstance = createOkHttpClient(configuration.nonSubscribeReadTimeout, parentOkHttpClient = transactionClientInstance) - subscriptionClientInstance = createOkHttpClient(configuration.subscribeTimeout, parentOkHttpClient = subscriptionClientInstance) - noSignatureClientInstance = - createOkHttpClient(configuration.nonSubscribeReadTimeout, withSignature = false, parentOkHttpClient = noSignatureClientInstance) + transactionClientInstance = createOkHttpClient( + configuration.nonSubscribeReadTimeout, + parentOkHttpClient = transactionClientInstance ?: configuration.baseOkHttpClient, + configureOkHttpClient = configuration.nonSubscribeOkHttpConfigureAction + ) + subscriptionClientInstance = createOkHttpClient( + configuration.subscribeTimeout, + parentOkHttpClient = subscriptionClientInstance ?: configuration.baseOkHttpClient, + configureOkHttpClient = configuration.subscribeOkHttpConfigureAction + ) + noSignatureClientInstance = createOkHttpClient( + configuration.nonSubscribeReadTimeout, + withSignature = false, + parentOkHttpClient = noSignatureClientInstance ?: configuration.baseOkHttpClient, + configureOkHttpClient = configuration.filesOkHttpConfigureAction + ) } val transactionInstance = createRetrofit(transactionClientInstance) @@ -97,6 +110,7 @@ class RetrofitManager( readTimeout: Int, withSignature: Boolean = true, parentOkHttpClient: OkHttpClient? = null, + configureOkHttpClient: ((OkHttpClient.Builder) -> Unit)? = null, ): OkHttpClient { val okHttpBuilder = parentOkHttpClient?.newBuilder() ?: OkHttpClient.Builder() @@ -105,42 +119,43 @@ class RetrofitManager( .readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) .connectTimeout(configuration.connectTimeout.toLong(), TimeUnit.SECONDS) - with(configuration) { - if (logVerbosity == PNLogVerbosity.BODY) { - okHttpBuilder.addInterceptor( - HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - }, - ) + configureOkHttpClient?.let { configureAction -> configureAction(okHttpBuilder) } + ?: with(configuration) { + if (logVerbosity == PNLogVerbosity.BODY) { + okHttpBuilder.addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }, + ) + } + + if (httpLoggingInterceptor != null) { + okHttpBuilder.addInterceptor(httpLoggingInterceptor!!) + } + + if (sslSocketFactory != null && x509ExtendedTrustManager != null) { + okHttpBuilder.sslSocketFactory( + configuration.sslSocketFactory!!, + configuration.x509ExtendedTrustManager!!, + ) + } + connectionSpec?.let { okHttpBuilder.connectionSpecs(listOf(it)) } + hostnameVerifier?.let { okHttpBuilder.hostnameVerifier(it) } + proxy?.let { okHttpBuilder.proxy(it) } + proxySelector?.let { okHttpBuilder.proxySelector(it) } + proxyAuthenticator?.let { okHttpBuilder.proxyAuthenticator(it) } + certificatePinner?.let { okHttpBuilder.certificatePinner(it) } } - if (httpLoggingInterceptor != null) { - okHttpBuilder.addInterceptor(httpLoggingInterceptor!!) - } - - if (sslSocketFactory != null && x509ExtendedTrustManager != null) { - okHttpBuilder.sslSocketFactory( - configuration.sslSocketFactory!!, - configuration.x509ExtendedTrustManager!!, - ) - } - connectionSpec?.let { okHttpBuilder.connectionSpecs(listOf(it)) } - hostnameVerifier?.let { okHttpBuilder.hostnameVerifier(it) } - proxy?.let { okHttpBuilder.proxy(it) } - proxySelector?.let { okHttpBuilder.proxySelector(it) } - proxyAuthenticator?.let { okHttpBuilder.proxyAuthenticator(it) } - certificatePinner?.let { okHttpBuilder.certificatePinner(it) } - } - if (withSignature) { okHttpBuilder.interceptors().removeAll { it is SignatureInterceptor } okHttpBuilder.addInterceptor(signatureInterceptor) } val okHttpClient = okHttpBuilder.build() - - configuration.maximumConnections?.let { okHttpClient.dispatcher.maxRequestsPerHost = it } - + if (configureOkHttpClient == null) { + configuration.maximumConnections?.let { okHttpClient.dispatcher.maxRequestsPerHost = it } + } return okHttpClient } @@ -150,33 +165,44 @@ class RetrofitManager( .baseUrl(pubnub.baseUrl) .addConverterFactory(pubnub.mapper.converterFactory) - if (configuration.googleAppEngineNetworking) { - retrofitBuilder.callFactory(Factory(configuration)) + val taggingCallFactory = if (configuration.googleAppEngineNetworking) { + TaggingCallFactory(Factory(configuration), this) } else if (callFactory != null) { - retrofitBuilder.callFactory(callFactory) + TaggingCallFactory(callFactory, this) } else { throw IllegalStateException("Can't instantiate PubNub") } + retrofitBuilder.callFactory(taggingCallFactory) return retrofitBuilder.build() } fun destroy(force: Boolean = false) { - closeExecutor(transactionClientInstance, force) - closeExecutor(subscriptionClientInstance, force) - closeExecutor(noSignatureClientInstance, force) + cancelCalls(transactionClientInstance, force) + cancelCalls(subscriptionClientInstance, force) + cancelCalls(noSignatureClientInstance, force) } - private fun closeExecutor( + private fun cancelCalls( client: OkHttpClient?, force: Boolean, ) { - if (client != null) { - client.dispatcher.cancelAll() - if (force) { - client.connectionPool.evictAll() - val executorService = client.dispatcher.executorService - executorService.shutdown() + client?.dispatcher?.queuedCalls()?.forEach { call -> + if (this == call.request().tag(RetrofitManager::class.java)) { + call.cancel() + } + } + if (force) { + client?.dispatcher?.runningCalls()?.forEach { call -> + if (this == call.request().tag(RetrofitManager::class.java)) { + call.cancel() + } } } } } + +private class TaggingCallFactory(private val originalFactory: Call.Factory, private val retrofitManager: RetrofitManager) : Call.Factory { + override fun newCall(request: Request): Call = originalFactory.newCall( + request.newBuilder().tag(RetrofitManager::class.java, retrofitManager).build() + ) +} diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/v2/PNConfigurationImpl.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/v2/PNConfigurationImpl.kt index c4c135a27..5f3742939 100644 --- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/v2/PNConfigurationImpl.kt +++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/v2/PNConfigurationImpl.kt @@ -11,6 +11,7 @@ import com.pubnub.api.v2.PNConfigurationOverride import okhttp3.Authenticator import okhttp3.CertificatePinner import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.slf4j.LoggerFactory import java.net.Proxy @@ -71,6 +72,10 @@ class PNConfigurationImpl( ) ), override val managePresenceListManually: Boolean = false, + override val baseOkHttpClient: OkHttpClient? = null, + override val subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, + override val nonSubscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, + override val filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = null, ) : PNConfiguration, PNConfigurationOverride { companion object { const val DEFAULT_DEDUPE_SIZE = 100 @@ -183,6 +188,15 @@ class PNConfigurationImpl( override var managePresenceListManually: Boolean = defaultConfiguration.managePresenceListManually + override var baseOkHttpClient: OkHttpClient? = defaultConfiguration.baseOkHttpClient + override var subscribeOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = defaultConfiguration.subscribeOkHttpConfigureAction + override var nonSubscribeOkHttpConfigureAction: ( + ( + OkHttpClient.Builder + ) -> Unit + )? = defaultConfiguration.nonSubscribeOkHttpConfigureAction + override var filesOkHttpConfigureAction: ((OkHttpClient.Builder) -> Unit)? = defaultConfiguration.filesOkHttpConfigureAction + override fun build(): PNConfiguration { return PNConfigurationImpl( userId = userId, @@ -224,6 +238,10 @@ class PNConfigurationImpl( pnsdkSuffixes = pnsdkSuffixes, retryConfiguration = retryConfiguration, managePresenceListManually = managePresenceListManually, + baseOkHttpClient = baseOkHttpClient, + subscribeOkHttpConfigureAction = subscribeOkHttpConfigureAction, + nonSubscribeOkHttpConfigureAction = nonSubscribeOkHttpConfigureAction, + filesOkHttpConfigureAction = filesOkHttpConfigureAction, ) } }