🐛 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}")
|