* AppRouter.cs: chore: 完善公共模块

* Common.csproj:
* HttpUtils.cs:
* TaskExtensions.cs:
* AppRouterController.cs:
oneRain 2019-11-04 15:45:42 +08:00
parent f171cee759
commit 5d88f601b3
5 changed files with 208 additions and 15 deletions

View File

@ -2,8 +2,10 @@
using Newtonsoft.Json;
namespace LeanCloud.Common {
public class AppRouterState {
public class AppRouter {
// 华东应用 App Id 后缀
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
// 美国应用 App Id 后缀
const string US_SUFFIX = "-MdYXbMMI";
[JsonProperty("ttl")]
@ -36,6 +38,11 @@ namespace LeanCloud.Common {
get; internal set;
}
[JsonProperty("play_server")]
public string PlayServer {
get; internal set;
}
public string Source {
get; internal set;
}
@ -44,51 +51,57 @@ namespace LeanCloud.Common {
get; internal set;
}
public AppRouterState() {
public AppRouter() {
FetchedAt = DateTimeOffset.Now;
}
public bool IsExpired {
get {
if (TTL == -1) {
return false;
}
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 suffix = appId.Substring(appId.Length - 9);
switch (suffix) {
case EAST_CHINA_SUFFIX:
// 华东
return new AppRouterState {
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 AppRouterState {
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 AppRouterState {
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",
};
}

View File

@ -6,11 +6,32 @@ using Newtonsoft.Json;
namespace LeanCloud.Common {
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)) {
throw new ArgumentNullException(nameof(appId));
}
@ -23,9 +44,9 @@ namespace LeanCloud.Common {
try {
if (currentState == null) {
try {
currentState = await QueryAsync(appId);
currentState = await QueryAsync();
} catch (Exception) {
currentState = AppRouterState.GetFallbackServers(appId);
currentState = AppRouter.GetFallbackServers(appId);
}
}
return currentState;
@ -34,7 +55,7 @@ namespace LeanCloud.Common {
}
}
async Task<AppRouterState> QueryAsync(string appId) {
async Task<AppRouter> QueryAsync() {
HttpClient client = null;
HttpRequestMessage request = null;
HttpResponseMessage response = null;
@ -51,9 +72,9 @@ namespace LeanCloud.Common {
response = await client.SendAsync(request);
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";
return state;
@ -73,5 +94,13 @@ namespace LeanCloud.Common {
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";
}
}
}

View File

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.1.0</ReleaseVersion>
<AssemblyName>LeanCloud.Common</AssemblyName>
</PropertyGroup>
<ItemGroup>
@ -10,5 +12,6 @@
<ItemGroup>
<Folder Include="Log\" />
<Folder Include="Http\" />
<Folder Include="Task\" />
</ItemGroup>
</Project>

View File

@ -3,7 +3,7 @@ using System.Text;
using System.Net.Http;
namespace LeanCloud.Common {
public class HttpUtils {
public static class HttpUtils {
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
if (client == null) {
return;

View File

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