Skip to content

Commit

Permalink
Http features
Browse files Browse the repository at this point in the history
  • Loading branch information
helto4real committed May 6, 2020
1 parent 4cf252f commit 62e8b44
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 8 deletions.
57 changes: 56 additions & 1 deletion src/App/NetDaemon.App/Common/INetDaemon.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Net;
using System.Text.Json;

namespace JoySoftware.HomeAssistant.NetDaemon.Common
{
Expand Down Expand Up @@ -348,6 +350,11 @@ public interface INetDaemonBase
/// <param name="id">Unique Id of the data</param>
/// <returns>The data persistent or null if not exists</returns>
ValueTask<T> GetDataAsync<T>(string id);

/// <summary>
/// Http features of NetDaemon is exposed through the Http property
/// </summary>
IHttpHandler Http { get; }
}


Expand Down Expand Up @@ -471,4 +478,52 @@ public interface INetDaemon : INetDaemonBase
/// <param name="entityIds">The unique id:s of the script</param>
IScript RunScript(INetDaemonApp app, params string[] entityIds);
}

/// <summary>
/// Implements all Http features of NetDaemon
/// </summary>
public interface IHttpHandler
{
/// <summary>
/// Returns a http client to use with http calls
/// </summary>
/// <param name="name">Logical name of the client to create</param>
/// <remarks>
/// This method uses the HttpClientFactory in the background for
/// more resource friendly usage of http client. You can cache the client
/// or dispose the client each usage in a using block.
/// Callers are also free to mutate the returned HttpClient
/// instance's public properties as desired.
/// </remarks>
HttpClient CreateHttpClient(string? name = null);


/// <summary>
/// Gets a json resopose deserialized
/// </summary>
/// <param name="url">Url</param>
/// <param name="options">Serialization options to use when serializing</param>
/// <param name="headers">name and value tuple of request headers, allowed values are string and IEnumerable of string</param>
/// <typeparam name="T">The type to use when deserializing</typeparam>
Task<T> GetJson<T>(string url, JsonSerializerOptions? options = null, params (string, object)[] headers);

/// <summary>
/// Post a object that are serialized to a json request and returns a deserializes json response
/// </summary>
/// <param name="url">Url</param>
/// <param name="request">The object to use as request</param>
/// <param name="options">Serialization options to use when serializing</param>
/// <param name="headers">name and value tuple of request headers, allowed values are string and IEnumerable of string</param>
/// <typeparam name="T">The type to use when deserializing</typeparam>
Task<T> PostJson<T>(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers);

/// <summary>
/// Post a object that are serialized to a json request
/// </summary>
/// <param name="url">Url</param>
/// <param name="request">The object to use as request</param>
/// <param name="options">Serialization options to use when serializing</param>
/// <param name="headers">name and value tuple of request headers, allowed values are string and IEnumerable of string</param>
Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers);
}
}
10 changes: 9 additions & 1 deletion src/App/NetDaemon.App/Common/NetDaemonApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ public abstract class NetDaemonApp : INetDaemonApp, INetDaemonBase
/// <inheritdoc/>
public ConcurrentDictionary<string, object> Global => _global;

/// <inheritdoc/>
public IHttpHandler Http
{
get
{
_ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
return _daemon!.Http;
}
}
/// <inheritdoc/>
public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false)
{
Expand Down Expand Up @@ -448,7 +457,6 @@ public IDelayResult DelayUntilStateChange(IEnumerable<string> entityIds, Func<En
return _daemon!.DelayUntilStateChange(entityIds, stateFunc);
}


/// <summary>
/// Implements the IEqualit.Equals method
/// </summary>
Expand Down
88 changes: 88 additions & 0 deletions src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using JoySoftware.HomeAssistant.NetDaemon.Common;

namespace JoySoftware.HomeAssistant.NetDaemon.Daemon
{
public class HttpHandler : IHttpHandler
{
private readonly IHttpClientFactory? _httpClientFactory;

public HttpHandler(IHttpClientFactory? httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public HttpClient CreateHttpClient(string? name = null)
{
_ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
return _httpClientFactory.CreateClient(name);
}

public async Task<T> GetJson<T>(string url, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
_ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");

var httpClient = _httpClientFactory.CreateClient();

AddHeaders(httpClient, headers);

var streamTask = httpClient.GetStreamAsync(url);

return await JsonSerializer.DeserializeAsync<T>(await streamTask.ConfigureAwait(false), options);
}

public async Task<T> PostJson<T>(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
_ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");

var httpClient = _httpClientFactory.CreateClient();

AddHeaders(httpClient, headers);

var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options);

var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost));

response.EnsureSuccessStatusCode();

var streamTask = response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(await streamTask.ConfigureAwait(false));
}

public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
_ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");

var httpClient = _httpClientFactory.CreateClient();

AddHeaders(httpClient, headers);

var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options);

var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost));

response.EnsureSuccessStatusCode();
}

private void AddHeaders(HttpClient httpClient, (string, object)[] headers)
{
if (headers is object && headers.Length > 0)
{
httpClient.DefaultRequestHeaders.Clear();
foreach (var (name, header) in headers)
{
if (header is string)
httpClient.DefaultRequestHeaders.Add(name, (string)header);
else if (header is IEnumerable<string>)
httpClient.DefaultRequestHeaders.Add(name, (IEnumerable<string>)header);
else
throw new ApplicationException($"Unsupported header, expected string or IEnumerable<string> for {name}");
}
}
}
}
}
19 changes: 18 additions & 1 deletion src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
Expand Down Expand Up @@ -40,6 +41,7 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
private readonly Scheduler _scheduler;

private readonly IDataRepository? _repository;
private readonly IHttpHandler? _httpHandler;

// Used for testing
internal ConcurrentDictionary<string, (string pattern, Func<string, EntityState?, EntityState?, Task> action)> InternalStateActions => _stateActions;
Expand Down Expand Up @@ -69,9 +71,14 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
private readonly List<(string, string, Func<dynamic?, Task>)> _companionServiceCallFunctionList
= new List<(string, string, Func<dynamic?, Task>)>();

public NetDaemonHost(IHassClient? hassClient, IDataRepository? repository, ILoggerFactory? loggerFactory = null)
public NetDaemonHost(
IHassClient? hassClient,
IDataRepository? repository,
ILoggerFactory? loggerFactory = null,
IHttpHandler? httpHandler = null)
{
loggerFactory ??= DefaultLoggerFactory;
_httpHandler = httpHandler;
Logger = loggerFactory.CreateLogger<NetDaemonHost>();
_hassClient = hassClient ?? throw new ArgumentNullException("HassClient can't be null!");
_scheduler = new Scheduler(loggerFactory: loggerFactory);
Expand All @@ -93,6 +100,15 @@ public NetDaemonHost(IHassClient? hassClient, IDataRepository? repository, ILogg
.AddConsole();
});

public IHttpHandler Http
{
get
{
_ = _httpHandler ?? throw new NullReferenceException("HttpHandler can not be null!");
return _httpHandler;
}
}

public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false) => _hassClient.CallService(domain, service, data, false);

public IEntity Entities(INetDaemonApp app, Func<IEntityProperties, bool> func)
Expand Down Expand Up @@ -743,6 +759,7 @@ public async ValueTask DisposeAsync()
await Stop().ConfigureAwait(false);
}
}

public class DelayResult : IDelayResult
{
private readonly TaskCompletionSource<bool> _delayTaskCompletionSource;
Expand Down
1 change: 1 addition & 0 deletions src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ItemGroup>
<PackageReference Include="JoySoftware.HassClient" Version="0.3.2-alpha" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\App\NetDaemon.App\NetDaemon.App.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/DaemonRunner/DaemonRunner/DaemonRunner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="JoySoftware.HassClient" Version="0.3.2-alpha" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.3" />
<PackageReference Include="YamlDotNet" Version="8.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />

Expand Down
14 changes: 10 additions & 4 deletions src/DaemonRunner/DaemonRunner/Service/RunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -18,6 +17,7 @@
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;

namespace JoySoftware.HomeAssistant.NetDaemon.DaemonRunner.Service
{
Expand Down Expand Up @@ -96,14 +96,19 @@ public static async Task Run(string[] args)
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureServices(services => { services.AddHostedService<RunnerService>(); });
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddHostedService<RunnerService>();
});
}

public class RunnerService : BackgroundService
{


const string _version = "dev";
private readonly IHttpClientFactory _httpClientFactory;

// private NetDaemonHost? _daemonHost;
private readonly ILogger<RunnerService> _logger;
Expand All @@ -114,8 +119,9 @@ public class RunnerService : BackgroundService
/// </summary>
private const int _reconnectIntervall = 40000;

public RunnerService(ILoggerFactory loggerFactory)
public RunnerService(ILoggerFactory loggerFactory, IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
_logger = loggerFactory.CreateLogger<RunnerService>();
_loggerFactory = loggerFactory;
}
Expand Down Expand Up @@ -165,7 +171,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
await Task.Delay(_reconnectIntervall, stoppingToken).ConfigureAwait(false); // Wait x seconds
}

await using var _daemonHost = new NetDaemonHost(new HassClient(_loggerFactory), new DataRepository(storageFolder), _loggerFactory);
await using var _daemonHost = new NetDaemonHost(new HassClient(_loggerFactory), new DataRepository(storageFolder), _loggerFactory, new HttpHandler(_httpClientFactory));

var daemonHostTask = _daemonHost.Run(config.Host, config.Port, config.Ssl, config.Token,
stoppingToken);
Expand Down
14 changes: 13 additions & 1 deletion tests/NetDaemon.Daemon.Tests/DaemonHostTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using JoySoftware.HomeAssistant.NetDaemon.Daemon;
using JoySoftware.HomeAssistant.NetDaemon.Daemon.Storage;
using Moq;
using NetDaemon.Daemon.Tests.Daemon;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -17,6 +19,7 @@ public partial class DaemonHostTestBase
private readonly LoggerMock _loggerMock;
private readonly HassClientMock _defaultHassClientMock;
private readonly Mock<IDataRepository> _defaultDataRepositoryMock;
private readonly HttpHandlerMock _defaultHttpHandlerMock;
private readonly NetDaemonHost _defaultDaemonHost;
private readonly NetDaemonHost _notConnectedDaemonHost;

Expand All @@ -26,7 +29,14 @@ internal DaemonHostTestBase()
_loggerMock = new LoggerMock();
_defaultHassClientMock = HassClientMock.DefaultMock;
_defaultDataRepositoryMock = new Mock<IDataRepository>();
_defaultDaemonHost = new NetDaemonHost(_defaultHassClientMock.Object, _defaultDataRepositoryMock.Object, _loggerMock.LoggerFactory);

_defaultHttpHandlerMock = new HttpHandlerMock();
_defaultDaemonHost = new NetDaemonHost(
_defaultHassClientMock.Object,
_defaultDataRepositoryMock.Object,
_loggerMock.LoggerFactory,
_defaultHttpHandlerMock.Object);

_defaultDaemonApp = new BaseTestApp();
_defaultDaemonApp.StartUpAsync(_defaultDaemonHost);

Expand All @@ -40,6 +50,8 @@ internal DaemonHostTestBase()

public HassClientMock DefaultHassClientMock => _defaultHassClientMock;

public HttpHandlerMock DefaultHttpHandlerMock => _defaultHttpHandlerMock;

public LoggerMock LoggerMock => _loggerMock;

public string HelloWorldData => "Hello world!";
Expand Down
Loading

0 comments on commit 62e8b44

Please sign in to comment.