mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-23 03:23:17 -04:00
Update to Ktor 3 and more (#13782)
* upgrade ktor version to `3.2.3` * Update EndpointImplementations.kt * update `androidx.core:core-ktx` to `1.16.0` * fix `Principal` deprecated message
This commit is contained in:
parent
e059d0dc4c
commit
99fa2cd964
@ -147,11 +147,11 @@ tasks.register<Exec>("run") {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.10.0")
|
||||
implementation("androidx.core:core-ktx:1.16.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.10.3")
|
||||
// Needed to convert e.g. Android 26 API calls to Android 21
|
||||
// If you remove this run `./gradlew :android:lintDebug` to ensure everything's okay.
|
||||
// If you want to upgrade this, check it's working by building an apk,
|
||||
// or by running `./gradlew :android:assembleRelease` which does that
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ object BuildConfig {
|
||||
const val appVersion = "4.17.12"
|
||||
|
||||
const val gdxVersion = "1.13.1"
|
||||
const val ktorVersion = "2.3.13"
|
||||
const val ktorVersion = "3.2.3"
|
||||
const val coroutinesVersion = "1.8.1"
|
||||
const val jnaVersion = "5.17.0"
|
||||
|
||||
|
@ -11,14 +11,11 @@ import com.unciv.logic.multiplayer.storage.ApiV2FileStorageWrapper
|
||||
import com.unciv.logic.multiplayer.storage.MultiplayerFileNotFoundException
|
||||
import com.unciv.utils.Concurrency
|
||||
import com.unciv.utils.Log
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.websocket.ClientWebSocketSession
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.FrameType
|
||||
import io.ktor.websocket.readText
|
||||
import io.ktor.websocket.close
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.websocket.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
@ -35,7 +32,7 @@ import java.time.Instant
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.collections.set
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Main class to interact with multiplayer servers implementing [ApiVersion.ApiV2]
|
||||
@ -339,7 +336,7 @@ class ApiV2(private val baseUrl: String) : ApiV2Wrapper(baseUrl), Disposable {
|
||||
* [delay] internally to quit waiting for the result of the operation.
|
||||
* This function may also throw arbitrary exceptions for network failures.
|
||||
*/
|
||||
suspend fun awaitPing(size: Int = 2, timeout: Long? = null): Double? {
|
||||
suspend fun awaitPing(size: Int = 2, timeout: Duration? = null): Double? {
|
||||
require(size < 2) { "Size too small to identify ping responses uniquely" }
|
||||
val body = ByteArray(size)
|
||||
Random().nextBytes(body)
|
||||
@ -501,7 +498,10 @@ class ApiV2(private val baseUrl: String) : ApiV2Wrapper(baseUrl), Disposable {
|
||||
* Note that this callback might not get called if no new WS connection was created.
|
||||
* It returns the measured round trip time in milliseconds if everything was fine.
|
||||
*/
|
||||
suspend fun ensureConnectedWebSocket(timeout: Long = DEFAULT_WEBSOCKET_PING_TIMEOUT, jobCallback: ((Job) -> Unit)? = null): Double? {
|
||||
suspend fun ensureConnectedWebSocket(
|
||||
timeout: Duration = DEFAULT_WEBSOCKET_PING_TIMEOUT,
|
||||
jobCallback: ((Job) -> Unit)? = null
|
||||
): Double? {
|
||||
val pingMeasurement = try {
|
||||
awaitPing(timeout = timeout)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.logic.multiplayer.apiv2
|
||||
|
||||
import java.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/** Name of the session cookie returned and expected by the server */
|
||||
internal const val SESSION_COOKIE_NAME = "id"
|
||||
@ -9,13 +10,13 @@ internal const val SESSION_COOKIE_NAME = "id"
|
||||
internal const val DEFAULT_LOBBY_MAX_PLAYERS = 32
|
||||
|
||||
/** Default ping frequency for outgoing WebSocket connection in seconds */
|
||||
internal const val DEFAULT_WEBSOCKET_PING_FREQUENCY = 15_000L
|
||||
internal val DEFAULT_WEBSOCKET_PING_FREQUENCY = 15.seconds
|
||||
|
||||
/** Default session timeout expected from multiplayer servers (unreliable) */
|
||||
internal val DEFAULT_SESSION_TIMEOUT = Duration.ofSeconds(15 * 60)
|
||||
internal val DEFAULT_SESSION_TIMEOUT = Duration.ofMinutes(15)
|
||||
|
||||
/** Default cache expiry timeout to indicate that certain data needs to be re-fetched */
|
||||
internal val DEFAULT_CACHE_EXPIRY = Duration.ofSeconds(30 * 60)
|
||||
internal val DEFAULT_CACHE_EXPIRY = Duration.ofMinutes(30)
|
||||
|
||||
/** Default timeout for a single request (miliseconds) */
|
||||
internal const val DEFAULT_REQUEST_TIMEOUT = 10_000L
|
||||
@ -24,4 +25,4 @@ internal const val DEFAULT_REQUEST_TIMEOUT = 10_000L
|
||||
internal const val DEFAULT_CONNECT_TIMEOUT = 5_000L
|
||||
|
||||
/** Default timeout for a single WebSocket PING-PONG roundtrip */
|
||||
internal const val DEFAULT_WEBSOCKET_PING_TIMEOUT = 10_000L
|
||||
internal val DEFAULT_WEBSOCKET_PING_TIMEOUT = 10.seconds
|
||||
|
@ -7,24 +7,17 @@
|
||||
package com.unciv.logic.multiplayer.apiv2
|
||||
|
||||
import com.unciv.utils.Log
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.cookies.get
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.cookies.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.request
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.client.statement.request
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.http.path
|
||||
import io.ktor.http.setCookie
|
||||
import io.ktor.util.network.UnresolvedAddressException
|
||||
import io.ktor.http.*
|
||||
import io.ktor.util.network.*
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
@ -46,7 +39,7 @@ private const val DEFAULT_RANDOM_PASSWORD_LENGTH = 32
|
||||
/**
|
||||
* Max age of a cached entry before it will be re-queried
|
||||
*/
|
||||
private const val MAX_CACHE_AGE_SECONDS = 60L
|
||||
private val MAX_CACHE_AGE = Duration.ofSeconds(60)
|
||||
|
||||
/**
|
||||
* Perform a HTTP request via [method] to [endpoint]
|
||||
@ -218,7 +211,7 @@ private object Cache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around [request] to cache responses to GET queries up to [MAX_CACHE_AGE_SECONDS]
|
||||
* Wrapper around [request] to cache responses to GET queries up to [MAX_CACHE_AGE]
|
||||
*/
|
||||
suspend fun get(
|
||||
endpoint: String,
|
||||
@ -230,7 +223,7 @@ private object Cache {
|
||||
retry: (suspend () -> Boolean)? = null
|
||||
): HttpResponse? {
|
||||
val result = responseCache[endpoint]
|
||||
if (cache && result != null && result.first.plusSeconds(MAX_CACHE_AGE_SECONDS).isAfter(Instant.now())) {
|
||||
if (cache && result != null && (result.first + MAX_CACHE_AGE).isAfter(Instant.now())) {
|
||||
return result.second
|
||||
}
|
||||
val response = request(HttpMethod.Get, endpoint, client, authHelper, refine, suppress, retry)
|
||||
|
@ -26,6 +26,8 @@ import kotlinx.serialization.json.ClassDiscriminatorMode
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@ -77,17 +79,17 @@ class ChatRestartException : CancellationException("Chat restart requested")
|
||||
class ChatStopException : CancellationException("Chat stop requested")
|
||||
|
||||
object ChatWebSocket {
|
||||
private const val MAX_RECONNECTION_ATTEMPTS = 100
|
||||
private val INITIAL_RECONNECT_TIME = 1.seconds
|
||||
private val MAX_RECONNECT_TIME = 64.seconds
|
||||
private val INITIAL_SESSION_WAIT_FOR_TIME = 5.seconds
|
||||
|
||||
private var isStarted = false
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
private var lastRetry = Clock.System.now()
|
||||
private var reconnectionAttempts = 0
|
||||
private var reconnectTimeSeconds = INITIAL_RECONNECT_TIME_SECONDS
|
||||
|
||||
private const val INITIAL_RECONNECT_TIME_SECONDS = 1
|
||||
private const val MAX_RECONNECTION_ATTEMPTS = 100
|
||||
private const val MAX_RECONNECT_TIME_SECONDS = 64
|
||||
private const val INITIAL_SESSION_WAIT_FOR_MS = 5_000L
|
||||
private var reconnectTime = INITIAL_RECONNECT_TIME
|
||||
|
||||
private var job: Job? = null
|
||||
private var session: DefaultClientWebSocketSession? = null
|
||||
@ -95,7 +97,7 @@ object ChatWebSocket {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private val client = HttpClient(CIO) {
|
||||
install(WebSockets) {
|
||||
pingInterval = 30_000
|
||||
pingInterval = 30.seconds
|
||||
contentConverter = KotlinxWebsocketSerializationConverter(Json {
|
||||
classDiscriminator = "type"
|
||||
// DO NOT OMIT
|
||||
@ -110,7 +112,7 @@ object ChatWebSocket {
|
||||
lastRetry = Clock.System.now()
|
||||
|
||||
reconnectionAttempts = 0
|
||||
reconnectionAttempts = INITIAL_RECONNECT_TIME_SECONDS
|
||||
reconnectTime = INITIAL_RECONNECT_TIME
|
||||
}
|
||||
|
||||
private fun getChatUrl(): Url = URLBuilder(
|
||||
@ -132,9 +134,9 @@ object ChatWebSocket {
|
||||
fun requestMessageSend(message: Message) {
|
||||
start()
|
||||
Concurrency.run("MultiplayerChatSendMessage") {
|
||||
withTimeoutOrNull(INITIAL_SESSION_WAIT_FOR_MS) {
|
||||
withTimeoutOrNull(INITIAL_SESSION_WAIT_FOR_TIME) {
|
||||
while (session == null) {
|
||||
delay(100)
|
||||
delay(100.milliseconds)
|
||||
}
|
||||
}
|
||||
session?.runCatching {
|
||||
@ -241,9 +243,8 @@ object ChatWebSocket {
|
||||
GlobalScope.launch {
|
||||
if (!force) {
|
||||
// exponential backoff same as described here: https://cloud.google.com/memorystore/docs/redis/exponential-backoff
|
||||
delay(Random.nextLong(1000) + 1000L * reconnectTimeSeconds)
|
||||
reconnectTimeSeconds =
|
||||
(reconnectTimeSeconds * 2).coerceAtMost(MAX_RECONNECT_TIME_SECONDS)
|
||||
delay(Random.nextLong(1000).milliseconds + reconnectTime)
|
||||
reconnectTime = (reconnectTime * 2).coerceAtMost(MAX_RECONNECT_TIME)
|
||||
if (job?.isActive == true) return@launch
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import java.io.File
|
||||
import java.util.Collections.synchronizedMap
|
||||
import java.util.Collections.synchronizedSet
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@ -140,7 +141,7 @@ private class WebSocketSessionManager {
|
||||
data class BasicAuthInfo(
|
||||
val userId: Uuid,
|
||||
val password: String,
|
||||
) : Principal
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if a [String] is a valid UUID
|
||||
@ -264,8 +265,8 @@ private class UncivServerRunner : CliktCommand() {
|
||||
}
|
||||
|
||||
if (chatV1Enabled) install(WebSockets) {
|
||||
pingPeriodMillis = 30_000
|
||||
timeoutMillis = 60_000
|
||||
pingPeriod = 30.seconds
|
||||
timeout = 60.seconds
|
||||
maxFrameSize = Long.MAX_VALUE
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
contentConverter = KotlinxWebsocketSerializationConverter(Json {
|
||||
@ -386,8 +387,7 @@ private class UncivServerRunner : CliktCommand() {
|
||||
|
||||
try {
|
||||
while (isActive) {
|
||||
val message = receiveDeserialized<Message>()
|
||||
when (message) {
|
||||
when (val message = receiveDeserialized<Message>()) {
|
||||
is Message.Chat -> {
|
||||
val gameId = message.gameId.toUuidOrNull()
|
||||
if (gameId == null) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user