我试图使用ktor向远程API发出简单的post请求。只有一个端点,请求和响应主体在JSON中。
我在用
我需要发布的端点是https://mods.factorio.com/api/v2/mods/releases/init_upload - 这里的细节。
我创造了一个客户
private val client = HttpClient(CIO) {
install(Resources)
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
defaultRequest {
header(HttpHeaders.Authorization, "Bearer 123")
}
followRedirects = false
expectSuccess = true
}我用它来请求发邮件
val response = client.post(portalUploadEndpoint) {
contentType(ContentType.Application.Json)
setBody(InitUploadRequest("123"))
}请求和响应体是JSON对象,我使用的是Kotlinx序列化。(请参阅问题底部的完整代码。)
不管怎么说我都犯了错误。我做错了什么吗?
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property nextElementName has not been initialized
at io.ktor.resources.serialization.ParametersEncoder.encodeValue(ParametersEncoder.kt:26)
at kotlinx.serialization.encoding.AbstractEncoder.encodeString(AbstractEncoder.kt:51)
at kotlinx.serialization.internal.StringSerializer.serialize(Primitives.kt:141)
at kotlinx.serialization.internal.StringSerializer.serialize(Primitives.kt:138)
at kotlinx.serialization.encoding.Encoder$DefaultImpls.encodeSerializableValue(Encoding.kt:282)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableValue(AbstractEncoder.kt:18)
at io.ktor.resources.serialization.ResourcesFormat.encodeToParameters(ResourcesFormat.kt:90)
at io.ktor.resources.UrlBuilderKt.href(UrlBuilder.kt:49)
at MainKt$main$1.invokeSuspend(main.kt:163)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at MainKt.main(main.kt:47)
at MainKt.main(main.kt)全码
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.resources.Resources
import io.ktor.client.plugins.resources.post
import io.ktor.client.request.header
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.URLBuilder
import io.ktor.http.contentType
import io.ktor.http.takeFrom
import io.ktor.resources.Resource
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
private val client = HttpClient(CIO) {
install(Resources)
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
defaultRequest {
header(HttpHeaders.Authorization, "Bearer 123")
}
followRedirects = false
expectSuccess = true
}
fun main() = runBlocking {
val apiBase = "https://mods.factorio.com/api/v2"
val uploadEndpoint = "mods/releases/"
val portalUploadEndpoint = URLBuilder(apiBase).apply {
takeFrom(uploadEndpoint)
}.buildString()
println(portalUploadEndpoint)
val response = client.post(portalUploadEndpoint) {
contentType(ContentType.Application.Json)
setBody(InitUploadRequest("123"))
}
println(response)
}
@Resource("/init_upload")
@Serializable
data class InitUploadRequest(
@SerialName("mod") val modName: String,
)
@Serializable(with = InitUploadResponse.Serializer::class)
sealed interface InitUploadResponse {
/**
* @param[uploadUrl] URL the mod zip file should be uploaded to
*/
@Serializable
data class Success(
@SerialName("upload_url") val uploadUrl: String,
) : InitUploadResponse
object Serializer :
JsonContentPolymorphicSerializer<InitUploadResponse>(InitUploadResponse::class) {
override fun selectDeserializer(element: JsonElement) = when {
"upload_url" in element.jsonObject -> Success.serializer()
else -> Failure.serializer()
}
}
}
@Serializable
data class Failure(
val error: String? = null,
val message: String? = null,
) : InitUploadResponse另请参阅
我也在YouTrack https://youtrack.jetbrains.com/issue/KTOR-4342/上报道过这件事
发布于 2022-05-18 15:32:43
问题是,您要将一个String (URL)传递给HttpClient.post扩展函数,该扩展函数需要一个Resource。您需要在那里传递一个资源,或者使用来自io.ktor.client.request包的HttpClient.post扩展函数。
https://stackoverflow.com/questions/72286223
复制相似问题