From 9e4ba4e77ef5811c703fe457481be55cdf8662a4 Mon Sep 17 00:00:00 2001 From: ryoii Date: Tue, 10 Oct 2023 14:12:18 +0800 Subject: [PATCH] Http CommonRouter and echo --- docs/adapter/HttpAdapter.md | 44 ++++++- .../api/http/adapter/http/router/base.kt | 7 +- .../api/http/adapter/http/router/common.kt | 59 +++++++++ .../adapter/http/router/CommonRouterTest.kt | 114 ++++++++++++++++++ 4 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/common.kt create mode 100644 mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/router/CommonRouterTest.kt diff --git a/docs/adapter/HttpAdapter.md b/docs/adapter/HttpAdapter.md index 8c841d4e..ca8f6aeb 100644 --- a/docs/adapter/HttpAdapter.md +++ b/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 + ## 认证与会话 ### 认证 diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/base.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/base.kt index 29b65f3e..f9d6fc5e 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/base.kt +++ b/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() } diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/common.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/adapter/http/router/common.kt new file mode 100644 index 00000000..24ebcff8 --- /dev/null +++ b/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() + call.forward(router.router, router.body) + } + } + + route("/echo") { + get { + call.respondText(call.request.queryString()) + } + + post { + call.respondText(call.receive()) + } + } +} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/router/CommonRouterTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/http/router/CommonRouterTest.kt new file mode 100644 index 00000000..9bb81dc9 --- /dev/null +++ b/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() + 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() + 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() + 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()) + } + } +} \ No newline at end of file