chore: 引入 Newtonsoft.Json;重构 AppRouter 逻辑
parent
19c1975880
commit
fdfec81181
|
@ -26,6 +26,7 @@ namespace LeanCloudTests {
|
||||||
Assert.NotNull(obj.ObjectId);
|
Assert.NotNull(obj.ObjectId);
|
||||||
Assert.NotNull(obj.CreatedAt);
|
Assert.NotNull(obj.CreatedAt);
|
||||||
Assert.NotNull(obj.UpdatedAt);
|
Assert.NotNull(obj.UpdatedAt);
|
||||||
|
await Task.Delay(10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,101 +3,58 @@ using System.Net;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using LeanCloud.Storage.Internal;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal
|
namespace LeanCloud.Storage.Internal
|
||||||
{
|
{
|
||||||
public class AppRouterController : IAppRouterController
|
public class AppRouterController : IAppRouterController
|
||||||
{
|
{
|
||||||
private AppRouterState currentState;
|
private AppRouterState currentState;
|
||||||
private object mutex = new object();
|
private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get current app's router state
|
/// Get current app's router state
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public AppRouterState Get()
|
public AppRouterState Get() {
|
||||||
{
|
if (string.IsNullOrEmpty(AVClient.CurrentConfiguration.ApplicationId)) {
|
||||||
if (string.IsNullOrEmpty(AVClient.CurrentConfiguration.ApplicationId))
|
|
||||||
{
|
|
||||||
throw new AVException(AVException.ErrorCode.NotInitialized, "ApplicationId can not be null.");
|
throw new AVException(AVException.ErrorCode.NotInitialized, "ApplicationId can not be null.");
|
||||||
}
|
}
|
||||||
AppRouterState state;
|
|
||||||
state = AppRouterState.GetInitial(AVClient.CurrentConfiguration.ApplicationId, AVClient.CurrentConfiguration.Region);
|
|
||||||
|
|
||||||
lock (mutex)
|
try {
|
||||||
{
|
locker.EnterUpgradeableReadLock();
|
||||||
if (currentState != null)
|
if (currentState != null && !currentState.IsExpired) {
|
||||||
{
|
return currentState;
|
||||||
if (!currentState.isExpired())
|
|
||||||
state = currentState;
|
|
||||||
}
|
}
|
||||||
|
// 从 AppRouter 获取服务器地址,只触发,不等待
|
||||||
|
QueryAsync(CancellationToken.None).OnSuccess(t => {
|
||||||
|
locker.EnterWriteLock();
|
||||||
|
currentState = t.Result;
|
||||||
|
currentState.Source = "router";
|
||||||
|
locker.ExitWriteLock();
|
||||||
|
});
|
||||||
|
return AppRouterState.GetFallbackServers(AVClient.CurrentConfiguration.ApplicationId, AVClient.CurrentConfiguration.Region);
|
||||||
|
} finally {
|
||||||
|
locker.ExitUpgradeableReadLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.isExpired())
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
state.FetchedAt = DateTime.Now + TimeSpan.FromMinutes(10);
|
|
||||||
}
|
|
||||||
Task.Factory.StartNew(RefreshAsync);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RefreshAsync()
|
public async Task<AppRouterState> QueryAsync(CancellationToken cancellationToken) {
|
||||||
{
|
|
||||||
return QueryAsync(CancellationToken.None).ContinueWith(t =>
|
|
||||||
{
|
|
||||||
if (!t.IsFaulted && !t.IsCanceled)
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
currentState = t.Result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AppRouterState> QueryAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
string appId = AVClient.CurrentConfiguration.ApplicationId;
|
string appId = AVClient.CurrentConfiguration.ApplicationId;
|
||||||
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
|
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
|
||||||
|
|
||||||
return AVClient.HttpGetAsync(new Uri(url)).ContinueWith(t =>
|
var ret = await AVClient.HttpGetAsync(new Uri(url));
|
||||||
{
|
if (ret.Item1 != HttpStatusCode.OK) {
|
||||||
var tcs = new TaskCompletionSource<AppRouterState>();
|
throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null);
|
||||||
if (t.Result.Item1 != HttpStatusCode.OK)
|
}
|
||||||
{
|
|
||||||
tcs.SetException(new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null));
|
return await JsonUtils.DeserializeObjectAsync<AppRouterState>(ret.Item2);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var result = Json.Parse(t.Result.Item2) as IDictionary<String, Object>;
|
|
||||||
tcs.SetResult(ParseAppRouterState(result));
|
|
||||||
}
|
|
||||||
return tcs.Task;
|
|
||||||
}).Unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AppRouterState ParseAppRouterState(IDictionary<string, object> jsonObj)
|
public void Clear() {
|
||||||
{
|
locker.EnterWriteLock();
|
||||||
var state = new AppRouterState()
|
|
||||||
{
|
|
||||||
TTL = (int)jsonObj["ttl"],
|
|
||||||
StatsServer = jsonObj["stats_server"] as string,
|
|
||||||
RealtimeRouterServer = jsonObj["rtm_router_server"] as string,
|
|
||||||
PushServer = jsonObj["push_server"] as string,
|
|
||||||
EngineServer = jsonObj["engine_server"] as string,
|
|
||||||
ApiServer = jsonObj["api_server"] as string,
|
|
||||||
Source = "network",
|
|
||||||
};
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
currentState = null;
|
currentState = null;
|
||||||
|
locker.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,59 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal
|
namespace LeanCloud.Storage.Internal
|
||||||
{
|
{
|
||||||
public class AppRouterState
|
public class AppRouterState
|
||||||
{
|
{
|
||||||
public long TTL { get; internal set; }
|
[JsonProperty("ttl")]
|
||||||
public string ApiServer { get; internal set; }
|
public long TTL {
|
||||||
public string EngineServer { get; internal set; }
|
get; internal set;
|
||||||
public string PushServer { get; internal set; }
|
}
|
||||||
public string RealtimeRouterServer { get; internal set; }
|
|
||||||
public string StatsServer { get; internal set; }
|
|
||||||
public string Source { get; internal set; }
|
|
||||||
|
|
||||||
public DateTime FetchedAt { get; internal set; }
|
[JsonProperty("api_server")]
|
||||||
|
public string ApiServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
private static object mutex = new object();
|
[JsonProperty("engine_server")]
|
||||||
|
public string EngineServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
public AppRouterState()
|
[JsonProperty("push_server")]
|
||||||
{
|
public string PushServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("rtm_router_server")]
|
||||||
|
public string RTMServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("stats_server")]
|
||||||
|
public string StatsServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Source {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime FetchedAt {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppRouterState() {
|
||||||
FetchedAt = DateTime.Now;
|
FetchedAt = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is this app router state expired.
|
/// Is this app router state expired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool isExpired()
|
public bool IsExpired {
|
||||||
{
|
get {
|
||||||
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -35,46 +62,43 @@ namespace LeanCloud.Storage.Internal
|
||||||
/// <param name="appId">Current app's appId</param>
|
/// <param name="appId">Current app's appId</param>
|
||||||
/// <param name="region">Current app's region</param>
|
/// <param name="region">Current app's region</param>
|
||||||
/// <returns>Initial app router state</returns>
|
/// <returns>Initial app router state</returns>
|
||||||
public static AppRouterState GetInitial(string appId, AVClient.Configuration.AVRegion region)
|
public static AppRouterState GetFallbackServers(string appId, AVClient.Configuration.AVRegion region) {
|
||||||
{
|
|
||||||
var regionValue = (int)region;
|
var regionValue = (int)region;
|
||||||
var prefix = appId.Substring(0, 8).ToLower();
|
var prefix = appId.Substring(0, 8).ToLower();
|
||||||
switch (regionValue)
|
switch (regionValue)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
// 华北
|
// 华北
|
||||||
return new AppRouterState()
|
return new AppRouterState {
|
||||||
{
|
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = String.Format("{0}.api.lncld.net", prefix),
|
ApiServer = $"{prefix}.api.lncld.net",
|
||||||
EngineServer = String.Format("{0}.engine.lncld.net", prefix),
|
EngineServer = $"{prefix}.engine.lncld.net",
|
||||||
PushServer = String.Format("{0}.push.lncld.net", prefix),
|
PushServer = $"{prefix}.push.lncld.net",
|
||||||
RealtimeRouterServer = String.Format("{0}.rtm.lncld.net", prefix),
|
RTMServer = $"{prefix}.rtm.lncld.net",
|
||||||
StatsServer = String.Format("{0}.stats.lncld.net", prefix),
|
StatsServer = $"{prefix}.stats.lncld.net",
|
||||||
Source = "initial",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
case 1:
|
case 1:
|
||||||
// 美国
|
// 美国
|
||||||
return new AppRouterState()
|
return new AppRouterState {
|
||||||
{
|
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = string.Format("{0}.api.lncldglobal.com", prefix),
|
ApiServer = $"{prefix}.api.lncldglobal.com",
|
||||||
EngineServer = string.Format("{0}.engine.lncldglobal.com", prefix),
|
EngineServer = $"{prefix}.engine.lncldglobal.com",
|
||||||
PushServer = string.Format("{0}.push.lncldglobal.com", prefix),
|
PushServer = $"{prefix}.push.lncldglobal.com",
|
||||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldglobal.com", prefix),
|
RTMServer = $"{prefix}.rtm.lncldglobal.com",
|
||||||
StatsServer = string.Format("{0}.stats.lncldglobal.com", prefix),
|
StatsServer = $"{prefix}.stats.lncldglobal.com",
|
||||||
Source = "initial",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
case 2:
|
case 2:
|
||||||
// 华东
|
// 华东
|
||||||
return new AppRouterState() {
|
return new AppRouterState {
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = string.Format("{0}.api.lncldapi.com", prefix),
|
ApiServer = $"{prefix}.api.lncldapi.com",
|
||||||
EngineServer = string.Format("{0}.engine.lncldapi.com", prefix),
|
EngineServer = $"{prefix}.engine.lncldapi.com",
|
||||||
PushServer = string.Format("{0}.push.lncldapi.com", prefix),
|
PushServer = $"{prefix}.push.lncldapi.com",
|
||||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldapi.com", prefix),
|
RTMServer = $"{prefix}.rtm.lncldapi.com",
|
||||||
StatsServer = string.Format("{0}.stats.lncldapi.com", prefix),
|
StatsServer = $"{prefix}.stats.lncldapi.com",
|
||||||
Source = "initial",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
throw new AVException(AVException.ErrorCode.OtherCause, "invalid region");
|
throw new AVException(AVException.ErrorCode.OtherCause, "invalid region");
|
||||||
|
|
|
@ -7,16 +7,7 @@ namespace LeanCloud.Storage.Internal
|
||||||
public interface IAppRouterController
|
public interface IAppRouterController
|
||||||
{
|
{
|
||||||
AppRouterState Get();
|
AppRouterState Get();
|
||||||
/// <summary>
|
|
||||||
/// Start refresh the app router.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task RefreshAsync();
|
|
||||||
void Clear();
|
|
||||||
/// <summary>
|
|
||||||
/// Query the app router.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>New AppRouterState</returns>
|
|
||||||
Task<AppRouterState> QueryAsync(CancellationToken cancellationToken);
|
Task<AppRouterState> QueryAsync(CancellationToken cancellationToken);
|
||||||
|
void Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal {
|
||||||
|
public static class JsonUtils {
|
||||||
|
public static Task<T> DeserializeObjectAsync<T>(string str) {
|
||||||
|
var tcs = new TaskCompletionSource<T>();
|
||||||
|
Task.Run(() => {
|
||||||
|
try {
|
||||||
|
tcs.SetResult(JsonConvert.DeserializeObject<T>(str));
|
||||||
|
} catch (Exception e) {
|
||||||
|
tcs.SetException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,4 +8,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Class1.cs" />
|
<Compile Remove="Class1.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Reference in New Issue