How ClashApiManager Works for Clash‑Compatible Clients in v2rayN
The ClashApiManager class is a singleton HTTP wrapper that enables v2rayN to control any Clash‑compatible core via its REST API, exposing async methods to fetch proxy groups, test node latency, switch active selectors, and manage runtime configuration.
The ClashApiManager serves as the central bridge between v2rayN’s UI and locally‑running Clash cores. Located in v2rayN/ServiceLib/Manager/ClashApiManager.cs, this manager encapsulates every interaction with the Clash HTTP API, converting low‑level network calls into strongly‑typed .NET models that view‑models consume. For developers and power users, understanding how the ClashApiManager works for Clash‑compatible clients reveals the exact mechanism behind proxy switching, delay testing, and live connection management.
Architecture and Core Responsibilities
The manager operates as a singleton (ClashApiManager.Instance) and delegates all transport concerns to HttpClientHelper, which handles retries, timeouts, and JSON deserialization. It exposes six primary operational areas:
- Proxy Discovery – Queries
/proxiesand/providers/proxiesendpoints to build the node hierarchy. - Latency Testing – Parallel‑tests every node against
/proxies/{name}/delaywith configurable timeout and test URL. - Selector Control – Switches active nodes via
PUT /proxies/{group}using header‑based payload. - Runtime Patching – Updates rule mode or log level through
PATCH /configs. - Config Reloading – Hot‑reloads entire configuration files via
PUT /configs?force=true. - Connection Inspection – Lists and terminates active connections through the
/connectionsendpoint.
All methods construct the base URL dynamically using GetApiUrl(), which binds to the loopback interface and the port stored in AppManager.Instance.StatePort2.
Building the API URL
Before any request, the manager resolves the local Clash controller address. As implemented in ClashApiManager.cs (lines 183‑186):
private string GetApiUrl()
{
return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
}
This ensures that even if the core restarts on a different ephemeral port, subsequent calls target the correct endpoint without manual reconfiguration.
Fetching Proxies and Providers
The GetClashProxiesAsync() method retrieves both the flat proxy list and provider‑sourced nodes. It issues concurrent GET requests and deserializes the responses into ClashProxies and ClashProviders models (lines 18‑25):
var url = $"{GetApiUrl()}/proxies";
var result = await HttpClientHelper.Instance.TryGetAsync(url);
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
var url2 = $"{GetApiUrl()}/providers/proxies";
var result2 = await HttpClientHelper.Instance.TryGetAsync(url2);
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
The implementation retries failed requests three times with a two‑second backoff, caching the resulting dictionary in _proxies for downstream UI consumption.
Testing Proxy Latency
Node health checks run in parallel to prevent UI blocking. The ClashProxiesDelayTest() method (lines 67‑85) constructs a URL template pointing to the Clash delay endpoint, then dispatches tasks for every node in the supplied lstProxy list:
var urlBase = $"{GetApiUrl()}/proxies/{0}/delay?timeout=10000&url="
+ AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
foreach (var it in lstProxy)
{
var url = string.Format(urlBase, it.Name);
tasks.Add(Task.Run(async () =>
{
var result = await HttpClientHelper.Instance.TryGetAsync(url);
await updateFunc?.Invoke(it, result);
}));
}
await Task.WhenAll(tasks);
await updateFunc?.Invoke(null, "");
The updateFunc callback—supplied by ClashProxiesViewModel—streams results back to the UI thread in real time, allowing live latency updates without freezing the interface.
Switching Active Proxy Nodes
When a user selects a new node within a selector group (e.g., switching from “Auto” to a specific server), ClashSetActiveProxy() executes a PUT request with a custom header (lines 13‑17):
var url = $"{GetApiUrl()}/proxies/{name}";
var headers = new Dictionary<string, string> { { "name", nameNode } };
await HttpClientHelper.Instance.PutAsync(url, headers);
If the Clash core returns HTTP 204, the view‑model updates its local selectedProxy.now property to reflect the change immediately, ensuring the UI stays synchronized with the core’s routing table.
Updating Runtime Configuration
For global settings like rule mode (Rule, Global, or Direct), the manager patches the running config via ClashConfigUpdate() (lines 31‑34):
var url = $"{GetApiUrl()}/configs";
await HttpClientHelper.Instance.PatchAsync(url, headers);
The headers dictionary contains key‑value pairs such as { "mode": "global" }, which Clash applies instantly without requiring a process restart.
Reloading Full Configuration Files
When users import a new profile, CoreConfigClashService invokes ClashConfigReload() to hot‑swap the entire configuration (lines 41‑45):
var url = $"{GetApiUrl()}/configs?force=true";
var headers = new Dictionary<string, string> { { "path", filePath } };
await HttpClientHelper.Instance.PutAsync(url, headers);
The force=true query parameter ensures Clash discards any in‑memory overrides and loads the file from disk exactly as specified.
How ViewModels Consume the Manager
ClashProxiesViewModel
Located in v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs, this view‑model orchestrates the proxy list UI:
- Initialization – Calls
ClashApiManager.Instance.GetClashProxiesAsync()to populate_proxiesand_providers, then hydrates the observable collection bound to the frontend. - Delay Testing – Invokes
ClashProxiesDelayTest()withblAll: trueto benchmark every node, using RxUI’sMainThreadSchedulerto marshal callbacks onto the UI thread. - Selector Switching – Executes
ClashSetActiveProxy(selectedGroup.Name, selectedNode.Name)when the user clicks a new node, updating the backing field only after the HTTP call succeeds.
ClashConnectionsViewModel
This view‑model manages the live connections tab in ClashConnectionsViewModel.cs (lines 53‑62):
- Polling – Periodically calls
GetClashConnectionsAsync()to fetch active TCP/UDP flows from/connections. - Termination – Sends
ClashConnectionClose(id)to issue a DELETE request against/connections/{id}, forcibly dropping the selected tunnel.
Practical Code Examples
Example 1 – Populating the Proxy List
public async Task LoadProxies()
{
var ret = await ClashApiManager.Instance.GetClashProxiesAsync();
if (ret?.Item1 != null)
{
_proxies = ret.Item1.proxies;
await RefreshProxyGroups();
}
}
Relevant source: ClashProxiesViewModel.cs (lines 64‑71).
Example 2 – Executing a Full Latency Test
await ClashApiManager.Instance.ClashProxiesDelayTest(
blAll: true,
lstProxy: ProxyDetails.ToList(),
async (item, json) =>
{
var result = new SpeedTestResult { IndexId = item.Name, Delay = json };
RxApp.MainThreadScheduler.Schedule(result, (scheduler, r) =>
{
_ = ProxiesDelayTestResult(r);
return Disposable.Empty;
});
});
Relevant source: ClashProxiesViewModel.cs (lines 80‑88).
Example 3 – Changing the Active Node
public async Task ChangeProxy(string groupName, string nodeName)
{
await ClashApiManager.Instance.ClashSetActiveProxy(groupName, nodeName);
// Update local state only after successful API call
selectedProxy.now = nodeName;
}
Relevant source: ClashProxiesViewModel.cs (lines 64‑66).
Summary
- Centralized HTTP Wrapper –
ClashApiManager.csabstracts all Clash REST endpoints, exposing async methods that return strongly‑typedClashProxiesandClashProvidersmodels. - Dynamic URL Resolution –
GetApiUrl()builds the controller address fromStatePort2, ensuring seamless reconnection after core restarts. - Parallel Latency Testing –
ClashProxiesDelayTest()usesTask.WhenAllto test multiple nodes concurrently, streaming results via callbacks to prevent UI freezes. - State Synchronization – ViewModels such as
ClashProxiesViewModelandClashConnectionsViewModelconsume the manager to keep the interface synchronized with the Clash core’s runtime state.
Frequently Asked Questions
Does ClashApiManager support Clash‑Meta or Mihomo cores?
Yes. Because the manager communicates through the standard Clash HTTP API specification, it is fully compatible with Clash‑Meta (now Mihomo) and other Clash‑compatible forks. The endpoint paths (/proxies, /configs, /connections) and request schemas remain consistent across these implementations, allowing v2rayN to control any core that exposes the standard REST interface.
How does the manager handle network failures or timeouts?
All HTTP operations route through HttpClientHelper.Instance, which implements a retry policy with exponential backoff. For proxy fetching, the code explicitly retries three times with a two‑second delay between attempts. If the Clash core is unreachable, the async method returns null or an empty result, and the calling view‑model logs the error without crashing the application.
Can I use ClashApiManager to modify DNS or TUN settings at runtime?
The manager exposes ClashConfigUpdate(), which sends a PATCH request to /configs. While the provided implementation focuses on rule mode switching, you can extend the headers dictionary to include any Clash runtime patch supported by your core (e.g., {"tun": {"enable": true}}), provided the core supports dynamic configuration updates for those specific fields.
Which file handles the actual reloading of a Clash configuration file?
CoreConfigClashService.cs invokes ClashApiManager.Instance.ClashConfigReload() when the user selects a new profile. This method performs a PUT request to /configs?force=true with the absolute file path in the headers, instructing the Clash core to reload the YAML configuration from disk immediately.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →