Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: upgrade to ktor v3 #914

Merged
merged 48 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9a5eeda
chore: upgrade to ktor v3
anssari1 Dec 23, 2024
d9104f4
Update logger
anssari1 Jan 9, 2025
1f02fc9
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 12, 2025
55234c6
Remove masked body fields from logging configuration
anssari1 Jan 12, 2025
7f1418a
Fix client mustache template
anssari1 Jan 12, 2025
77d2417
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 13, 2025
246f14d
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 14, 2025
1e7cacf
Fix conflicts with main
anssari1 Jan 14, 2025
2556c98
fixes
anssari1 Jan 14, 2025
35440c1
Fix paginator
anssari1 Jan 14, 2025
a9ec653
Fix
anssari1 Jan 14, 2025
c761c04
fix tests
anssari1 Jan 14, 2025
29db31c
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 15, 2025
f7b3da6
Add nested masked fields
anssari1 Jan 16, 2025
3106d0a
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 19, 2025
671f0d9
remove EJMask
anssari1 Jan 20, 2025
8a6c125
Restore masked body fields client config
anssari1 Jan 20, 2025
43efa4e
remove unused class
anssari1 Jan 20, 2025
0e9631d
fixes
anssari1 Jan 20, 2025
5cd32b2
fix
anssari1 Jan 20, 2025
34cd955
fix
anssari1 Jan 20, 2025
b514edb
fix
anssari1 Jan 20, 2025
7eae480
remove ununused import
anssari1 Jan 20, 2025
444ef90
Truncate long log messages
anssari1 Jan 20, 2025
12a9226
fix tests
anssari1 Jan 20, 2025
287a070
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 20, 2025
8b4a3a1
fix client template
anssari1 Jan 20, 2025
3cfb31a
Update masking regex
anssari1 Jan 23, 2025
2fc3714
update masking regexes
anssari1 Jan 26, 2025
f30ee45
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 26, 2025
5b7685e
Fix unit tests
anssari1 Jan 28, 2025
857b1d2
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 28, 2025
edf48d4
update logging masked fields provider test
anssari1 Jan 28, 2025
6e49561
fix
anssari1 Jan 28, 2025
a8b9f19
Update log masker test
anssari1 Jan 28, 2025
6865f29
fix
anssari1 Jan 28, 2025
ca8e0ea
update log masker test
anssari1 Jan 28, 2025
16bbb94
Update masking regexes
anssari1 Jan 28, 2025
173a5cb
add paginator tests for empty responses
anssari1 Jan 28, 2025
24c82e3
add fallback body to paginator to handle empty responses
anssari1 Jan 29, 2025
a685716
update unit tests
anssari1 Jan 29, 2025
dd44e7a
Merge branch 'main' into nanssari/ktor_3
dtayeh Jan 29, 2025
89492ab
updates
anssari1 Jan 29, 2025
f1f8801
update unit test
anssari1 Jan 29, 2025
132dffc
update masking unit test
anssari1 Jan 30, 2025
ec7093d
Merge branch 'main' into nanssari/ktor_3
anssari1 Jan 30, 2025
1cda0ad
Merge branch 'main' into nanssari/ktor_3
anssari1 Feb 3, 2025
fa083ee
Merge branch 'main' into nanssari/ktor_3
anssari1 Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<kotlin.version>2.1.0</kotlin.version>
<ktor.version>2.3.13</ktor.version>
<ktor.version>3.0.3</ktor.version>

<ktlint-plugin.version>3.3.0</ktlint-plugin.version>
<build-helper-maven-plugin.version>3.6.0</build-helper-maven-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import okhttp3.Dispatcher
import okhttp3.OkHttpClient

// Create a Dispatcher with limits
val dispatcher = Dispatcher().apply {
val configuredDispatcher = Dispatcher().apply {
maxRequests = 10000 // Maximum number of concurrent requests
maxRequestsPerHost = 1000
}
Expand All @@ -63,7 +63,7 @@ val DEFAULT_HTTP_CLIENT_ENGINE: HttpClientEngine =
OkHttp.create {
config {
eventListenerFactory(OkHttpEventListener.FACTORY)
dispatcher(dispatcher)
dispatcher(configuredDispatcher)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@ abstract class ExpediaGroupClient(
return self()
}
}

@Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients.
abstract class BuilderWithHttpClient<SELF : Client.BuilderWithHttpClient<SELF>> : Client.BuilderWithHttpClient<SELF>() {
/** Sets the API auth endpoint to use for requests. */
protected var authEndpoint: String? = null

/** Sets the API auth endpoint to use for requests.
*
* @param authEndpoint The API auth endpoint to use for requests.
* @return The [Builder] instance.
*/
fun authEndpoint(authEndpoint: String): SELF {
this.authEndpoint = authEndpoint
return self()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@
*/
package com.expediagroup.sdk.core.configuration.provider

import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.connectionTimeout
import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.endpoint
import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.name
import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.requestTimeout
import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.socketTimeout
import com.expediagroup.sdk.core.constant.Constant

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
*/
package com.expediagroup.sdk.core.constant

import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.HttpTimeoutConfig

internal object Constant {
const val EMPTY_STRING = ""
const val TEN_SECONDS_IN_MILLIS = 10_000L
const val FIFTEEN_SECONDS_IN_MILLIS = 15_000L
const val INFINITE_TIMEOUT = HttpTimeout.INFINITE_TIMEOUT_MS
const val INFINITE_TIMEOUT = HttpTimeoutConfig.INFINITE_TIMEOUT_MS

private const val SUCCESSFUL_STATUS_CODES_RANGE_START = 200
private const val SUCCESSFUL_STATUS_CODES_RANGE_END = 299
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.expediagroup.sdk.core.constant

import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED

internal object LogMaskingRegex {
val FIELD_REGEX = "^[a-zA-Z0-9-_]+$".toRegex()

val NUMBER_FIELD_REGEX = "(?<=[\"']?number[\"']?:\\s?[\"'])(\\s*\\d{15,16}\\s*)(?=[\"'])".toRegex()
const val REPLACEMENT_TEMPLATE = "\"$1$2${OMITTED}\""
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@
package com.expediagroup.sdk.core.constant.provider

internal object LogMaskingRegexProvider {
fun getMaskedFieldsRegex(maskedBodyFields: Set<String>) = "(?<=[\"']?(${maskedBodyFields.joinToString("|")})[\"']?:\\s?[\"'])(\\s*[^\"']+\\s*)(?=[\"'])".toRegex()
fun getMaskedFieldsRegex(maskedBodyFields: Set<String>, value: String = "[^\\\"]+"): Regex {
val fields = maskedBodyFields.joinToString("|")
return "\"(${fields})(\\\\*\"\\s*:\\s*\\\\*\")${value}(?:\\\\?\"|)".toRegex()
anssari1 marked this conversation as resolved.
Show resolved Hide resolved
}

fun getMaskedFieldsRegex(maskedBodyField: String, value: String = "[^\\\"]+"): Regex {
val fields = setOf(maskedBodyField)
return getMaskedFieldsRegex(fields, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,10 @@
package com.expediagroup.sdk.core.model.paging

import com.expediagroup.sdk.core.client.Client
import com.expediagroup.sdk.core.constant.HeaderValue
import com.expediagroup.sdk.core.model.Response
import com.expediagroup.sdk.core.plugin.logging.GZipEncoder.decode
import com.expediagroup.sdk.core.plugin.logging.contentEncoding
import io.ktor.client.statement.HttpResponse
import io.ktor.util.InternalAPI
import io.ktor.util.moveToByteArray
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.bits.Memory
import io.ktor.client.statement.bodyAsBytes
import kotlinx.coroutines.runBlocking
import java.nio.ByteBuffer

internal interface ResponseState<T> {
fun getNextResponse(): Response<T>
Expand Down Expand Up @@ -75,23 +68,9 @@ internal class FetchLinkState<T>(
}

private suspend fun parseBody(response: HttpResponse): T {
return if (decodeBody(response).isEmpty()) fallbackBody else getBody(response)
}

private suspend fun decodeBody(response: HttpResponse): String {
val byteReadChannel = prepareByteReadChannel(response)
val decodedByteReadChannel = if (response.contentEncoding().equals(HeaderValue.GZIP)) client.httpClient.decode(byteReadChannel) else byteReadChannel
val bodyString: String = decodedByteReadChannel.readRemaining().readText()
return bodyString
}

@OptIn(InternalAPI::class)
private suspend fun prepareByteReadChannel(response: HttpResponse): ByteReadChannel {
val bufferSize = response.content.availableForRead
val buffer = ByteBuffer.allocate(bufferSize)
val numberOfBytesRead = response.content.peekTo(Memory(buffer), 0, 0, 0, bufferSize.toLong()).toInt()
val byteReadChannel = ByteReadChannel(buffer.moveToByteArray(), 0, numberOfBytesRead)
return byteReadChannel
// response.bodyAsBytes() applies all plugins
// if content-length header is set, response.contentLength could be used instead
return if (response.bodyAsBytes().isEmpty()) fallbackBody else getBody(response)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfigurati
import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType.BASIC
import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType.BEARER
import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType.SIGNATURE
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.AuthConfig
import io.ktor.client.request.HttpRequestBuilder

internal interface AuthenticationStrategy {
fun loadAuth(auth: Auth) {}
fun loadAuth(auth: AuthConfig) {}

fun isTokenAboutToExpire(): Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ package com.expediagroup.sdk.core.plugin.authentication.strategy

import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.AuthConfig
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
import io.ktor.client.plugins.auth.providers.basic
import io.ktor.client.request.HttpRequestBuilder

internal class BasicAuthenticationStrategy(
private val configs: AuthenticationConfiguration
) : AuthenticationStrategy {
override fun loadAuth(auth: Auth) {
auth.basic {
override fun loadAuth(authConfig: AuthConfig) {
authConfig.basic {
sendWithoutRequest { true }
credentials {
BasicAuthCredentials(username = configs.credentials.key, password = configs.credentials.secret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfigurati
import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.AuthConfig
import io.ktor.client.plugins.auth.authProviders
import io.ktor.client.plugins.auth.providers.BearerAuthProvider
import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.plugin
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.basicAuth
import io.ktor.client.request.parameter
Expand All @@ -53,8 +53,8 @@ internal class ExpediaGroupAuthenticationStrategy(
private val log = ExpediaGroupLoggerFactory.getLogger(javaClass)
private var bearerTokenStorage = BearerTokensInfo.emptyBearerTokenInfo

override fun loadAuth(auth: Auth) {
auth.bearer {
override fun loadAuth(authConfig: AuthConfig) {
authConfig.bearer {
sendWithoutRequest { request ->
isIdentityRequest(request)
}
Expand Down Expand Up @@ -94,7 +94,7 @@ internal class ExpediaGroupAuthenticationStrategy(

private fun clearTokens(client: HttpClient) {
log.info(LoggingMessage.TOKEN_CLEARING_IN_PROGRESS)
client.plugin(Auth).providers.filterIsInstance<BearerAuthProvider>().first().clearToken()
client.authProviders.filterIsInstance<BearerAuthProvider>().first().clearToken()
bearerTokenStorage = BearerTokensInfo.emptyBearerTokenInfo
log.info(LoggingMessage.TOKEN_CLEARING_SUCCESSFUL)
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import org.slf4j.Logger

internal class ExpediaGroupLogger(private val logger: Logger, private val client: Client? = null) : Logger by logger {

private val mask = LogMasker(getMaskedBodyFieldFilters())

override fun info(msg: String) {
if (logger.isInfoEnabled) {
logger.info(decorate(msg))
Expand All @@ -42,11 +40,7 @@ internal class ExpediaGroupLogger(private val logger: Logger, private val client
}
}

private fun decorate(msg: String): String = "$LOGGING_PREFIX ${mask(msg)}"
private fun decorate(msg: String): String = "$LOGGING_PREFIX ${mask(msg, getMaskedBodyFields())}"

private fun getMaskedBodyFields(): Set<String> = client?.getLoggingMaskedFieldsProvider()?.getMaskedBodyFields() ?: LogMaskingFields.DEFAULT_MASKED_BODY_FIELDS

private fun getMaskedBodyFieldFilters(): Iterable<ExpediaGroupJsonFieldFilter> = listOf(
ExpediaGroupJsonFieldFilter(getMaskedBodyFields().toTypedArray()),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
*/
package com.expediagroup.sdk.core.plugin.logging

import com.ebay.ejmask.core.BaseFilter
import com.ebay.ejmask.core.EJMask
import com.ebay.ejmask.core.EJMaskInitializer
import com.ebay.ejmask.core.util.LoggerUtil
import com.expediagroup.sdk.core.constant.LogMaskingRegex.REPLACEMENT_TEMPLATE
import com.expediagroup.sdk.core.constant.provider.LogMaskingRegexProvider

internal class LogMasker(
filters: Iterable<BaseFilter>
) : (String) -> String {
init {
LoggerUtil.register { _, _, _ -> /* disable logging */ }
filters.forEach { EJMaskInitializer.addFilter(it) }
}
internal fun mask(
message: String,
maskedBodyFields: Set<String>
): String {
val masks: List<Mask> =
listOf(
Mask { LogMaskingRegexProvider.getMaskedFieldsRegex(maskedBodyFields) },
Mask { LogMaskingRegexProvider.getMaskedFieldsRegex("number", "\\d{12,19}")}
)

override fun invoke(message: String): String = EJMask.mask(message)
return masks.fold(message) { acc, mask -> mask.mask(acc) }
}

internal fun interface Mask {
fun getRegex(): Regex

fun mask(string: String) = string.replace(this.getRegex(), REPLACEMENT_TEMPLATE)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal data class LoggingConfiguration(
override val httpClientConfiguration: HttpClientConfig<out HttpClientEngineConfig>,
val maskedLoggingHeaders: Set<String>,
val maskedLoggingBodyFields: Set<String>,
val level: LogLevel = LogLevel.HEADERS,
val level: LogLevel = LogLevel.ALL,
val getLogger: (client: Client) -> Logger = createCustomLogger
) : KtorPluginConfiguration(httpClientConfiguration) {
companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ internal object LoggingPlugin : Plugin<LoggingConfiguration> {
client.getLoggingMaskedFieldsProvider().getMaskedHeaderFields().contains(header)
}
}
configurations.httpClientConfiguration.install(RequestBodyLogger)
configurations.httpClientConfiguration.install(ResponseBodyLogger)
}
}

Expand Down
Loading
Loading