chore: 引入 Newtonsoft.Json;重构 AppRouter 逻辑

oneRain 2019-07-30 17:43:50 +08:00
parent 19c1975880
commit fdfec81181
6 changed files with 115 additions and 120 deletions

View File

@ -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);
} }
} }
} }

View File

@ -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();
} }
} }
} }

View File

@ -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");

View File

@ -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();
} }
} }

View File

@ -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;
}
}
}

View File

@ -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>