fix: IllegalStateException(closed) from reading HTTP response (#5989)

* fix: incorrect closing

If we take InputStream or BufferedSource, it will be returned after closing...

* exception log
This commit is contained in:
木葉 Scarlet
2025-04-03 20:42:02 +08:00
committed by GitHub
parent cb37348662
commit 0dd6af946b
3 changed files with 44 additions and 16 deletions

View File

@@ -31,6 +31,7 @@ import okio.buffer
import okio.sink
import java.io.File
import java.io.InputStream
import java.io.Reader
import java.util.concurrent.TimeUnit
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@@ -99,21 +100,47 @@ enum class HttpMethod {
GET, POST, PUT, DELETE, PATCH
}
/**
* Parse body from [Response].
*
* If [T] is one of following types, it should be closed after using:
* [InputStream] / [BufferedSource] / [Reader]
*/
inline fun <reified T> Response.parse(): T {
return use {
when (T::class.java) {
String::class.java -> body.string() as T
Unit::class.java -> Unit as T
InputStream::class.java -> body.byteStream() as T
BufferedSource::class.java -> body.source() as T
NativeImageBackedTexture::class.java -> body.byteStream().use { stream ->
NativeImageBackedTexture(NativeImage.read(stream)) as T
}
else -> decode<T>(body.charStream())
return when (T::class.java) {
String::class.java -> body.string()
Unit::class.java -> close()
InputStream::class.java -> body.byteStream()
BufferedSource::class.java -> body.source()
Reader::class.java -> body.charStream()
NativeImageBackedTexture::class.java -> body.byteStream().use { stream ->
NativeImageBackedTexture(NativeImage.read(stream))
}
}
else -> decode(body.charStream())
} as T
}
/**
* Read all UTF-8 lines from [BufferedSource] as an [Iterator].
*
* When there are no more lines to read, the source is closed automatically.
*/
fun BufferedSource.utf8Lines(): Iterator<String> =
object : AbstractIterator<String>() {
override fun computeNext() {
val nextLine = readUtf8Line()
if (nextLine != null) {
setNext(nextLine)
} else {
close()
done()
}
}
}
/**
* Save response body to file.
*/
fun Response.toFile(file: File) = use { response ->
file.sink().buffer().use(response.body.source()::readAll)
}

View File

@@ -22,12 +22,12 @@ package net.ccbluex.liquidbounce.api.services.cdn
import net.ccbluex.liquidbounce.api.core.BaseApi
import net.ccbluex.liquidbounce.api.core.CLIENT_CDN
import net.ccbluex.liquidbounce.api.core.utf8Lines
import net.ccbluex.liquidbounce.api.models.cdn.IpcConfiguration
import okio.BufferedSource
object ClientCdn : BaseApi(CLIENT_CDN) {
suspend fun requestDiscordConfiguration() = get<IpcConfiguration>("/discord.json")
suspend fun requestStaffList(address: String): Set<String> = get<BufferedSource>("/staffs/$address").use {
generateSequence { it.readUtf8Line() }.toHashSet()
}
suspend fun requestStaffList(address: String): Set<String> =
get<BufferedSource>("/staffs/$address").utf8Lines().asSequence().toHashSet()
}

View File

@@ -72,7 +72,7 @@ object ModuleAntiStaff : ClientModule("AntiStaff", Category.MISC) {
if (serverStaffList.containsKey(address)) {
return
}
serverStaffList[address] = emptySet<String>()
serverStaffList[address] = emptySet()
withScope {
loadStaffList(address)
@@ -87,7 +87,7 @@ object ModuleAntiStaff : ClientModule("AntiStaff", Category.MISC) {
if (serverStaffList.containsKey(address)) {
return@sequenceHandler
}
serverStaffList[address] = emptySet<String>()
serverStaffList[address] = emptySet()
// Keeps us from loading the staff list multiple times
waitUntil { inGame && mc.currentScreen != null }
@@ -131,6 +131,7 @@ object ModuleAntiStaff : ClientModule("AntiStaff", Category.MISC) {
NotificationEvent.Severity.ERROR)
}
} catch (exception: Exception) {
logger.error("Failed to load staff list of $address", exception)
notification("AntiStaff", message("staffsFailed", address, exception.javaClass.simpleName),
NotificationEvent.Severity.ERROR)
}