Added a new RouteFileCache and more response extensions. Bumped package version to 1.3.4.
This commit is contained in:
		| @@ -28,6 +28,8 @@ namespace MontoyaTech.Rest.Net.Example | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public static RouteFileCache FileCache = new RouteFileCache(100 * 1024 * 1024); | ||||
|  | ||||
|         public static void Main(string[] args) | ||||
|         { | ||||
|             File.WriteAllText("test.txt", "hello from a file"); | ||||
| @@ -98,7 +100,9 @@ namespace MontoyaTech.Rest.Net.Example | ||||
|         [RouteResponse(typeof(string))] | ||||
|         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")] | ||||
|   | ||||
| @@ -114,6 +114,33 @@ namespace MontoyaTech.Rest.Net | ||||
|             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> | ||||
|         /// Sets the response content type to a file and compresses the given file content to the response. | ||||
|         /// </summary> | ||||
| @@ -142,6 +169,35 @@ namespace MontoyaTech.Rest.Net | ||||
|             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> | ||||
|         /// Sets the response content type to html and writes the given html to it. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|     <AssemblyName>MontoyaTech.Rest.Net</AssemblyName> | ||||
|     <RootNamespace>MontoyaTech.Rest.Net</RootNamespace> | ||||
|     <GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
|     <Version>1.3.3</Version> | ||||
|     <Version>1.3.4</Version> | ||||
|     <PackageReleaseNotes></PackageReleaseNotes> | ||||
|     <PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon> | ||||
|   </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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user