Skip to content
Permalink
Browse files

Adding REST Client

  • Loading branch information
farajfarook committed Nov 23, 2019
1 parent 47c9034 commit a7c609e6d778325310769341a6407b027307a2c6
@@ -1,6 +1,6 @@
env:
global:
- PACKAGE_VERSION="3.0.0-rc7"
- PACKAGE_VERSION="3.0.0-rc8"
- PACKAGE_ICON="https://www.enbiso.com/logo.svg"
- PACKAGE_PROJECT="https://nlib.enbiso.com"
- PACKAGE_REPO="https://github.com/enbiso/Enbiso.NLib"
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Enbiso.NLib.RestClient</PackageId>

<PackageVersion Condition=" '$(PACKAGE_VERSION)' == '' ">0.0.1-local</PackageVersion>
<PackageVersion Condition=" '$(PACKAGE_VERSION)' != '' ">$(PACKAGE_VERSION)</PackageVersion>

<Authors>Faraj Farook</Authors>
<Description>Simple REST Client with OpenID Connect Client Credential Security</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Copyright>$(PACKAGE_COPYRIGHT)</Copyright>
<PackageTags>Enbiso Enbiso.NLib RestClient</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<NeutralLanguage>en-AU</NeutralLanguage>
<Owners>enbiso</Owners>
<PackageReleaseNotes>Initial Release</PackageReleaseNotes>
<Summary>Enbiso - Simple REST Client with OpenID Connect Client Credential Security</Summary>
<Title>Simple REST Client</Title>

<PackageIconUrl>$(PACKAGE_ICON)</PackageIconUrl>
<PackageProjectUrl>$(PACKAGE_PROJECT)</PackageProjectUrl>
<RepositoryUrl>$(PACKAGE_REPO)</RepositoryUrl>
<LangVersion>8</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.*" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.0.*" />
<PackageReference Include="System.Text.Json" Version="4.6.0" />
</ItemGroup>

</Project>
@@ -0,0 +1,15 @@
using System;
using System.Net;

namespace Enbiso.NLib.RestClient
{
public class RestRequestFailedException: Exception
{
public HttpStatusCode StatusCode { get; }

public RestRequestFailedException(HttpStatusCode statusCode, string message) : base(message)
{
StatusCode = statusCode;
}
}
}
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;

namespace Enbiso.NLib.RestClient
{
public interface IRestClient
{
Task<HttpResponseMessage> Post(string service, string path, object payload);
Task<HttpResponseMessage> Get(string service, string path);
Task<HttpResponseMessage> Put(string service, string path, object payload);
Task<HttpResponseMessage> Delete(string service, string path);
Task<T> Post<T>(string service, string path, object payload);
Task<T> Get<T>(string service, string path);
Task<T> Put<T>(string service, string path, object payload);
Task<T> Delete<T>(string service, string path);
}

public class RestClient: IRestClient
{
private readonly HttpClient _client;
private readonly RestClientOptions _options;

public RestClient(IHttpClientFactory clientFactory, IOptions<RestClientOptions> options)
{
_client = clientFactory.CreateClient();
_options = options.Value;
}

public Task<HttpResponseMessage> Post(string service, string path, object payload)
=> _retryPerform(service, path,
endpoint => _client.PostAsync(endpoint,
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")));

public Task<HttpResponseMessage> Put(string service, string path, object payload)
=> _retryPerform(service, path,
endpoint => _client.PutAsync(endpoint,
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")));

public Task<HttpResponseMessage> Get(string service, string path)
=> _retryPerform(service, path, endpoint => _client.GetAsync(endpoint));

public Task<HttpResponseMessage> Delete(string service, string path)
=> _retryPerform(service, path, endpoint => _client.DeleteAsync(endpoint));

public async Task<T> Post<T>(string service, string path, object payload) =>
await ParseResponse<T>(await Post(service, path, payload));

public async Task<T> Get<T>(string service, string path) => await ParseResponse<T>(await Get(service, path));

public async Task<T> Put<T>(string service, string path, object payload) =>
await ParseResponse<T>(await Put(service, path, payload));

public async Task<T> Delete<T>(string service, string path) =>
await ParseResponse<T>(await Delete(service, path));

private async Task UpdateToken()
{
if (string.IsNullOrEmpty(_options.TokenEndpoint)) return;
var resp = await _client.PostAsync(_options.TokenEndpoint, new FormUrlEncodedContent(
new Dictionary<string, string>
{
{"client_id", _options.ClientId},
{"client_secret", _options.ClientSecret},
{"grant_type", _options.GrantType},
{"scope", _options.Scope},
}));

if (!resp.IsSuccessStatusCode)
throw new Exception("Unable to fetch user information from Identity Service");
var content = await resp.Content.ReadAsStringAsync();
var token = JsonSerializer.Deserialize<Token>(content);
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(token.TokenType, token.AccessToken);
}

private async Task<HttpResponseMessage> _retryPerform(string service, string path, Func<string, Task<HttpResponseMessage>> perform)
{
if (string.IsNullOrWhiteSpace(_client.DefaultRequestHeaders.Authorization?.Parameter))
await UpdateToken();

if(!_options.Services.ContainsKey(service))
throw new Exception($"Service {service} not found");

var endpoint = $"{_options.Services[service]}{path}";

var resp = await perform.Invoke(endpoint);
if (resp.StatusCode != HttpStatusCode.Unauthorized) return resp;

await UpdateToken();
resp = await perform.Invoke(endpoint);
return resp;
}

private static async Task<T> ParseResponse<T>(HttpResponseMessage resp)
{
var content = await resp.Content.ReadAsStringAsync();
if (!resp.IsSuccessStatusCode)
throw new RestRequestFailedException(resp.StatusCode, content);
return JsonSerializer.Deserialize<T>(content);
}

private class Token
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("expires_in")]
public int ExpireIn { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;

namespace Enbiso.NLib.RestClient
{
public static class ServiceExtensions
{
public static IServiceCollection AddRestClient(this IServiceCollection services, Action<RestClientOptions> optSetup)
{
services.AddOptions();
services.Configure(optSetup);
services.AddSingleton<IRestClient, RestClient>();
return services;
}
}

public class RestClientOptions
{
public string TokenEndpoint { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string Scope { get; set; }
public IDictionary<string, string> Services { get; set; }
public string GrantType { get; set; } = "client_credentials";
}
}
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enbiso.NLib.EventBus.Nats.T
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enbiso.NLib.EventLogger.Mongo", "Enbiso.NLib.EventLogger.Mongo\Enbiso.NLib.EventLogger.Mongo.csproj", "{F4592242-6555-4253-BAFE-A0B021836DE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enbiso.NLib.RestClient", "Enbiso.NLib.RestClient\Enbiso.NLib.RestClient.csproj", "{027C77FD-2EC1-4C26-B366-A4DCBCFDB37B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -131,6 +133,10 @@ Global
{F4592242-6555-4253-BAFE-A0B021836DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4592242-6555-4253-BAFE-A0B021836DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4592242-6555-4253-BAFE-A0B021836DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{027C77FD-2EC1-4C26-B366-A4DCBCFDB37B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{027C77FD-2EC1-4C26-B366-A4DCBCFDB37B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{027C77FD-2EC1-4C26-B366-A4DCBCFDB37B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{027C77FD-2EC1-4C26-B366-A4DCBCFDB37B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

0 comments on commit a7c609e

Please sign in to comment.
You can’t perform that action at this time.