Added a new RouteFileCache and more response extensions. Bumped package version to 1.3.4.
This commit is contained in:
parent
662bd03ddc
commit
2b892dfd66
@ -28,6 +28,8 @@ namespace MontoyaTech.Rest.Net.Example
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RouteFileCache FileCache = new RouteFileCache(100 * 1024 * 1024);
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
File.WriteAllText("test.txt", "hello from a file");
|
File.WriteAllText("test.txt", "hello from a file");
|
||||||
@ -98,7 +100,9 @@ namespace MontoyaTech.Rest.Net.Example
|
|||||||
[RouteResponse(typeof(string))]
|
[RouteResponse(typeof(string))]
|
||||||
public static HttpListenerResponse CompressFile(HttpListenerContext context)
|
public static HttpListenerResponse CompressFile(HttpListenerContext context)
|
||||||
{
|
{
|
||||||
return context.Response.WithStatus(HttpStatusCode.OK).WithCompressedFile("test.txt");
|
var content = FileCache.Cache("test.txt");
|
||||||
|
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithCompressedFile("test.txt", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RouteGroup("Test")]
|
[RouteGroup("Test")]
|
||||||
|
@ -114,6 +114,33 @@ namespace MontoyaTech.Rest.Net
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a file and writes the file content with name to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="mimeType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static HttpListenerResponse WithFile(this HttpListenerResponse response, string fileName, byte[] content, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
throw new ArgumentException("fileName must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(fileName).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(fileName)}""");
|
||||||
|
response.SendChunked = true;
|
||||||
|
|
||||||
|
using (var responseStream = response.OutputStream)
|
||||||
|
responseStream.Write(content, 0, content.Length);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the response content type to a file and compresses the given file content to the response.
|
/// Sets the response content type to a file and compresses the given file content to the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -142,6 +169,35 @@ namespace MontoyaTech.Rest.Net
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a file and writes the file content with name to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="mimeType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static HttpListenerResponse WithCompressedFile(this HttpListenerResponse response, string fileName, byte[] content, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(fileName).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(fileName)}""");
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
response.SendChunked = true;
|
||||||
|
|
||||||
|
using (var responseStream = response.OutputStream)
|
||||||
|
using (var compressedStream = new GZipStream(responseStream, CompressionMode.Compress, true))
|
||||||
|
compressedStream.Write(content, 0, content.Length);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the response content type to html and writes the given html to it.
|
/// Sets the response content type to html and writes the given html to it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
||||||
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<Version>1.3.3</Version>
|
<Version>1.3.4</Version>
|
||||||
<PackageReleaseNotes></PackageReleaseNotes>
|
<PackageReleaseNotes></PackageReleaseNotes>
|
||||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
185
Rest.Net/RouteFileCache.cs
Normal file
185
Rest.Net/RouteFileCache.cs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a FileCache that can be used with Routes to speed
|
||||||
|
/// up response times.
|
||||||
|
/// </summary>
|
||||||
|
public class RouteFileCache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a CachedFile.
|
||||||
|
/// </summary>
|
||||||
|
public class CachedFile
|
||||||
|
{
|
||||||
|
public string FilePath;
|
||||||
|
|
||||||
|
public byte[] Content;
|
||||||
|
|
||||||
|
public int HitCount;
|
||||||
|
|
||||||
|
public DateTime Cached = DateTime.Now;
|
||||||
|
|
||||||
|
public DateTime LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
public CachedFile(string filePath, byte[] content)
|
||||||
|
{
|
||||||
|
this.FilePath = filePath;
|
||||||
|
this.Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The files that are currently cached.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, CachedFile> CachedFiles = new Dictionary<string, CachedFile>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current number of bytes cached.
|
||||||
|
/// </summary>
|
||||||
|
public int BytesCached { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max bytes that can be cached at a time.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxBytesCached { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RouteFileCache with the max bytes that can be cached.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxBytes"></param>
|
||||||
|
public RouteFileCache(int maxBytes)
|
||||||
|
{
|
||||||
|
this.MaxBytesCached = maxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not a file was cached and the content of the file if it was.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Cached(string filePath, out byte[] content)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
content = null;
|
||||||
|
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
content = cached.Content;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a new file and returns the content, if the file was already cached the content will be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="FileNotFoundException"></exception>
|
||||||
|
public byte[] Cache(string filePath)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
{
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var content = File.ReadAllBytes(filePath);
|
||||||
|
|
||||||
|
this.Cache(filePath, content);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Failed to find file: " + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
return cached.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a new file with the content, if the file was already cached this will just count as an access and overwrite the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
public void Cache(string filePath, byte[] content)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
{
|
||||||
|
if ((this.BytesCached + content.Length) > this.MaxBytesCached)
|
||||||
|
this.Free((this.BytesCached + content.Length) - this.MaxBytesCached);
|
||||||
|
|
||||||
|
var cached = new CachedFile(filePath, content);
|
||||||
|
|
||||||
|
this.BytesCached += content.Length;
|
||||||
|
|
||||||
|
this.CachedFiles.Add(filePath, cached);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
cached.Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to free up space in the cache by a specific amount of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="neededBytes"></param>
|
||||||
|
public void Free(int neededBytes)
|
||||||
|
{
|
||||||
|
while (this.BytesCached + neededBytes >= this.MaxBytesCached)
|
||||||
|
{
|
||||||
|
CachedFile weakest = null;
|
||||||
|
|
||||||
|
foreach (var pair in this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (weakest == null)
|
||||||
|
weakest = pair.Value;
|
||||||
|
else if (pair.Value.LastAccess < weakest.LastAccess && pair.Value.HitCount < weakest.HitCount)
|
||||||
|
weakest = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weakest != null)
|
||||||
|
{
|
||||||
|
this.CachedFiles.Remove(weakest.FilePath);
|
||||||
|
|
||||||
|
this.BytesCached -= weakest.Content.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user