6
6
using System . Collections . Concurrent ;
7
7
using System . Runtime . InteropServices ;
8
8
using System ;
9
+ using System . Security . Cryptography ;
9
10
10
11
namespace HttpServer
11
12
{
@@ -16,11 +17,13 @@ public sealed class Session
16
17
public int Finished { get ; set ; } = 0 ;
17
18
}
18
19
20
+ public sealed record FileContent ( byte [ ] buffer , string hash ) ;
21
+
19
22
public sealed class Program
20
23
{
21
24
private bool Verbose = false ;
22
25
private ConcurrentDictionary < string , Session > Sessions = new ConcurrentDictionary < string , Session > ( ) ;
23
- private Dictionary < string , byte [ ] > cache = new Dictionary < string , byte [ ] > ( StringComparer . OrdinalIgnoreCase ) ;
26
+ private Dictionary < string , FileContent > cache = new ( StringComparer . OrdinalIgnoreCase ) ;
24
27
25
28
public static int Main ( )
26
29
{
@@ -104,7 +107,7 @@ private void HandleRequest(HttpListener listener)
104
107
ReceivePostAsync ( context ) ;
105
108
}
106
109
107
- private async Task < byte [ ] ? > GetFileContent ( string path )
110
+ private async Task < FileContent ? > GetFileContent ( string path )
108
111
{
109
112
if ( Verbose )
110
113
await Console . Out . WriteLineAsync ( $ "get content for: { path } ") ;
@@ -124,9 +127,13 @@ private void HandleRequest(HttpListener listener)
124
127
if ( Verbose )
125
128
await Console . Out . WriteLineAsync ( $ "adding content to cache for: { path } ") ;
126
129
127
- cache [ path ] = content ;
130
+ using HashAlgorithm hashAlgorithm = SHA256 . Create ( ) ;
131
+ byte [ ] hash = hashAlgorithm . ComputeHash ( content ) ;
132
+ var fc = new FileContent ( content , "sha256-" + Convert . ToBase64String ( hash ) ) ;
133
+
134
+ cache [ path ] = fc ;
128
135
129
- return content ;
136
+ return fc ;
130
137
}
131
138
132
139
private async void ReceivePostAsync ( HttpListenerContext context )
@@ -189,14 +196,14 @@ private async void ServeAsync(HttpListenerContext context)
189
196
else if ( path . StartsWith ( "/" ) )
190
197
path = path . Substring ( 1 ) ;
191
198
192
- byte [ ] ? buffer ;
199
+ FileContent ? fc ;
193
200
try
194
201
{
195
- buffer = await GetFileContent ( path ) ;
202
+ fc = await GetFileContent ( path ) ;
196
203
197
- if ( buffer != null && throttleMbps > 0 )
204
+ if ( fc != null && throttleMbps > 0 )
198
205
{
199
- double delaySeconds = ( buffer . Length * 8 ) / ( throttleMbps * 1024 * 1024 ) ;
206
+ double delaySeconds = ( fc . buffer . Length * 8 ) / ( throttleMbps * 1024 * 1024 ) ;
200
207
int delayMs = ( int ) ( delaySeconds * 1000 ) ;
201
208
if ( session != null )
202
209
{
@@ -246,12 +253,20 @@ private async void ServeAsync(HttpListenerContext context)
246
253
}
247
254
}
248
255
}
249
- catch ( Exception )
256
+ catch ( System . IO . DirectoryNotFoundException )
257
+ {
258
+ if ( Verbose )
259
+ Console . WriteLine ( $ "Not found: { path } ") ;
260
+ fc = null ;
261
+ }
262
+ catch ( Exception ex )
250
263
{
251
- buffer = null ;
264
+ if ( Verbose )
265
+ Console . WriteLine ( $ "Exception: { ex } ") ;
266
+ fc = null ;
252
267
}
253
268
254
- if ( buffer != null )
269
+ if ( fc != null )
255
270
{
256
271
string ? contentType = null ;
257
272
if ( path . EndsWith ( ".wasm" ) )
@@ -270,7 +285,7 @@ private async void ServeAsync(HttpListenerContext context)
270
285
{
271
286
Console . WriteLine ( "Faking 500 " + url ) ;
272
287
context . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
273
- await stream . WriteAsync ( buffer , 0 , 0 ) . ConfigureAwait ( false ) ;
288
+ await stream . WriteAsync ( fc . buffer , 0 , 0 ) . ConfigureAwait ( false ) ;
274
289
await stream . FlushAsync ( ) ;
275
290
context . Response . Close ( ) ;
276
291
return ;
@@ -279,25 +294,36 @@ private async void ServeAsync(HttpListenerContext context)
279
294
if ( contentType != null )
280
295
context . Response . ContentType = contentType ;
281
296
282
- context . Response . ContentLength64 = buffer . Length ;
283
- context . Response . AppendHeader ( "cache-control" , "public, max-age=31536000" ) ;
297
+ // context.Response.AppendHeader("cache-control", "public, max-age=31536000");
284
298
context . Response . AppendHeader ( "Cross-Origin-Embedder-Policy" , "require-corp" ) ;
285
299
context . Response . AppendHeader ( "Cross-Origin-Opener-Policy" , "same-origin" ) ;
300
+ context . Response . AppendHeader ( "ETag" , fc . hash ) ;
286
301
287
302
// test download re-try
288
303
if ( url . Query . Contains ( "testAbort" ) )
289
304
{
290
305
Console . WriteLine ( "Faking abort " + url ) ;
291
- await stream . WriteAsync ( buffer , 0 , 10 ) . ConfigureAwait ( false ) ;
306
+ context . Response . ContentLength64 = fc . buffer . Length ;
307
+ await stream . WriteAsync ( fc . buffer , 0 , 10 ) . ConfigureAwait ( false ) ;
292
308
await stream . FlushAsync ( ) ;
293
309
await Task . Delay ( 100 ) ;
294
310
context . Response . Abort ( ) ;
295
311
return ;
296
312
}
313
+ var ifNoneMatch = context . Request . Headers . Get ( "If-None-Match" ) ;
314
+ if ( ifNoneMatch == fc . hash )
315
+ {
316
+ context . Response . StatusCode = 304 ;
317
+ await stream . FlushAsync ( ) ;
318
+ stream . Close ( ) ;
319
+ context . Response . Close ( ) ;
320
+ return ;
321
+ }
297
322
298
323
try
299
324
{
300
- await stream . WriteAsync ( buffer ) . ConfigureAwait ( false ) ;
325
+ context . Response . ContentLength64 = fc . buffer . Length ;
326
+ await stream . WriteAsync ( fc . buffer ) . ConfigureAwait ( false ) ;
301
327
}
302
328
catch ( Exception e )
303
329
{
0 commit comments