11using Microsoft . AspNetCore . Builder ;
22using Microsoft . AspNetCore . Http ;
3+ using System . Collections . Concurrent ;
34using System . Diagnostics ;
45using System . Linq ;
56using System . Net . Http ;
@@ -10,6 +11,47 @@ namespace TechStacks;
1011
1112public 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