Skip to content

Commit

Permalink
Http Client
Browse files Browse the repository at this point in the history
  • Loading branch information
Meowv committed Aug 4, 2020
1 parent 93f0dfa commit 7d31011
Show file tree
Hide file tree
Showing 23 changed files with 989 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace Plus.Http.Client.Authentication
{
public interface IRemoteServiceHttpClientAuthenticator
{
Task AuthenticateAsync(RemoteServiceHttpClientAuthenticateContext context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Plus.DependencyInjection;
using System.Threading.Tasks;

namespace Plus.Http.Client.Authentication
{
[Dependency(TryRegister = true)]
public class NullRemoteServiceHttpClientAuthenticator : IRemoteServiceHttpClientAuthenticator, ISingletonDependency
{
public Task AuthenticateAsync(RemoteServiceHttpClientAuthenticateContext context)
{
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Net.Http;

namespace Plus.Http.Client.Authentication
{
public class RemoteServiceHttpClientAuthenticateContext
{
public HttpClient Client { get; }

public HttpRequestMessage Request { get; }

public RemoteServiceConfiguration RemoteService { get; }

public string RemoteServiceName { get; }

public RemoteServiceHttpClientAuthenticateContext(
HttpClient client,
HttpRequestMessage request,
RemoteServiceConfiguration remoteService,
string remoteServiceName)
{
Client = client;
Request = request;
RemoteService = remoteService;
RemoteServiceName = remoteServiceName;
}
}
}
42 changes: 42 additions & 0 deletions Plus.Core/Plus/Http/Client/DynamicProxying/ApiDescriptionCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Nito.AsyncEx;
using Plus.DependencyInjection;
using Plus.Http.Modeling;
using Plus.Threading;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Plus.Http.Client.DynamicProxying
{
public class ApiDescriptionCache : IApiDescriptionCache, ISingletonDependency
{
protected ICancellationTokenProvider CancellationTokenProvider { get; }

private readonly Dictionary<string, ApplicationApiDescriptionModel> _cache;
private readonly SemaphoreSlim _semaphore;

public ApiDescriptionCache(ICancellationTokenProvider cancellationTokenProvider)
{
CancellationTokenProvider = cancellationTokenProvider;
_cache = new Dictionary<string, ApplicationApiDescriptionModel>();
_semaphore = new SemaphoreSlim(1, 1);
}

public async Task<ApplicationApiDescriptionModel> GetAsync(
string baseUrl,
Func<Task<ApplicationApiDescriptionModel>> factory)
{
using (await _semaphore.LockAsync(CancellationTokenProvider.Token))
{
var model = _cache.GetOrDefault(baseUrl);
if (model == null)
{
_cache[baseUrl] = model = await factory();
}

return model;
}
}
}
}
116 changes: 116 additions & 0 deletions Plus.Core/Plus/Http/Client/DynamicProxying/ApiDescriptionFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Plus.DependencyInjection;
using Plus.Http.Modeling;
using Plus.Threading;
using System;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;

namespace Plus.Http.Client.DynamicProxying
{
public class ApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency
{
public ICancellationTokenProvider CancellationTokenProvider { get; set; }

protected IApiDescriptionCache Cache { get; }

private static readonly JsonSerializerSettings SharedJsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

public ApiDescriptionFinder(IApiDescriptionCache cache)
{
Cache = cache;
CancellationTokenProvider = NullCancellationTokenProvider.Instance;
}

public async Task<ActionApiDescriptionModel> FindActionAsync(HttpClient client, string baseUrl, Type serviceType, MethodInfo method)
{
var apiDescription = await GetApiDescriptionAsync(client, baseUrl);

//TODO: Cache finding?

var methodParameters = method.GetParameters().ToArray();

foreach (var module in apiDescription.Modules.Values)
{
foreach (var controller in module.Controllers.Values)
{
if (!controller.Implements(serviceType))
{
continue;
}

foreach (var action in controller.Actions.Values)
{
if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length)
{
var found = true;

for (int i = 0; i < methodParameters.Length; i++)
{
if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i]))
{
found = false;
break;
}
}

if (found)
{
return action;
}
}
}
}
}

throw new PlusException($"Could not found remote action for method: {method} on the URL: {baseUrl}");
}

public virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionAsync(HttpClient client, string baseUrl)
{
return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl));
}

protected virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionFromServerAsync(HttpClient client, string baseUrl)
{
var response = await client.GetAsync(
baseUrl.EnsureEndsWith('/') + "api/Plus/api-definition",
CancellationTokenProvider.Token
);

if (!response.IsSuccessStatusCode)
{
throw new PlusException("Remote service returns error! StatusCode = " + response.StatusCode);
}

var content = await response.Content.ReadAsStringAsync();

var result = JsonConvert.DeserializeObject(
content,
typeof(ApplicationApiDescriptionModel), SharedJsonSerializerSettings);

return (ApplicationApiDescriptionModel)result;
}

protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter)
{
return NormalizeTypeName(actionParameter.TypeAsString) ==
NormalizeTypeName(methodParameter.ParameterType.GetFullNameWithAssemblyName());
}

protected virtual string NormalizeTypeName(string typeName)
{
const string placeholder = "%COREFX%";
const string netCoreLib = "System.Private.CoreLib";
const string netFxLib = "mscorlib";

return typeName.Replace(netCoreLib, placeholder).Replace(netFxLib, placeholder);
}
}
}
22 changes: 22 additions & 0 deletions Plus.Core/Plus/Http/Client/DynamicProxying/ApiVersionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace Plus.Http.Client.DynamicProxying
{
public class ApiVersionInfo //TODO: Rename to not conflict with api versioning apis
{
public string BindingSource { get; }
public string Version { get; }

public ApiVersionInfo(string bindingSource, string version)
{
BindingSource = bindingSource;
Version = version;
}

public bool ShouldSendInQueryString()
{
//TODO: Constant! TODO: Other sources!
return !BindingSource.IsIn("Path");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Plus.DependencyInjection;
using System.Net.Http;

namespace Plus.Http.Client.DynamicProxying
{
public class DefaultDynamicProxyHttpClientFactory : IDynamicProxyHttpClientFactory, ITransientDependency
{
private readonly IHttpClientFactory _httpClientFactory;

public DefaultDynamicProxyHttpClientFactory(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public HttpClient Create()
{
return _httpClientFactory.CreateClient();
}

public HttpClient Create(string name)
{
return _httpClientFactory.CreateClient(name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Plus.Http.Client.DynamicProxying
{
public class DynamicHttpClientProxyConfig
{
public Type Type { get; }

public string RemoteServiceName { get; }

public DynamicHttpClientProxyConfig(Type type, string remoteServiceName)
{
Type = type;
RemoteServiceName = remoteServiceName;
}
}
}
Loading

0 comments on commit 7d31011

Please sign in to comment.