Browse Source

Better remote error handling + cleanup

Former-commit-id: 67cb999f6a10932131f5f4a723587a45bae2904d
af/merge-core
James South 12 years ago
parent
commit
e92ebb8b8f
  1. 143
      src/ImageProcessor.Web/Caching/DiskCache.cs
  2. 47
      src/ImageProcessor.Web/Helpers/RemoteFile.cs
  3. 13
      src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs
  4. 4
      src/ImageProcessor/Configuration/NativeMethods.cs
  5. 2
      src/TestWebsites/MVC/Web.config

143
src/ImageProcessor.Web/Caching/DiskCache.cs

@ -16,7 +16,6 @@ namespace ImageProcessor.Web.Caching
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Hosting;
using ImageProcessor.Web.Configuration;
@ -130,14 +129,65 @@ namespace ImageProcessor.Web.Caching
}
#region Methods
#region Internal
#region Public
/// <summary>
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// </summary>
/// <param name="path">
/// The path to the folder.
/// </param>
public static void TrimCachedFolders(string path)
{
string directory = Path.GetDirectoryName(path);
if (directory != null)
{
DirectoryInfo directoryInfo = new DirectoryInfo(directory);
DirectoryInfo parentDirectoryInfo = directoryInfo.Parent;
if (parentDirectoryInfo != null)
{
// UNC folders can throw exceptions if the file doesn't exist.
foreach (DirectoryInfo enumerateDirectory in parentDirectoryInfo.SafeEnumerateDirectories())
{
IEnumerable<FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc);
int count = files.Count();
foreach (FileInfo fileInfo in files)
{
try
{
// If the group count is equal to the max count minus 1 then we know we
// have reduced the number of items below the maximum allowed.
// We'll cleanup any orphaned expired files though.
if (!IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1)
{
break;
}
// Remove from the cache and delete each CachedImage.
CacheIndexer.Remove(fileInfo.Name);
fileInfo.Delete();
count -= 1;
}
// ReSharper disable once EmptyGeneralCatchClause
catch
{
// Do nothing; skip to the next file.
}
}
}
}
}
}
/// <summary>
/// Adds an image to the cache.
/// </summary>
/// <param name="cachedPath">
/// The path to the cached image.
/// </param>
internal void AddImageToCache(string cachedPath)
public void AddImageToCache(string cachedPath)
{
string key = Path.GetFileNameWithoutExtension(cachedPath);
CachedImage cachedImage = new CachedImage
@ -159,7 +209,7 @@ namespace ImageProcessor.Web.Caching
/// <returns>
/// True if the the original file is new or has been updated; otherwise, false.
/// </returns>
internal bool IsNewOrUpdatedFile(string cachedPath)
public bool IsNewOrUpdatedFile(string cachedPath)
{
bool isUpdated = false;
CachedImage cachedImage = CacheIndexer.GetValue(cachedPath);
@ -172,7 +222,7 @@ namespace ImageProcessor.Web.Caching
else
{
// Check to see if the cached image is set to expire.
if (this.IsExpired(cachedImage.CreationTimeUtc))
if (IsExpired(cachedImage.CreationTimeUtc))
{
CacheIndexer.Remove(cachedPath);
isUpdated = true;
@ -181,72 +231,22 @@ namespace ImageProcessor.Web.Caching
return isUpdated;
}
/// <summary>
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// </summary>
/// <param name="path">
/// The path to the folder.
/// </param>
/// <returns>
/// The <see cref="T:System.Threading.Tasks.Task"/>.
/// </returns>
internal async Task TrimCachedFolderAsync(string path)
{
await Task.Run(() => this.TrimCachedFolders(path));
}
#endregion
#region Private
/// <summary>
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// Gets a value indicating whether the given images creation date is out with
/// the prescribed limit.
/// </summary>
/// <param name="path">
/// The path to the folder.
/// <param name="creationDate">
/// The creation date.
/// </param>
private void TrimCachedFolders(string path)
/// <returns>
/// The true if the date is out with the limit, otherwise; false.
/// </returns>
private static bool IsExpired(DateTime creationDate)
{
string directory = Path.GetDirectoryName(path);
if (directory != null)
{
DirectoryInfo directoryInfo = new DirectoryInfo(directory);
DirectoryInfo parentDirectoryInfo = directoryInfo.Parent;
if (parentDirectoryInfo != null)
{
// UNC folders can throw exceptions if the file doesn't exist.
foreach (DirectoryInfo enumerateDirectory in parentDirectoryInfo.SafeEnumerateDirectories())
{
IEnumerable<FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc);
int count = files.Count();
foreach (FileInfo fileInfo in files)
{
try
{
// If the group count is equal to the max count minus 1 then we know we
// have reduced the number of items below the maximum allowed.
// We'll cleanup any orphaned expired files though.
if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1)
{
break;
}
// Remove from the cache and delete each CachedImage.
CacheIndexer.Remove(fileInfo.Name);
fileInfo.Delete();
count -= 1;
}
// ReSharper disable once EmptyGeneralCatchClause
catch
{
// Do nothing; skip to the next file.
}
}
}
}
}
return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration);
}
/// <summary>
@ -305,21 +305,6 @@ namespace ImageProcessor.Web.Caching
this.virtualCachedPath = Path.Combine(VirtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/");
}
}
/// <summary>
/// Gets a value indicating whether the given images creation date is out with
/// the prescribed limit.
/// </summary>
/// <param name="creationDate">
/// The creation date.
/// </param>
/// <returns>
/// The true if the date is out with the limit, otherwise; false.
/// </returns>
private bool IsExpired(DateTime creationDate)
{
return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration);
}
#endregion
#endregion
}

47
src/ImageProcessor.Web/Helpers/RemoteFile.cs

@ -13,12 +13,15 @@ namespace ImageProcessor.Web.Helpers
#region Using
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using ImageProcessor.Web.Configuration;
#endregion
@ -241,27 +244,43 @@ namespace ImageProcessor.Web.Helpers
/// </returns>
internal async Task<WebResponse> GetWebResponseAsync()
{
WebResponse response = await this.GetWebRequest().GetResponseAsync();
WebResponse response = null;
try
{
response = await this.GetWebRequest().GetResponseAsync();
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
throw new HttpException(404, "No image exists at " + Uri);
}
long contentLength = response.ContentLength;
throw;
}
// WebResponse.ContentLength doesn't always know the value, it returns -1 in this case.
if (contentLength == -1)
if (response != null)
{
// Response headers may still have the Content-Length inside of it.
string headerContentLength = response.Headers["Content-Length"];
long contentLength = response.ContentLength;
if (!string.IsNullOrWhiteSpace(headerContentLength))
// WebResponse.ContentLength doesn't always know the value, it returns -1 in this case.
if (contentLength == -1)
{
contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture);
// Response headers may still have the Content-Length inside of it.
string headerContentLength = response.Headers["Content-Length"];
if (!string.IsNullOrWhiteSpace(headerContentLength))
{
contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture);
}
}
}
// We don't need to check the url here since any external urls are available only from the web.config.
if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize))
{
response.Close();
throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed.");
// We don't need to check the url here since any external urls are available only from the web.config.
if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize))
{
response.Close();
throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed.");
}
}
return response;

13
src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs

@ -225,6 +225,9 @@ namespace ImageProcessor.Web.HttpModules
{
string cachedPath = cachedPathObject.ToString();
// Trim the cache.
DiskCache.TrimCachedFolders(cachedPath);
// Fire the post processing event.
EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
if (handler != null)
@ -347,7 +350,7 @@ namespace ImageProcessor.Web.HttpModules
{
string hashedUrlParameters = urlParameters.ToMD5Fingerprint();
// TODO: Add hash for querystring parameters.
// TODO: Add hash for querystring parameters?
imageName += hashedUrlParameters;
fullPath += hashedUrlParameters;
}
@ -425,9 +428,6 @@ namespace ImageProcessor.Web.HttpModules
// Add to the cache.
cache.AddImageToCache(cachedPath);
// Trim the cache.
await cache.TrimCachedFolderAsync(cachedPath);
// Store the cached path, response type, and cache dependency in the context for later retrieval.
context.Items[CachedPathKey] = cachedPath;
context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType;
@ -440,7 +440,7 @@ namespace ImageProcessor.Web.HttpModules
}
else
{
using (await Locker.LockAsync(cachedPath))
using (Locker.Lock(cachedPath))
{
// Check to see if the file exists.
// ReSharper disable once AssignNullToNotNullAttribute
@ -459,9 +459,6 @@ namespace ImageProcessor.Web.HttpModules
// Add to the cache.
cache.AddImageToCache(cachedPath);
// Trim the cache.
await cache.TrimCachedFolderAsync(cachedPath);
// Store the cached path, response type, and cache dependencies in the context for later retrieval.
context.Items[CachedPathKey] = cachedPath;
context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType;

4
src/ImageProcessor/Configuration/NativeMethods.cs

@ -27,7 +27,7 @@ namespace ImageProcessor.Configuration
/// an executable module.
/// </param>
/// <returns>If the function succeeds, the return value is a handle to the module; otherwise null.</returns>
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibrary(string libname);
/// <summary>
@ -38,7 +38,7 @@ namespace ImageProcessor.Configuration
/// <param name="hModule">A handle to the loaded library module.
/// The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle.</param>
/// <returns>If the function succeeds, the return value is nonzero; otherwise zero.</returns>
[DllImport("kernel32", CharSet = CharSet.Auto)]
[DllImport("kernel32", SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
/// <summary>

2
src/TestWebsites/MVC/Web.config

@ -28,7 +28,7 @@
<system.web>
<httpRuntime targetFramework="4.5" />
<customErrors mode="Off"/>
<!--<customErrors mode="Off"/>-->
<compilation debug="true" targetFramework="4.5" />
<pages>

Loading…
Cancel
Save