Created
November 17, 2019 07:41
-
-
Save dotMorten/e86178f289c7f13bc759dc3cc5aa5583 to your computer and use it in GitHub Desktop.
Quick and dirty implementation of etag and cache control: Note: threading issues! Don't use as is
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Net.Http; | |
using System.Security.Cryptography; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace HttpCacheClient | |
{ | |
public class CacheHttpHandler : DelegatingHandler | |
{ | |
private readonly string foldername; | |
public CacheHttpHandler(string foldername = "cache") : base(new SocketsHttpHandler()) | |
{ | |
this.foldername = foldername; | |
if (!Directory.Exists(foldername)) | |
Directory.CreateDirectory(foldername); | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
base.InnerHandler.Dispose(); | |
base.Dispose(disposing); | |
} | |
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
bool isCacheable = (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head) && | |
request.Headers.Authorization == null && request.Content == null; | |
string etag = null; | |
string cachefile = null; | |
if (isCacheable) | |
{ | |
var hash = MD5Hash(request.RequestUri.OriginalString); | |
cachefile = Path.Combine(foldername, hash); | |
if (File.Exists(cachefile)) | |
{ | |
long expires; | |
using (var fs = File.OpenRead(cachefile +".dat")) | |
{ | |
using(var br = new BinaryReader(fs)) | |
{ | |
expires = br.ReadInt64(); | |
etag = br.ReadString(); | |
} | |
} | |
if (new DateTime(expires) > DateTime.UtcNow) | |
{ | |
var resp = new HttpResponseMessage(System.Net.HttpStatusCode.OK); | |
resp.Content = new StreamContent(File.OpenRead(cachefile)); | |
return resp; | |
} | |
if(string.IsNullOrEmpty(etag)) | |
{ | |
File.Delete(cachefile + ".dat"); | |
File.Delete(cachefile); | |
etag = null; | |
} | |
else | |
{ | |
request.Headers.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue(etag)); | |
} | |
} | |
} | |
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); | |
if (!string.IsNullOrEmpty(etag)) | |
{ | |
if (response.StatusCode == System.Net.HttpStatusCode.NotModified) | |
{ | |
WriteMetadata(response, cachefile); //update metadata | |
response.StatusCode = System.Net.HttpStatusCode.OK; | |
response.Content = new StreamContent(File.OpenRead(cachefile)); | |
return response; | |
} | |
else | |
{ | |
File.Delete(cachefile); | |
File.Delete(cachefile + ".dat"); | |
} | |
} | |
if (isCacheable && response.StatusCode == System.Net.HttpStatusCode.OK) | |
{ | |
bool canCache = response.Headers.ETag?.Tag != null || response.Headers.CacheControl != null && | |
!response.Headers.CacheControl.NoCache && | |
!response.Headers.CacheControl.NoStore && | |
response.Headers.CacheControl.Public; | |
if(canCache) | |
{ | |
using (var fs = File.Create(cachefile)) | |
{ | |
await response.Content.CopyToAsync(fs).ConfigureAwait(false); | |
response.Content.Dispose(); | |
} | |
WriteMetadata(response, cachefile); | |
response.Content = new StreamContent(File.OpenRead(cachefile)); | |
} | |
} | |
return response; | |
} | |
private static void WriteMetadata(HttpResponseMessage response, string file) | |
{ | |
using (var fs = File.Create(file +".dat")) | |
{ | |
using (BinaryWriter bw = new BinaryWriter(fs)) | |
{ | |
bw.Write(response.Headers.CacheControl.MaxAge.HasValue ? DateTime.UtcNow.Add(response.Headers.CacheControl.MaxAge.Value).Ticks : 0L); | |
bw.Write(response.Headers.ETag?.Tag ?? string.Empty); | |
} | |
} | |
} | |
private static MD5CryptoServiceProvider md5provider = new MD5CryptoServiceProvider(); | |
private static string MD5Hash(string input) | |
{ | |
StringBuilder hash = new StringBuilder(); | |
byte[] bytes = md5provider.ComputeHash(new UTF8Encoding().GetBytes(input)); | |
for (int i = 0; i < bytes.Length; i++) | |
{ | |
hash.Append(bytes[i].ToString("x2")); | |
} | |
return hash.ToString(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment