diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt index c1b903440..7a0aff2ea 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt @@ -22,6 +22,8 @@ import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvid import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy import io.ktor.client.HttpClient import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.okhttp.OkHttp +import okhttp3.OkHttpClient /** * The integration point between the SDK Core and the product SDKs. @@ -39,7 +41,15 @@ abstract class BaseRapidClient( clientConfiguration.toProvider(), RapidConfigurationProvider ) - private val _httpClient: HttpClient = buildHttpClient(_configurationProvider, AuthenticationStrategy.AuthenticationType.SIGNATURE, httpClientEngine) + + private val _httpClientEngine: HttpClientEngine = clientConfiguration.okHttpClient?.let { + + OkHttp.create { + preconfigured = it + } + } ?: httpClientEngine + + private val _httpClient: HttpClient = buildHttpClient(_configurationProvider, AuthenticationStrategy.AuthenticationType.SIGNATURE, _httpClientEngine) init { finalize() @@ -53,5 +63,23 @@ abstract class BaseRapidClient( /** A [BaseRapidClient] builder. */ @Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients. - abstract class Builder> : Client.Builder() + abstract class Builder> : BuilderExtension() + + /** A [BaseRapidClient] builder with ability to pass a custom okhttp client. */ + @Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients. + abstract class BuilderWithHttpClient> : Client.Builder() { + + protected var okHttpClient: OkHttpClient? = null + + override fun self(): SELF { + return this as SELF + } + + /** Sets the [OkHttpClient] to use for the [BaseRapidClient]. */ + fun okHttpClient(okHttpClient: OkHttpClient): SELF { + this.okHttpClient = okHttpClient + return self() + } + + } } diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt index b3f550c53..2fd66f5af 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt @@ -53,5 +53,5 @@ abstract class BaseXapClient( /** A [BaseXapClient] builder. */ @Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients. - abstract class Builder> : Client.Builder() + abstract class Builder> : BuilderExtension() } diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt index 11ed5ea8d..c3b642d68 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt @@ -61,7 +61,7 @@ val dispatcher = Dispatcher().apply { val DEFAULT_HTTP_CLIENT_ENGINE: HttpClientEngine = OkHttp.create { config { - eventListener(OkHttpEventListener) + eventListenerFactory(OkHttpEventListener.FACTORY) dispatcher(dispatcher) } } @@ -147,7 +147,7 @@ abstract class Client( suspend fun performGet(url: String): HttpResponse = httpHandler.performGet(httpClient, url) /** - * A [Client] builder. + * A [Client] base builder. */ abstract class Builder> { /** Sets the API key to use for authentication. */ @@ -159,33 +159,6 @@ abstract class Client( /** Sets the API endpoint to use for requests. */ protected var endpoint: String? = null - /** - * Sets the request timeout in milliseconds. - * - * Request timeout is the time period from the start of the request to the completion of the response. - * - * Default is infinite - no timeout. - */ - protected var requestTimeout: Long? = null - - /** - * Sets the connection timeout in milliseconds. - * - * Connection timeout is the time period from the start of the request to the establishment of the connection with the server. - * - * Default is 10 seconds (10000 milliseconds). - */ - protected var connectionTimeout: Long? = null - - /** - * Sets the socket timeout in milliseconds. - * - * Socket timeout is the maximum period of inactivity between two consecutive data packets. - * - * Default is 15 seconds (15000 milliseconds). - */ - protected var socketTimeout: Long? = null - /** Sets tne body fields to be masked in logging. */ protected var maskedLoggingHeaders: Set? = null @@ -223,6 +196,70 @@ abstract class Client( return self() } + /** + * Sets tne headers to be masked in logging. + * + * @param headers the headers to be masked in logging. + * @return The [Builder] instance. + */ + fun maskedLoggingHeaders(vararg headers: String): SELF { + this.maskedLoggingHeaders = headers.toSet() + log.info(LoggingMessageProvider.getRuntimeConfigurationProviderMessage(ConfigurationName.MASKED_LOGGING_HEADERS, headers.joinToString())) + return self() + } + + /** + * Sets tne body fields to be masked in logging. + * + * @param fields the body fields to be masked in logging. + * @return The [Builder] instance. + */ + fun maskedLoggingBodyFields(vararg fields: String): SELF { + this.maskedLoggingBodyFields = fields.toSet() + log.info(LoggingMessageProvider.getRuntimeConfigurationProviderMessage(ConfigurationName.MASKED_LOGGING_BODY_FIELDS, fields.joinToString())) + return self() + } + + /** Create a [Client] object. */ + abstract fun build(): Client + + @Suppress("UNCHECKED_CAST") // This is safe because of the type parameter + protected open fun self(): SELF = this as SELF + } + + + /** + * A [Client] extension builder. + */ + abstract class BuilderExtension> : Builder() { + + /** + * Sets the request timeout in milliseconds. + * + * Request timeout is the time period from the start of the request to the completion of the response. + * + * Default is infinite - no timeout. + */ + protected var requestTimeout: Long? = null + + /** + * Sets the connection timeout in milliseconds. + * + * Connection timeout is the time period from the start of the request to the establishment of the connection with the server. + * + * Default is 10 seconds (10000 milliseconds). + */ + protected var connectionTimeout: Long? = null + + /** + * Sets the socket timeout in milliseconds. + * + * Socket timeout is the maximum period of inactivity between two consecutive data packets. + * + * Default is 15 seconds (15000 milliseconds). + */ + protected var socketTimeout: Long? = null + /** * Sets the request timeout in milliseconds. * Request timeout is the time period from the start of the request to the completion of the response. @@ -265,37 +302,14 @@ abstract class Client( return self() } - /** - * Sets tne headers to be masked in logging. - * - * @param headers the headers to be masked in logging. - * @return The [Builder] instance. - */ - fun maskedLoggingHeaders(vararg headers: String): SELF { - this.maskedLoggingHeaders = headers.toSet() - log.info(LoggingMessageProvider.getRuntimeConfigurationProviderMessage(ConfigurationName.MASKED_LOGGING_HEADERS, headers.joinToString())) - return self() - } - - /** - * Sets tne body fields to be masked in logging. - * - * @param fields the body fields to be masked in logging. - * @return The [Builder] instance. - */ - fun maskedLoggingBodyFields(vararg fields: String): SELF { - this.maskedLoggingBodyFields = fields.toSet() - log.info(LoggingMessageProvider.getRuntimeConfigurationProviderMessage(ConfigurationName.MASKED_LOGGING_BODY_FIELDS, fields.joinToString())) - return self() - } - /** Create a [Client] object. */ - abstract fun build(): Client + abstract override fun build(): Client - @Suppress("UNCHECKED_CAST") // This is safe because of the type parameter - protected open fun self(): SELF = this as SELF } + } /** Executes the hooks for the client. */ fun T.finalize() = Hooks.execute(this) + + diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt index cf28f895d..892a6d9a3 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt @@ -53,7 +53,7 @@ abstract class ExpediaGroupClient( /** An [ExpediaGroupClient] builder. */ @Suppress("unused") // This is used by the generated SDK clients. - abstract class Builder> : Client.Builder() { + abstract class Builder> : BuilderExtension() { /** Sets the API auth endpoint to use for requests. */ protected var authEndpoint: String? = null diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt index 25dbd741a..4845cdf55 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt @@ -19,28 +19,41 @@ import com.expediagroup.sdk.core.constant.HeaderKey import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory import okhttp3.Call import okhttp3.Connection -import okhttp3.EventListener import okhttp3.Handshake import okhttp3.Protocol import okhttp3.Request import okhttp3.Response +import okhttp3.EventListener import java.io.IOException import java.net.InetSocketAddress import java.net.Proxy +import java.util.UUID +import java.util.concurrent.atomic.AtomicReference + +data class OkHttpEventListener private constructor(private val transactionId: AtomicReference) : EventListener() { -object OkHttpEventListener : EventListener() { private val log = ExpediaGroupLoggerFactory.getLogger(this::class.java) - fun Call.getTransactionId() = request().headers[HeaderKey.TRANSACTION_ID] + // Factory for creating EventListeners with transaction IDs + companion object { + // Expose the factory as a static property + val FACTORY: EventListener.Factory = object : EventListener.Factory { + override fun create(call: Call): EventListener { + val transactionIdHeader = call.request().header(HeaderKey.TRANSACTION_ID) + val transactionId = AtomicReference(UUID.fromString(transactionIdHeader)) + return OkHttpEventListener(transactionId) + } + } + } override fun callStart(call: Call) { super.callStart(call) - log.debug("Call start for transaction-id: [${call.getTransactionId()}]") + log.debug("Call start for transaction-id: [${transactionId.get()}]") } override fun callEnd(call: Call) { super.callEnd(call) - log.debug("Call end for transaction-id: [${call.getTransactionId()}]") + log.debug("Call end for transaction-id: [${transactionId.get()}]") } override fun callFailed( @@ -48,12 +61,12 @@ object OkHttpEventListener : EventListener() { ioe: IOException ) { super.callFailed(call, ioe) - log.debug("Call failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") + log.debug("Call failed for transaction-id: [${transactionId.get()}] with exception message: ${ioe.message}") } override fun canceled(call: Call) { super.canceled(call) - log.debug("Call canceled for transaction-id: [${call.getTransactionId()}]") + log.debug("Call canceled for transaction-id: [${transactionId.get()}]") } override fun connectStart( @@ -62,7 +75,7 @@ object OkHttpEventListener : EventListener() { proxy: Proxy ) { super.connectStart(call, inetSocketAddress, proxy) - log.debug("Connect start for transaction-id: [${call.getTransactionId()}]") + log.debug("Connect start for transaction-id: [${transactionId.get()}]") } override fun connectEnd( @@ -72,7 +85,7 @@ object OkHttpEventListener : EventListener() { protocol: Protocol? ) { super.connectEnd(call, inetSocketAddress, proxy, protocol) - log.debug("Connect end for transaction-id: [${call.getTransactionId()}]") + log.debug("Connect end for transaction-id: [${transactionId.get()}]") } override fun connectFailed( @@ -83,7 +96,7 @@ object OkHttpEventListener : EventListener() { ioe: IOException ) { super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) - log.debug("Connect failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") + log.debug("Connect failed for transaction-id: [${transactionId.get()}] with exception message: ${ioe.message}") } override fun connectionAcquired( @@ -91,7 +104,7 @@ object OkHttpEventListener : EventListener() { connection: Connection ) { super.connectionAcquired(call, connection) - log.debug("Connection acquired for transaction-id: [${call.getTransactionId()}]") + log.debug("Connection acquired for transaction-id: [${transactionId.get()}]") } override fun connectionReleased( @@ -99,12 +112,12 @@ object OkHttpEventListener : EventListener() { connection: Connection ) { super.connectionReleased(call, connection) - log.debug("Connection released for transaction-id: [${call.getTransactionId()}]") + log.debug("Connection released for transaction-id: [${transactionId.get()}]") } override fun secureConnectStart(call: Call) { super.secureConnectStart(call) - log.debug("Secure connect start for transaction-id: [${call.getTransactionId()}]") + log.debug("Secure connect start for transaction-id: [${transactionId.get()}]") } override fun secureConnectEnd( @@ -112,12 +125,12 @@ object OkHttpEventListener : EventListener() { handshake: Handshake? ) { super.secureConnectEnd(call, handshake) - log.debug("Secure connect end for transaction-id: [${call.getTransactionId()}]") + log.debug("Secure connect end for transaction-id: [${transactionId.get()}]") } override fun requestHeadersStart(call: Call) { super.requestHeadersStart(call) - log.debug("Sending request headers start for transaction-id: [${call.getTransactionId()}]") + log.debug("Sending request headers start for transaction-id: [${transactionId.get()}]") } override fun requestHeadersEnd( @@ -125,12 +138,12 @@ object OkHttpEventListener : EventListener() { request: Request ) { super.requestHeadersEnd(call, request) - log.debug("Sending request headers end for transaction-id: [${call.getTransactionId()}]") + log.debug("Sending request headers end for transaction-id: [${transactionId.get()}]") } override fun requestBodyStart(call: Call) { super.requestBodyStart(call) - log.debug("Sending request body start for transaction-id: [${call.getTransactionId()}]") + log.debug("Sending request body start for transaction-id: [${transactionId.get()}]") } override fun requestBodyEnd( @@ -138,7 +151,7 @@ object OkHttpEventListener : EventListener() { byteCount: Long ) { super.requestBodyEnd(call, byteCount) - log.debug("Sending request body end for transaction-id: [${call.getTransactionId()}] with byte count: $byteCount") + log.debug("Sending request body end for transaction-id: [${transactionId.get()}] with byte count: $byteCount") } override fun requestFailed( @@ -146,12 +159,12 @@ object OkHttpEventListener : EventListener() { ioe: IOException ) { super.requestFailed(call, ioe) - log.debug("Request failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") + log.debug("Request failed for transaction-id: [${transactionId.get()}] with exception message: ${ioe.message}") } override fun responseHeadersStart(call: Call) { super.responseHeadersStart(call) - log.debug("Receiving response headers start for transaction-id: [${call.getTransactionId()}]") + log.debug("Receiving response headers start for transaction-id: [${transactionId.get()}]") } override fun responseHeadersEnd( @@ -159,12 +172,12 @@ object OkHttpEventListener : EventListener() { response: Response ) { super.responseHeadersEnd(call, response) - log.debug("Receiving response headers end for transaction-id: [${call.getTransactionId()}]") + log.debug("Receiving response headers end for transaction-id: [${transactionId.get()}]") } override fun responseBodyStart(call: Call) { super.responseBodyStart(call) - log.debug("Receiving response body start for transaction-id: [${call.getTransactionId()}]") + log.debug("Receiving response body start for transaction-id: [${transactionId.get()}]") } override fun responseBodyEnd( @@ -172,7 +185,7 @@ object OkHttpEventListener : EventListener() { byteCount: Long ) { super.responseBodyEnd(call, byteCount) - log.debug("Receiving response body end for transaction-id: [${call.getTransactionId()}] with byte count: $byteCount") + log.debug("Receiving response body end for transaction-id: [${transactionId.get()}] with byte count: $byteCount") } override fun responseFailed( @@ -180,6 +193,6 @@ object OkHttpEventListener : EventListener() { ioe: IOException ) { super.responseFailed(call, ioe) - log.debug("Receiving response failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") + log.debug("Receiving response failed for transaction-id: [${transactionId.get()}] with exception message: ${ioe.message}") } } diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt index cac3637de..523d116f2 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt @@ -16,6 +16,7 @@ package com.expediagroup.sdk.core.configuration import com.expediagroup.sdk.core.client.BaseRapidClient +import okhttp3.OkHttpClient /** * Configuration for the [BaseRapidClient]. @@ -37,5 +38,6 @@ data class RapidClientConfiguration( override val connectionTimeout: Long? = null, override val socketTimeout: Long? = null, override val maskedLoggingHeaders: Set? = null, - override val maskedLoggingBodyFields: Set? = null + override val maskedLoggingBodyFields: Set? = null, + val okHttpClient: OkHttpClient? = null ) : ClientConfiguration diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt index 0004086b7..0d9b9a955 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt @@ -37,4 +37,6 @@ internal object ConfigurationName { const val RUNTIME_CONFIGURATION_PROVIDER = "runtime configuration" const val CONFIGURATION_COLLECTOR = "configuration collector" + + const val OKHTTP_CLIENT = "okhttp client" }