From 767cdc1f6f6a63ce997fc9476911e2c361f9d402 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Wed, 20 Feb 2013 20:33:05 -0500 Subject: Pushing missing changes --- .../Net/Handlers/StaticFileHandler.cs | 513 +++++++++++---------- 1 file changed, 264 insertions(+), 249 deletions(-) (limited to 'MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs') diff --git a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs index 11438b164b..3967d15c35 100644 --- a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs +++ b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs @@ -1,249 +1,264 @@ -using MediaBrowser.Common.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net.Handlers -{ - public class StaticFileHandler : BaseHandler - { - public override bool HandlesRequest(HttpListenerRequest request) - { - return false; - } - - private string _path; - public virtual string Path - { - get - { - if (!string.IsNullOrWhiteSpace(_path)) - { - return _path; - } - - return QueryString["path"]; - } - set - { - _path = value; - } - } - - private Stream SourceStream { get; set; } - - protected override bool SupportsByteRangeRequests - { - get - { - return true; - } - } - - private bool ShouldCompressResponse(string contentType) - { - // Can't compress these - if (IsRangeRequest) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // It will take some work to support compression within this handler - return false; - } - - protected override long? GetTotalContentLength() - { - return SourceStream.Length; - } - - protected override Task GetResponseInfo() - { - ResponseInfo info = new ResponseInfo - { - ContentType = MimeTypes.GetMimeType(Path), - }; - - try - { - SourceStream = File.OpenRead(Path); - } - catch (FileNotFoundException ex) - { - info.StatusCode = 404; - Logger.LogException(ex); - } - catch (DirectoryNotFoundException ex) - { - info.StatusCode = 404; - Logger.LogException(ex); - } - catch (UnauthorizedAccessException ex) - { - info.StatusCode = 403; - Logger.LogException(ex); - } - - info.CompressResponse = ShouldCompressResponse(info.ContentType); - - if (SourceStream != null) - { - info.DateLastModified = File.GetLastWriteTimeUtc(Path); - } - - return Task.FromResult(info); - } - - protected override Task WriteResponseToOutputStream(Stream stream) - { - if (IsRangeRequest) - { - KeyValuePair requestedRange = RequestedRanges.First(); - - // If the requested range is "0-" and we know the total length, we can optimize by avoiding having to buffer the content into memory - if (requestedRange.Value == null && TotalContentLength != null) - { - return ServeCompleteRangeRequest(requestedRange, stream); - } - if (TotalContentLength.HasValue) - { - // This will have to buffer a portion of the content into memory - return ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream); - } - - // This will have to buffer the entire content into memory - return ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream); - } - - return SourceStream.CopyToAsync(stream); - } - - protected override void DisposeResponseStream() - { - base.DisposeResponseStream(); - - if (SourceStream != null) - { - SourceStream.Dispose(); - } - } - - /// - /// Handles a range request of "bytes=0-" - /// This will serve the complete content and add the content-range header - /// - private Task ServeCompleteRangeRequest(KeyValuePair requestedRange, Stream responseStream) - { - long totalContentLength = TotalContentLength.Value; - - long rangeStart = requestedRange.Key; - long rangeEnd = totalContentLength - 1; - long rangeLength = 1 + rangeEnd - rangeStart; - - // Content-Length is the length of what we're serving, not the original content - HttpListenerContext.Response.ContentLength64 = rangeLength; - HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); - - if (rangeStart > 0) - { - SourceStream.Position = rangeStart; - } - - return SourceStream.CopyToAsync(responseStream); - } - - /// - /// Serves a partial range request where the total content length is not known - /// - private async Task ServePartialRangeRequestWithUnknownTotalContentLength(KeyValuePair requestedRange, Stream responseStream) - { - // Read the entire stream so that we can determine the length - byte[] bytes = await ReadBytes(SourceStream, 0, null).ConfigureAwait(false); - - long totalContentLength = bytes.LongLength; - - long rangeStart = requestedRange.Key; - long rangeEnd = requestedRange.Value ?? (totalContentLength - 1); - long rangeLength = 1 + rangeEnd - rangeStart; - - // Content-Length is the length of what we're serving, not the original content - HttpListenerContext.Response.ContentLength64 = rangeLength; - HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); - - await responseStream.WriteAsync(bytes, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false); - } - - /// - /// Serves a partial range request where the total content length is already known - /// - private async Task ServePartialRangeRequestWithKnownTotalContentLength(KeyValuePair requestedRange, Stream responseStream) - { - long totalContentLength = TotalContentLength.Value; - long rangeStart = requestedRange.Key; - long rangeEnd = requestedRange.Value ?? (totalContentLength - 1); - long rangeLength = 1 + rangeEnd - rangeStart; - - // Only read the bytes we need - byte[] bytes = await ReadBytes(SourceStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false); - - // Content-Length is the length of what we're serving, not the original content - HttpListenerContext.Response.ContentLength64 = rangeLength; - - HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); - - await responseStream.WriteAsync(bytes, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false); - } - - /// - /// Reads bytes from a stream - /// - /// The input stream - /// The starting position - /// The number of bytes to read, or null to read to the end. - private async Task ReadBytes(Stream input, int start, int? count) - { - if (start > 0) - { - input.Position = start; - } - - if (count == null) - { - var buffer = new byte[16 * 1024]; - - using (var ms = new MemoryStream()) - { - int read; - while ((read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) - { - await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); - } - return ms.ToArray(); - } - } - else - { - var buffer = new byte[count.Value]; - - using (var ms = new MemoryStream()) - { - int read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - - await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); - - return ms.ToArray(); - } - } - - } - } -} +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Kernel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Net.Handlers +{ + /// + /// Represents an http handler that serves static content + /// + public class StaticFileHandler : BaseHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The kernel. + public StaticFileHandler(IKernel kernel) + { + Initialize(kernel); + } + + /// + /// The _path + /// + private string _path; + /// + /// Gets or sets the path to the static resource + /// + /// The path. + public string Path + { + get + { + if (!string.IsNullOrWhiteSpace(_path)) + { + return _path; + } + + return QueryString["path"]; + } + set + { + _path = value; + } + } + + /// + /// Gets or sets the last date modified of the resource + /// + /// The last date modified. + public DateTime? LastDateModified { get; set; } + + /// + /// Gets or sets the content type of the resource + /// + /// The type of the content. + public string ContentType { get; set; } + + /// + /// Gets or sets the content type of the resource + /// + /// The etag. + public Guid Etag { get; set; } + + /// + /// Gets or sets the source stream of the resource + /// + /// The source stream. + public Stream SourceStream { get; set; } + + /// + /// 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; + } + + return true; + } + + /// + /// Gets or sets the duration of the cache. + /// + /// The duration of the cache. + public TimeSpan? CacheDuration { get; set; } + + /// + /// Gets the total length of the content. + /// + /// The response info. + /// System.Nullable{System.Int64}. + protected override long? GetTotalContentLength(ResponseInfo responseInfo) + { + // If we're compressing the response, content length must be the compressed length, which we don't know + if (responseInfo.CompressResponse && ClientSupportsCompression) + { + return null; + } + + return SourceStream.Length; + } + + /// + /// Gets the response info. + /// + /// Task{ResponseInfo}. + protected override Task GetResponseInfo() + { + var info = new ResponseInfo + { + ContentType = ContentType ?? MimeTypes.GetMimeType(Path), + Etag = Etag, + DateLastModified = LastDateModified + }; + + if (SourceStream == null && !string.IsNullOrEmpty(Path)) + { + // FileShare must be ReadWrite in case someone else is currently writing to it. + SourceStream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); + } + + info.CompressResponse = ShouldCompressResponse(info.ContentType); + + info.SupportsByteRangeRequests = !info.CompressResponse || !ClientSupportsCompression; + + if (!info.DateLastModified.HasValue && !string.IsNullOrWhiteSpace(Path)) + { + info.DateLastModified = File.GetLastWriteTimeUtc(Path); + } + + if (CacheDuration.HasValue) + { + info.CacheDuration = CacheDuration.Value; + } + + if (SourceStream == null && string.IsNullOrEmpty(Path)) + { + throw new ResourceNotFoundException(); + } + + return Task.FromResult(info); + } + + /// + /// Writes the response to output stream. + /// + /// The stream. + /// The response info. + /// Total length of the content. + /// Task. + protected override Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? totalContentLength) + { + if (IsRangeRequest && totalContentLength.HasValue) + { + var requestedRange = RequestedRanges.First(); + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + return ServeCompleteRangeRequest(requestedRange, stream, totalContentLength.Value); + } + + // This will have to buffer a portion of the content into memory + return ServePartialRangeRequest(requestedRange.Key, requestedRange.Value.Value, stream, totalContentLength.Value); + } + + return SourceStream.CopyToAsync(stream); + } + + /// + /// Disposes the response stream. + /// + protected override void DisposeResponseStream() + { + if (SourceStream != null) + { + SourceStream.Dispose(); + } + + base.DisposeResponseStream(); + } + + /// + /// Handles a range request of "bytes=0-" + /// This will serve the complete content and add the content-range header + /// + /// The requested range. + /// The response stream. + /// Total length of the content. + /// Task. + private Task ServeCompleteRangeRequest(KeyValuePair requestedRange, Stream responseStream, long totalContentLength) + { + var rangeStart = requestedRange.Key; + var rangeEnd = totalContentLength - 1; + var rangeLength = 1 + rangeEnd - rangeStart; + + // Content-Length is the length of what we're serving, not the original content + HttpListenerContext.Response.ContentLength64 = rangeLength; + HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); + + if (rangeStart > 0) + { + SourceStream.Position = rangeStart; + } + + return SourceStream.CopyToAsync(responseStream); + } + + /// + /// Serves a partial range request + /// + /// The range start. + /// The range end. + /// The response stream. + /// Total length of the content. + /// Task. + private async Task ServePartialRangeRequest(long rangeStart, long rangeEnd, Stream responseStream, long totalContentLength) + { + var rangeLength = 1 + rangeEnd - rangeStart; + + // Content-Length is the length of what we're serving, not the original content + HttpListenerContext.Response.ContentLength64 = rangeLength; + HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); + + SourceStream.Position = rangeStart; + + // Fast track to just copy the stream to the end + if (rangeEnd == totalContentLength - 1) + { + await SourceStream.CopyToAsync(responseStream).ConfigureAwait(false); + } + else + { + // Read the bytes we need + var buffer = new byte[Convert.ToInt32(rangeLength)]; + await SourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + + await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false); + } + } + } +} -- cgit v1.2.3