统一http客户端,自动 session cookie

This commit is contained in:
jingrow 2026-03-11 22:10:33 +08:00
parent 496efd8d31
commit 829174439f
12 changed files with 151 additions and 198 deletions

View File

@ -2,30 +2,30 @@
# For license information, please see license.txt
"""
API v2 路由 - 转发到 SaaS
API v2 routes - Forward to SaaS using unified HTTP client
"""
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
import time
from jingrow.utils.auth import saas_get
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v2")
def _forward_to_saas(endpoint: str, params: dict = None) -> dict:
"""通用转发函数"""
url = f"{Config.jingrow_server_url}/api/v2{endpoint}"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
"""Forward request to SaaS using unified HTTP client"""
start_time = time.time()
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
print(f"[v2] Forwarding to: {endpoint}")
resp = saas_get(f"/api/v2{endpoint}", params=params)
req_time = time.time() - start_time
print(f"[v2] Response: status={resp.status_code}, time={req_time:.3f}s, endpoint={endpoint}")
resp = requests.get(url, headers=headers, params=params, timeout=30)
if resp.status_code == 200:
return resp.json()
logger.error(f"[v2] SaaS error {endpoint}: {resp.status_code}")
@ -34,14 +34,14 @@ def _forward_to_saas(endpoint: str, params: dict = None) -> dict:
@router.get("/pagetype/{pagetype}/meta")
async def get_meta(pagetype: str):
"""获取 PageType 元数据 - 转发到 SaaS 端"""
"""Get PageType metadata - Forward to SaaS"""
result = _forward_to_saas(f"/pagetype/{pagetype}/meta")
return JSONResponse(content=result)
@router.get("/pagetype/{pagetype}/count")
async def get_count(pagetype: str, request: Request):
"""获取 PageType 计数 - 转发到 SaaS 端"""
"""Get PageType count - Forward to SaaS"""
params = dict(request.query_params)
result = _forward_to_saas(f"/pagetype/{pagetype}/count", params)
return JSONResponse(content=result)

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Client 相关白名单函数 - 转发到 SaaS
Client whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_get, saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def get_list(**kwargs):
"""获取列表 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.client.get_list"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get list - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.client.get_list', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", [])
logger.error(f"[get_list] SaaS error: {resp.status_code} - {resp.text[:200]}")
@ -33,15 +24,8 @@ def get_list(**kwargs):
@jingrow.whitelist()
def get_value(**kwargs):
"""获取值 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.client.get_value"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get value - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.client.get_value', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message")
logger.error(f"[get_value] SaaS error: {resp.status_code} - {resp.text[:200]}")
@ -50,15 +34,8 @@ def get_value(**kwargs):
@jingrow.whitelist()
def get_count(**kwargs):
"""获取计数 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.client.get_count"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.get(url, headers=headers, params=kwargs, timeout=30)
"""Get count - Forward to SaaS"""
resp = saas_get('/api/action/jingrow.client.get_count', params=kwargs)
if resp.status_code == 200:
return resp.json().get("message", 0)
logger.error(f"[get_count] SaaS error: {resp.status_code} - {resp.text[:200]}")

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
User 相关白名单函数 - 转发到 SaaS
User whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def get_all_roles(**kwargs):
"""获取所有角色 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.core.pagetype.user.user.get_all_roles"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get all roles - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.core.pagetype.user.user.get_all_roles', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", [])
logger.error(f"[user.get_all_roles] SaaS error: {resp.status_code}")

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Desktop 相关白名单函数 - 转发到 SaaS
Desktop whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def get_workspace_sidebar_items(**kwargs):
"""获取工作区侧边栏项目 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.desktop.get_workspace_sidebar_items"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get workspace sidebar items - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.desktop.get_workspace_sidebar_items', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[get_workspace_sidebar_items] SaaS error: {resp.status_code}")
@ -33,15 +24,8 @@ def get_workspace_sidebar_items(**kwargs):
@jingrow.whitelist()
def get_desktop_page(**kwargs):
"""获取桌面页面 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.desktop.get_desktop_page"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get desktop page - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.desktop.get_desktop_page', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[get_desktop_page] SaaS error: {resp.status_code}")

View File

@ -2,32 +2,33 @@
# For license information, please see license.txt
"""
Form load 相关白名单函数 - 转发到 SaaS
Form load whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
import time
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def getdoc(**kwargs):
"""获取文档 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.form.load.getdoc"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
"""Get document - Forward to SaaS using unified HTTP client"""
start_time = time.time()
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
print(f"[getdoc] Requesting: pagetype={kwargs.get('pagetype')}, name={kwargs.get('name')}")
resp = saas_post('/api/action/jingrow.desk.form.load.getdoc', json=kwargs)
req_time = time.time() - start_time
print(f"[getdoc] SaaS response: status={resp.status_code}, time={req_time:.3f}s")
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
if resp.status_code == 200:
# SaaS getdoc 直接返回 {docs: [...], docinfo: {...}} 格式
# 不包装在 message 里
return resp.json()
logger.error(f"[getdoc] SaaS error: {resp.status_code} - {resp.text[:200]}")
result = resp.json()
resp_size = len(resp.content) / 1024 # KB
print(f"[getdoc] Total time: {time.time() - start_time:.3f}s, response size: {resp_size:.1f}KB")
return result
print(f"[getdoc] SaaS error: {resp.status_code} - {resp.text[:200]}")
return {}

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Dashboard Chart 相关白名单函数 - 转发到 SaaS
Dashboard Chart whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def get(**kwargs):
"""获取 Dashboard Chart - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.pagetype.dashboard_chart.dashboard_chart.get"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get Dashboard Chart - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.pagetype.dashboard_chart.dashboard_chart.get', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[dashboard_chart.get] SaaS error: {resp.status_code}")

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Dashboard Chart Source 相关白名单函数 - 转发到 SaaS
Dashboard Chart Source whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def get_config(**kwargs):
"""获取 Chart Source 配置 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.pagetype.dashboard_chart_source.dashboard_chart_source.get_config"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get Chart Source config - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.pagetype.dashboard_chart_source.dashboard_chart_source.get_config', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[dashboard_chart_source.get_config] SaaS error: {resp.status_code}")

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Query Report 相关白名单函数 - 转发到 SaaS
Query Report whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def run(**kwargs):
"""运行查询报表 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.query_report.run"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Run query report - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.query_report.run', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[query_report.run] SaaS error: {resp.status_code}")
@ -33,15 +24,8 @@ def run(**kwargs):
@jingrow.whitelist()
def get_script(**kwargs):
"""获取报表脚本 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.query_report.get_script"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Get report script - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.query_report.get_script', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", {})
logger.error(f"[query_report.get_script] SaaS error: {resp.status_code}")

View File

@ -2,29 +2,20 @@
# For license information, please see license.txt
"""
Search 相关白名单函数 - 转发到 SaaS
Search whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
import logging
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_post
logger = logging.getLogger(__name__)
@jingrow.whitelist()
def search_link(**kwargs):
"""搜索链接 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.desk.search.search_link"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.post(url, headers=headers, json=kwargs, timeout=30)
"""Search link - Forward to SaaS"""
resp = saas_post('/api/action/jingrow.desk.search.search_link', json=kwargs)
if resp.status_code == 200:
return resp.json().get("message", [])
logger.error(f"[search_link] SaaS error: {resp.status_code}")

View File

@ -2,7 +2,7 @@
# For license information, please see license.txt
"""
Jingrow 白名单路由服务
Jingrow whitelist routing service
"""
from fastapi import APIRouter, Request, HTTPException
@ -11,7 +11,7 @@ import importlib
from typing import Any, Dict
import logging
from jingrow import get_whitelisted_function
from jingrow.utils.auth import get_jingrow_api_headers
from jingrow.utils.auth import get_jingrow_api_headers, set_request_session_cookie
from jingrow.utils.jingrow_api import get_logged_user
from jingrow.utils.app_manager import ensure_apps_on_sys_path
@ -20,41 +20,41 @@ router = APIRouter(prefix="/api/action")
async def authenticate_request(request: Request, allow_guest: bool) -> bool:
"""
认证请求支持两种认证方式
1. Session Cookie 认证
2. API Key 认证
Authenticate request. Supports:
1. Session Cookie authentication
2. API Key authentication
"""
if allow_guest:
return True
# 方式1: 检查 Session Cookie 认证
# Method 1: Check Session Cookie authentication
session_cookie = request.cookies.get('sid')
if session_cookie:
try:
user = get_logged_user(session_cookie)
if user:
logger.info(f"Session认证成功: {user}")
# Store session cookie in ContextVar for downstream use
set_request_session_cookie(session_cookie)
logger.info(f"Session authenticated: {user}")
return True
except Exception as e:
logger.warning(f"Session认证失败: {e}")
logger.warning(f"Session authentication failed: {e}")
# 方式2: 检查 API Key 认证
# Method 2: Check API Key authentication
auth_header = request.headers.get('Authorization')
if auth_header and auth_header.startswith('token '):
try:
# 验证API Key格式: token key:secret
token_part = auth_header[6:] # 移除 "token " 前缀
token_part = auth_header[6:]
if ':' in token_part:
api_key, api_secret = token_part.split(':', 1)
# 验证API Key是否有效
expected_headers = get_jingrow_api_headers()
if expected_headers and expected_headers.get('Authorization') == auth_header:
logger.info("API Key认证成功")
logger.info("API Key authenticated")
return True
except Exception as e:
logger.warning(f"API Key认证失败: {e}")
logger.warning(f"API Key authentication failed: {e}")
logger.warning("认证失败: 未提供有效的认证信息")
logger.warning("Authentication failed: no valid credentials")
return False
async def _process_whitelist_call(request: Request, full_module_path: str):

View File

@ -2,26 +2,17 @@
# For license information, please see license.txt
"""
会话相关白名单函数 - 转发到 SaaS
Session whitelist functions - Forward to SaaS using unified HTTP client
"""
import jingrow
import requests
from jingrow.config import Config
from jingrow.utils.auth import get_request_session_cookie
from jingrow.utils.auth import saas_get
@jingrow.whitelist()
def get_boot_info():
"""获取启动信息 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.sessions.get_boot_info"
headers = {"Accept": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.get(url, headers=headers, timeout=30)
"""Get boot info - Forward to SaaS"""
resp = saas_get('/api/action/jingrow.sessions.get_boot_info')
if resp.status_code == 200:
return resp.json().get("message", {})
return {}
@ -29,15 +20,8 @@ def get_boot_info():
@jingrow.whitelist()
def get_translations():
"""获取翻译 - 转发到 SaaS 端"""
url = f"{Config.jingrow_server_url}/api/action/jingrow.sessions.get_translations"
headers = {"Accept": "application/json"}
session_cookie = get_request_session_cookie()
if session_cookie:
headers["Cookie"] = f"sid={session_cookie}"
resp = requests.get(url, headers=headers, timeout=30)
"""Get translations - Forward to SaaS"""
resp = saas_get('/api/action/jingrow.sessions.get_translations')
if resp.status_code == 200:
return resp.json().get("message", {})
return {}

View File

@ -2,10 +2,10 @@ import threading
import sys
import os
import requests
from typing import Optional
from typing import Optional, Dict, Any
from contextvars import ContextVar
# 导入配置
# Import config
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))))
from jingrow.config import Config
@ -14,20 +14,79 @@ _thread_local = threading.local()
# ContextVar for async-safe session cookie storage
_request_session_cookie: ContextVar[Optional[str]] = ContextVar('request_session_cookie', default=None)
# Global HTTP session with connection pooling
_http_session: Optional[requests.Session] = None
def _get_http_session() -> requests.Session:
"""Get or create global HTTP session with connection pooling"""
global _http_session
if _http_session is None:
_http_session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_connections=10,
pool_maxsize=20,
max_retries=2
)
_http_session.mount('http://', adapter)
_http_session.mount('https://', adapter)
return _http_session
def saas_request(method: str, endpoint: str, **kwargs) -> requests.Response:
"""
Unified HTTP client for SaaS requests with connection pooling and auto session cookie.
Args:
method: HTTP method (GET, POST, etc.)
endpoint: API endpoint (e.g., '/api/action/jingrow.xxx' or '/api/v2/pagetype/User/meta')
**kwargs: Additional arguments for requests (json, params, data, timeout, etc.)
Returns:
requests.Response
Example:
# GET request
resp = saas_request('GET', '/api/v2/pagetype/User/meta')
# POST request
resp = saas_request('POST', '/api/action/jingrow.desk.form.load.getdoc', json={'pagetype': 'User', 'name': 'Administrator'})
"""
url = f"{Config.jingrow_server_url}{endpoint}"
headers = kwargs.pop('headers', {})
headers.setdefault('Accept', 'application/json')
headers.setdefault('Content-Type', 'application/json')
# Auto-add session cookie from ContextVar
session_cookie = get_request_session_cookie()
if session_cookie:
headers['Cookie'] = f"sid={session_cookie}"
timeout = kwargs.pop('timeout', 30)
session = _get_http_session()
return session.request(method.upper(), url, headers=headers, timeout=timeout, **kwargs)
def saas_get(endpoint: str, **kwargs) -> requests.Response:
"""Convenience method for GET requests to SaaS"""
return saas_request('GET', endpoint, **kwargs)
def saas_post(endpoint: str, **kwargs) -> requests.Response:
"""Convenience method for POST requests to SaaS"""
return saas_request('POST', endpoint, **kwargs)
def set_request_session_cookie(cookie: Optional[str]):
"""设置当前请求的 session cookie异步安全"""
"""Set current request's session cookie (async-safe)"""
_request_session_cookie.set(cookie)
def get_request_session_cookie() -> Optional[str]:
"""获取当前请求的 session cookie异步安全"""
"""Get current request's session cookie (async-safe)"""
return _request_session_cookie.get()
def set_context(context):
"""设置当前线程的context"""
"""Set current thread's context"""
_thread_local.context = context
def get_jingrow_api_headers():
"""获取 Jingrow API 认证头部,用于系统级调用"""
"""Get Jingrow API auth headers for system-level calls"""
api_key = Config.jingrow_api_key
api_secret = Config.jingrow_api_secret