* AppRouter.cs: chore: 完善公共模块
* Common.csproj: * HttpUtils.cs: * TaskExtensions.cs: * AppRouterController.cs:
parent
f171cee759
commit
5d88f601b3
|
@ -2,8 +2,10 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class AppRouterState {
|
public class AppRouter {
|
||||||
|
// 华东应用 App Id 后缀
|
||||||
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
|
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
|
||||||
|
// 美国应用 App Id 后缀
|
||||||
const string US_SUFFIX = "-MdYXbMMI";
|
const string US_SUFFIX = "-MdYXbMMI";
|
||||||
|
|
||||||
[JsonProperty("ttl")]
|
[JsonProperty("ttl")]
|
||||||
|
@ -36,6 +38,11 @@ namespace LeanCloud.Common {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonProperty("play_server")]
|
||||||
|
public string PlayServer {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
public string Source {
|
public string Source {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
@ -44,51 +51,57 @@ namespace LeanCloud.Common {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppRouterState() {
|
public AppRouter() {
|
||||||
FetchedAt = DateTimeOffset.Now;
|
FetchedAt = DateTimeOffset.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExpired {
|
public bool IsExpired {
|
||||||
get {
|
get {
|
||||||
|
if (TTL == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL);
|
return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppRouterState GetFallbackServers(string appId) {
|
public static AppRouter GetFallbackServers(string appId) {
|
||||||
var prefix = appId.Substring(0, 8).ToLower();
|
var prefix = appId.Substring(0, 8).ToLower();
|
||||||
var suffix = appId.Substring(appId.Length - 9);
|
var suffix = appId.Substring(appId.Length - 9);
|
||||||
switch (suffix) {
|
switch (suffix) {
|
||||||
case EAST_CHINA_SUFFIX:
|
case EAST_CHINA_SUFFIX:
|
||||||
// 华东
|
// 华东
|
||||||
return new AppRouterState {
|
return new AppRouter {
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = $"{prefix}.api.lncldapi.com",
|
ApiServer = $"{prefix}.api.lncldapi.com",
|
||||||
EngineServer = $"{prefix}.engine.lncldapi.com",
|
EngineServer = $"{prefix}.engine.lncldapi.com",
|
||||||
PushServer = $"{prefix}.push.lncldapi.com",
|
PushServer = $"{prefix}.push.lncldapi.com",
|
||||||
RTMServer = $"{prefix}.rtm.lncldapi.com",
|
RTMServer = $"{prefix}.rtm.lncldapi.com",
|
||||||
StatsServer = $"{prefix}.stats.lncldapi.com",
|
StatsServer = $"{prefix}.stats.lncldapi.com",
|
||||||
|
PlayServer = $"{prefix}.play.lncldapi.com",
|
||||||
Source = "fallback",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
case US_SUFFIX:
|
case US_SUFFIX:
|
||||||
// 美国
|
// 美国
|
||||||
return new AppRouterState {
|
return new AppRouter {
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = $"{prefix}.api.lncldglobal.com",
|
ApiServer = $"{prefix}.api.lncldglobal.com",
|
||||||
EngineServer = $"{prefix}.engine.lncldglobal.com",
|
EngineServer = $"{prefix}.engine.lncldglobal.com",
|
||||||
PushServer = $"{prefix}.push.lncldglobal.com",
|
PushServer = $"{prefix}.push.lncldglobal.com",
|
||||||
RTMServer = $"{prefix}.rtm.lncldglobal.com",
|
RTMServer = $"{prefix}.rtm.lncldglobal.com",
|
||||||
StatsServer = $"{prefix}.stats.lncldglobal.com",
|
StatsServer = $"{prefix}.stats.lncldglobal.com",
|
||||||
|
PlayServer = $"{prefix}.play.lncldglobal.com",
|
||||||
Source = "fallback",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
// 华北
|
// 华北
|
||||||
return new AppRouterState {
|
return new AppRouter {
|
||||||
TTL = -1,
|
TTL = -1,
|
||||||
ApiServer = $"{prefix}.api.lncld.net",
|
ApiServer = $"{prefix}.api.lncld.net",
|
||||||
EngineServer = $"{prefix}.engine.lncld.net",
|
EngineServer = $"{prefix}.engine.lncld.net",
|
||||||
PushServer = $"{prefix}.push.lncld.net",
|
PushServer = $"{prefix}.push.lncld.net",
|
||||||
RTMServer = $"{prefix}.rtm.lncld.net",
|
RTMServer = $"{prefix}.rtm.lncld.net",
|
||||||
StatsServer = $"{prefix}.stats.lncld.net",
|
StatsServer = $"{prefix}.stats.lncld.net",
|
||||||
|
PlayServer = $"{prefix}.play.lncld.net",
|
||||||
Source = "fallback",
|
Source = "fallback",
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -6,11 +6,32 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class AppRouterController {
|
public class AppRouterController {
|
||||||
private AppRouterState currentState;
|
readonly string appId;
|
||||||
|
|
||||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1);
|
AppRouter currentState;
|
||||||
|
|
||||||
public async Task<AppRouterState> Get(string appId) {
|
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<AppRouter> Get() {
|
||||||
if (string.IsNullOrEmpty(appId)) {
|
if (string.IsNullOrEmpty(appId)) {
|
||||||
throw new ArgumentNullException(nameof(appId));
|
throw new ArgumentNullException(nameof(appId));
|
||||||
}
|
}
|
||||||
|
@ -23,9 +44,9 @@ namespace LeanCloud.Common {
|
||||||
try {
|
try {
|
||||||
if (currentState == null) {
|
if (currentState == null) {
|
||||||
try {
|
try {
|
||||||
currentState = await QueryAsync(appId);
|
currentState = await QueryAsync();
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
currentState = AppRouterState.GetFallbackServers(appId);
|
currentState = AppRouter.GetFallbackServers(appId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return currentState;
|
return currentState;
|
||||||
|
@ -34,7 +55,7 @@ namespace LeanCloud.Common {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<AppRouterState> QueryAsync(string appId) {
|
async Task<AppRouter> QueryAsync() {
|
||||||
HttpClient client = null;
|
HttpClient client = null;
|
||||||
HttpRequestMessage request = null;
|
HttpRequestMessage request = null;
|
||||||
HttpResponseMessage response = null;
|
HttpResponseMessage response = null;
|
||||||
|
@ -51,9 +72,9 @@ namespace LeanCloud.Common {
|
||||||
|
|
||||||
response = await client.SendAsync(request);
|
response = await client.SendAsync(request);
|
||||||
string content = await response.Content.ReadAsStringAsync();
|
string content = await response.Content.ReadAsStringAsync();
|
||||||
HttpUtils.PrintResponse(response);
|
HttpUtils.PrintResponse(response, content);
|
||||||
|
|
||||||
AppRouterState state = JsonConvert.DeserializeObject<AppRouterState>(content);
|
AppRouter state = JsonConvert.DeserializeObject<AppRouter>(content);
|
||||||
state.Source = "router";
|
state.Source = "router";
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
@ -73,5 +94,13 @@ namespace LeanCloud.Common {
|
||||||
public void Clear() {
|
public void Clear() {
|
||||||
currentState = null;
|
currentState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsInternationalApp(string appId) {
|
||||||
|
if (appId.Length < 9) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string suffix = appId.Substring(appId.Length - 9);
|
||||||
|
return suffix == "-MdYXbMMI";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||||
|
<AssemblyName>LeanCloud.Common</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -10,5 +12,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Log\" />
|
<Folder Include="Log\" />
|
||||||
<Folder Include="Http\" />
|
<Folder Include="Http\" />
|
||||||
|
<Folder Include="Task\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.Text;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class HttpUtils {
|
public static class HttpUtils {
|
||||||
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LeanCloud.Common {
|
||||||
|
/// <summary>
|
||||||
|
/// Provides helper methods that allow us to use terser code elsewhere.
|
||||||
|
/// </summary>
|
||||||
|
public static class TaskExtensions {
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures a task (even null) is awaitable.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<T> Safe<T>(this Task<T> task) {
|
||||||
|
return task ?? Task.FromResult<T>(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures a task (even null) is awaitable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task Safe(this Task task) {
|
||||||
|
return task ?? Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void PartialAccessor<T>(ref T arg);
|
||||||
|
|
||||||
|
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> self,
|
||||||
|
TKey key,
|
||||||
|
TValue defaultValue) {
|
||||||
|
if (self.TryGetValue(key, out TValue value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
||||||
|
return Equals(a, b) ||
|
||||||
|
(a != null && b != null &&
|
||||||
|
a.SequenceEqual(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<TResult> OnSuccess<TResult>(this Task task,
|
||||||
|
Func<Task, TResult> continuation) {
|
||||||
|
return task.ContinueWith(t => {
|
||||||
|
if (t.IsFaulted) {
|
||||||
|
var ex = t.Exception.Flatten();
|
||||||
|
if (ex.InnerExceptions.Count == 1) {
|
||||||
|
ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw();
|
||||||
|
} else {
|
||||||
|
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||||
|
}
|
||||||
|
// Unreachable
|
||||||
|
return Task.FromResult(default(TResult));
|
||||||
|
} else if (t.IsCanceled) {
|
||||||
|
var tcs = new TaskCompletionSource<TResult>();
|
||||||
|
tcs.SetCanceled();
|
||||||
|
return tcs.Task;
|
||||||
|
} else {
|
||||||
|
return Task.FromResult(continuation(t));
|
||||||
|
}
|
||||||
|
}).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<TResult> OnSuccess<TIn, TResult>(this Task<TIn> task,
|
||||||
|
Func<Task<TIn>, TResult> continuation) {
|
||||||
|
return ((Task)task).OnSuccess(t => continuation((Task<TIn>)t));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task OnSuccess<TIn>(this Task<TIn> task, Action<Task<TIn>> continuation) {
|
||||||
|
return task.OnSuccess((Func<Task<TIn>, object>)(t => {
|
||||||
|
continuation(t);
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task OnSuccess(this Task task, Action<Task> continuation) {
|
||||||
|
return task.OnSuccess((Func<Task, object>)(t => {
|
||||||
|
continuation(t);
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskScheduler
|
||||||
|
public static Task<TResult> OnSuccess<TResult>(this Task task,
|
||||||
|
Func<Task, TResult> continuation, TaskScheduler scheduler) {
|
||||||
|
return task.ContinueWith(t => {
|
||||||
|
if (t.IsFaulted) {
|
||||||
|
var ex = t.Exception.Flatten();
|
||||||
|
if (ex.InnerExceptions.Count == 1) {
|
||||||
|
ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw();
|
||||||
|
} else {
|
||||||
|
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||||
|
}
|
||||||
|
// Unreachable
|
||||||
|
return Task.FromResult(default(TResult));
|
||||||
|
} else if (t.IsCanceled) {
|
||||||
|
var tcs = new TaskCompletionSource<TResult>();
|
||||||
|
tcs.SetCanceled();
|
||||||
|
return tcs.Task;
|
||||||
|
} else {
|
||||||
|
return Task.FromResult(continuation(t));
|
||||||
|
}
|
||||||
|
}, scheduler).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<TResult> OnSuccess<TIn, TResult>(this Task<TIn> task,
|
||||||
|
Func<Task<TIn>, TResult> continuation, TaskScheduler scheduler) {
|
||||||
|
return ((Task)task).OnSuccess(t => continuation((Task<TIn>)t), scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task OnSuccess<TIn>(this Task<TIn> task,
|
||||||
|
Action<Task<TIn>> continuation, TaskScheduler scheduler) {
|
||||||
|
return task.OnSuccess((Func<Task<TIn>, object>)(t => {
|
||||||
|
continuation(t);
|
||||||
|
return null;
|
||||||
|
}), scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task OnSuccess(this Task task,
|
||||||
|
Action<Task> continuation, TaskScheduler scheduler) {
|
||||||
|
return task.OnSuccess((Func<Task, object>)(t => {
|
||||||
|
continuation(t);
|
||||||
|
return null;
|
||||||
|
}), scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task WhileAsync(Func<Task<bool>> predicate, Func<Task> body) {
|
||||||
|
Func<Task> iterate = null;
|
||||||
|
iterate = () => {
|
||||||
|
return predicate().OnSuccess(t => {
|
||||||
|
if (!t.Result) {
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
return body().OnSuccess(_ => iterate()).Unwrap();
|
||||||
|
}).Unwrap();
|
||||||
|
};
|
||||||
|
return iterate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue