mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 06:21:07 +08:00
fix(api): add bounded timeouts to Nacos remote settings HTTP requests (#37444)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Crazywoola <100913391+crazywoola@users.noreply.github.com>
This commit is contained in:
parent
f53d7d4287
commit
9ccbfbaf9d
@ -774,3 +774,11 @@ EVENT_BUS_LISTENER_JOIN_TIMEOUT_MS=2000
|
||||
ENABLE_HUMAN_INPUT_TIMEOUT_TASK=true
|
||||
# Human input timeout check interval in minutes
|
||||
HUMAN_INPUT_TIMEOUT_TASK_INTERVAL=1
|
||||
|
||||
# Nacos remote settings source HTTP timeouts (seconds).
|
||||
# Bound how long requests to the Nacos endpoint wait before failing, so a slow or
|
||||
# unresponsive Nacos server cannot stall API startup or token refresh.
|
||||
# Read timeout for Nacos requests (default: 10.0)
|
||||
DIFY_ENV_NACOS_REQUEST_TIMEOUT=10.0
|
||||
# Connect timeout for Nacos requests (default: 3.0)
|
||||
DIFY_ENV_NACOS_CONNECT_TIMEOUT=3.0
|
||||
|
||||
@ -20,6 +20,12 @@ class NacosHttpClient:
|
||||
self.token: str | None = None
|
||||
self.token_ttl = 18000
|
||||
self.token_expire_time: float = 0
|
||||
# Bounded timeouts so a slow or unresponsive Nacos server cannot hang the API
|
||||
# service indefinitely during startup or token refresh.
|
||||
self.timeout = httpx.Timeout(
|
||||
float(os.getenv("DIFY_ENV_NACOS_REQUEST_TIMEOUT", "10.0")),
|
||||
connect=float(os.getenv("DIFY_ENV_NACOS_CONNECT_TIMEOUT", "3.0")),
|
||||
)
|
||||
|
||||
def http_request(
|
||||
self, url: str, method: str = "GET", headers: dict[str, str] | None = None, params: dict[str, str] | None = None
|
||||
@ -28,12 +34,17 @@ class NacosHttpClient:
|
||||
headers = {}
|
||||
if params is None:
|
||||
params = {}
|
||||
full_url = "http://" + self.server + url
|
||||
try:
|
||||
self._inject_auth_info(headers, params)
|
||||
response = httpx.request(method, url="http://" + self.server + url, headers=headers, params=params)
|
||||
response = httpx.request(method, url=full_url, headers=headers, params=params, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except httpx.TimeoutException as e:
|
||||
logger.warning("Request to Nacos timed out (url=%s, timeout=%s): %s", full_url, self.timeout, e)
|
||||
return f"Request to Nacos timed out: {e}"
|
||||
except httpx.RequestError as e:
|
||||
logger.warning("Request to Nacos failed (url=%s): %s", full_url, e)
|
||||
return f"Request to Nacos failed: {e}"
|
||||
|
||||
def _inject_auth_info(self, headers: dict[str, str], params: dict[str, str], module: str = "config") -> None:
|
||||
@ -78,13 +89,16 @@ class NacosHttpClient:
|
||||
params = {"username": self.username, "password": self.password}
|
||||
url = "http://" + self.server + "/nacos/v1/auth/login"
|
||||
try:
|
||||
resp = httpx.request("POST", url, headers=None, params=params)
|
||||
resp = httpx.request("POST", url, headers=None, params=params, timeout=self.timeout)
|
||||
resp.raise_for_status()
|
||||
response_data = resp.json()
|
||||
self.token = response_data.get("accessToken")
|
||||
self.token_ttl = response_data.get("tokenTtl", 18000)
|
||||
self.token_expire_time = current_time + self.token_ttl - 10
|
||||
return self.token
|
||||
except httpx.TimeoutException:
|
||||
logger.exception("[get-access-token] request to Nacos timed out (url=%s, timeout=%s)", url, self.timeout)
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception("[get-access-token] exception occur")
|
||||
raise
|
||||
|
||||
51
api/tests/unit_tests/configs/test_nacos_http_client.py
Normal file
51
api/tests/unit_tests/configs/test_nacos_http_client.py
Normal file
@ -0,0 +1,51 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
|
||||
from configs.remote_settings_sources.nacos.http_request import NacosHttpClient
|
||||
|
||||
|
||||
def _ok_response(text: str = "ok", json_data: dict | None = None) -> MagicMock:
|
||||
response = MagicMock()
|
||||
response.text = text
|
||||
response.raise_for_status.return_value = None
|
||||
if json_data is not None:
|
||||
response.json.return_value = json_data
|
||||
return response
|
||||
|
||||
|
||||
def test_http_request_passes_bounded_timeout():
|
||||
client = NacosHttpClient()
|
||||
with patch("configs.remote_settings_sources.nacos.http_request.httpx.request") as mock_request:
|
||||
mock_request.return_value = _ok_response()
|
||||
client.http_request("/nacos/v1/cs/configs")
|
||||
|
||||
timeout = mock_request.call_args.kwargs["timeout"]
|
||||
assert isinstance(timeout, httpx.Timeout)
|
||||
assert timeout.read is not None
|
||||
assert timeout.connect is not None
|
||||
|
||||
|
||||
def test_http_request_returns_graceful_message_on_timeout():
|
||||
client = NacosHttpClient()
|
||||
with patch(
|
||||
"configs.remote_settings_sources.nacos.http_request.httpx.request",
|
||||
side_effect=httpx.ConnectTimeout("connection timed out"),
|
||||
):
|
||||
result = client.http_request("/nacos/v1/cs/configs")
|
||||
|
||||
assert "Nacos" in result
|
||||
assert "timed out" in result.lower()
|
||||
|
||||
|
||||
def test_get_access_token_passes_bounded_timeout():
|
||||
client = NacosHttpClient()
|
||||
client.username = "user"
|
||||
client.password = "pass"
|
||||
with patch("configs.remote_settings_sources.nacos.http_request.httpx.request") as mock_request:
|
||||
mock_request.return_value = _ok_response(json_data={"accessToken": "tok", "tokenTtl": 100})
|
||||
token = client.get_access_token(force_refresh=True)
|
||||
|
||||
assert token == "tok"
|
||||
timeout = mock_request.call_args.kwargs["timeout"]
|
||||
assert isinstance(timeout, httpx.Timeout)
|
||||
@ -481,5 +481,11 @@ MILVUS_ENABLE_HYBRID_SEARCH=False
|
||||
ENABLE_HUMAN_INPUT_TIMEOUT_TASK=true
|
||||
HUMAN_INPUT_TIMEOUT_TASK_INTERVAL=1
|
||||
|
||||
# Nacos remote settings source HTTP timeouts (seconds).
|
||||
# Bound how long requests to the Nacos endpoint wait before failing, so a slow or
|
||||
# unresponsive Nacos server cannot stall API startup or token refresh.
|
||||
DIFY_ENV_NACOS_REQUEST_TIMEOUT=10.0
|
||||
DIFY_ENV_NACOS_CONNECT_TIMEOUT=3.0
|
||||
|
||||
# uv cache dir
|
||||
UV_CACHE_DIR=/tmp/uv_cache
|
||||
|
||||
Loading…
Reference in New Issue
Block a user