🐛 Bug: 1. Fix the bug where different base URLs use the same client.
Browse files2. Fix the bug where Vercel uses a different event loop, causing httpx's connection pool to attempt to reuse the same connection in a different event loop, which is not allowed in asyncio.
💰 Sponsors: Thanks to @PowerHunter for the ¥1800 sponsorship, sponsorship information has been added to the README.
- README.md +1 -1
- README_CN.md +1 -1
- main.py +23 -16
README.md
CHANGED
@@ -333,7 +333,7 @@ curl -X POST http://127.0.0.1:8000/v1/chat/completions \
|
|
333 |
|
334 |
We thank the following sponsors for their support:
|
335 |
<!-- ¥1000 -->
|
336 |
-
- @PowerHunter: ¥
|
337 |
|
338 |
## How to sponsor us
|
339 |
|
|
|
333 |
|
334 |
We thank the following sponsors for their support:
|
335 |
<!-- ¥1000 -->
|
336 |
+
- @PowerHunter: ¥1800
|
337 |
|
338 |
## How to sponsor us
|
339 |
|
README_CN.md
CHANGED
@@ -333,7 +333,7 @@ curl -X POST http://127.0.0.1:8000/v1/chat/completions \
|
|
333 |
|
334 |
我们感谢以下赞助商的支持:
|
335 |
<!-- ¥1000 -->
|
336 |
-
- @PowerHunter:¥
|
337 |
|
338 |
## 如何赞助我们
|
339 |
|
|
|
333 |
|
334 |
我们感谢以下赞助商的支持:
|
335 |
<!-- ¥1000 -->
|
336 |
+
- @PowerHunter:¥1800
|
337 |
|
338 |
## 如何赞助我们
|
339 |
|
main.py
CHANGED
@@ -48,6 +48,8 @@ from sqlalchemy.sql import sqltypes
|
|
48 |
|
49 |
# 添加新的环境变量检查
|
50 |
DISABLE_DATABASE = os.getenv("DISABLE_DATABASE", "false").lower() == "true"
|
|
|
|
|
51 |
|
52 |
async def create_tables():
|
53 |
if DISABLE_DATABASE:
|
@@ -594,16 +596,28 @@ app.add_middleware(StatsMiddleware)
|
|
594 |
class ClientManager:
|
595 |
def __init__(self, pool_size=100):
|
596 |
self.pool_size = pool_size
|
597 |
-
self.clients = {} # {
|
598 |
|
599 |
async def init(self, default_config):
|
600 |
self.default_config = default_config
|
601 |
|
602 |
@asynccontextmanager
|
603 |
-
async def get_client(self, timeout_value, proxy=None):
|
604 |
# 直接获取或创建客户端,不使用锁
|
605 |
timeout_value = int(timeout_value)
|
606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
607 |
timeout = httpx.Timeout(
|
608 |
connect=15.0,
|
609 |
read=timeout_value,
|
@@ -620,39 +634,32 @@ class ClientManager:
|
|
620 |
|
621 |
if proxy:
|
622 |
# 解析代理URL
|
623 |
-
from urllib.parse import urlparse
|
624 |
parsed = urlparse(proxy)
|
625 |
-
|
626 |
-
# 修改这里: 将 socks5h 转换为 socks5
|
627 |
scheme = parsed.scheme.rstrip('h')
|
628 |
-
# print("scheme", scheme)
|
629 |
|
630 |
if scheme == 'socks5':
|
631 |
try:
|
632 |
from httpx_socks import AsyncProxyTransport
|
633 |
-
# 使用修改后的scheme创建代理URL
|
634 |
proxy = proxy.replace('socks5h://', 'socks5://')
|
635 |
-
# 创建SOCKS5代理传输
|
636 |
transport = AsyncProxyTransport.from_url(proxy)
|
637 |
client_config["transport"] = transport
|
638 |
except ImportError:
|
639 |
logger.error("httpx-socks package is required for SOCKS proxy support")
|
640 |
raise ImportError("Please install httpx-socks package for SOCKS proxy support: pip install httpx-socks")
|
641 |
else:
|
642 |
-
# 对于HTTP/HTTPS代理使用原有方式
|
643 |
client_config["proxies"] = {
|
644 |
"http://": proxy,
|
645 |
"https://": proxy
|
646 |
}
|
647 |
|
648 |
-
self.clients[
|
649 |
|
650 |
try:
|
651 |
-
yield self.clients[
|
652 |
except Exception as e:
|
653 |
-
if
|
654 |
-
tmp_client = self.clients[
|
655 |
-
del self.clients[
|
656 |
await tmp_client.aclose() # 然后关闭客户端
|
657 |
raise e
|
658 |
|
@@ -830,7 +837,7 @@ async def process_request(request: Union[RequestModel, ImageGenerationRequest, A
|
|
830 |
# print("proxy", proxy)
|
831 |
|
832 |
try:
|
833 |
-
async with app.state.client_manager.get_client(timeout_value, proxy) as client:
|
834 |
# 打印client配置信息
|
835 |
# logger.info(f"Client config - Timeout: {client.timeout}")
|
836 |
# logger.info(f"Client config - Headers: {client.headers}")
|
|
|
48 |
|
49 |
# 添加新的环境变量检查
|
50 |
DISABLE_DATABASE = os.getenv("DISABLE_DATABASE", "false").lower() == "true"
|
51 |
+
IS_VERCEL = os.path.dirname(os.path.abspath(__file__)).startswith('/var/task')
|
52 |
+
logger.info("IS_VERCEL: %s", IS_VERCEL)
|
53 |
|
54 |
async def create_tables():
|
55 |
if DISABLE_DATABASE:
|
|
|
596 |
class ClientManager:
|
597 |
def __init__(self, pool_size=100):
|
598 |
self.pool_size = pool_size
|
599 |
+
self.clients = {} # {host_timeout_proxy: AsyncClient}
|
600 |
|
601 |
async def init(self, default_config):
|
602 |
self.default_config = default_config
|
603 |
|
604 |
@asynccontextmanager
|
605 |
+
async def get_client(self, timeout_value, base_url, proxy=None):
|
606 |
# 直接获取或创建客户端,不使用锁
|
607 |
timeout_value = int(timeout_value)
|
608 |
+
|
609 |
+
# 从base_url中提取主机名
|
610 |
+
parsed_url = urlparse(base_url)
|
611 |
+
host = parsed_url.netloc
|
612 |
+
|
613 |
+
# 创建唯一的客户端键
|
614 |
+
client_key = f"{host}_{timeout_value}"
|
615 |
+
if proxy:
|
616 |
+
# 对代理URL进行规范化处理
|
617 |
+
proxy_normalized = proxy.replace('socks5h://', 'socks5://')
|
618 |
+
client_key += f"_{proxy_normalized}"
|
619 |
+
|
620 |
+
if client_key not in self.clients or IS_VERCEL:
|
621 |
timeout = httpx.Timeout(
|
622 |
connect=15.0,
|
623 |
read=timeout_value,
|
|
|
634 |
|
635 |
if proxy:
|
636 |
# 解析代理URL
|
|
|
637 |
parsed = urlparse(proxy)
|
|
|
|
|
638 |
scheme = parsed.scheme.rstrip('h')
|
|
|
639 |
|
640 |
if scheme == 'socks5':
|
641 |
try:
|
642 |
from httpx_socks import AsyncProxyTransport
|
|
|
643 |
proxy = proxy.replace('socks5h://', 'socks5://')
|
|
|
644 |
transport = AsyncProxyTransport.from_url(proxy)
|
645 |
client_config["transport"] = transport
|
646 |
except ImportError:
|
647 |
logger.error("httpx-socks package is required for SOCKS proxy support")
|
648 |
raise ImportError("Please install httpx-socks package for SOCKS proxy support: pip install httpx-socks")
|
649 |
else:
|
|
|
650 |
client_config["proxies"] = {
|
651 |
"http://": proxy,
|
652 |
"https://": proxy
|
653 |
}
|
654 |
|
655 |
+
self.clients[client_key] = httpx.AsyncClient(**client_config)
|
656 |
|
657 |
try:
|
658 |
+
yield self.clients[client_key]
|
659 |
except Exception as e:
|
660 |
+
if client_key in self.clients:
|
661 |
+
tmp_client = self.clients[client_key]
|
662 |
+
del self.clients[client_key] # 先删除引用
|
663 |
await tmp_client.aclose() # 然后关闭客户端
|
664 |
raise e
|
665 |
|
|
|
837 |
# print("proxy", proxy)
|
838 |
|
839 |
try:
|
840 |
+
async with app.state.client_manager.get_client(timeout_value, url, proxy) as client:
|
841 |
# 打印client配置信息
|
842 |
# logger.info(f"Client config - Timeout: {client.timeout}")
|
843 |
# logger.info(f"Client config - Headers: {client.headers}")
|