inti
This commit is contained in:
122
Terribledev.PullForwardCache/Program.cs
Normal file
122
Terribledev.PullForwardCache/Program.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddMemoryCache();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
string cachePath = Environment.GetEnvironmentVariable("CACHE_PATH") ?? "cache";
|
||||
if (!Directory.Exists(cachePath))
|
||||
{
|
||||
Directory.CreateDirectory(cachePath);
|
||||
}
|
||||
|
||||
app.MapGet("/{url}", async (string url, IHttpClientFactory clientFactory, IMemoryCache memoryCache) =>
|
||||
{
|
||||
var client = clientFactory.CreateClient();
|
||||
string encodedUrl = WebUtility.UrlEncode(url);
|
||||
string filePath = Path.Combine(cachePath, encodedUrl);
|
||||
string metadataPath = Path.Combine(cachePath, $"{encodedUrl}.json");
|
||||
|
||||
if (memoryCache.TryGetValue(encodedUrl, out FileMetadata? mdFromCache) && DateTime.Now <= mdFromCache?.Expiry)
|
||||
{
|
||||
return Results.File(filePath, mdFromCache.ContentType ?? "application/octet-stream");
|
||||
}
|
||||
|
||||
if (File.Exists(filePath) && File.Exists(metadataPath))
|
||||
{
|
||||
var metadata = JsonSerializer.Deserialize<FileMetadata>(await File.ReadAllTextAsync(metadataPath)) ?? new FileMetadata();
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
if (!string.IsNullOrEmpty(metadata.ETag))
|
||||
{
|
||||
request.Headers.IfNoneMatch.Add(EntityTagHeaderValue.Parse(metadata.ETag));
|
||||
}
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
metadata.CachedAt = DateTime.Now;
|
||||
metadata.Expiry = DateTime.Now.AddHours(24);
|
||||
await WriteMetadataAsync(metadataPath, metadata, memoryCache, encodedUrl);
|
||||
return Results.File(filePath, metadata.ContentType ?? "application/octet-stream");
|
||||
}
|
||||
else if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await SaveAndReturnFile(response, filePath, metadataPath, memoryCache, encodedUrl, url);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return Results.StatusCode((int)response.StatusCode);
|
||||
}
|
||||
|
||||
return await SaveAndReturnFile(response, filePath, metadataPath, memoryCache, encodedUrl, url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem(ex.Message, statusCode: 500);
|
||||
}
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the response content to disk and updates the metadata.
|
||||
/// </summary>
|
||||
async Task<IResult> SaveAndReturnFile(HttpResponseMessage response, string filePath, string metadataPath, IMemoryCache memoryCache, string encodedUrl, string url)
|
||||
{
|
||||
var content = await response.Content.ReadAsByteArrayAsync();
|
||||
var contentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
|
||||
var eTag = response.Headers.ETag?.ToString();
|
||||
|
||||
await File.WriteAllBytesAsync(filePath, content);
|
||||
|
||||
var metadata = new FileMetadata
|
||||
{
|
||||
Url = url,
|
||||
ContentType = contentType,
|
||||
CachedAt = DateTime.Now,
|
||||
Expiry = DateTime.Now.AddHours(24),
|
||||
ETag = eTag
|
||||
};
|
||||
|
||||
await WriteMetadataAsync(metadataPath, metadata, memoryCache, encodedUrl);
|
||||
return Results.File(filePath, contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes metadata to disk and caches it in memory.
|
||||
/// </summary>
|
||||
async Task WriteMetadataAsync(string metadataPath, FileMetadata metadata, IMemoryCache memoryCache, string cacheKey)
|
||||
{
|
||||
await File.WriteAllTextAsync(metadataPath, JsonSerializer.Serialize(metadata));
|
||||
memoryCache.Set(cacheKey, metadata, TimeSpan.FromMinutes(15));
|
||||
}
|
||||
|
||||
public class FileMetadata
|
||||
{
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string? ContentType { get; set; }
|
||||
public DateTime CachedAt { get; set; }
|
||||
public DateTime Expiry { get; set; }
|
||||
public string? ETag { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user