Kotlin Ktor 我有多部分表单数据请求,我需要拦截并添加更多参数并从拦截器内部重新发送它

问题描述 投票:0回答:1

这是我作为多部分设置文件和纯文本参数发送的请求。

suspend fun updateUserProfileInformation(
        docList: List<File>,
        type: String?,
        reason: String?,
        address: String?,
        requiredOtp: String?
    ): CommonOtpRes {
        val formData = formData {
            for (file in docList) {
                this.appendInput(
                    key = "documents",
                    headers = Headers.build {
                        append("Content-Type", "image/jpeg")
                        append(HttpHeaders.ContentLength, file.length())
                        append(HttpHeaders.ContentDisposition, "filename=${file.name}")
                    },
                    size = file.length()
                ) { buildPacket { writeFully(file.readBytes()) } }
            }
            append("type", type ?: "")
            append("reason", reason ?: "")
            if(!address.isNullOrEmpty())
                append("address", address)
            if(!requiredOtp.isNullOrEmpty())
                append("requiredOTPS", requiredOtp)
        }
        val response: HttpResponse = client.post("$VERSION/user-request/submit") {
            setBody(
                MultiPartFormDataContent(
                    formData
                )
            )
        }
        return response.body()
    }

现在我需要做的是,一旦被调用并收到响应,我需要检查是否收到某些特定的状态代码,然后我需要修改请求并使用更新的参数重新发送它,克隆以前的请求正文并添加其他类型参数给它。我收到 request.body 类型作为 StreamRequestBody 类型,现在这个类是内部的并且不能直接访问,因此需要找到一种方法来修改内容并使用新的附加参数重新发送它。

调试请求正文

到目前为止我所尝试的,原始是收到响应后来自拦截器内部的请求。

if(original.body is RequestBody && original.body?.isOneShot() == true) {
                val buffer = Buffer()
                original.body?.writeTo(buffer)
                val bodyString = buffer.readUtf8()
                Timber.e("Body Contents: $bodyString")
}

但是这里收到的内容是空的。

以前使用Retrofit,这就是我实现相同目标的方法,但使用ktor 这是不可能的。

val builder: MultipartBody.Builder = MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                val requestBody = original.body as MultipartBody
                for (part in requestBody.parts) {
                    if (part.body.contentType() == "application/json; charset=utf-8".toMediaTypeOrNull() && part.headers?.get(
                            "Content-Disposition"
                        )?.contains("type") == true
                    ) {
                        builder.addFormDataPart("type", Gson().toJson(list))
                    } else {
                        builder.addPart(part)
                    }
                }
                val multipartReq = original.newBuilder()
                    .method(original.method, builder.build())
                    .build()
                return multipartReq
android okhttp ktor ktor-client
1个回答
0
投票

这在 Ktor 中比较困难,因为必须在

multipart/form-data
钩子中解析
Send
内容。您可以使用
CIOMultipartDataBase
内部类解析请求正文,然后将接收到的部分附加到
FormPart
列表中,该列表可用于为
MultiPartFormDataContent
类创建表单数据。可以使用
HttpRequestBuilder.takeFrom
方法复制原始请求。这是一个例子:

val client = HttpClient(CIO) {
    install(createClientPlugin("test") {
        on(Send) { request ->
            val call = proceed(request)

            val multipart = call.request.content

            if (multipart !is MultiPartFormDataContent) return@on call

            val readChannel = GlobalScope.writer {
                multipart.writeTo(channel)
            }.channel


            val multipartParser = CIOMultipartDataBase(EmptyCoroutineContext, readChannel, multipart.contentType.toString(), multipart.contentLength)

            // Copy parts
            val newParts = mutableListOf<FormPart<*>>()
            while (true) { 
                val part = multipartParser.readPart() ?: break

                if (part.contentType == ContentType.Application.Json && part.contentDisposition?.name == "type") {
                    newParts.add(FormPart("type", "{}"))
                } else {
                    when (part) {
                        is PartData.FormItem -> {
                            val name = part.name
                            if (name != null) {
                                newParts.add(FormPart(name, part.value, part.headers))
                            }
                        }

                        is PartData.FileItem -> {
                            val name = part.name
                            if (name != null) {
                                newParts.add(FormPart(name, InputProvider(block = part.provider), part.headers))
                            }
                        }

                        else -> error("")
                    }
                }
            }

            val newRequest = HttpRequestBuilder().takeFrom(request) // Create a copy of the request
            newRequest.setBody(
                MultiPartFormDataContent(
                    parts = formData(*newParts.toTypedArray()),
                    boundary = multipart.boundary
                )
            )

            proceed(newRequest)
        }
    })
}

val response = client.post("https://httpbin.org/post") {
    setBody(
        MultiPartFormDataContent(
            formData {
                append("description", "Ktor logo")
                append("type", "123", Headers.build {
                    append(HttpHeaders.ContentType, "application/json")
                })
                append("image", File("archive.zip").readBytes(), Headers.build {
                    append(HttpHeaders.ContentType, "image/png")
                    append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"")
                })
            },
            boundary = "WebAppBoundary"
        )
    )
}
© www.soinside.com 2019 - 2024. All rights reserved.