Skip to content

Commit 357ae2d

Browse files
committed
Cache static files in memory to skip proxy hop
1 parent 7c0027f commit 357ae2d

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

TechStacks/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
"/swagger",
148148
]);
149149

150-
//app.UseStaticFiles(); // All static assets are now served by Next.js
150+
app.UseStaticFiles(); // static assets are served by Next.js
151151
app.UseCookiePolicy();
152152
app.UseCors();
153153

TechStacks/Proxy.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.AspNetCore.Builder;
22
using Microsoft.AspNetCore.Http;
3+
using System.Collections.Concurrent;
34
using System.Diagnostics;
45
using System.Linq;
56
using System.Net.Http;
@@ -10,6 +11,47 @@ namespace TechStacks;
1011

1112
public static class Proxy
1213
{
14+
public static string[] CacheFileExtensions = [
15+
".js",
16+
".css",
17+
".ico",
18+
".png",
19+
".jpg",
20+
".jpeg",
21+
".gif",
22+
".svg",
23+
".woff",
24+
".woff2",
25+
".ttf",
26+
".eot",
27+
".otf",
28+
".map"
29+
];
30+
31+
public static Func<HttpContext, bool> ShouldCache = context =>
32+
{
33+
// Ignore Cache-Control headers
34+
if (context.Request.Headers.TryGetValue("Cache-Control", out var cacheControlValues))
35+
{
36+
return false;
37+
}
38+
39+
var path = context.Request.Path.Value ?? string.Empty;
40+
if (path.Length > 0)
41+
{
42+
foreach (var ext in CacheFileExtensions)
43+
{
44+
if (path.EndsWith(ext, StringComparison.OrdinalIgnoreCase))
45+
{
46+
return true;
47+
}
48+
}
49+
}
50+
return false;
51+
};
52+
53+
public static ConcurrentDictionary<string, (string mimeType, byte[] data)> Cache { get; } = new();
54+
1355
public static bool TryStartNode(string workingDirectory, out Process process, string logPrefix="[node]")
1456
{
1557
process = new Process
@@ -62,6 +104,14 @@ static bool IsHopByHopHeader(string headerName)
62104
public static async Task HttpToNode(HttpContext context, HttpClient nextClient)
63105
{
64106
var request = context.Request;
107+
var cacheKey = request.Path.Value ?? string.Empty;
108+
if (Cache.TryGetValue(cacheKey, out var cached))
109+
{
110+
Console.WriteLine("Cache hit: " + cacheKey);
111+
context.Response.ContentType = cached.mimeType;
112+
await context.Response.Body.WriteAsync(cached.data, context.RequestAborted);
113+
return;
114+
}
65115

66116
// Build relative URI (path + query)
67117
var path = request.Path.HasValue ? request.Path.Value : "/";
@@ -115,7 +165,19 @@ public static async Task HttpToNode(HttpContext context, HttpClient nextClient)
115165
// ASP.NET Core will set its own transfer-encoding
116166
context.Response.Headers.Remove("transfer-encoding");
117167

118-
await response.Content.CopyToAsync(context.Response.Body, context.RequestAborted);
168+
if (context.Response.StatusCode == StatusCodes.Status200OK && ShouldCache(context))
169+
{
170+
var bytes = await response.Content.ReadAsByteArrayAsync();
171+
var mimeType = response.Content.Headers.ContentType?.ToString()
172+
?? ServiceStack.MimeTypes.GetMimeType(cacheKey);
173+
Cache[cacheKey] = (mimeType, bytes);
174+
Console.WriteLine("Cache miss: " + cacheKey + " |size| " + bytes.Length);
175+
await context.Response.Body.WriteAsync(bytes, context.RequestAborted);
176+
}
177+
else
178+
{
179+
await response.Content.CopyToAsync(context.Response.Body, context.RequestAborted);
180+
}
119181
}
120182

121183
public static void MapNotFoundToNode(WebApplication app, HttpClient nextClient, string[] ignorePaths)

0 commit comments

Comments
 (0)