11"""Admin API routes"""
22import asyncio
33import json
4- import httpx
54from fastapi import APIRouter , Depends , HTTPException , Header , Request
65from fastapi .responses import JSONResponse
76from pydantic import BaseModel
87from typing import Optional , List , Dict , Any
98import secrets
109import time
1110import re
11+ import urllib .error
12+ import urllib .request
1213from urllib .parse import urlparse
1314from curl_cffi .requests import AsyncSession
1415from ..core .auth import AuthManager
1819from ..services .proxy_manager import ProxyManager
1920from ..services .concurrency_manager import ConcurrencyManager
2021
22+ try :
23+ import httpx
24+ except ImportError :
25+ httpx = None
26+
2127router = APIRouter ()
2228
2329# Dependency injection
@@ -177,6 +183,72 @@ def _get_remote_browser_client_config() -> tuple[str, str, int]:
177183 return base_url , api_key , timeout
178184
179185
186+ def _build_remote_browser_http_timeout (read_timeout : float ) -> Any :
187+ read_value = max (3.0 , float (read_timeout ))
188+ write_value = min (10.0 , max (3.0 , read_value ))
189+ if httpx is None :
190+ return read_value
191+ return httpx .Timeout (
192+ connect = 2.5 ,
193+ read = read_value ,
194+ write = write_value ,
195+ pool = 2.5 ,
196+ )
197+
198+
199+ def _parse_json_response_text (text : str ) -> Optional [Any ]:
200+ if not text :
201+ return None
202+ try :
203+ return json .loads (text )
204+ except Exception :
205+ return None
206+
207+
208+ async def _stdlib_json_http_request (
209+ method : str ,
210+ url : str ,
211+ headers : Dict [str , str ],
212+ payload : Optional [Dict [str , Any ]],
213+ timeout : int ,
214+ ) -> tuple [int , Optional [Any ], str ]:
215+ req_headers = dict (headers or {})
216+ req_headers .setdefault ("Accept" , "application/json" )
217+ request_method = (method or "GET" ).upper ()
218+ request_data : Optional [bytes ] = None
219+
220+ if payload is not None :
221+ req_headers ["Content-Type" ] = "application/json; charset=utf-8"
222+ if request_method != "GET" :
223+ request_data = json .dumps (payload ).encode ("utf-8" )
224+
225+ def do_request () -> tuple [int , str ]:
226+ request = urllib .request .Request (
227+ url = url ,
228+ data = request_data ,
229+ headers = req_headers ,
230+ method = request_method ,
231+ )
232+ opener = urllib .request .build_opener (urllib .request .ProxyHandler ({}))
233+ try :
234+ with opener .open (request , timeout = max (1.0 , float (timeout ))) as response :
235+ status_code = int (getattr (response , "status" , 0 ) or response .getcode () or 0 )
236+ body = response .read ()
237+ charset = response .headers .get_content_charset () or "utf-8"
238+ return status_code , body .decode (charset , errors = "replace" )
239+ except urllib .error .HTTPError as exc :
240+ body = exc .read ()
241+ charset = exc .headers .get_content_charset () if exc .headers else None
242+ return int (getattr (exc , "code" , 0 ) or 0 ), body .decode (charset or "utf-8" , errors = "replace" )
243+
244+ try :
245+ status_code , text = await asyncio .to_thread (do_request )
246+ except Exception as e :
247+ raise RuntimeError (f"远程打码服务请求失败: { e } " ) from e
248+
249+ return status_code , _parse_json_response_text (text ), text
250+
251+
180252async def _sync_json_http_request (
181253 method : str ,
182254 url : str ,
@@ -189,18 +261,27 @@ async def _sync_json_http_request(
189261 request_method = (method or "GET" ).upper ()
190262 request_kwargs : Dict [str , Any ] = {
191263 "headers" : req_headers ,
192- "timeout" : timeout ,
264+ "timeout" : _build_remote_browser_http_timeout ( timeout ) ,
193265 }
194266
195267 if payload is not None :
196268 req_headers ["Content-Type" ] = "application/json; charset=utf-8"
197269 if request_method != "GET" :
198270 request_kwargs ["json" ] = payload
199271
272+ if httpx is None :
273+ return await _stdlib_json_http_request (
274+ method = method ,
275+ url = url ,
276+ headers = req_headers ,
277+ payload = payload ,
278+ timeout = timeout ,
279+ )
280+
200281 try :
201282 # remote_browser 控制面是服务间 JSON API,使用 httpx 避免 curl_cffi 在当前
202283 # Windows + impersonate 场景下 POST body 丢失导致 FastAPI 直接判定 body 缺失。
203- async with httpx .AsyncClient (follow_redirects = True ) as session :
284+ async with httpx .AsyncClient (follow_redirects = False , trust_env = False ) as session :
204285 response = await session .request (
205286 method = request_method ,
206287 url = url ,
@@ -211,12 +292,7 @@ async def _sync_json_http_request(
211292
212293 status_code = int (getattr (response , "status_code" , 0 ) or 0 )
213294 text = response .text or ""
214- parsed : Optional [Any ] = None
215- if text :
216- try :
217- parsed = response .json ()
218- except Exception :
219- parsed = None
295+ parsed = _parse_json_response_text (text )
220296
221297 return status_code , parsed , text
222298
0 commit comments