fix(proxy): 根路由/无 v1 前缀兼容,访问裸地址不再 404
This commit is contained in:
parent
a53899c6cf
commit
cf53d7c66f
@ -16,8 +16,8 @@ android {
|
||||
applicationId = "com.rainy.token"
|
||||
minSdk = 31
|
||||
targetSdk = 35
|
||||
versionCode = 49
|
||||
versionName = "1.7.29"
|
||||
versionCode = 50
|
||||
versionName = "1.7.30"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@ -98,42 +98,83 @@ class KtorLocalProxyServer @Inject constructor(
|
||||
json()
|
||||
}
|
||||
routing {
|
||||
get("/") {
|
||||
call.respond(
|
||||
mapOf(
|
||||
"status" to "ok",
|
||||
"endpoints" to listOf("GET /health", "GET /v1/models", "POST /v1/chat/completions"),
|
||||
"base_url" to "http://<手机热点或局域网IP>:8787/v1"
|
||||
)
|
||||
)
|
||||
}
|
||||
get("/v1") {
|
||||
call.respond(
|
||||
mapOf(
|
||||
"status" to "ok",
|
||||
"endpoints" to listOf("GET /health", "GET /v1/models", "POST /v1/chat/completions"),
|
||||
"base_url" to "http://<手机热点或局域网IP>:8787/v1"
|
||||
)
|
||||
)
|
||||
}
|
||||
get("/health") {
|
||||
call.respond(mapOf("status" to "ok"))
|
||||
}
|
||||
get("/v1/models") {
|
||||
if (!authorized(call, apiKey)) {
|
||||
call.respond(HttpStatusCode.Unauthorized, errorBody("未授权"))
|
||||
return@get
|
||||
call.respondModels(apiKey, sub2Api)
|
||||
}
|
||||
// 部分客户端 Base URL 不带 /v1 时兼容
|
||||
get("/models") {
|
||||
call.respondModels(apiKey, sub2Api)
|
||||
}
|
||||
post("/v1/chat/completions") {
|
||||
call.respondChatCompletion(apiKey, router)
|
||||
}
|
||||
post("/chat/completions") {
|
||||
call.respondChatCompletion(apiKey, router)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ApplicationCall.respondModels(
|
||||
apiKey: String?,
|
||||
sub2Api: Sub2ApiChatProxy,
|
||||
) {
|
||||
if (!authorized(this, apiKey)) {
|
||||
respond(HttpStatusCode.Unauthorized, errorBody("未授权"))
|
||||
return
|
||||
}
|
||||
val result = sub2Api.forwardModels()
|
||||
if (result == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, errorBody("Sub2API 未配置或未登录,请在设置中填写 API Key"))
|
||||
respond(HttpStatusCode.BadRequest, errorBody("Sub2API 未配置或未登录,请在设置中填写 API Key"))
|
||||
} else {
|
||||
call.respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, ""))
|
||||
respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, ""))
|
||||
}
|
||||
}
|
||||
post("/v1/chat/completions") {
|
||||
if (!authorized(call, apiKey)) {
|
||||
call.respond(HttpStatusCode.Unauthorized, errorBody("未授权"))
|
||||
return@post
|
||||
|
||||
private suspend fun ApplicationCall.respondChatCompletion(
|
||||
apiKey: String?,
|
||||
router: ProviderRouter,
|
||||
) {
|
||||
if (!authorized(this, apiKey)) {
|
||||
respond(HttpStatusCode.Unauthorized, errorBody("未授权"))
|
||||
return
|
||||
}
|
||||
val rawBody = call.receiveText()
|
||||
val rawBody = receiveText()
|
||||
if (rawBody.length > MAX_REQUEST_BYTES) {
|
||||
call.respond(HttpStatusCode(413, ""), errorBody("请求体过大"))
|
||||
return@post
|
||||
respond(HttpStatusCode(413, ""), errorBody("请求体过大"))
|
||||
return
|
||||
}
|
||||
val conversationId = call.request.headers["X-Conversation-Id"] ?: extractUser(rawBody)
|
||||
val conversationId = request.headers["X-Conversation-Id"] ?: extractUser(rawBody)
|
||||
try {
|
||||
if (extractStream(rawBody)) {
|
||||
val stream = router.openStreamingChat(rawBody, conversationId)
|
||||
if (stream == null) {
|
||||
call.respond(
|
||||
respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
errorBody("所有上游均不可用或未配置,请检查设置")
|
||||
)
|
||||
} else {
|
||||
call.respondOutputStream(
|
||||
respondOutputStream(
|
||||
contentType = contentTypeOf(stream.contentType),
|
||||
status = HttpStatusCode(stream.status, "")
|
||||
) {
|
||||
@ -154,31 +195,29 @@ class KtorLocalProxyServer @Inject constructor(
|
||||
} else {
|
||||
val result = router.forwardChat(rawBody, conversationId)
|
||||
if (result == null) {
|
||||
call.respond(
|
||||
respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
errorBody("所有上游均不可用或未配置,请检查设置")
|
||||
)
|
||||
} else {
|
||||
call.respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, ""))
|
||||
respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, ""))
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
val detail = e.message ?: "未知错误"
|
||||
call.respond(
|
||||
respond(
|
||||
HttpStatusCode.BadGateway,
|
||||
errorBody("上游网络错误:$detail")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
val detail = e.message ?: "未知错误"
|
||||
call.respond(
|
||||
respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
errorBody("网关内部错误:$detail")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun authorized(call: ApplicationCall, apiKey: String?): Boolean {
|
||||
if (apiKey.isNullOrBlank()) return true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user