Added cache breaking based on hash rather than version

This commit is contained in:
kayone 2014-08-31 00:40:23 -07:00
parent dbcabd6df6
commit c4e8e3ebc7
15 changed files with 90 additions and 31 deletions

View File

@ -19,7 +19,7 @@ namespace NzbDrone.Api.Frontend
return false; return false;
} }
if (context.Request.Query.v == BuildInfo.Version) return true; if (((DynamicDictionary)context.Request.Query).ContainsKey("h")) return true;
if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase)) if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase))
{ {

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Crypto;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Frontend.Mappers
{
public interface ICacheBreakerProvider
{
string AddCacheBreakerToPath(string resourceUrl);
}
public class CacheBreakerProvider : ICacheBreakerProvider
{
private readonly IEnumerable<IMapHttpRequestsToDisk> _diskMappers;
private readonly IHashProvider _hashProvider;
public CacheBreakerProvider(IEnumerable<IMapHttpRequestsToDisk> diskMappers, IHashProvider hashProvider)
{
_diskMappers = diskMappers;
_hashProvider = hashProvider;
}
public string AddCacheBreakerToPath(string resourceUrl)
{
if (!ShouldBreakCache(resourceUrl))
{
return resourceUrl;
}
var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl));
var pathToFile = mapper.Map(resourceUrl);
var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64();
return resourceUrl + "?h=" + hash;
}
private static bool ShouldBreakCache(string path)
{
return !path.EndsWith(".ics") && !path.EndsWith("main");
}
}
}

View File

@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = Path.Combine("Content", "Images", "favicon.ico"); var path = Path.Combine("Content", "Images", "favicon.ico");

View File

@ -5,6 +5,7 @@ namespace NzbDrone.Api.Frontend.Mappers
{ {
public interface IMapHttpRequestsToDisk public interface IMapHttpRequestsToDisk
{ {
string Map(string resourceUrl);
bool CanHandle(string resourceUrl); bool CanHandle(string resourceUrl);
Response GetResponse(string resourceUrl); Response GetResponse(string resourceUrl);
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Nancy; using Nancy;
using NLog; using NLog;
@ -12,26 +13,31 @@ namespace NzbDrone.Api.Frontend.Mappers
public class IndexHtmlMapper : StaticResourceMapperBase public class IndexHtmlMapper : StaticResourceMapperBase
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath; private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static String API_KEY; private static String API_KEY;
private static String URL_BASE; private static String URL_BASE;
private string _generatedContent
;
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
Func<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger) Logger logger)
: base(diskProvider, logger) : base(diskProvider, logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_cacheBreakProviderFactory = cacheBreakProviderFactory;
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html"); _indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
API_KEY = configFileProvider.ApiKey; API_KEY = configFileProvider.ApiKey;
URL_BASE = configFileProvider.UrlBase; URL_BASE = configFileProvider.UrlBase;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
return _indexPath; return _indexPath;
} }
@ -54,28 +60,31 @@ namespace NzbDrone.Api.Frontend.Mappers
var text = GetIndexText(); var text = GetIndexText();
var stream = new MemoryStream(); var stream = new MemoryStream();
using (var writer = new StreamWriter(stream)) var writer = new StreamWriter(stream);
{
writer.Write(text); writer.Write(text);
writer.Flush(); writer.Flush();
}
stream.Position = 0; stream.Position = 0;
return stream; return stream;
} }
private string GetIndexText() private string GetIndexText()
{ {
var text = _diskProvider.ReadAllText(_indexPath); if (RuntimeInfoBase.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
text = ReplaceRegex.Replace(text, match => URL_BASE + match.Value); _generatedContent = _diskProvider.ReadAllText(_indexPath);
text = text.Replace(".css", ".css?v=" + BuildInfo.Version); var cacheBreakProvider = _cacheBreakProviderFactory();
text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
text = text.Replace("API_ROOT", URL_BASE + "/api");
text = text.Replace("API_KEY", API_KEY);
text = text.Replace("APP_VERSION", BuildInfo.Version.ToString());
return text; _generatedContent = ReplaceRegex.Replace(_generatedContent, match => cacheBreakProvider.AddCacheBreakerToPath(URL_BASE + match.Value));
_generatedContent = _generatedContent.Replace("API_ROOT", URL_BASE + "/api");
_generatedContent = _generatedContent.Replace("API_KEY", API_KEY);
_generatedContent = _generatedContent.Replace("APP_VERSION", BuildInfo.Version.ToString());
return _generatedContent;
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path); path = Path.GetFileName(path);

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);

View File

@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);

View File

@ -27,7 +27,7 @@ namespace NzbDrone.Api.Frontend.Mappers
} }
} }
protected abstract string Map(string resourceUrl); public abstract string Map(string resourceUrl);
public abstract bool CanHandle(string resourceUrl); public abstract bool CanHandle(string resourceUrl);

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string Map(string resourceUrl) public override string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path); path = Path.GetFileName(path);

View File

@ -139,6 +139,7 @@
<Compile Include="Extensions\RequestExtensions.cs" /> <Compile Include="Extensions\RequestExtensions.cs" />
<Compile Include="Frontend\CacheableSpecification.cs" /> <Compile Include="Frontend\CacheableSpecification.cs" />
<Compile Include="Frontend\Mappers\BackupFileMapper.cs" /> <Compile Include="Frontend\Mappers\BackupFileMapper.cs" />
<Compile Include="Frontend\Mappers\CacheBreakerProvider.cs" />
<Compile Include="Frontend\Mappers\FaviconMapper.cs" /> <Compile Include="Frontend\Mappers\FaviconMapper.cs" />
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" /> <Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" /> <Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />

View File

@ -3,16 +3,21 @@ using NzbDrone.Common.Disk;
namespace NzbDrone.Common.Crypto namespace NzbDrone.Common.Crypto
{ {
public class Md5HashProvider public interface IHashProvider
{
byte[] ComputeMd5(string path);
}
public class HashProvider : IHashProvider
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
public Md5HashProvider(IDiskProvider diskProvider) public HashProvider(IDiskProvider diskProvider)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
} }
public byte[] ComputeHash(string path) public byte[] ComputeMd5(string path)
{ {
using (var md5 = MD5.Create()) using (var md5 = MD5.Create())
{ {

View File

@ -73,7 +73,7 @@
<Compile Include="Composition\IContainer.cs" /> <Compile Include="Composition\IContainer.cs" />
<Compile Include="ConsoleService.cs" /> <Compile Include="ConsoleService.cs" />
<Compile Include="ConvertBase32.cs" /> <Compile Include="ConvertBase32.cs" />
<Compile Include="Crypto\Md5HashProvider.cs" /> <Compile Include="Crypto\HashProvider.cs" />
<Compile Include="DictionaryExtensions.cs" /> <Compile Include="DictionaryExtensions.cs" />
<Compile Include="Disk\DiskProviderBase.cs" /> <Compile Include="Disk\DiskProviderBase.cs" />
<Compile Include="Disk\IDiskProvider.cs" /> <Compile Include="Disk\IDiskProvider.cs" />

View File

@ -23,11 +23,11 @@
<link href="/Content/update.css" rel='stylesheet' type='text/css'/> <link href="/Content/update.css" rel='stylesheet' type='text/css'/>
<link href="/Content/overrides.css" rel='stylesheet' type='text/css'/> <link href="/Content/overrides.css" rel='stylesheet' type='text/css'/>
<link rel="apple-touch-icon" href="/Content/Images/touch/57.png?v=2"/> <link rel="apple-touch-icon" href="/Content/Images/touch/57.png"/>
<link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png?v=2"/> <link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png"/>
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png?v=2"/> <link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png"/>
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png?v=2"/> <link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png"/>
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico?v=2"/> <link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/>
<link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" /> <link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" />
</head> </head>