diff --git a/Common/AppRouter/AppRouter.cs b/Common/AppRouter/AppRouter.cs index 6b0ae61..a46ed81 100644 --- a/Common/AppRouter/AppRouter.cs +++ b/Common/AppRouter/AppRouter.cs @@ -1,110 +1,69 @@ using System; +using System.Threading.Tasks; +using System.Net.Http; +using System.Collections.Generic; using Newtonsoft.Json; namespace LeanCloud.Common { public class AppRouter { - // 华东应用 App Id 后缀 - const string EAST_CHINA_SUFFIX = "-9Nh9j0Va"; - // 美国应用 App Id 后缀 - const string US_SUFFIX = "-MdYXbMMI"; + private readonly string appId; - [JsonProperty("ttl")] - public long TTL { - get; internal set; + private readonly string server; + + private AppServer appServer; + + public AppRouter(string appId, string server) { + if (!IsInternalApp(appId) && string.IsNullOrEmpty(server)) { + // 国内节点必须配置自定义域名 + throw new Exception("Please init with your server url."); + } + this.appId = appId; + this.server = server; } - [JsonProperty("api_server")] - public string ApiServer { - get; internal set; - } + public async Task GetApiServer() { + // 优先返回用户自定义域名 + if (!string.IsNullOrEmpty(server)) { + return server; + } + // 判断节点地区 + if (!IsInternalApp(appId)) { + // 国内节点必须配置自定义域名 + throw new Exception("Please init with your server url."); + } + // 向 App Router 请求地址 + if (appServer == null || appServer.IsExpired) { + try { + HttpRequestMessage request = new HttpRequestMessage { + RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"), + Method = HttpMethod.Get + }; + HttpClient client = new HttpClient(); + HttpUtils.PrintRequest(client, request); + HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + request.Dispose(); - [JsonProperty("engine_server")] - public string EngineServer { - get; internal set; - } + string resultString = await response.Content.ReadAsStringAsync(); + response.Dispose(); + HttpUtils.PrintResponse(response, resultString); - [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; - } - - [JsonProperty("play_server")] - public string PlayServer { - get; internal set; - } - - public string Source { - get; internal set; - } - - public DateTimeOffset FetchedAt { - get; internal set; - } - - public AppRouter() { - FetchedAt = DateTimeOffset.Now; - } - - public bool IsExpired { - get { - if (TTL == -1) { - return false; + Dictionary data = JsonConvert.DeserializeObject>(resultString); + appServer = new AppServer(data); + } catch (Exception e) { + Logger.Error(e.Message); + // 拉取服务地址失败后,使用国际节点的默认服务地址 + appServer = AppServer.GetInternalFallbackAppServer(appId); } - return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL); } + return appServer.ApiServer; } - public static AppRouter GetFallbackServers(string appId) { - var prefix = appId.Substring(0, 8).ToLower(); - var suffix = appId.Substring(appId.Length - 9); - switch (suffix) { - case EAST_CHINA_SUFFIX: - // 华东 - return new AppRouter { - TTL = -1, - 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", - PlayServer = $"{prefix}.play.lncldapi.com", - Source = "fallback", - }; - case US_SUFFIX: - // 美国 - return new AppRouter { - TTL = -1, - 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", - PlayServer = $"{prefix}.play.lncldglobal.com", - Source = "fallback", - }; - default: - // 华北 - return new AppRouter { - TTL = -1, - 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", - PlayServer = $"{prefix}.play.lncld.net", - Source = "fallback", - }; + private static bool IsInternalApp(string appId) { + if (appId.Length < 9) { + return false; } + string suffix = appId.Substring(appId.Length - 9); + return suffix == "-MdYXbMMI"; } } } \ No newline at end of file diff --git a/Common/AppRouter/AppRouterController.cs b/Common/AppRouter/AppRouterController.cs deleted file mode 100644 index f66f19e..0000000 --- a/Common/AppRouter/AppRouterController.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using Newtonsoft.Json; - -namespace LeanCloud.Common { - public class AppRouterController { - readonly string appId; - - AppRouter currentState; - - readonly SemaphoreSlim locker = new SemaphoreSlim(1); - - public AppRouterController(string appId, string server) { - if (!IsInternationalApp(appId) && string.IsNullOrEmpty(server)) { - // 国内 App 必须设置域名 - throw new ArgumentException("You must init with your domain."); - } - if (!string.IsNullOrEmpty(server)) { - currentState = new AppRouter { - ApiServer = server, - EngineServer = server, - PushServer = server, - RTMServer = server, - StatsServer = server, - PlayServer = server, - TTL = -1 - }; - } - this.appId = appId; - } - - public async Task Get() { - if (string.IsNullOrEmpty(appId)) { - throw new ArgumentNullException(nameof(appId)); - } - - if (currentState != null && !currentState.IsExpired) { - return currentState; - } - - await locker.WaitAsync(); - try { - if (currentState == null) { - try { - currentState = await QueryAsync(); - } catch (Exception) { - currentState = AppRouter.GetFallbackServers(appId); - } - } - return currentState; - } finally { - locker.Release(); - } - } - - async Task QueryAsync() { - HttpClient client = null; - HttpRequestMessage request = null; - HttpResponseMessage response = null; - - try { - string url = string.Format("https://app-router.com/2/route?appId={0}", appId); - - client = new HttpClient(); - request = new HttpRequestMessage { - RequestUri = new Uri(url), - Method = HttpMethod.Get - }; - HttpUtils.PrintRequest(client, request); - - response = await client.SendAsync(request); - string content = await response.Content.ReadAsStringAsync(); - HttpUtils.PrintResponse(response, content); - - AppRouter state = JsonConvert.DeserializeObject(content); - state.Source = "router"; - - return state; - } finally { - if (client != null) { - client.Dispose(); - } - if (request != null) { - request.Dispose(); - } - if (response != null) { - response.Dispose(); - } - } - } - - public void Clear() { - currentState = null; - } - - static bool IsInternationalApp(string appId) { - if (appId.Length < 9) { - return false; - } - string suffix = appId.Substring(appId.Length - 9); - return suffix == "-MdYXbMMI"; - } - } -} diff --git a/Common/AppRouter/AppServer.cs b/Common/AppRouter/AppServer.cs new file mode 100644 index 0000000..0ae29ae --- /dev/null +++ b/Common/AppRouter/AppServer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; + +namespace LeanCloud.Common { + public class AppServer { + public string ApiServer { + get; private set; + } + + public string EngineServer { + get; private set; + } + + public string PushServer { + get; private set; + } + + public string RTMServer { + get; private set; + } + + public bool IsExpired { + get { + return ttl != -1 && DateTime.Now > expiredAt; + } + } + + private readonly DateTime expiredAt; + + private readonly int ttl; + + public AppServer(Dictionary data) { + ApiServer = GetUrlWithScheme(data["api_server"] as string); + PushServer = GetUrlWithScheme(data["push_server"] as string); + EngineServer = GetUrlWithScheme(data["engine_server"] as string); + ttl = (int)(long)data["ttl"]; + expiredAt = DateTime.Now.AddSeconds(ttl); + } + + private static string GetUrlWithScheme(string url) { + return url.StartsWith("https://") ? url : $"https://{url}"; + } + + internal static AppServer GetInternalFallbackAppServer(string appId) { + string prefix = appId.Substring(0, 8).ToLower(); + return new AppServer(new Dictionary { + { "api_server", $"https://{prefix}.api.lncldglobal.com" }, + { "push_server", $"https://{prefix}.engine.lncldglobal.com" }, + { "engine_server", $"https://{prefix}.push.lncldglobal.com" }, + { "ttl", -1 } + }); + } + } +} diff --git a/Storage/Internal/Http/LCHttpClient.cs b/Storage/Internal/Http/LCHttpClient.cs index c7e9fc3..238e416 100644 --- a/Storage/Internal/Http/LCHttpClient.cs +++ b/Storage/Internal/Http/LCHttpClient.cs @@ -12,19 +12,21 @@ using LeanCloud.Common; namespace LeanCloud.Storage.Internal.Http { internal class LCHttpClient { - readonly string appId; + private readonly string appId; readonly string appKey; - readonly string server; + private readonly string server; - readonly string sdkVersion; + private readonly string sdkVersion; readonly string apiVersion; - HttpClient client; + readonly AppRouter appRouter; - MD5 md5; + readonly HttpClient client; + + readonly MD5 md5; internal LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) { this.appId = appId; @@ -33,6 +35,8 @@ namespace LeanCloud.Storage.Internal.Http { this.sdkVersion = sdkVersion; this.apiVersion = apiVersion; + appRouter = new AppRouter(appId, server); + client = new HttpClient(); ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", LeanCloud.SDKVersion); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product)); @@ -45,7 +49,7 @@ namespace LeanCloud.Storage.Internal.Http { internal async Task Get(string path, Dictionary headers = null, Dictionary queryParams = null) { - string url = BuildUrl(path, queryParams); + string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Get @@ -71,7 +75,7 @@ namespace LeanCloud.Storage.Internal.Http { Dictionary headers = null, Dictionary data = null, Dictionary queryParams = null) { - string url = BuildUrl(path, queryParams); + string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Post, @@ -104,7 +108,7 @@ namespace LeanCloud.Storage.Internal.Http { Dictionary headers = null, Dictionary data = null, Dictionary queryParams = null) { - string url = BuildUrl(path, queryParams); + string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Put, @@ -134,7 +138,7 @@ namespace LeanCloud.Storage.Internal.Http { } internal async Task Delete(string path) { - string url = BuildUrl(path); + string url = await BuildUrl(path); HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Delete @@ -170,8 +174,9 @@ namespace LeanCloud.Storage.Internal.Http { return new LCException(code, message); } - string BuildUrl(string path, Dictionary queryParams = null) { - string url = $"{server}/{apiVersion}/{path}"; + async Task BuildUrl(string path, Dictionary queryParams = null) { + string apiServer = await appRouter.GetApiServer(); + string url = $"{apiServer}/{apiVersion}/{path}"; if (queryParams != null) { IEnumerable queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}"); string queries = string.Join("&", queryPairs); diff --git a/Test/Common.Test/AppRouterTest.cs b/Test/Common.Test/AppRouterTest.cs deleted file mode 100644 index b037029..0000000 --- a/Test/Common.Test/AppRouterTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Threading.Tasks; -using NUnit.Framework; -using LeanCloud.Common; - -namespace Common.Test { - public class AppRouterTest { - static void Print(LogLevel level, string info) { - switch (level) { - case LogLevel.Debug: - TestContext.Out.WriteLine($"[DEBUG] {info}"); - break; - case LogLevel.Warn: - TestContext.Out.WriteLine($"[WARNING] {info}"); - break; - case LogLevel.Error: - TestContext.Out.WriteLine($"[ERROR] {info}"); - break; - default: - TestContext.Out.WriteLine(info); - break; - } - } - - [SetUp] - public void SetUp() { - Logger.LogDelegate += Print; - } - - [TearDown] - public void TearDown() { - Logger.LogDelegate -= Print; - } - - [Test] - public void ChineseApp() { - Exception e = Assert.Catch(() => { - string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz"; - AppRouterController appRouter = new AppRouterController(appId, null); - TestContext.WriteLine("init done"); - }); - TestContext.WriteLine(e.Message); - } - - [Test] - public async Task ChineseAppWithDomain() { - string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz"; - string server = "https://bmyv4rks.lc-cn-n1-shared.com"; - AppRouterController appRouterController = new AppRouterController(appId, server); - AppRouter appRouterState = await appRouterController.Get(); - Assert.AreEqual(appRouterState.ApiServer, server); - Assert.AreEqual(appRouterState.EngineServer, server); - Assert.AreEqual(appRouterState.PushServer, server); - Assert.AreEqual(appRouterState.RTMServer, server); - Assert.AreEqual(appRouterState.StatsServer, server); - Assert.AreEqual(appRouterState.PlayServer, server); - } - - [Test] - public void InternationalApp() { - string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-MdYXbMMI"; - _ = new AppRouterController(appId, null); - TestContext.WriteLine("International app init done"); - } - } -} \ No newline at end of file diff --git a/Test/Common.Test/Common.Test.csproj b/Test/Common.Test/Common.Test.csproj deleted file mode 100644 index e7ec085..0000000 --- a/Test/Common.Test/Common.Test.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netcoreapp3.0 - - false - 0.1.0 - - - - - - - - - - - - diff --git a/Test/Common.Test/Test.cs b/Test/Common.Test/Test.cs deleted file mode 100644 index 381a004..0000000 --- a/Test/Common.Test/Test.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Linq; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace Common.Test { - public class Test { - [Test] - public async Task AsyncFor() { - for (int i = 0; i < 5; i++) { - await Task.Delay(1000); - TestContext.WriteLine($"{i} done at {DateTimeOffset.UtcNow}"); - } - } - - [Test] - public void ConcurrentCollection() { - List list = new List(); - for (int i = 0; i < 1000; i++) { - Task.Run(() => { - list.Add(i); - }); - } - TestContext.WriteLine($"{list.Count}"); - - ConcurrentQueue queue = new ConcurrentQueue(); - for (int i = 0; i < 1000; i++) { - Task.Run(() => { - queue.Enqueue(i); - }); - } - TestContext.WriteLine($"{queue.Count}"); - } - - [Test] - public void ObjectType() { - List list = new List { 1, "hello", 2, "world" }; - TestContext.WriteLine(list is IList); - object[] objs = { 1, "hi", 3 }; - TestContext.WriteLine(objs is IList); - List subList = list.OfType().ToList(); - foreach (object obj in subList) { - TestContext.WriteLine(obj); - } - } - - [Test] - public void CollectionExcept() { - List list1 = new List { 1, 2, 3, 4, 5 }; - List list2 = new List { 4, 5, 6 }; - IEnumerable deltaList = list1.Except(list2).ToList(); - foreach (int delta in deltaList) { - TestContext.WriteLine(delta); - } - - Dictionary dict1 = new Dictionary { - { "a", 1 }, - { "b", 2 } - }; - Dictionary dict2 = new Dictionary { - { "b", 2 }, - { "c", 3 } - }; - IEnumerable> deltaDict = dict1.Except(dict2); - foreach (KeyValuePair delta in deltaDict) { - TestContext.WriteLine($"{delta.Key} : {delta.Value}"); - } - } - - [Test] - public void Union() { - Dictionary dict1 = new Dictionary { - { "a", 1 }, - { "b", 2 }, - { "c", 3 } - }; - Dictionary dict2 = new Dictionary { - { "b", "b" }, - { "c", "c" }, - { "d", "d" } - }; - IEnumerable keys = dict1.Keys.Union(dict2.Keys); - foreach (string key in keys) { - TestContext.WriteLine(key); - } - } - } -} diff --git a/Test/Storage.Test/FileTest.cs b/Test/Storage.Test/FileTest.cs index 187b7cb..9259f58 100644 --- a/Test/Storage.Test/FileTest.cs +++ b/Test/Storage.Test/FileTest.cs @@ -72,10 +72,11 @@ namespace LeanCloud.Test { [Test] public async Task AWS() { - Logger.LogDelegate += Utils.Print; - LeanCloud.Initialize("UlCpyvLm8aMzQsW6KnP6W3Wt-MdYXbMMI", "PyCTYoNoxCVoKKg394PBeS4r", "https://ulcpyvlm.api.lncldglobal.com"); - LCFile file = new LCFile("avatar", APKFilePath); - await file.Save(); + LeanCloud.Initialize("UlCpyvLm8aMzQsW6KnP6W3Wt-MdYXbMMI", "PyCTYoNoxCVoKKg394PBeS4r"); + LCFile file = new LCFile("avatar", AvatarFilePath); + await file.Save((count, total) => { + TestContext.WriteLine($"progress: {count}/{total}"); + }); TestContext.WriteLine(file.ObjectId); Assert.NotNull(file.ObjectId); } diff --git a/csharp-sdk.sln b/csharp-sdk.sln index fb1e951..2a42599 100644 --- a/csharp-sdk.sln +++ b/csharp-sdk.sln @@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Test\Common.Test\Common.Test.csproj", "{237B7A92-28F6-4227-BE52-7647139B1820}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,14 +27,9 @@ Global {531F8181-FFE0-476E-9D0A-93F13CAD1183}.Debug|Any CPU.Build.0 = Debug|Any CPU {531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.ActiveCfg = Release|Any CPU {531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.Build.0 = Release|Any CPU - {237B7A92-28F6-4227-BE52-7647139B1820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {237B7A92-28F6-4227-BE52-7647139B1820}.Debug|Any CPU.Build.0 = Debug|Any CPU - {237B7A92-28F6-4227-BE52-7647139B1820}.Release|Any CPU.ActiveCfg = Release|Any CPU - {237B7A92-28F6-4227-BE52-7647139B1820}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {531F8181-FFE0-476E-9D0A-93F13CAD1183} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} - {237B7A92-28F6-4227-BE52-7647139B1820} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution version = 0.1.0