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