* AppRouterController.cs: chore: 提取公共模块,和 Play 共享。包括

AppRouter,Json,日志等

* csharp-sdk.sln:
* RTM.csproj:
* Common.csproj:
* HttpUtils.cs:
* Tests.cs:
* JsonExtensions.cs:
* Storage.csproj:
* JustTest.cs:
* AppRouterTest.cs:
* AppRouterState.cs:
* ObjectTest.cs:
* Common.Test.csproj:
* Common.Test.csproj:
* AppRouterTest.cs:
* Storage.Test.csproj:
* AVObjectController.cs:
oneRain 2019-11-01 18:19:35 +08:00
parent 68a047ba1e
commit f171cee759
16 changed files with 254 additions and 104 deletions

View File

@ -1,64 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
namespace LeanCloud.Storage.Internal {
public class AppRouterController {
private AppRouterState currentState;
private readonly SemaphoreSlim locker = new SemaphoreSlim(1);
public async Task<AppRouterState> Get(string appId) {
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(appId);
} catch (Exception) {
currentState = AppRouterState.GetFallbackServers(appId);
}
}
return currentState;
} finally {
locker.Release();
}
}
async Task<AppRouterState> QueryAsync(string appId) {
Console.WriteLine("QueryAsync");
string url = string.Format("https://app-router.com/2/route?appId={0}", appId);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
HttpResponseMessage response = await client.SendAsync(request);
client.Dispose();
request.Dispose();
string content = await response.Content.ReadAsStringAsync();
response.Dispose();
AppRouterState state = JsonConvert.DeserializeObject<AppRouterState>(content);
state.Source = "router";
return state;
}
public void Clear() {
currentState = null;
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
namespace LeanCloud.Common {
public class AppRouterController {
private AppRouterState currentState;
private readonly SemaphoreSlim locker = new SemaphoreSlim(1);
public async Task<AppRouterState> Get(string appId) {
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(appId);
} catch (Exception) {
currentState = AppRouterState.GetFallbackServers(appId);
}
}
return currentState;
} finally {
locker.Release();
}
}
async Task<AppRouterState> QueryAsync(string appId) {
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);
AppRouterState state = JsonConvert.DeserializeObject<AppRouterState>(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;
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace LeanCloud.Storage.Internal {
namespace LeanCloud.Common {
public class AppRouterState {
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
const string US_SUFFIX = "-MdYXbMMI";
@ -93,6 +93,5 @@ namespace LeanCloud.Storage.Internal {
};
}
}
}
}

14
Common/Common.csproj Normal file
View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Log\" />
<Folder Include="Http\" />
</ItemGroup>
</Project>

49
Common/Http/HttpUtils.cs Normal file
View File

@ -0,0 +1,49 @@
using System.Linq;
using System.Text;
using System.Net.Http;
namespace LeanCloud.Common {
public class HttpUtils {
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
if (client == null) {
return;
}
if (request == null) {
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== HTTP Request Start ===");
sb.AppendLine($"URL: {request.RequestUri}");
sb.AppendLine($"Method: {request.Method}");
sb.AppendLine($"Headers: ");
foreach (var header in client.DefaultRequestHeaders) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
foreach (var header in request.Headers) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
if (request.Content != null) {
foreach (var header in request.Content.Headers) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
}
if (!string.IsNullOrEmpty(content)) {
sb.AppendLine($"Content: {content}");
}
sb.AppendLine("=== HTTP Request End ===");
Logger.Debug(sb.ToString());
}
public static void PrintResponse(HttpResponseMessage response, string content = null) {
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== HTTP Response Start ===");
sb.AppendLine($"URL: {response.RequestMessage.RequestUri}");
sb.AppendLine($"Status Code: {response.StatusCode}");
if (!string.IsNullOrEmpty(content)) {
sb.AppendLine($"Content: {content}");
}
sb.AppendLine("=== HTTP Response End ===");
Logger.Debug(sb.ToString());
}
}
}

View File

@ -1,8 +1,7 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace LeanCloud.Storage.Internal {
namespace LeanCloud.Common {
/// <summary>
/// 为 Json 解析提供异步接口
/// </summary>

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.1.0</ReleaseVersion>
</PropertyGroup>
<ItemGroup>
@ -9,7 +10,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Storage\Storage\Storage.csproj" />
<ProjectReference Include="..\..\AppRouter\AppRouter\AppRouter.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Internal\WebSocket\Unity\websocket-sharp.dll" />

View File

@ -6,11 +6,7 @@ namespace LeanCloud.Test {
public class AppRouterTest {
[Test]
public async Task GetServers() {
var appRouter = new AppRouterController();
for (int i = 0; i < 1000; i++) {
var state = await appRouter.Get("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz");
TestContext.Out.WriteLine(state.ApiServer);
}
}
}
}

View File

@ -27,6 +27,16 @@ namespace LeanCloud.Test {
}
}
[Test]
public void Zip() {
List<int> l1 = new List<int> { 1, 2, 3, 4 };
List<int> l2 = new List<int> { 1, 1, 2 };
var l3 = l1.Zip(l2, (e1, e2) => $"{e1}-{e2}");
foreach (var e in l3) {
TestContext.Out.WriteLine($"{e}");
}
}
[Test]
public void GenericType() {
List<int> list = new List<int> { 1, 1, 2, 3, 5, 8 };

View File

@ -167,11 +167,21 @@ namespace LeanCloud.Test {
List<AVObject> objList = new List<AVObject>();
for (int i = 0; i < 5; i++) {
AVObject obj = AVObject.Create("Foo");
obj.ACL = new AVACL {
PublicReadAccess = true,
PublicWriteAccess = i % 2 == 0
};
obj["content"] = "batch object";
objList.Add(obj);
}
await objList.SaveAllAsync();
try {
await AVObject.DeleteAllAsync(objList);
} catch (AggregateException e) {
foreach (AVException ie in e.InnerExceptions) {
TestContext.Out.WriteLine($"{ie.Code} : {ie.Message}");
}
}
}
[Test]

View File

@ -16,6 +16,5 @@
<ItemGroup>
<ProjectReference Include="..\Storage\Storage.csproj" />
<ProjectReference Include="..\..\AppRouter\AppRouter\AppRouter.csproj" />
</ItemGroup>
</Project>

View File

@ -122,7 +122,7 @@ namespace LeanCloud.Storage.Internal {
return tasks;
}
private IList<Task<IDictionary<string, object>>> ExecuteBatchRequest(IList<AVCommand> requests,
private async Task<IList<Task<IDictionary<string, object>>>> ExecuteBatchRequest(IList<AVCommand> requests,
CancellationToken cancellationToken) {
var tasks = new List<Task<IDictionary<string, object>>>();
int batchSize = requests.Count;
@ -151,26 +151,16 @@ namespace LeanCloud.Storage.Internal {
{ "requests", encodedRequests }
}
};
AVPlugins.Instance.CommandRunner.RunCommandAsync<IList<object>>(command, cancellationToken).ContinueWith(t => {
if (t.IsFaulted || t.IsCanceled) {
foreach (var tcs in tcss) {
if (t.IsFaulted) {
tcs.TrySetException(t.Exception);
} else if (t.IsCanceled) {
tcs.TrySetCanceled();
}
}
return;
}
var resultsArray = t.Result.Item2;
try {
var response = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IList<object>>(command, cancellationToken);
var resultsArray = response.Item2;
int resultLength = resultsArray.Count;
if (resultLength != batchSize) {
foreach (var tcs in tcss) {
tcs.TrySetException(new InvalidOperationException(
"Batch command result count expected: " + batchSize + " but was: " + resultLength + "."));
}
return;
}
for (int i = 0; i < batchSize; ++i) {
@ -188,9 +178,11 @@ namespace LeanCloud.Storage.Internal {
"Invalid batch command response."));
}
}
});
return tasks;
} catch (Exception e) {
foreach (var tcs in tcss) {
tcs.TrySetException(e);
}
}
}
}
}

View File

@ -12,6 +12,6 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppRouter\AppRouter\AppRouter.csproj" />
<ProjectReference Include="..\..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
using System.Threading.Tasks;
using NUnit.Framework;
using LeanCloud.Common;
namespace Common.Test {
public class Tests {
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() {
TestContext.Out.WriteLine("Set up");
Logger.LogDelegate += Print;
}
[TearDown]
public void TearDown() {
TestContext.Out.WriteLine("Tear down");
Logger.LogDelegate -= Print;
}
[Test]
public async Task AppRouter() {
var appRouter = new AppRouterController();
for (int i = 0; i < 100; i++) {
var state = await appRouter.Get("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz");
TestContext.Out.WriteLine(state.ApiServer);
}
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@ -27,14 +27,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery.Test", "LiveQuery
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Storage\Storage.Test\Storage.Test.csproj", "{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AppRouter", "AppRouter", "{1F05195D-2CAA-4214-8DAE-FE14A6B905DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppRouter", "AppRouter\AppRouter\AppRouter.csproj", "{D34FC092-042A-44CE-A9E2-56B996BDCF42}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage\Storage.csproj", "{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM", "RTM\RTM\RTM.csproj", "{D4A30F70-AAED-415D-B940-023B3D7241EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Test\Common.Test\Common.Test.csproj", "{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -77,10 +79,6 @@ Global
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF}.Release|Any CPU.Build.0 = Release|Any CPU
{D34FC092-042A-44CE-A9E2-56B996BDCF42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D34FC092-042A-44CE-A9E2-56B996BDCF42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D34FC092-042A-44CE-A9E2-56B996BDCF42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D34FC092-042A-44CE-A9E2-56B996BDCF42}.Release|Any CPU.Build.0 = Release|Any CPU
{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59DA32A0-4CD3-424A-8584-D08B8D1E2B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -89,6 +87,14 @@ Global
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.Build.0 = Release|Any CPU
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.Build.0 = Release|Any CPU
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{659D19F0-9A40-42C0-886C-555E64F16848} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
@ -100,9 +106,9 @@ Global
{3251B4D8-D11A-4D90-8626-27FEE266B066} = {5B895B7A-1F6E-40A5-8081-43B334D2C076}
{F907012C-74DF-4575-AFE6-E8DAACC26D24} = {5B895B7A-1F6E-40A5-8081-43B334D2C076}
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
{D34FC092-042A-44CE-A9E2-56B996BDCF42} = {1F05195D-2CAA-4214-8DAE-FE14A6B905DD}
{59DA32A0-4CD3-424A-8584-D08B8D1E2B98} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
{D4A30F70-AAED-415D-B940-023B3D7241EE} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F}
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
version = 0.1.0