commonMain.aws.sdk.kotlin.runtime.http.AwsUserAgentMetadata.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-http-jvm Show documentation
Show all versions of aws-http-jvm Show documentation
HTTP core for AWS service clients
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.runtime.http
import aws.sdk.kotlin.runtime.InternalSdkApi
import aws.sdk.kotlin.runtime.http.operation.ConfigMetadata
import aws.sdk.kotlin.runtime.http.operation.CustomUserAgentMetadata
import aws.sdk.kotlin.runtime.http.operation.FeatureMetadata
import aws.smithy.kotlin.runtime.util.*
import kotlin.jvm.JvmInline
internal const val AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"
public const val AWS_APP_ID_ENV: String = "AWS_SDK_UA_APP_ID"
// non-standard environment variables/properties
public const val AWS_APP_ID_PROP: String = "aws.userAgentAppId"
internal const val FRAMEWORK_METADATA_ENV = "AWS_FRAMEWORK_METADATA"
internal const val FRAMEWORK_METADATA_PROP = "aws.frameworkMetadata"
/**
* Metadata used to populate the `User-Agent` and `x-amz-user-agent` headers
*/
public data class AwsUserAgentMetadata(
val sdkMetadata: SdkMetadata,
val apiMetadata: ApiMetadata,
val osMetadata: OsMetadata,
val languageMetadata: LanguageMetadata,
val execEnvMetadata: ExecutionEnvMetadata? = null,
val frameworkMetadata: FrameworkMetadata? = null,
val appId: String? = null,
val customMetadata: CustomUserAgentMetadata? = null,
) {
public companion object {
/**
* Load user agent configuration data from the current environment
*/
public fun fromEnvironment(
apiMeta: ApiMetadata,
appId: String? = null,
): AwsUserAgentMetadata = loadAwsUserAgentMetadataFromEnvironment(PlatformProvider.System, apiMeta, appId)
}
/**
* New-style user agent header value for `x-amz-user-agent`
*/
val xAmzUserAgent: String
/* ABNF for the user agent:
ua-string:
sdk-metadata RWS
[internal-metadata RWS]
ua-metadata
[api-metadata RWS]
os-metadata RWS
language-metadata RWS
[env-metadata RWS]
; ordering is not strictly required in the following section
*(config-metadata RWS)
[appId]
*(feat-metadata RWS)
*(framework-metadata RWS)
*/
get() = buildList {
add(sdkMetadata)
customMetadata?.extras?.takeIf { it.containsKey("internal") }?.let { add("md/internal") }
add(uaPair("ua", "2.0")) // User agent specification version 2.0
add(apiMetadata)
add(osMetadata)
add(languageMetadata)
execEnvMetadata?.let(::add)
customMetadata?.typedExtras?.filterIsInstance()?.forEach(::add)
appId?.let { add(uaPair("app", it)) }
customMetadata?.typedExtras?.filterIsInstance()?.forEach(::add)
frameworkMetadata?.let(::add)
customMetadata?.extras?.let {
val wrapper = AdditionalMetadata(it.filterKeys { it != "internal" })
add("$wrapper")
}
}.joinToString(separator = " ")
/**
* Legacy user agent header value for `UserAgent`
*/
val userAgent: String
get() = "$sdkMetadata"
}
internal fun loadAwsUserAgentMetadataFromEnvironment(platform: PlatformProvider, apiMeta: ApiMetadata, appIdValue: String? = null): AwsUserAgentMetadata {
val sdkMeta = SdkMetadata("kotlin", apiMeta.version)
val osInfo = platform.osInfo()
val osMetadata = OsMetadata(osInfo.family, osInfo.version)
val langMeta = platformLanguageMetadata()
val appId = appIdValue ?: platform.getProperty(AWS_APP_ID_PROP) ?: platform.getenv(AWS_APP_ID_ENV)
val frameworkMetadata = FrameworkMetadata.fromEnvironment(platform)
val customMetadata = CustomUserAgentMetadata.fromEnvironment(platform)
return AwsUserAgentMetadata(
sdkMeta,
apiMeta,
osMetadata,
langMeta,
detectExecEnv(platform),
frameworkMetadata = frameworkMetadata,
appId = appId,
customMetadata = customMetadata,
)
}
/**
* Wrapper around additional metadata kv-pairs that handles formatting
*/
@JvmInline
internal value class AdditionalMetadata(private val extras: Map) {
override fun toString(): String = extras.entries.joinToString(separator = " ") { entry ->
uaPair("md", entry.key, entry.value.takeUnless { it.equals("true", ignoreCase = true) })
}
}
/**
* SDK metadata
* @property name The SDK (language) name
* @property version The SDK version
*/
@InternalSdkApi
public data class SdkMetadata(val name: String, val version: String) {
override fun toString(): String = uaPair("aws-sdk-$name", version)
}
/**
* API metadata
* @property serviceId The service ID (sdkId) in use (e.g. "Api Gateway")
* @property version The version of the client (note this may be the same as [SdkMetadata.version] for SDK's
* that don't independently version clients from one another.
*/
@InternalSdkApi
public data class ApiMetadata(val serviceId: String, val version: String) {
override fun toString(): String {
val formattedServiceId = serviceId.replace(" ", "-").lowercase()
return uaPair("api", formattedServiceId, version)
}
}
/**
* Operating system metadata
*/
@InternalSdkApi
public data class OsMetadata(val family: OsFamily, val version: String? = null) {
override fun toString(): String {
// os-family = windows / linux / macos / android / ios / other
val familyStr = when (family) {
OsFamily.Unknown -> "other"
else -> family.toString()
}
return uaPair("os", familyStr, version)
}
}
/**
* Programming language metadata
* @property version The kotlin version in use
* @property extras Additional key value pairs appropriate for the language/runtime (e.g.`jvmVm=OpenJdk`, etc)
*/
@InternalSdkApi
public data class LanguageMetadata(
val version: String = KotlinVersion.CURRENT.toString(),
// additional metadata key/value pairs
val extras: Map = emptyMap(),
) {
override fun toString(): String = buildString {
append(uaPair("lang", "kotlin", version))
if (extras.isNotEmpty()) {
val wrapper = AdditionalMetadata(extras)
append(" $wrapper")
}
}
}
// provide platform specific metadata
internal expect fun platformLanguageMetadata(): LanguageMetadata
/**
* Execution environment metadata
* @property name The execution environment name (e.g. "lambda")
*/
@InternalSdkApi
public data class ExecutionEnvMetadata(val name: String) {
override fun toString(): String = uaPair("exec-env", name)
}
/**
* Framework metadata (e.g. name = "amplify" version = "1.2.3")
* @property name The framework name
* @property version The framework version
*/
@InternalSdkApi
public data class FrameworkMetadata(val name: String, val version: String) {
internal companion object {
internal fun fromEnvironment(provider: PlatformEnvironProvider): FrameworkMetadata? {
val kvPair = provider.getProperty(FRAMEWORK_METADATA_PROP) ?: provider.getenv(FRAMEWORK_METADATA_ENV)
return kvPair?.let {
val kv = kvPair.split(':', limit = 2)
check(kv.size == 2) { "Invalid value for FRAMEWORK_METADATA: $kvPair; must be of the form `name:version`" }
FrameworkMetadata(kv[0], kv[1])
}
}
}
override fun toString(): String = uaPair("lib", name, version)
}
private fun detectExecEnv(platform: PlatformEnvironProvider): ExecutionEnvMetadata? =
platform.getenv(AWS_EXECUTION_ENV)?.let {
ExecutionEnvMetadata(it)
}
// token_no_hash = 1*tchar_no_hash
// tchar_no_hash = "!" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
private val VALID_TCHAR_NO_HASH = setOf('!', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~')
internal fun uaPair(category: String, key: String, value: String? = null): String =
if (value == null) {
"${category.encodeUaToken()}/${key.encodeUaToken()}"
} else {
"${category.encodeUaToken()}/${key.encodeUaToken()}#${value.encodeUaToken()}"
}
private fun String.encodeUaToken(): String {
val str = this
return buildString(str.length) {
for (chr in str) {
when (chr) {
' ' -> append("_")
in 'a'..'z', in 'A'..'Z', in '0'..'9', in VALID_TCHAR_NO_HASH -> append(chr)
else -> continue
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy