Compare commits

...

21 Commits

Author SHA1 Message Date
Jeff Leung 5cad97c9ac Move more constant texts in a constant string 2024-09-10 20:20:45 -07:00
Jeff Leung cc61b02959 Update README for extensbility hints. 2024-02-27 21:14:37 -08:00
Jeff Leung 0dfe68656d Merge branch 'master' of ssh://git.startmywifi.com:2251/AS1024/GeoFeed 2024-02-27 20:53:28 -08:00
Jeff Leung 6544dc5a3d Some refactoring here 2024-02-27 20:53:18 -08:00
Jeff Leung 08e518c340 Remove reference to musl x64 as this was breaking multiarch builds 2024-02-27 20:27:46 -08:00
Jeff Leung e82d198ee7 Merge branch 'master' of ssh://git.startmywifi.com:2251/AS1024/GeoFeed 2024-02-27 20:17:36 -08:00
Jeff Leung 4c6a9e6dec Add /tmp 2024-02-27 20:16:39 -08:00
Jeff Leung feb1326cee Add bare image option 2024-02-27 19:29:32 -08:00
Jeff Leung 626ab99888 Add comment on example YAML file 2024-02-27 16:52:25 -08:00
Jeff Leung 9c8189bc33 Fix port to 8080 for Caddy reverse proxy 2024-02-27 16:51:15 -08:00
Jeff Leung 956aa8b3b0 Add example docker compose files to the repository 2024-02-27 16:49:43 -08:00
Jeff Leung 7d5b2a7aac Mark the same for the aot minimal version 2024-02-27 15:38:31 -08:00
Jeff Leung 5f089fb01f Use a stream instead of putting the entire string in memory 2024-02-27 15:30:13 -08:00
Jeff Leung af3cc81180 Code cleanup 2024-01-18 19:07:18 -08:00
Jeff Leung e0189381f1 Rename the GetGeofeed Method to Async 2024-01-18 16:15:41 -08:00
Jeff Leung 3a56e2426e Move geofeed web logic to a seperate project as core lib as asp.net core project is causing weird issues 2024-01-18 16:15:04 -08:00
Jeff Leung 0c3edd64f3 Unify logic of generating the webside unlike a different version we had for MVC and Minimal API 2024-01-18 15:57:01 -08:00
Jeff Leung 2d95fcb1a1 Fix preloading issues 2024-01-16 23:45:33 -08:00
Jeff Leung 7c952c134a Preload GeoFeed on start 2024-01-16 13:16:33 -08:00
Jeff Leung 7721dfb669 Update the sqlite provider to say it was retrieved from the persistent cache 2024-01-16 13:06:16 -08:00
Jeff Leung c1ed05a335 Append when the GeoFeed is retrieved and if it was generated from in memory cache 2024-01-16 13:05:58 -08:00
25 changed files with 248 additions and 176 deletions

View File

@ -1,8 +1,8 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using AS1024.GeoFeed.Core.Tools;
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
{
@ -58,7 +58,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
List<IPGeoFeed> cachedData = [];
results.ForEach(cachedData.Add);
return cachedData.ToGeoFeedCsv();
return cachedData.ToGeoFeedCsv(true, true);
}
private async Task DBContextMigrate()

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,72 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Text;
namespace AS1024.GeoFeed.Core.WebLogic
{
public class GeoFeedReturn
{
private const string GeoFeedCacheKey = "GeoFeedData";
private const string GeoFeedMimeTypeReturn = "text/csv";
private const string GeoFeedFileName = "geofeed.csv";
private readonly IGeoFeedProvider provider;
private readonly ILogger<GeoFeedReturn> logger;
private readonly IGeoFeedPersistentCacheProvider cacheProvider;
private readonly IMemoryCache memoryCache;
private readonly IWebHostEnvironment environment;
public GeoFeedReturn(IGeoFeedProvider provider,
ILogger<GeoFeedReturn> logger,
IGeoFeedPersistentCacheProvider cacheProvider,
IMemoryCache memoryCache,
IWebHostEnvironment environment)
{
this.provider = provider;
this.logger = logger;
this.cacheProvider = cacheProvider;
this.memoryCache = memoryCache;
this.environment = environment;
}
public async Task<IResult> GetGeoFeed()
{
bool isCached = true;
try
{
if (!memoryCache.TryGetValue(GeoFeedCacheKey, out List<IPGeoFeed>? feed))
{
isCached = false;
feed = await provider.GetGeoFeedDataAsync();
if (environment.IsProduction())
{
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(15));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}
}
return Results.File(Encoding.UTF8.GetBytes(feed.ToGeoFeedCsv(true, isCached)),
GeoFeedMimeTypeReturn,
GeoFeedFileName);
}
catch (HttpRequestException ex)
{
logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}");
string geoFeedData = cacheProvider.GetGeoFeed();
return Results.File(Encoding.UTF8.GetBytes(geoFeedData),
GeoFeedMimeTypeReturn,
GeoFeedFileName);
}
catch (Exception ex)
{
logger.LogError($"Error: {ex}");
}
return Results.NoContent();
}
}
}

View File

@ -0,0 +1,12 @@
{
"profiles": {
"AS1024.GeoFeed.Core.WebLogic": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:54455;http://localhost:54456"
}
}
}

View File

@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>

View File

@ -39,10 +39,10 @@ namespace AS1024.GeoFeed.Core.CacheService
{
var scope = host.Services.CreateScope();
var persistentCacheProvider =
var persistentCacheProvider =
scope.ServiceProvider.GetRequiredService<IGeoFeedPersistentCacheProvider>();
var results = await feedProvider.GetGeoFeedData();
var results = await feedProvider.GetGeoFeedDataAsync();
await persistentCacheProvider.CacheGeoFeed(results);
}
catch (Exception)

View File

@ -1,13 +1,7 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace AS1024.GeoFeed.Core.CacheService

View File

@ -41,7 +41,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedPreloader
private async Task StartPreLoad()
{
logger.LogInformation("Preloading GeoFeed data in memory...");
List<IPGeoFeed> feed = await provider.GetGeoFeedData();
List<IPGeoFeed> feed = await provider.GetGeoFeedDataAsync();
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(45));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}

View File

@ -6,7 +6,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedProviders
{
public class NetBoxGeoFeedProvider : NetBoxGeoFeedProviderBase, IGeoFeedProvider
{
public NetBoxGeoFeedProvider(IConfiguration configuration, ILogger<NetBoxGeoFeedProvider> logger, IHttpClientFactory httpClientFactory)
public NetBoxGeoFeedProvider(IConfiguration configuration, ILogger<NetBoxGeoFeedProvider> logger, IHttpClientFactory httpClientFactory)
: base(configuration, logger, httpClientFactory)
{
}

View File

@ -1,11 +1,11 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Models;
using System.Text.Json;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Web;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text.Json;
using System.Web;
namespace AS1024.GeoFeed.Core.GeoFeedProviders
{
@ -31,7 +31,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedProviders
this.httpClientFactory = httpClientFactory;
}
public async Task<List<IPGeoFeed>> GetGeoFeedData()
public async Task<List<IPGeoFeed>> GetGeoFeedDataAsync()
{
List<IPGeoFeed> geoFeed = [];
using HttpClient client = httpClientFactory.CreateClient();
@ -52,7 +52,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedProviders
{
break;
}
string stringResult = await result.Content.ReadAsStringAsync();
var stringResult = await result.Content.ReadAsStreamAsync();
jsonData = DeserializeJsonData(stringResult);
if (jsonData?.Results == null || jsonData.Results.Count == 0)
@ -93,7 +93,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedProviders
return geoFeed;
}
protected virtual NetboxData? DeserializeJsonData(string stringResult)
protected virtual NetboxData? DeserializeJsonData(Stream stringResult)
{
return JsonSerializer.Deserialize<NetboxData>(stringResult, new JsonSerializerOptions
{

View File

@ -1,9 +1,4 @@
using AS1024.GeoFeed.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AS1024.GeoFeed.Core.Interfaces
{

View File

@ -5,6 +5,6 @@ namespace AS1024.GeoFeed.Core.Interfaces
public interface IGeoFeedProvider
{
public string GeoFeedProviderName { get; }
public Task<List<IPGeoFeed>> GetGeoFeedData();
public Task<List<IPGeoFeed>> GetGeoFeedDataAsync();
}
}

View File

@ -5,16 +5,35 @@ namespace AS1024.GeoFeed.Core.Tools
{
public static class GeoFeedTools
{
public static string ToGeoFeedCsv(this List<IPGeoFeed> geoFeeds)
/// <summary>
/// Returns a CSV string for a given GeoFeed retrieved from various sources.
/// </summary>
/// <param name="geoFeeds">GeoFeed returned from the source of truth.</param>
/// <param name="timeStamp">If a timestamp should be appended at the header.</param>
/// <param name="isCached">If the result is cached.</param>
/// <returns>CSV formatted string of GeoFeed data.</returns>
public static string ToGeoFeedCsv(this List<IPGeoFeed> geoFeeds, bool timeStamp = false, bool isCached = false)
{
if (geoFeeds == null) throw new ArgumentNullException(nameof(geoFeeds));
StringBuilder csvContent = new();
// Append timestamp header if required
if (timeStamp)
csvContent.AppendFormat("# GeoFeed generated on {0:R}\n", DateTime.UtcNow);
// Append cache status if required
if (isCached)
csvContent.AppendLine("# Geofeed data is returned from local in memory cache");
// Iterate over each GeoFeed entry to append its details to the CSV content
foreach (IPGeoFeed feed in geoFeeds)
{
csvContent.AppendLine($"{feed.Prefix},{feed.GeolocCountry},{feed.GeolocRegion},{feed.GeolocCity},{feed.GeolocPostalCode}");
// Using AppendFormat for a cleaner and more readable approach to constructing CSV lines
csvContent.AppendFormat("{0},{1},{2},{3},{4}\n", feed.Prefix, feed.GeolocCountry, feed.GeolocRegion, feed.GeolocCity, feed.GeolocPostalCode);
}
return csvContent.ToString();
}
}
}
}

View File

@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
</ItemGroup>

View File

@ -0,0 +1,24 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
# Install clang/zlib1g-dev dependencies for publishing to native
RUN apk add clang zlib-static zlib-dev musl-dev libc6-compat cmake autoconf make openssl-dev openssl-libs-static icu-static icu-dev
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj", "AS1024.GeoFeed.MinimalAPI/"]
RUN dotnet restore "./AS1024.GeoFeed.MinimalAPI/./AS1024.GeoFeed.MinimalAPI.csproj"
COPY . .
WORKDIR "/src/AS1024.GeoFeed.MinimalAPI"
RUN dotnet build "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:StaticOpenSslLinking=true /p:StaticExecutable=true /p:StaticallyLinked=true /p:StripSymbols=true /p:DebugType=None /p:DebugSymbols=false
FROM scratch AS final
WORKDIR /tmp
WORKDIR /app
EXPOSE 8080
COPY --from=publish /app/publish .
COPY --from=build /etc/ssl/certs/* /etc/ssl/certs/
ENTRYPOINT ["./AS1024.GeoFeed.MinimalAPI"]

View File

@ -14,7 +14,7 @@ namespace AS1024.GeoFeed.MinimalAPI
{
}
protected override NetboxData? DeserializeJsonData(string stringResult)
protected override NetboxData? DeserializeJsonData(Stream stringResult)
{
return JsonSerializer.Deserialize(stringResult, AppJsonSerializerContext.Default.NetboxData);
}

View File

@ -1,15 +1,7 @@
using AS1024.GeoFeed.Core.CacheService;
using AS1024.GeoFeed.Core.GeoFeedProviders;
using AS1024.GeoFeed.Core.GeoFeedPreloader;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Models;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Text;
using AS1024.GeoFeed.Core.WebLogic;
namespace AS1024.GeoFeed.MinimalAPI
{
@ -21,73 +13,29 @@ namespace AS1024.GeoFeed.MinimalAPI
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddTransient<IGeoFeedProvider, NetboxAoTGeoFeedProvider>();
builder.Services.AddHostedService<GeoFeedCacheService>();
builder.Services.AddHostedService<PreLoadGeoFeed>();
builder.Services.AddTransient<IGeoFeedPersistentCacheProvider, GeoFeedLocalFileCache>();
builder.Services.AddScoped<GeoFeedReturn>();
builder.Services.AddMemoryCache();
builder.Services.AddLogging();
builder.Services.AddHttpClient();
builder.Services.ConfigureHttpJsonOptions(options => {
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
var app = builder.Build();
app.Map("/geofeed.csv", async (IGeoFeedProvider provider,
ILogger<Program> logger,
IGeoFeedPersistentCacheProvider cacheProvider,
IMemoryCache memoryCache,
IWebHostEnvironment environment) => {
return await GeoFeedDataRunner(provider, logger, cacheProvider, memoryCache, environment);
});
app.Map("/geofeed.csv", async (GeoFeedReturn feedReturn) =>
{
return await feedReturn.GetGeoFeed();
});
app.Map("/geofeed", async (IGeoFeedProvider provider,
ILogger<Program> logger,
IGeoFeedPersistentCacheProvider cacheProvider,
IMemoryCache memoryCache,
IWebHostEnvironment environment) => {
return await GeoFeedDataRunner(provider, logger, cacheProvider, memoryCache, environment);
});
app.Map("/geofeed", async (GeoFeedReturn feedReturn) =>
{
return await feedReturn.GetGeoFeed();
});
app.Run();
}
protected static async Task<IResult> GeoFeedDataRunner(IGeoFeedProvider provider,
ILogger<Program> logger,
IGeoFeedPersistentCacheProvider cacheProvider,
IMemoryCache memoryCache,
IWebHostEnvironment environment)
{
try
{
if (!memoryCache.TryGetValue("Geofeed", out List<IPGeoFeed>? feed))
{
feed = await provider.GetGeoFeedData();
if (environment.IsProduction())
{
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(15));
memoryCache.Set("Geofeed", feed, cacheEntryOptions);
}
}
return Results.File(Encoding.UTF8.GetBytes(feed.ToGeoFeedCsv()),
"text/csv",
"geofeed.csv");
}
catch (HttpRequestException ex)
{
logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}");
string geoFeedData = cacheProvider.GetGeoFeed();
return Results.File(Encoding.UTF8.GetBytes(geoFeedData),
"text/csv",
"geofeed.csv");
}
catch (Exception ex)
{
logger.LogError($"Error: {ex}");
}
return Results.NoContent();
}
}
}

View File

@ -1,11 +1,10 @@
using System.Text.Json.Serialization;
namespace AS1024.GeoFeed.Models
namespace AS1024.GeoFeed.Models
{
public class NetboxData
{
public List<Result>? Results { get; set; }
public string? Next { get; set; }
public string? Previous { get; set; }
public string? Previous { get; set; }
}
public class Result
@ -41,7 +40,8 @@ namespace AS1024.GeoFeed.Models
/// <summary>
/// This class represents the IP GeoFeed Entry
/// </summary>
public class IPGeoFeed : CustomFields {
public class IPGeoFeed : CustomFields
{
/// <summary>
/// Represents the IP Prefix for the associated GeoFeed entry
/// </summary>

View File

@ -14,7 +14,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core", "AS10
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.MinimalAPI", "AS1024.GeoFeed.MinimalAPI\AS1024.GeoFeed.MinimalAPI.csproj", "{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.SqliteGeoFeedCache", "AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj", "{3459BB31-FA7A-44D1-872D-C5338ACFBF80}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core.SqliteGeoFeedCache", "AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj", "{3459BB31-FA7A-44D1-872D-C5338ACFBF80}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.WebLogic", "AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj", "{58BDCE89-FCC0-478F-BBDE-B89833712AAB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -42,6 +44,10 @@ Global
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.Build.0 = Release|Any CPU
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -23,6 +23,7 @@
<ItemGroup>
<ProjectReference Include="..\AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
</ItemGroup>

View File

@ -1,9 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using AS1024.GeoFeed.Core.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using AS1024.GeoFeed.Models;
using System.Text;
using AS1024.GeoFeed.Core.Tools;
using AS1024.GeoFeed.Core.WebLogic;
using Microsoft.AspNetCore.Mvc;
namespace AS1024.GeoFeed.Controllers
{
@ -13,75 +9,18 @@ namespace AS1024.GeoFeed.Controllers
public class GeofeedController : ControllerBase
{
private readonly IGeoFeedProvider builder;
private readonly IMemoryCache memoryCache;
private readonly IWebHostEnvironment environment;
private readonly ILogger<GeofeedController> logger;
private readonly IGeoFeedPersistentCacheProvider geoFeedPersistentCache;
private const string GeoFeedCacheKey = "GeoFeedData";
private readonly GeoFeedReturn feedReturn;
public GeofeedController(IGeoFeedProvider builder,
IMemoryCache memoryCache,
IWebHostEnvironment environment,
ILogger<GeofeedController> logger,
IGeoFeedPersistentCacheProvider geoFeedPersistentCache) {
this.logger = logger;
this.geoFeedPersistentCache = geoFeedPersistentCache;
this.builder = builder;
this.memoryCache = memoryCache;
this.environment = environment;
public GeofeedController(GeoFeedReturn feedReturn)
{
this.feedReturn = feedReturn;
}
[HttpGet]
[Route("")]
public async Task<IActionResult> Get()
public async Task<IResult> Get()
{
try
{
if (!memoryCache.TryGetValue(GeoFeedCacheKey, out List<IPGeoFeed>? feed))
{
feed = await builder.GetGeoFeedData();
if (environment.IsProduction())
{
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(15));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}
}
return ReturnFile(feed);
} catch (HttpRequestException ex)
{
logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}");
var cachedData = geoFeedPersistentCache.GetGeoFeed();
return ReturnFile(cachedData);
}
catch (Exception ex)
{
logger.LogError($"Geofeed generation failed. Exception: {ex}");
return StatusCode(500);
}
}
[NonAction]
private IActionResult ReturnFile(List<IPGeoFeed>? feed)
{
string csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format.
return ReturnFile(csvContent);
}
[NonAction]
private IActionResult ReturnFile(string csvContent)
{
byte[] contentBytes = Encoding.UTF8.GetBytes(csvContent);
string contentType = "text/csv";
return new FileContentResult(contentBytes, contentType)
{
FileDownloadName = "geofeed.csv"
};
return await feedReturn.GetGeoFeed();
}
}
}

View File

@ -1,9 +1,10 @@
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.CacheService;
using AS1024.GeoFeed.Core.GeoFeedPreloader;
using AS1024.GeoFeed.Core.GeoFeedProviders;
using Microsoft.EntityFrameworkCore;
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
using AS1024.GeoFeed.Core.CacheService;
using AS1024.GeoFeed.Core.Interfaces;
using AS1024.GeoFeed.Core.WebLogic;
using Microsoft.EntityFrameworkCore;
namespace AS1024.GeoFeed
{
@ -15,6 +16,7 @@ namespace AS1024.GeoFeed
builder.Services.AddHostedService<PreLoadGeoFeed>();
builder.Services.AddTransient<IGeoFeedProvider, NetBoxGeoFeedProvider>();
builder.Services.AddScoped<GeoFeedReturn>();
builder.Services.AddDbContext<GeoFeedCacheDbContext>(
options =>
{

View File

@ -107,8 +107,10 @@ The application provides the following key endpoints:
## Security and Compliance
This application is designed to always communicate over HTTPS with NetBox, ensuring that the data transfer is encrypted and secure.
This application is designed to always communicate over HTTPS with NetBox, ensuring that the data transfer is encrypted and secure.
---
## Extending Beyond NetBox
For more information about configuring and using this application, please refer to the official .NET documentation and the NetBox API guide.
If your current IPAM solution is not netbox and wish to extend this web application to use the desired IPAM solution of choice, the interface `IGeoFeedProvider` is available for extensibility. To use your custom IPAM backend ensure that `NetboxAoTGeoFeedProvider` and `NetboxGeoFeedProvider` are not registered in the dependency injection container in the Web Apps. Once unregistered, register your custom IPAM communication backend provider to your needs and the web app should work in both AOT and MVC mode.
Currently the Minimal API implementation of this web application only supports code that does not require reflection. This is a known limitation of native AOT deployments. If your code utilizes reflection or is not properly adapted for source generation, the minimal API version will **not work**.

14
docker/Caddyfile Normal file
View File

@ -0,0 +1,14 @@
{$GEOFEEDDOMAIN}:443 {
log {
level INFO
output file {$LOG_FILE} {
roll_size 10MB
roll_keep 10
}
}
# Use the ACME HTTP-01 challenge to get a cert for the configured domain.
tls {$EMAIL}
reverse_proxy geofeed:8080
}

28
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,28 @@
version: '3.7'
services:
geofeed:
# use the image tag aot-minimal for the AOT version for fastest startup performance
image: git.startmywifi.com/as1024/geofeed:latest
restart: always
volumes:
- './data:/data'
environment:
- ASPNETCORE_URLS=http://+:8080
- ConnectionString__LocalFeedCache=Data Source=/data/geofeed-cache.db
- APIKey=APIKeyHere
- NetBoxHost=netboxhosthere
caddy:
image: caddy:2
restart: always
ports:
- 80:80
- 443:443
- 443:443/udp
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-config:/config
- ./caddy-data:/data
environment:
GEOFEEDDOMAIN: "https://geofeed.exampleas.net"
EMAIL: "noc@example.com" # The email address to use for ACME registration.
LOG_FILE: "/data/access.log"