diff --git a/Storage/Storage.Test/ObjectControllerTests.cs b/Storage/Storage.Test/ObjectControllerTests.cs index fc99e8f..c0b118f 100644 --- a/Storage/Storage.Test/ObjectControllerTests.cs +++ b/Storage/Storage.Test/ObjectControllerTests.cs @@ -26,6 +26,7 @@ namespace LeanCloudTests { Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.UpdatedAt); + await Task.Delay(10000); } } } diff --git a/Storage/Storage/Internal/AppRouter/AppRouterController.cs b/Storage/Storage/Internal/AppRouter/AppRouterController.cs index 1e0c73d..137b96f 100644 --- a/Storage/Storage/Internal/AppRouter/AppRouterController.cs +++ b/Storage/Storage/Internal/AppRouter/AppRouterController.cs @@ -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(); /// /// Get current app's router state /// /// - 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 QueryAsync(CancellationToken cancellationToken) - { + public async Task 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(); - 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; - 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(ret.Item2); } - private static AppRouterState ParseAppRouterState(IDictionary 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(); } } } diff --git a/Storage/Storage/Internal/AppRouter/AppRouterState.cs b/Storage/Storage/Internal/AppRouter/AppRouterState.cs index b2e61f9..61438ed 100644 --- a/Storage/Storage/Internal/AppRouter/AppRouterState.cs +++ b/Storage/Storage/Internal/AppRouter/AppRouterState.cs @@ -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; } /// /// Is this app router state expired. /// - public bool isExpired() - { - return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL); + public bool IsExpired { + get { + return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL); + } } /// @@ -35,46 +62,43 @@ namespace LeanCloud.Storage.Internal /// Current app's appId /// Current app's region /// Initial app router state - 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"); diff --git a/Storage/Storage/Internal/AppRouter/IAppRouterController.cs b/Storage/Storage/Internal/AppRouter/IAppRouterController.cs index 771ee18..24c3366 100644 --- a/Storage/Storage/Internal/AppRouter/IAppRouterController.cs +++ b/Storage/Storage/Internal/AppRouter/IAppRouterController.cs @@ -7,16 +7,7 @@ namespace LeanCloud.Storage.Internal public interface IAppRouterController { AppRouterState Get(); - /// - /// Start refresh the app router. - /// - /// - Task RefreshAsync(); - void Clear(); - /// - /// Query the app router. - /// - /// New AppRouterState Task QueryAsync(CancellationToken cancellationToken); + void Clear(); } } diff --git a/Storage/Storage/Internal/Utilities/JsonExtensions.cs b/Storage/Storage/Internal/Utilities/JsonExtensions.cs new file mode 100644 index 0000000..af58f27 --- /dev/null +++ b/Storage/Storage/Internal/Utilities/JsonExtensions.cs @@ -0,0 +1,19 @@ +using System; +using Newtonsoft.Json; +using System.Threading.Tasks; + +namespace LeanCloud.Storage.Internal { + public static class JsonUtils { + public static Task DeserializeObjectAsync(string str) { + var tcs = new TaskCompletionSource(); + Task.Run(() => { + try { + tcs.SetResult(JsonConvert.DeserializeObject(str)); + } catch (Exception e) { + tcs.SetException(e); + } + }); + return tcs.Task; + } + } +} diff --git a/Storage/Storage/Storage.csproj b/Storage/Storage/Storage.csproj index 6de1371..46e9e03 100644 --- a/Storage/Storage/Storage.csproj +++ b/Storage/Storage/Storage.csproj @@ -8,4 +8,7 @@ + + +