Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to .NET 8 and fix HttpClient memory leak #13

Merged
merged 6 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
name: Unit Tests on Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up dotnet
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v4
with:
dotnet-version: '6.x'
dotnet-version: '8'
- run: dotnet test
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /build

# Copy csproj and restore as distinct layers
Expand All @@ -10,7 +10,7 @@ COPY sama/ ./
RUN dotnet publish -c Release -o out --no-restore /p:MvcRazorCompileOnPublish=true

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /opt/sama
COPY --from=build-env /build/out .
ENTRYPOINT ["dotnet", "sama.dll"]
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile-buildx-amd64
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Forcing amd64 for buildx per https://github.com/dotnet/dotnet-docker/issues/1537#issuecomment-755351628
FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim-amd64 AS build-env
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build-env
WORKDIR /build

# Copy csproj and restore as distinct layers
Expand All @@ -11,7 +11,7 @@ COPY sama/ ./
RUN dotnet publish -c Release -o out --no-restore /p:MvcRazorCompileOnPublish=true

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /opt/sama
COPY --from=build-env /build/out .
ENTRYPOINT ["dotnet", "sama.dll"]
Expand Down
2 changes: 1 addition & 1 deletion sama/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public async Task<IActionResult> Edit(Guid? id)
var isRemote = (user == null && userId[0] == 0 && userId[1] == 0 && userId[2] == 0 && userId[3] == 0);
if (isRemote)
{
ViewData["IsCurrentUser"] = (id == Guid.Parse(_userManager.GetUserId(User)));
ViewData["IsCurrentUser"] = (id == Guid.Parse(_userManager.GetUserId(User)!));
return View("EditRemote");
}

Expand Down
15 changes: 1 addition & 14 deletions sama/Controllers/EndpointsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,8 @@
namespace sama.Controllers
{
[Authorize]
public class EndpointsController : Controller
public class EndpointsController(ApplicationDbContext _context, StateService _stateService, UserManagementService _userService, AggregateNotificationService _notifier) : Controller
{
private readonly ApplicationDbContext _context;
private readonly StateService _stateService;
private readonly UserManagementService _userService;
private readonly AggregateNotificationService _notifier;

public EndpointsController(ApplicationDbContext context, StateService stateService, UserManagementService userService, AggregateNotificationService notifier)
{
_context = context;
_stateService = stateService;
_userService = userService;
_notifier = notifier;
}

[AllowAnonymous]
public async Task<IActionResult> IndexRedirect()
{
Expand Down
51 changes: 26 additions & 25 deletions sama/Extensions/EndpointHttpExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using sama.Models;
using sama.Models;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.Json.Nodes;

namespace sama.Extensions
{
public static class EndpointHttpExtensions
{
private static JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() };

public static string? GetHttpLocation(this Endpoint endpoint) =>
GetValue<string>(endpoint, "Location");

Expand Down Expand Up @@ -45,43 +41,48 @@ public static void SetHttpCustomTlsCert(this Endpoint endpoint, string? pemEncod

private static List<T>? GetValueList<T>(Endpoint endpoint, string name, List<T>? defaultValue = null)
{
var list = GetValue<List<object>>(endpoint, name);
if (list == null)
EnsureHttp(endpoint);
if (string.IsNullOrWhiteSpace(endpoint.JsonConfig)) return defaultValue;

var node = JsonNode.Parse(endpoint.JsonConfig);
var matches = node?.AsObject().Where(kvp => kvp.Key == name);
if (matches?.Any() ?? false)
{
return defaultValue;
if (matches.First().Value == null) return defaultValue;
var array = matches.First().Value!.AsArray();
if (array == null) return defaultValue;
return array.Select(n => n!.GetValue<T>()).ToList();
}
return list.Select(o => (T)Convert.ChangeType(o, typeof(T))).ToList();
// else
return defaultValue;
}

private static T? GetValue<T>(Endpoint endpoint, string name, T? defaultValue = default(T))
{
EnsureHttp(endpoint);
if (string.IsNullOrWhiteSpace(endpoint.JsonConfig)) return defaultValue;

var obj = JsonConvert.DeserializeObject<ExpandoObject>(endpoint.JsonConfig, JsonSettings) as IDictionary<string, object>;
if (obj == null)
var node = JsonNode.Parse(endpoint.JsonConfig);
var matches = node?.AsObject().Where(kvp => kvp.Key == name);
if (matches?.Any() ?? false)
{
throw new ArgumentException($"Unable to get value for '{name}'");
if (matches.First().Value == null) return defaultValue;
return matches.First().Value!.GetValue<T>();
}
if (!obj.ContainsKey(name))
{
return defaultValue;
}
return (T)obj[name];
// else
return defaultValue;
}

private static void SetValue<T>(Endpoint endpoint, string name, T? value)
{
EnsureHttp(endpoint);

var json = (string.IsNullOrWhiteSpace(endpoint.JsonConfig) ? "{}" : endpoint.JsonConfig);
var obj = JsonConvert.DeserializeObject<ExpandoObject>(json, JsonSettings) ?? throw new ArgumentException($"Unable to deserialize '{name}'");
obj!.Remove(name, out object _);
if (!obj.TryAdd(name, value))
{
throw new ArgumentException($"Unable to set value for '{name}'");
}
endpoint.JsonConfig = JsonConvert.SerializeObject(obj, JsonSettings);

var nodeObj = JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException($"Unable to deserialize '{name}'");
nodeObj.Remove(name);
nodeObj.Add(name, JsonValue.Create(value));
endpoint.JsonConfig = nodeObj.ToJsonString();
}

private static void EnsureHttp(Endpoint endpoint)
Expand Down
27 changes: 18 additions & 9 deletions sama/Extensions/EndpointIcmpExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using sama.Models;
using sama.Models;
using System;
using System.Dynamic;
using System.Linq;
using System.Text.Json.Nodes;

namespace sama.Extensions
{
public static class EndpointIcmpExtensions
{
private static JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() };
private const string IcmpAddressKey = "Address";

public static string? GetIcmpAddress(this Endpoint endpoint)
{
EnsureIcmp(endpoint);
if (string.IsNullOrWhiteSpace(endpoint.JsonConfig)) return null;

return JsonConvert.DeserializeObject<dynamic>(endpoint.JsonConfig, JsonSettings)?.Address?.ToObject<string>();
var node = JsonNode.Parse(endpoint.JsonConfig);
var matches = node?.AsObject().Where(kvp => kvp.Key == IcmpAddressKey);
if (matches?.Any() ?? false)
{
if (matches.First().Value == null) return null;
return matches.First().Value!.GetValue<string>();
}
// else
return null;
}

public static void SetIcmpAddress(this Endpoint endpoint, string? address)
{
EnsureIcmp(endpoint);

var json = (string.IsNullOrWhiteSpace(endpoint.JsonConfig) ? "{}" : endpoint.JsonConfig);
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json, JsonSettings) ?? throw new ArgumentException("Unable to deserialize JSON settings for endpoint");
obj.Address = address;
endpoint.JsonConfig = JsonConvert.SerializeObject(obj, JsonSettings);

var nodeObj = JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException($"Unable to deserialize Address");
nodeObj.Remove(IcmpAddressKey);
nodeObj.Add(IcmpAddressKey, JsonValue.Create(address));
endpoint.JsonConfig = nodeObj.ToJsonString();
}

private static void EnsureIcmp(Endpoint endpoint)
Expand Down
22 changes: 22 additions & 0 deletions sama/HttpHandlerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Net.Http;
using System.Net.Security;

namespace sama;

public class HttpHandlerFactory
{
public virtual HttpMessageHandler Create(bool allowAutoRedirect, SslClientAuthenticationOptions? sslOptions)
{
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.Zero,
AllowAutoRedirect = allowAutoRedirect,
};
if (sslOptions != null)
{
handler.SslOptions = sslOptions;
}
return handler;
}
}
2 changes: 1 addition & 1 deletion sama/Services/GraphiteNotificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public virtual void NotifySingleResult(Endpoint endpoint, EndpointCheckResult re
}
catch (Exception ex)
{
_logger.LogError(0, "Unable to send Graphite notification", ex);
_logger.LogError(ex, "Unable to send Graphite notification");
}
}

Expand Down
Loading
Loading