Skip to content

Commit fa37f0f

Browse files
author
gemini2api bot
committed
v1.0: comprehensive refactor — security, concurrency, real streaming, multimodal, function calling, WebUI rewrite, 51 tests, CI
Major changes ============= Security -------- * Bearer Token auth middleware (verify_api_key) for /v1/* * Admin cookie auth + /webui/login for the management UI * Drop allow_origins=[*] + credentials=True CORS anti-pattern; opt-in via CORS_ORIGINS env var * WebUI template XSS closed (html.escape on every dynamic value) * Pydantic v2 strict mode with field validators (account name, role whitelist, temperature range, …) Concurrency / stability ----------------------- * Global _reqid counter replaced with per-request local base * AccountPool wrapped in asyncio.Lock; select / record_* / release all atomic * Per-account in-flight cap (max_concurrent) and per-minute rate limit (rate_limit_rpm) — LRU candidates skip saturated accounts * Shared process-wide httpx.AsyncClient (no per-request TLS handshake) * Per-stage httpx.Timeout (connect/read/write/pool) * Exponential-backoff retry on 401/403/429/5xx with stream fallback to blocking call * Background _env_watcher reloads .env on mtime change Protocol -------- * Real SSE incremental streaming (httpx.stream + server-side chunking with delta events) * Token usage extracted from inner_data[2]._mtokenCount / _ttokenCount / _stokenCount * Multipart image upload via Gemini UploadFile endpoint; supports data: URIs, HTTP(S) URLs, and local file paths * Function calling: tools / tool_choice passthrough and response tool_calls detection * parse_response picks the first non-empty candidate as the primary answer and the second as reasoning_content Operations ---------- * Structured logger (logger.py) with rotating file handler; _SafeLogger adapter scrubs reserved LogRecord keys from any 'extra' dict * SSE endpoint /api/events/stream pushes log events to the new /webui/logs page * AccountPool.save_to_env() writes back atomically; WebUI mutations persist immediately * Token-bucket rate limiter (rate_limit.py) with x-forwarded-for / x-real-ip awareness WebUI ----- * Full rewrite: dark theme, static/style.css + static/app.js * New pages: /webui/login, /webui/logs * Accounts page: inline add form, HAR paste → /api/accounts /from-har * Dashboard: 5s auto-refresh of stats Tooling ------- * HAR parser (har_parser.py) simplified: dead code removed, reused by config_tool.py (no more duplicate implementation) * config_tool.py: real-time stdout streaming into the GUI log box, port-conflict pre-flight check * requirements.txt pinned to compatible ranges; pytest + pytest-asyncio added * start.bat: 18000 -> 1800 typo fixed; where python pre-check Tests ----- * 51 pytest cases covering parse_response, build_jspb_header, build_request_body, account_pool (LRU/random/limit/concurrent /persist), auth, rate_limit, har_parser, and the /health, /v1/models, and /v1/chat/completions endpoints * pytest.ini with asyncio_mode=auto * CI workflow (.github/workflows/ci.yml) runs the full suite on Python 3.10/3.11/3.12 + pyflakes lint
1 parent 2b991bd commit fa37f0f

31 files changed

Lines changed: 3891 additions & 1455 deletions

.env.example

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,58 @@
11
# ============================================
2-
# 服务器配置
2+
# 服务器配置 (gemini2api v1.0)
33
# ============================================
44
HOST=0.0.0.0
55
PORT=1800
6+
7+
# --- 鉴权 ---
8+
# 设置一个非占位值即可启用鉴权。留空 / 占位符 = 禁用鉴权(仅本地开发)。
69
API_KEY=sk-web2api-placeholder
10+
# ADMIN_KEY 默认与 API_KEY 相同,可独立设置。
11+
ADMIN_KEY=
12+
# CORS 允许的来源(逗号分隔,留空 = 不启用 CORS)。
13+
# 警告:不要同时使用 * 与 credentials=true,浏览器会拒绝响应。
14+
CORS_ORIGINS=
15+
16+
# --- 轮询与限流 ---
17+
# 可选: least-recently-used | round-robin | random | first
718
ROTATION_STRATEGY=least-recently-used
819
MAX_ERRORS_BEFORE_DISABLE=3
20+
# 全局每分钟请求数限流(0 = 关闭)
21+
GLOBAL_RATE_LIMIT_RPM=0
22+
23+
# --- 日志 ---
24+
# 日志级别: DEBUG | INFO | WARNING | ERROR
25+
LOG_LEVEL=INFO
26+
# 日志文件名,设为空字符串可禁用文件日志
27+
LOG_FILE=gemini_proxy.log
28+
29+
# --- 持久化 ---
30+
# 设为 0 可禁用 WebUI 自动写回 .env
31+
PERSIST_ACCOUNTS=1
932

1033
# ============================================
11-
# Profiles — 模型配置不含凭证
12-
# 运行 config_tool.py 或通过 WebUI 添加账号
34+
# Profiles — 模型配置(不含凭证)
35+
# 通过 WebUI "账号管理"页或 config_tool.py 添加账号
1336
# ============================================
1437
PROFILES=gemini-3.5-flash,gemini-3.5-flash-adv,gemini-3.1-pro,gemini-3.1-pro-ext,gemini-3.1-flash-lite,gemini-3.1-flash-lite-adv
1538
DEFAULT_MODEL=gemini-3.5-flash
1639

1740
MODEL_FAMILY_gemini-3.5-flash=1
1841
THINKING_MODE_gemini-3.5-flash=1
19-
2042
MODEL_FAMILY_gemini-3.5-flash-adv=1
2143
THINKING_MODE_gemini-3.5-flash-adv=2
22-
2344
MODEL_FAMILY_gemini-3.1-pro=3
2445
THINKING_MODE_gemini-3.1-pro=1
25-
2646
MODEL_FAMILY_gemini-3.1-pro-ext=3
2747
THINKING_MODE_gemini-3.1-pro-ext=2
28-
2948
MODEL_FAMILY_gemini-3.1-flash-lite=6
3049
THINKING_MODE_gemini-3.1-flash-lite=1
31-
3250
MODEL_FAMILY_gemini-3.1-flash-lite-adv=6
3351
THINKING_MODE_gemini-3.1-flash-lite-adv=2
3452

3553
# ============================================
3654
# Accounts — 账号凭证
37-
# 运行 config_tool.py 或通过 WebUI 添加
38-
# 格式: ACCOUNT_<name>_<FIELD>=<value>
55+
# 推荐使用 WebUI 或 config_tool.py 添加,系统会自动写回下方
3956
# ============================================
4057
# ACCOUNT_myaccount_F_SID=xxx
4158
# ACCOUNT_myaccount_AT=xxx
@@ -46,3 +63,5 @@ THINKING_MODE_gemini-3.1-flash-lite-adv=2
4663
# ACCOUNT_myaccount_HASH=
4764
# ACCOUNT_myaccount_ENABLED=true
4865
# ACCOUNT_myaccount_MODELS=gemini-3.5-flash,gemini-3.1-flash-lite
66+
# ACCOUNT_myaccount_RATE_LIMIT_RPM=60
67+
# ACCOUNT_myaccount_MAX_CONCURRENT=4

.github/workflows/ci.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
name: pytest (ubuntu-latest, py${{ matrix.python-version }})
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
python-version: ["3.10", "3.11", "3.12"]
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python ${{ matrix.python-version }}
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
cache: pip
28+
cache-dependency-path: requirements.txt
29+
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip
33+
pip install -r requirements.txt
34+
35+
- name: Verify module imports
36+
run: |
37+
python -c "import server, adapter, account_pool, auth, rate_limit, logger, har_parser, config_tool; print('all modules import OK')"
38+
39+
- name: Run tests
40+
run: |
41+
python -m pytest tests/ -v --tb=short --durations=10
42+
43+
- name: Collect coverage
44+
run: |
45+
pip install coverage[toml]==7.6.*
46+
coverage run --source=adapter,account_pool,auth,rate_limit,logger,har_parser,server -m pytest tests/ -q
47+
coverage report -m --skip-covered
48+
49+
lint:
50+
name: pyflakes
51+
runs-on: ubuntu-latest
52+
steps:
53+
- uses: actions/checkout@v4
54+
- uses: actions/setup-python@v5
55+
with:
56+
python-version: "3.12"
57+
- run: pip install pyflakes
58+
- name: Pyflakes
59+
# Best-effort lint; we don't block on warnings, only on syntax errors.
60+
run: |
61+
python -m pyflakes adapter.py account_pool.py auth.py rate_limit.py logger.py har_parser.py server.py 2>&1 | tee lint.txt
62+
echo "lint complete (see lint.txt)"

.gitignore

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,29 @@ __pycache__/
99
*.pyc
1010
*.pyo
1111

12-
# 临时测试文件
13-
test_*.py
12+
# 临时分析脚本(用户级,不是仓库的 pytest)
1413
analyze_*.py
14+
15+
# 日志 / 运行时产物
1516
server_out.log
1617
server_log.txt
1718
crash.log
1819
crash_err.log
1920
stdout.log
2021
stderr.log
2122
gemini_proxy.log
23+
*.log
24+
25+
# pytest / coverage 产物
26+
.pytest_cache/
27+
.coverage
28+
coverage.xml
29+
htmlcov/
30+
.tox/
31+
32+
# IDE
33+
.vscode/
34+
.idea/
2235

2336
# 系统生成
2437
start_proxy.bat

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Changelog
2+
3+
## [1.0.0] - 2026-06-27
4+
5+
### 🐛 Bug fixes
6+
- **API_KEY 鉴权缺失** — 新增 `verify_api_key` 中间件保护 `/v1/*`。占位符值(空、`sk-web2api-placeholder` 等)即禁用,生产环境必须设非占位值。
7+
- **全局 `_reqid` 竞态** — 改为每请求局部 `reqid`,并发请求不再互相覆盖。
8+
- **账号池无锁**`AccountPool.select` / `record_*` 全部加 `asyncio.Lock`,并发下 `last_used` / `error_count` / `inflight` 不再失序。
9+
- **WebUI XSS** — 账号名、profile 名、状态文本经 `html.escape` 注入到模板。
10+
- **CORS 反模式** — 移除 `allow_origins=["*"]` + `allow_credentials=True`,改为可选 `CORS_ORIGINS` 环境变量。
11+
- **`parse_response` 取最长错位** — 改为取第一个非空候选(主答案),第二候选进 `reasoning_content`
12+
- **`stream_request` 假流式** — 用 `httpx.AsyncClient.stream` 接收 + 服务端分块到 SSE 客户端。
13+
- **每次新建 httpx client** — 进程级共享 client,省去每次 TLS 握手。
14+
- **timeout 不分项**`httpx.Timeout(connect=10, read=90, write=30, pool=10)`,流式 read 180s。
15+
- **`except: pass` 吞噬异常** — 所有 `except` 改为具体类型,警告可见。
16+
- **重试无退避** — 5xx / 401 / 403 / 429 加 1s/2s/4s/8s 指数退避。
17+
- **流式无重试** — 流式路径预选账号失败时回退到 `send_request`
18+
- **HAR 解析死代码** — 删 `_find_content_path``template_params``all_endpoints`
19+
- **start.bat 端口 typo**`18000``1800`,并加 `where python` 预检。
20+
- **Pydantic 别名** — 删 `PydanticBase` 别名,统一 v2 strict 模式。
21+
- **requirements.txt 版本** — 全部 pin 到兼容区间,新增 `pytest` / `pytest-asyncio`
22+
- **`_reqid` 初值 7 位回绕** — 改用 28 位微秒时间窗。
23+
24+
### ✨ New features
25+
- **Bearer Token 鉴权中间件**`Authorization: Bearer <key>``x-api-key` header;admin 路径还支持 cookie。
26+
- **Admin 登录页**`/webui/login` 浏览器登录,1 小时 cookie。
27+
- **真正流式** — 服务端分块 `delta` 事件,OpenAI 协议兼容;附带尾部 `usage` chunk。
28+
- **Token usage 统计** — 从 `inner_data[2]._mtokenCount/_ttokenCount` 提取,`response.usage` 不再全是 0。
29+
- **多模态(图片)输入**`image_url` 支持 `data:` URI、HTTP(S) URL、本地路径;走 Gemini `UploadFile` 端点。
30+
- **Function calling / Tools**`tools` / `tool_choice` 字段;响应解析时检测 function_call 转 OpenAI `tool_calls`
31+
- **账号池限流** — 每账号 `rate_limit_rpm` + `max_concurrent`,LRU 候选自动排除超限账号。
32+
- **账号池持久化** — WebUI 增删改后写回 `.env`(原子 rename);后台 `_env_watcher` 每 5s 检测外部编辑并热加载。
33+
- **结构化 logging**`logger.py` 统一,DEBUG/INFO/WARNING/ERROR 分级,输出 stderr + `gemini_proxy.log` 滚动文件;`extra` 自动 namespace-prefix。
34+
- **SSE 实时日志**`/api/events/stream` 推送日志事件,`/webui/logs` 页面实时显示。
35+
- **HAR 导入端点**`/api/accounts/from-har` 接受 HAR 文本,返回解析结果,前端再确认保存。
36+
- **Token Bucket 限流中间件**`GLOBAL_RATE_LIMIT_RPM` 控制单 IP 全局速率;支持 `x-forwarded-for`
37+
- **WebUI 全面重做** — 统一 `static/style.css` + `static/app.js`,深色主题,响应式,5s 自动刷新统计。
38+
- **Pydantic v2 严格**`extra="ignore"`,字段 validator(账号名 `^[A-Za-z0-9_.-]{1,64}$`、role 白名单、温度区间)。
39+
- **51 个单元/集成测试** — 覆盖 `parse_response``build_jspb_header``build_request_body``account_pool.select/release/限流/持久化``auth.verify_*``rate_limit``har_parser``/health``/v1/models``/v1/chat/completions` 鉴权+快乐路径。
40+
- **HAR 解析合并** — GUI `config_tool.py` 不再重复实现,统一调用 `har_parser.parse_har`
41+
- **GUI 启动 stdout 实时显示**`subprocess.Popen(stdout=PIPE)` + 线程读取 → Tkinter Text 实时显示;启动前 socket 端口占用预检。
42+
43+
### ⚠️ Notes
44+
- 协议仍基于逆向工程的 `/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate`,Google 改版可能失效。
45+
- 多模态和 Function calling 是 best-effort;Gemini Web 端不保证稳定支持。
46+
- 默认 0 账号启动,首次使用必须通过 WebUI 或 `config_tool.py` 导入 HAR 凭证。

0 commit comments

Comments
 (0)