From e2dcddc5ac43846baea0f9b1a0fc62844dd9ee1d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 23 Mar 2013 22:45:00 -0400 Subject: made compression and caching available to plugin api endpoints --- .../HttpServer/BaseRestService.cs | 470 --------------------- 1 file changed, 470 deletions(-) delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs') diff --git a/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs b/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs deleted file mode 100644 index ff2273750..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs +++ /dev/null @@ -1,470 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; -using ServiceStack.Common; -using ServiceStack.Common.Web; -using ServiceStack.ServiceHost; -using ServiceStack.ServiceInterface; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Threading.Tasks; -using MimeTypes = MediaBrowser.Common.Net.MimeTypes; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// - /// Class BaseRestService - /// - public class BaseRestService : Service, IRestfulService - { - /// - /// Gets or sets the logger. - /// - /// The logger. - public ILogger Logger { get; set; } - - /// - /// Gets a value indicating whether this instance is range request. - /// - /// true if this instance is range request; otherwise, false. - protected bool IsRangeRequest - { - get - { - return !string.IsNullOrEmpty(RequestContext.GetHeader("Range")); - } - } - - /// - /// To the optimized result. - /// - /// - /// The result. - /// System.Object. - /// result - protected object ToOptimizedResult(T result) - where T : class - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - return RequestContext.ToOptimizedResult(result); - } - - /// - /// To the optimized result using cache. - /// - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// System.Object. - /// cacheKey - protected object ToOptimizedResultUsingCache(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration); - - if (result != null) - { - // Return null so that service stack won't do anything - return null; - } - - return ToOptimizedResult(factoryFn()); - } - - /// - /// To the cached result. - /// - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// Type of the content. - /// System.Object. - /// cacheKey - protected object ToCachedResult(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - Response.ContentType = contentType; - - var key = cacheKey.ToString("N"); - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration); - - if (result != null) - { - // Return null so that service stack won't do anything - return null; - } - - return factoryFn(); - } - - /// - /// To the static file result. - /// - /// The path. - /// if set to true [headers only]. - /// System.Object. - /// path - protected object ToStaticFileResult(string path, bool headersOnly = false) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var dateModified = File.GetLastWriteTimeUtc(path); - - var cacheKey = path + dateModified.Ticks; - - return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path)), headersOnly); - } - - /// - /// Gets the file stream. - /// - /// The path. - /// Stream. - private Stream GetFileStream(string path) - { - return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); - } - - /// - /// To the static result. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// Type of the content. - /// The factory fn. - /// if set to true [headers only]. - /// System.Object. - /// cacheKey - protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func> factoryFn, bool headersOnly = false) - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - Response.ContentType = contentType; - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration); - - if (result != null) - { - // Return null so that service stack won't do anything - return null; - } - - var compress = ShouldCompressResponse(contentType); - - return ToStaticResult(contentType, factoryFn, compress, headersOnly).Result; - } - - /// - /// Shoulds the compress response. - /// - /// Type of the content. - /// true if XXXX, false otherwise - private bool ShouldCompressResponse(string contentType) - { - // It will take some work to support compression with byte range requests - if (IsRangeRequest) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return true; - } - - /// - /// To the static result. - /// - /// Type of the content. - /// The factory fn. - /// if set to true [compress]. - /// if set to true [headers only]. - /// System.Object. - private async Task ToStaticResult(string contentType, Func> factoryFn, bool compress, bool headersOnly = false) - { - if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType)) - { - Response.ContentType = contentType; - - var stream = await factoryFn().ConfigureAwait(false); - - var httpListenerResponse = (HttpListenerResponse) Response.OriginalResponse; - httpListenerResponse.SendChunked = false; - - if (IsRangeRequest) - { - return new RangeRequestWriter(RequestContext.GetHeader("Range"), httpListenerResponse, stream, headersOnly); - } - - httpListenerResponse.ContentLength64 = stream.Length; - return headersOnly ? null : new StreamWriter(stream, Logger); - } - - if (headersOnly) - { - return null; - } - - string content; - - using (var stream = await factoryFn().ConfigureAwait(false)) - { - using (var reader = new StreamReader(stream)) - { - content = await reader.ReadToEndAsync().ConfigureAwait(false); - } - } - - var contents = content.Compress(RequestContext.CompressionType); - - return new CompressedResult(contents, RequestContext.CompressionType, contentType); - } - - /// - /// Pres the process optimized result. - /// - /// The cache key. - /// The cache key string. - /// The last date modified. - /// Duration of the cache. - /// System.Object. - private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - Response.AddHeader("ETag", cacheKeyString); - - if (IsNotModified(cacheKey, lastDateModified, cacheDuration)) - { - AddAgeHeader(lastDateModified); - AddExpiresHeader(cacheKeyString, cacheDuration); - //ctx.Response.SendChunked = false; - - Response.StatusCode = 304; - - return new byte[]{}; - } - - SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration); - - return null; - } - - /// - /// Determines whether [is not modified] [the specified cache key]. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// true if [is not modified] [the specified cache key]; otherwise, false. - private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - var isNotModified = true; - - var ifModifiedSinceHeader = RequestContext.GetHeader("If-Modified-Since"); - - if (!string.IsNullOrEmpty(ifModifiedSinceHeader)) - { - DateTime ifModifiedSince; - - if (DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSince)) - { - isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified); - } - } - - var ifNoneMatchHeader = RequestContext.GetHeader("If-None-Match"); - - // Validate If-None-Match - if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader))) - { - Guid ifNoneMatch; - - if (Guid.TryParse(ifNoneMatchHeader ?? string.Empty, out ifNoneMatch)) - { - if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) - { - return true; - } - } - } - - return false; - } - - /// - /// Determines whether [is not modified] [the specified if modified since]. - /// - /// If modified since. - /// Duration of the cache. - /// The date modified. - /// true if [is not modified] [the specified if modified since]; otherwise, false. - private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified) - { - if (dateModified.HasValue) - { - var lastModified = NormalizeDateForComparison(dateModified.Value); - ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); - - return lastModified <= ifModifiedSince; - } - - if (cacheDuration.HasValue) - { - var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value); - - if (DateTime.UtcNow < cacheExpirationDate) - { - return true; - } - } - - return false; - } - - - /// - /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that - /// - /// The date. - /// DateTime. - private DateTime NormalizeDateForComparison(DateTime date) - { - return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); - } - - /// - /// Sets the caching headers. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant - // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching - if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) - { - AddAgeHeader(lastDateModified); - Response.AddHeader("LastModified", lastDateModified.Value.ToString("r")); - } - - if (cacheDuration.HasValue) - { - Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds)); - } - else if (!string.IsNullOrEmpty(cacheKey)) - { - Response.AddHeader("Cache-Control", "public"); - } - else - { - Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - Response.AddHeader("pragma", "no-cache, no-store, must-revalidate"); - } - - AddExpiresHeader(cacheKey, cacheDuration); - } - - /// - /// Adds the expires header. - /// - /// The cache key. - /// Duration of the cache. - private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration) - { - if (cacheDuration.HasValue) - { - Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r")); - } - else if (string.IsNullOrEmpty(cacheKey)) - { - Response.AddHeader("Expires", "-1"); - } - } - - /// - /// Adds the age header. - /// - /// The last date modified. - private void AddAgeHeader(DateTime? lastDateModified) - { - if (lastDateModified.HasValue) - { - Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture)); - } - } - - /// - /// Gets the routes. - /// - /// IEnumerable{RouteInfo}. - public virtual IEnumerable GetRoutes() - { - return new RouteInfo[] {}; - } - } -} -- cgit v1.2.3