Skip to content

Commit

Permalink
Merge pull request #7 from f-lab-edu/feat/jvm-sdk-async
Browse files Browse the repository at this point in the history
재시도 로직 구현 및 테스트 추가
  • Loading branch information
Stark-Industries0417 authored Jan 10, 2025
2 parents 6a8d5db + b67c54e commit 3d216ae
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 12 deletions.
52 changes: 51 additions & 1 deletion dummy/src/test/kotlin/MainIntegrationTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import com.gamedatahub.network.JvmNetworkClient
import com.gamedatahub.datacollection.DataCollector
import com.gamedatahub.serialization.JsonSerializer
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
Expand All @@ -9,6 +10,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import serialization.JacksonSerializer
import java.io.File
import java.util.concurrent.TimeUnit

class MainIntegrationTest {

Expand All @@ -22,7 +24,12 @@ class MainIntegrationTest {
mockWebServer = MockWebServer()
mockWebServer.start()

networkClient = JvmNetworkClient.Builder().build()
networkClient = JvmNetworkClient.Builder()
.enableRetry(true)
.maxRetries(3)
.retryDelayMillis(10)
.backoffFactor(2.0)
.build()
jsonSerializer = JacksonSerializer(clazz = User::class.java)
dataCollector = DataCollector(networkClient, jsonSerializer)
}
Expand Down Expand Up @@ -85,4 +92,47 @@ class MainIntegrationTest {

configFile.delete()
}

@Test
fun `서버 실패 시 정해진 횟수만큼 재시도 후 실패한다`() {
repeat(networkClient.config.maxRetries) {
mockWebServer.enqueue(MockResponse().setResponseCode(500))
}

val serverUrl = mockWebServer.url("/test").toString()
networkClient.postDataAsync(serverUrl, "{ \"key\": \"value\" }")

Thread.sleep(200)

assertEquals(4, mockWebServer.requestCount, "요청 횟수가 재시도 설정에 맞지 않습니다.")
}

@Test
fun `재시도 중 서버가 성공하면 요청이 종료된다`() {
repeat(networkClient.config.maxRetries - 1) {
mockWebServer.enqueue(MockResponse().setResponseCode(500))
}
mockWebServer.enqueue(MockResponse().setResponseCode(200))

val serverUrl = mockWebServer.url("/test").toString()
networkClient.postDataAsync(serverUrl, "{ \"key\": \"value\" }")

Thread.sleep(200)

assertEquals(3, mockWebServer.requestCount, "재시도 중간에 요청이 성공하지 않았습니다.")
}

@Test
fun `재시도 설정이 비활성화된 경우 한 번만 요청한다`() {
val clientWithoutRetry = JvmNetworkClient.Builder()
.enableRetry(false)
.build()
mockWebServer.enqueue(MockResponse().setResponseCode(500))

val serverUrl = mockWebServer.url("/test").toString()
clientWithoutRetry.postDataAsync(serverUrl, "{ \"key\": \"value\" }")

Thread.sleep(200)
assertEquals(1, mockWebServer.requestCount, "비활성화된 재시도에서 요청 횟수가 잘못되었습니다.")
}
}
43 changes: 32 additions & 11 deletions jvm-sdk/src/main/kotlin/com/gamedatahub/network/JvmNetworkClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import java.io.File
import java.io.IOException

data class NetworkClientConfig(
var isRetryEnabled: Boolean = true,
var maxRetries: Int = 2,
var retryDelayMillis: Long = 1000,
var backoffFactor: Double = 2.0
val isRetryEnabled: Boolean = false,
val maxRetries: Int = 1,
val retryDelayMillis: Long = 1000,
val backoffFactor: Double = 2.0
)

class JvmNetworkClient private constructor(
Expand All @@ -23,22 +23,43 @@ class JvmNetworkClient private constructor(
) : NetworkClient {

override fun postDataAsync(url: String, data: String) {
client.makePostRequest(url, data)
client.makePostRequestAsync(url, data)
}

private fun OkHttpClient.makePostRequest(url: String, data: String) {
private fun OkHttpClient.makePostRequestAsync(
url: String,
data: String,
attempt: Int = 0
) {
val requestBody = data.toRequestBody("application/json".toMediaType())

val request = Request.Builder()
.url(url)
.post(requestBody)
.build()

this.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("HTTP: ${response.code} - ${response.message}")
val maxAttempts = config.maxRetries
var delay = config.retryDelayMillis

this.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
var tmpAttempt = attempt
tmpAttempt++
if (tmpAttempt <= maxAttempts && config.isRetryEnabled) {
Thread.sleep(delay)
delay = (delay * config.backoffFactor).toLong()
makePostRequestAsync(url, data, tmpAttempt)
} else {
println("Request failed after ${tmpAttempt} attempts: ${e.message}")
}
}

override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
response.use {
if (!response.isSuccessful)
onFailure(call, IOException("HTTP ${response.code}: ${response.message}"))
}
}
}
})
}

class Builder {
Expand Down

0 comments on commit 3d216ae

Please sign in to comment.