From a96c3977f659172692f03edd281810b80c825abd Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 9 Dec 2017 17:02:04 -0800 Subject: [PATCH] Add an Etag to client.js --- Ooui.AspNetCore/OouiMiddlewareExtensions.cs | 19 ++++++--- Ooui/UI.cs | 43 ++++++++++++++++++--- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Ooui.AspNetCore/OouiMiddlewareExtensions.cs b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs index deaa51e..0d78459 100644 --- a/Ooui.AspNetCore/OouiMiddlewareExtensions.cs +++ b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs @@ -30,12 +30,19 @@ namespace Microsoft.AspNetCore.Builder if (context.Request.Path == jsPath) { var response = context.Response; var clientJsBytes = Ooui.UI.ClientJsBytes; - response.StatusCode = 200; - response.ContentLength = clientJsBytes.Length; - response.ContentType = "application/javascript; charset=utf-8"; - response.Headers.Add ("Cache-Control", "public, max-age=3600"); - using (var s = response.Body) { - await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false); + var clientJsEtag = Ooui.UI.ClientJsEtag; + if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == clientJsEtag) { + response.StatusCode = 304; + } + else { + response.StatusCode = 200; + response.ContentLength = clientJsBytes.Length; + response.ContentType = "application/javascript; charset=utf-8"; + response.Headers.Add ("Cache-Control", "public, max-age=60"); + response.Headers.Add ("Etag", clientJsEtag); + using (var s = response.Body) { + await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false); + } } } else if (context.Request.Path == WebSocketHandler.WebSocketPath) { diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 6b2faee..c8e0dd7 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -14,6 +14,9 @@ namespace Ooui { static readonly ManualResetEvent started = new ManualResetEvent (false); + [ThreadStatic] + static System.Security.Cryptography.SHA256 sha256; + static CancellationTokenSource serverCts; static readonly Dictionary publishedPaths = @@ -26,8 +29,10 @@ namespace Ooui public static StyleSelectors Styles => rules; static readonly byte[] clientJsBytes; + static readonly string clientJsEtag; public static byte[] ClientJsBytes => clientJsBytes; + public static string ClientJsEtag => clientJsEtag; public static string Template { get; set; } = $@" @@ -79,6 +84,22 @@ namespace Ooui clientJsBytes = Encoding.UTF8.GetBytes (r.ReadToEnd ()); } } + clientJsEtag = "\"" + Hash (clientJsBytes) + "\""; + } + + static string Hash (byte[] bytes) + { + var sha = sha256; + if (sha == null) { + sha = System.Security.Cryptography.SHA256.Create (); + sha256 = sha; + } + var data = sha.ComputeHash (bytes); + StringBuilder sBuilder = new StringBuilder (); + for (int i = 0; i < data.Length; i++) { + sBuilder.Append (data[i].ToString ("x2")); + } + return sBuilder.ToString (); } static void Publish (string path, RequestHandler handler) @@ -232,12 +253,22 @@ namespace Ooui var response = listenerContext.Response; if (path == "/ooui.js") { - response.ContentLength64 = clientJsBytes.LongLength; - response.ContentType = "application/javascript"; - response.ContentEncoding = Encoding.UTF8; - response.AddHeader ("Cache-Control", "public, max-age=3600"); - using (var s = response.OutputStream) { - s.Write (clientJsBytes, 0, clientJsBytes.Length); + var inm = listenerContext.Request.Headers.Get ("If-None-Match"); + if (string.IsNullOrEmpty (inm) || inm != clientJsEtag) { + response.StatusCode = 200; + response.ContentLength64 = clientJsBytes.LongLength; + response.ContentType = "application/javascript"; + response.ContentEncoding = Encoding.UTF8; + response.AddHeader ("Cache-Control", "public, max-age=60"); + response.AddHeader ("Etag", clientJsEtag); + using (var s = response.OutputStream) { + s.Write (clientJsBytes, 0, clientJsBytes.Length); + } + response.Close (); + } + else { + response.StatusCode = 304; + response.Close (); } } else {