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.CreatedAt);
Assert.NotNull(obj.UpdatedAt);
await Task.Delay(10000);
}
}
}

View File

@ -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;
}
}
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)
{
// 从 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();
}
}
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);
}
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;
return await JsonUtils.DeserializeObjectAsync<AppRouterState>(ret.Item2);
}
public void Clear()
{
public void Clear() {
locker.EnterWriteLock();
currentState = null;
locker.ExitWriteLock();
}
}
}

View File

@ -1,33 +1,60 @@
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()
{
public bool IsExpired {
get {
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
}
}
/// <summary>
/// Get the initial usable router state
@ -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");

View File

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

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>
<Compile Remove="Class1.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
</Project>