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