From ea1cc79e50f87f7506c60f5faa52322cd6b63706 Mon Sep 17 00:00:00 2001 From: ryoii Date: Mon, 9 Oct 2023 17:38:30 +0800 Subject: [PATCH] Support http forward --- .../http/adapter/http/support/HttpForward.kt | 45 ++++++ .../adapter/http/plugin/HttpForwardTest.kt | 130 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/support/HttpForward.kt create mode 100644 mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/plugin/HttpForwardTest.kt diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/support/HttpForward.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/support/HttpForward.kt new file mode 100644 index 00000000..3cfbc9d2 --- /dev/null +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/support/HttpForward.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.api.http.adapter.http.support + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.util.pipeline.* + +suspend fun ApplicationCall.forward(forward: String) { + application.execute(ApplicationForwardCall(this, forward)) +} + +suspend fun ApplicationCall.forward(forward: String, body: Any) { + application.execute(ApplicationForwardCall(this, forward, body)) +} + +internal fun forwardReceivePipeline(body: Any): ApplicationReceivePipeline = ApplicationReceivePipeline().apply { + intercept(ApplicationReceivePipeline.Transform) { proceedWith(body) } +} + +internal class ApplicationForwardCall( + val call: ApplicationCall, forward: String, body: Any? = null +) : ApplicationCall by call { + override val request: ApplicationRequest = DelegateApplicationRequest(call.request, forward, body) +} + +internal class DelegateApplicationRequest( + private val delegate: ApplicationRequest, forward: String, body: Any? +) : ApplicationRequest by delegate { + private val _pipeline by lazy { body?.let { forwardReceivePipeline(it) } ?: delegate.pipeline } + override val local = DelegateRequestConnectionPoint(delegate.local, forward) + override val pipeline: ApplicationReceivePipeline = _pipeline +} + +internal class DelegateRequestConnectionPoint( + private val delegate: RequestConnectionPoint, override val uri: String +) : RequestConnectionPoint by delegate \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/plugin/HttpForwardTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/plugin/HttpForwardTest.kt new file mode 100644 index 00000000..0cb29b2e --- /dev/null +++ b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/plugin/HttpForwardTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2023 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.api.http.adapter.http.plugin + +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.doublereceive.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.testing.* +import net.mamoe.mirai.api.http.adapter.http.support.forward +import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.LongTargetDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.NudgeDTO +import net.mamoe.mirai.api.http.adapter.internal.serializer.BuiltinJsonSerializer +import kotlin.test.Test +import kotlin.test.assertEquals + +class HttpForwardTest { + + @Test + fun testGetRequestForward() = testApplication { + routing { + get("/test") { + call.forward("/forward") + } + + get("/forward") { + call.respondText(call.parameters["key"] ?: "null") + } + } + + client.get("/test") { + parameter("key", "value") + }.also { + assertEquals(HttpStatusCode.OK, it.status) + assertEquals("value", it.bodyAsText()) + } + } + + @Test + fun testPostRequestForwardReceiveBody() = testApplication { + install(ContentNegotiation) { json(json=BuiltinJsonSerializer.buildJson()) } + + routing { + post("/test") { + call.forward("/forward") + } + + post("/forward") { + val receive = call.receive() + call.respondText(receive.target.toString()) + } + } + + client.post("/test") { + contentType(ContentType.Application.Json) + setBody("""{"target":123}""") + }.also { + assertEquals(HttpStatusCode.OK, it.status) + assertEquals("123", it.bodyAsText()) + } + } + + @Test + fun testPostRequestForwardDoubleReceiveBody() = testApplication { + install(DoubleReceive) + install(ContentNegotiation) { json(json=BuiltinJsonSerializer.buildJson()) } + + routing { + post("/test") { + val receive = call.receive() + assertEquals(123, receive.target) + call.forward("/forward") + } + + post("/forward") { + val receive = call.receive() + call.respondText(receive.target.toString()) + } + } + + client.post("/test") { + contentType(ContentType.Application.Json) + setBody("""{"target":123}""") + }.also { + assertEquals(HttpStatusCode.OK, it.status) + assertEquals("123", it.bodyAsText()) + } + } + + @Test + fun testPostRequestForwardResetBody() = testApplication { + // No need for DoubleReceive + install(DoubleReceive) + install(HttpRouterMonitor) + install(ContentNegotiation) { json(json=BuiltinJsonSerializer.buildJson()) } + + routing { + post("/test") { + val receive = call.receive() + assertEquals(123, receive.target) + call.forward("/forward", NudgeDTO(321, 321, "kind")) + } + + post("/forward") { + val receive = call.receive() + call.respondText(receive.target.toString()) + } + } + + client.post("/test") { + contentType(ContentType.Application.Json) + setBody("""{"target":123}""") + }.also { + assertEquals(HttpStatusCode.OK, it.status) + assertEquals("321", it.bodyAsText()) + } + } +} \ No newline at end of file