Przeglądaj źródła

Http CommonRouter and echo

ryoii 2 lat temu
rodzic
commit
9e4ba4e77e

+ 43 - 1
docs/adapter/HttpAdapter.md

@@ -27,7 +27,9 @@ adapterSettings:
 #### 专有接口
 
 专有接口为该 `adapter` 特有的接口
-
++ **[测试](#测试接口)**
+  + [路由](#路由)
+  + [回响](#回响)
 + **[认证与会话](#认证与会话)**
   + [认证](#认证)
   + [绑定](#绑定)
@@ -104,6 +106,46 @@ adapterSettings:
   + [注册命令](#注册命令)
   + [命令接收](#命令接收)
 
+## 测试
+
+### 路由
+
+#### 接口名称
+```
+[GET] /router/{path}
+[GET] /router?router={path}
+[GET} /router/{path1}?router={path2}  // 使用 path1
+
+[POST] /router
+[POST] /router/{path}
+```
+此方法为万能路由,通过 path 参数将请求 **转发** 到其他接口
+
++ 对于 [GET] 请求,通过 query 传递参数
++ 对于 [POST] 请求,通过 body 传递参数
++ 对于 [POST] multi-part 请求,建议通过 `/router/{path}` 请求
+
+#### Post Body 请求:
+```json5
+{
+  "router": "/sendFriendMessage",
+  "body": {}
+}
+```
+
+### 回响
+
+#### 接口名称
+```
+[GET] /echo
+[POST] /echo
+```
+此方法将请求的 query 或 body 原样返回
+
+> 为保证 server 不按不确定的格式解析传入参数,统一以字符串形式返回
+> 
+> POST 请求返回 Content-Type: text/plain
+
 ## 认证与会话
 
 ### 认证

+ 6 - 1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/base.kt

@@ -18,6 +18,7 @@ import io.ktor.server.plugins.doublereceive.*
 import net.mamoe.mirai.api.http.adapter.http.HttpAdapter
 import net.mamoe.mirai.api.http.adapter.http.plugin.Authorization
 import net.mamoe.mirai.api.http.adapter.http.plugin.GlobalExceptionHandler
+import net.mamoe.mirai.api.http.adapter.http.plugin.HttpForward
 import net.mamoe.mirai.api.http.adapter.http.plugin.HttpRouterMonitor
 import net.mamoe.mirai.api.http.adapter.internal.serializer.BuiltinJsonSerializer
 import net.mamoe.mirai.api.http.context.MahContextHolder
@@ -34,7 +35,10 @@ fun Application.httpModule(adapter: HttpAdapter) {
         }
     }
 
-    install(ContentNegotiation) { json(json = BuiltinJsonSerializer.buildJson()) }
+    val jsonSerializer = BuiltinJsonSerializer.buildJson()
+
+    install(ContentNegotiation) { json(jsonSerializer) }
+    install(HttpForward) { jsonElementBodyConvertor(jsonSerializer) }
     install(GlobalExceptionHandler) { printTrace = MahContextHolder.debug }
     install(Authorization)
     if (MahContextHolder.debug) {
@@ -52,4 +56,5 @@ fun Application.httpModule(adapter: HttpAdapter) {
     fileRouter()
     commandRouter()
     announcementRouter()
+    commonRouter()
 }

+ 59 - 0
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/common.kt

@@ -0,0 +1,59 @@
+/*
+ * 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.router
+
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonElement
+import net.mamoe.mirai.api.http.adapter.http.plugin.forward
+
+@Serializable
+internal data class CommonRouter(
+    val router: String,
+    val body: JsonElement?
+)
+
+internal fun Application.commonRouter() = routing {
+
+    route("/router") {
+        get("/{pathRouter}") {
+            val router = call.parameters["pathRouter"] ?: return@get
+            call.forward(router + "?" + call.request.queryString())
+        }
+
+        get {
+            val router = call.request.queryParameters["router"] ?: return@get
+            call.forward(router + "?" + call.request.queryString())
+        }
+
+        post("/{router}") {
+            call.forward(call.parameters["router"] ?: "")
+        }
+
+        post {
+            val router = call.receive<CommonRouter>()
+            call.forward(router.router, router.body)
+        }
+    }
+
+    route("/echo") {
+        get {
+            call.respondText(call.request.queryString())
+        }
+
+        post {
+            call.respondText(call.receive<String>())
+        }
+    }
+}

+ 114 - 0
mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/router/CommonRouterTest.kt

@@ -0,0 +1,114 @@
+/*
+ * 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.router
+
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.dto.LongListRestfulResult
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class CommonRouterTest {
+
+    @Test
+    fun testGetRouterWithPath() = testHttpApplication {
+
+        client.get("/router/botList").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+
+            val body = it.body<LongListRestfulResult>()
+            assertEquals(StateCode.Success.code, body.code)
+            assertTrue(body.data.isEmpty())
+        }
+    }
+
+    @Test
+    fun testGetRouterWithQuery() = testHttpApplication {
+        client.get("/router?router=botList").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+
+            val body = it.body<LongListRestfulResult>()
+            assertEquals(StateCode.Success.code, body.code)
+            assertTrue(body.data.isEmpty())
+        }
+    }
+
+    @Test
+    fun testGetRouterWithPathAndQuery() = testHttpApplication {
+        client.get("/router/xxx?router=botList").also {
+            assertEquals(HttpStatusCode.NotFound, it.status)
+            println(it.bodyAsText())
+        }
+
+        client.get("/router/botList?router=xxx").also {
+            val body = it.body<LongListRestfulResult>()
+            assertEquals(StateCode.Success.code, body.code)
+            assertTrue(body.data.isEmpty())
+        }
+    }
+
+    @Test
+    fun testGetRouterPassQuery() = testHttpApplication {
+        client.get("/router/echo?qq=123").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("qq=123", it.bodyAsText())
+        }
+
+        client.get("/router?router=echo&qq=123").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("router=echo&qq=123", it.bodyAsText())
+        }
+
+        client.get("/router?router=/echo&qq=123").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("router=/echo&qq=123", it.bodyAsText())
+        }
+
+        client.get("/router?router=%2Fecho&qq=123").also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("router=%2Fecho&qq=123", it.bodyAsText())
+        }
+    }
+
+    @Test
+    fun testPostRouter() = testHttpApplication {
+        client.post {
+            url("/router/echo?qq=123")
+            setBody("hello world")
+        }.also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("hello world", it.bodyAsText())
+        }
+    }
+
+    @Test
+    fun testPostRouterWithJson() = testHttpApplication {
+        client.post("/router") {
+            contentType(ContentType.Application.Json)
+            setBody("""{"router": "echo", "body": "hello world"}""")
+        }.also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("\"hello world\"", it.bodyAsText())
+        }
+
+        client.post("/router") {
+            contentType(ContentType.Application.Json)
+            setBody("""{"router": "echo", "body": null}""")
+        }.also {
+            assertEquals(HttpStatusCode.OK, it.status)
+            assertEquals("null", it.bodyAsText())
+        }
+    }
+}