feat(proxy): forward client HTTP method instead of hard-coding POST

The forwarder used to call client.post(&url) / http::Method::POST in
both the reqwest and hyper paths, and the Gemini route table only
registered POST /v1beta/*. As a result anything the Gemini SDK / CLI
sent as GET (models list, models/<id> info) hit a 404 at the router
and bypassed the local proxy's stats, rectifiers, and failover.

Thread the request method end-to-end:

- ProviderAdapter forwarder API now takes the http::Method by reference
  per attempt and dispatches client.request(method, &url) for reqwest
  and method.clone() for the hyper raw path.
- All five callers in handlers.rs (handle_messages_for_app for Claude /
  Claude Desktop, handle_chat_completions, handle_responses,
  handle_responses_compact, handle_gemini) pull the method out of the
  incoming axum::extract::Request and pass it on.
- handle_gemini tolerates an empty body (GET endpoints have none) and
  the forwarder skips serializing / sending a body for GET / HEAD —
  attaching JSON to a GET makes Gemini reject the request.
- server.rs swaps the Gemini routes to any(handle_gemini) so the same
  handler handles GET / POST / PUT / DELETE, and adds /gemini/v1/*
  for the GA path version.
This commit is contained in:
Jason
2026-05-13 23:36:36 +08:00
parent f2ae9823cb
commit 5d3d9067af
3 changed files with 51 additions and 11 deletions
+10 -3
View File
@@ -16,7 +16,7 @@ use super::{
use crate::database::Database;
use axum::{
extract::DefaultBodyLimit,
routing::{get, post},
routing::{any, get, post},
Router,
};
use hyper_util::rt::TokioIo;
@@ -331,8 +331,15 @@ impl ProxyServer {
post(handlers::handle_responses_compact),
)
// Gemini API (支持带前缀和不带前缀)
.route("/v1beta/*path", post(handlers::handle_gemini))
.route("/gemini/v1beta/*path", post(handlers::handle_gemini))
//
// 用 `any(..)` 覆盖所有 HTTP 方法:除了 POST `:generateContent` /
// `:streamGenerateContent` / `:countTokens` 之外,Gemini SDK / CLI 还会发
// GET `/models`、GET `/models/<id>` 等只读端点。如果只挂 POST,这些 GET
// 请求会在路由层 404,绕过本地代理的统计、整流和故障转移。
.route("/v1beta/*path", any(handlers::handle_gemini))
.route("/gemini/v1beta/*path", any(handlers::handle_gemini))
// Gemini 的 GA 版本也叫 /v1,给原 SDK 留一条出口
.route("/gemini/v1/*path", any(handlers::handle_gemini))
// 提高默认请求体大小限制(避免 413 Payload Too Large
.layer(DefaultBodyLimit::max(200 * 1024 * 1024))
.with_state(self.state.clone())