From 2ca4b7d03adfa3cc7c9c6a597a11762142d5b34b Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Mon, 4 Mar 2013 00:43:06 -0500 Subject: Created IConfigurationManager --- MediaBrowser.Common/Net/Handlers/BaseHandler.cs | 808 --------------------- .../Net/Handlers/IHttpServerHandler.cs | 32 - .../Net/Handlers/StaticFileHandler.cs | 264 ------- 3 files changed, 1104 deletions(-) delete mode 100644 MediaBrowser.Common/Net/Handlers/BaseHandler.cs delete mode 100644 MediaBrowser.Common/Net/Handlers/IHttpServerHandler.cs delete mode 100644 MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs (limited to 'MediaBrowser.Common/Net') diff --git a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs deleted file mode 100644 index 5d26c7e920..0000000000 --- a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs +++ /dev/null @@ -1,808 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Kernel; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net.Handlers -{ - /// - /// Class BaseHandler - /// - public abstract class BaseHandler : IHttpServerHandler - where TKernelType : IKernel - { - /// - /// Initializes the specified kernel. - /// - /// The kernel. - public void Initialize(IKernel kernel) - { - Kernel = (TKernelType)kernel; - } - - /// - /// Gets or sets the kernel. - /// - /// The kernel. - protected TKernelType Kernel { get; private set; } - - /// - /// Gets the URL suffix used to determine if this handler can process a request. - /// - /// The URL suffix. - protected virtual string UrlSuffix - { - get - { - var name = GetType().Name; - - const string srch = "Handler"; - - if (name.EndsWith(srch, StringComparison.OrdinalIgnoreCase)) - { - name = name.Substring(0, name.Length - srch.Length); - } - - return "api/" + name; - } - } - - /// - /// Handleses the request. - /// - /// The request. - /// true if XXXX, false otherwise - public virtual bool HandlesRequest(HttpListenerRequest request) - { - var name = '/' + UrlSuffix.TrimStart('/'); - - var url = Kernel.WebApplicationName + name; - - return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Gets or sets the compressed stream. - /// - /// The compressed stream. - private Stream CompressedStream { get; set; } - - /// - /// Gets a value indicating whether [use chunked encoding]. - /// - /// null if [use chunked encoding] contains no value, true if [use chunked encoding]; otherwise, false. - public virtual bool? UseChunkedEncoding - { - get - { - return null; - } - } - - /// - /// The original HttpListenerContext - /// - /// The HTTP listener context. - protected HttpListenerContext HttpListenerContext { get; set; } - - /// - /// The _query string - /// - private NameValueCollection _queryString; - /// - /// The original QueryString - /// - /// The query string. - public NameValueCollection QueryString - { - get - { - // HttpListenerContext.Request.QueryString is not decoded properly - return _queryString; - } - } - - /// - /// The _requested ranges - /// - private List> _requestedRanges; - /// - /// Gets the requested ranges. - /// - /// The requested ranges. - protected IEnumerable> RequestedRanges - { - get - { - if (_requestedRanges == null) - { - _requestedRanges = new List>(); - - if (IsRangeRequest) - { - // Example: bytes=0-,32-63 - var ranges = HttpListenerContext.Request.Headers["Range"].Split('=')[1].Split(','); - - foreach (var range in ranges) - { - var vals = range.Split('-'); - - long start = 0; - long? end = null; - - if (!string.IsNullOrEmpty(vals[0])) - { - start = long.Parse(vals[0]); - } - if (!string.IsNullOrEmpty(vals[1])) - { - end = long.Parse(vals[1]); - } - - _requestedRanges.Add(new KeyValuePair(start, end)); - } - } - } - - return _requestedRanges; - } - } - - /// - /// Gets a value indicating whether this instance is range request. - /// - /// true if this instance is range request; otherwise, false. - protected bool IsRangeRequest - { - get - { - return HttpListenerContext.Request.Headers.AllKeys.Contains("Range"); - } - } - - /// - /// Gets a value indicating whether [client supports compression]. - /// - /// true if [client supports compression]; otherwise, false. - protected bool ClientSupportsCompression - { - get - { - var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty; - - return enc.Equals("*", StringComparison.OrdinalIgnoreCase) || - enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || - enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1; - } - } - - /// - /// Gets the compression method. - /// - /// The compression method. - private string CompressionMethod - { - get - { - var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty; - - if (enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || enc.Equals("*", StringComparison.OrdinalIgnoreCase)) - { - return "deflate"; - } - if (enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1) - { - return "gzip"; - } - - return null; - } - } - - /// - /// Processes the request. - /// - /// The CTX. - /// Task. - public virtual async Task ProcessRequest(HttpListenerContext ctx) - { - HttpListenerContext = ctx; - - ctx.Response.AddHeader("Access-Control-Allow-Origin", "*"); - - ctx.Response.KeepAlive = true; - - try - { - await ProcessRequestInternal(ctx).ConfigureAwait(false); - } - catch (InvalidOperationException ex) - { - HandleException(ctx.Response, ex, 422); - - throw; - } - catch (ResourceNotFoundException ex) - { - HandleException(ctx.Response, ex, 404); - - throw; - } - catch (FileNotFoundException ex) - { - HandleException(ctx.Response, ex, 404); - - throw; - } - catch (DirectoryNotFoundException ex) - { - HandleException(ctx.Response, ex, 404); - - throw; - } - catch (UnauthorizedAccessException ex) - { - HandleException(ctx.Response, ex, 401); - - throw; - } - catch (ArgumentException ex) - { - HandleException(ctx.Response, ex, 400); - - throw; - } - catch (Exception ex) - { - HandleException(ctx.Response, ex, 500); - - throw; - } - finally - { - DisposeResponseStream(); - } - } - - /// - /// Appends the error message. - /// - /// The response. - /// The ex. - /// The status code. - private void HandleException(HttpListenerResponse response, Exception ex, int statusCode) - { - response.StatusCode = statusCode; - - response.Headers.Add("Status", statusCode.ToString(new CultureInfo("en-US"))); - - response.Headers.Remove("Age"); - response.Headers.Remove("Expires"); - response.Headers.Remove("Cache-Control"); - response.Headers.Remove("Etag"); - response.Headers.Remove("Last-Modified"); - - response.ContentType = "text/plain"; - - //Logger.ErrorException("Error processing request", ex); - - if (!string.IsNullOrEmpty(ex.Message)) - { - response.AddHeader("X-Application-Error-Code", ex.Message); - } - - var bytes = Encoding.UTF8.GetBytes(ex.Message); - - var stream = CompressedStream ?? response.OutputStream; - - // This could fail, but try to add the stack trace as the body content - try - { - stream.Write(bytes, 0, bytes.Length); - } - catch (Exception ex1) - { - //Logger.ErrorException("Error dumping stack trace", ex1); - } - } - - /// - /// Processes the request internal. - /// - /// The CTX. - /// Task. - private async Task ProcessRequestInternal(HttpListenerContext ctx) - { - var responseInfo = await GetResponseInfo().ConfigureAwait(false); - - // Let the client know if byte range requests are supported or not - if (responseInfo.SupportsByteRangeRequests) - { - ctx.Response.Headers["Accept-Ranges"] = "bytes"; - } - else if (!responseInfo.SupportsByteRangeRequests) - { - ctx.Response.Headers["Accept-Ranges"] = "none"; - } - - if (responseInfo.IsResponseValid && responseInfo.SupportsByteRangeRequests && IsRangeRequest) - { - // Set the initial status code - // When serving a range request, we need to return status code 206 to indicate a partial response body - responseInfo.StatusCode = 206; - } - - ctx.Response.ContentType = responseInfo.ContentType; - - if (responseInfo.Etag.HasValue) - { - ctx.Response.Headers["ETag"] = responseInfo.Etag.Value.ToString("N"); - } - - var isCacheValid = true; - - // Validate If-Modified-Since - if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since")) - { - DateTime ifModifiedSince; - - if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince)) - { - isCacheValid = IsCacheValid(ifModifiedSince.ToUniversalTime(), responseInfo.CacheDuration, - responseInfo.DateLastModified); - } - } - - // Validate If-None-Match - if (isCacheValid && - (responseInfo.Etag.HasValue || !string.IsNullOrEmpty(ctx.Request.Headers["If-None-Match"]))) - { - Guid ifNoneMatch; - - if (Guid.TryParse(ctx.Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch)) - { - if (responseInfo.Etag.HasValue && responseInfo.Etag.Value == ifNoneMatch) - { - responseInfo.StatusCode = 304; - } - } - } - - LogResponse(ctx, responseInfo); - - if (responseInfo.IsResponseValid) - { - await OnProcessingRequest(responseInfo).ConfigureAwait(false); - } - - if (responseInfo.IsResponseValid) - { - await ProcessUncachedRequest(ctx, responseInfo).ConfigureAwait(false); - } - else - { - if (responseInfo.StatusCode == 304) - { - AddAgeHeader(ctx.Response, responseInfo); - AddExpiresHeader(ctx.Response, responseInfo); - } - - ctx.Response.StatusCode = responseInfo.StatusCode; - ctx.Response.SendChunked = false; - } - } - - /// - /// The _null task result - /// - private readonly Task _nullTaskResult = Task.FromResult(true); - - /// - /// Called when [processing request]. - /// - /// The response info. - /// Task. - protected virtual Task OnProcessingRequest(ResponseInfo responseInfo) - { - return _nullTaskResult; - } - - /// - /// Logs the response. - /// - /// The CTX. - /// The response info. - private void LogResponse(HttpListenerContext ctx, ResponseInfo responseInfo) - { - // Don't log normal 200's - if (responseInfo.StatusCode == 200) - { - return; - } - - var log = new StringBuilder(); - - log.AppendLine(string.Format("Url: {0}", ctx.Request.Url)); - - log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k]))); - - var msg = "Http Response Sent (" + responseInfo.StatusCode + ") to " + ctx.Request.RemoteEndPoint; - - if (Kernel.Configuration.EnableHttpLevelLogging) - { - //Logger.LogMultiline(msg, LogSeverity.Debug, log); - } - } - - /// - /// Processes the uncached request. - /// - /// The CTX. - /// The response info. - /// Task. - private async Task ProcessUncachedRequest(HttpListenerContext ctx, ResponseInfo responseInfo) - { - var totalContentLength = GetTotalContentLength(responseInfo); - - // By default, use chunked encoding if we don't know the content length - var useChunkedEncoding = UseChunkedEncoding == null ? (totalContentLength == null) : UseChunkedEncoding.Value; - - // Don't force this to true. HttpListener will default it to true if supported by the client. - if (!useChunkedEncoding) - { - ctx.Response.SendChunked = false; - } - - // Set the content length, if we know it - if (totalContentLength.HasValue) - { - ctx.Response.ContentLength64 = totalContentLength.Value; - } - - var compressResponse = responseInfo.CompressResponse && ClientSupportsCompression; - - // Add the compression header - if (compressResponse) - { - ctx.Response.AddHeader("Content-Encoding", CompressionMethod); - ctx.Response.AddHeader("Vary", "Accept-Encoding"); - } - - // 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 (responseInfo.DateLastModified.HasValue && (!responseInfo.Etag.HasValue || responseInfo.CacheDuration.Ticks > 0)) - { - ctx.Response.Headers[HttpResponseHeader.LastModified] = responseInfo.DateLastModified.Value.ToString("r"); - AddAgeHeader(ctx.Response, responseInfo); - } - - // Add caching headers - ConfigureCaching(ctx.Response, responseInfo); - - // Set the status code - ctx.Response.StatusCode = responseInfo.StatusCode; - - if (responseInfo.IsResponseValid) - { - // Finally, write the response data - var outputStream = ctx.Response.OutputStream; - - if (compressResponse) - { - if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase)) - { - CompressedStream = new DeflateStream(outputStream, CompressionLevel.Fastest, true); - } - else - { - CompressedStream = new GZipStream(outputStream, CompressionLevel.Fastest, true); - } - - outputStream = CompressedStream; - } - - await WriteResponseToOutputStream(outputStream, responseInfo, totalContentLength).ConfigureAwait(false); - } - else - { - ctx.Response.SendChunked = false; - } - } - - /// - /// Configures the caching. - /// - /// The response. - /// The response info. - private void ConfigureCaching(HttpListenerResponse response, ResponseInfo responseInfo) - { - if (responseInfo.CacheDuration.Ticks > 0) - { - response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(responseInfo.CacheDuration.TotalSeconds); - } - else if (responseInfo.Etag.HasValue) - { - response.Headers[HttpResponseHeader.CacheControl] = "public"; - } - else - { - response.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, must-revalidate"; - response.Headers[HttpResponseHeader.Pragma] = "no-cache, no-store, must-revalidate"; - } - - AddExpiresHeader(response, responseInfo); - } - - /// - /// Adds the expires header. - /// - /// The response. - /// The response info. - private void AddExpiresHeader(HttpListenerResponse response, ResponseInfo responseInfo) - { - if (responseInfo.CacheDuration.Ticks > 0) - { - response.Headers[HttpResponseHeader.Expires] = DateTime.UtcNow.Add(responseInfo.CacheDuration).ToString("r"); - } - else if (!responseInfo.Etag.HasValue) - { - response.Headers[HttpResponseHeader.Expires] = "-1"; - } - } - - /// - /// Adds the age header. - /// - /// The response. - /// The response info. - private void AddAgeHeader(HttpListenerResponse response, ResponseInfo responseInfo) - { - if (responseInfo.DateLastModified.HasValue) - { - response.Headers[HttpResponseHeader.Age] = Convert.ToInt32((DateTime.UtcNow - responseInfo.DateLastModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); - } - } - - /// - /// Writes the response to output stream. - /// - /// The stream. - /// The response info. - /// Length of the content. - /// Task. - protected abstract Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? contentLength); - - /// - /// Disposes the response stream. - /// - protected virtual void DisposeResponseStream() - { - if (CompressedStream != null) - { - try - { - CompressedStream.Dispose(); - } - catch (Exception ex) - { - //Logger.ErrorException("Error disposing compressed stream", ex); - } - } - - try - { - //HttpListenerContext.Response.OutputStream.Dispose(); - HttpListenerContext.Response.Close(); - } - catch (Exception ex) - { - //Logger.ErrorException("Error disposing response", ex); - } - } - - /// - /// Determines whether [is cache valid] [the specified if modified since]. - /// - /// If modified since. - /// Duration of the cache. - /// The date modified. - /// true if [is cache valid] [the specified if modified since]; otherwise, false. - private bool IsCacheValid(DateTime ifModifiedSince, TimeSpan cacheDuration, DateTime? dateModified) - { - if (dateModified.HasValue) - { - DateTime lastModified = NormalizeDateForComparison(dateModified.Value); - ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); - - return lastModified <= ifModifiedSince; - } - - DateTime cacheExpirationDate = ifModifiedSince.Add(cacheDuration); - - 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); - } - - /// - /// Gets the total length of the content. - /// - /// The response info. - /// System.Nullable{System.Int64}. - protected virtual long? GetTotalContentLength(ResponseInfo responseInfo) - { - return null; - } - - /// - /// Gets the response info. - /// - /// Task{ResponseInfo}. - protected abstract Task GetResponseInfo(); - - /// - /// Gets a bool query string param. - /// - /// The name. - /// true if XXXX, false otherwise - protected bool GetBoolQueryStringParam(string name) - { - var val = QueryString[name] ?? string.Empty; - - return val.Equals("1", StringComparison.OrdinalIgnoreCase) || val.Equals("true", StringComparison.OrdinalIgnoreCase); - } - - /// - /// The _form values - /// - private Hashtable _formValues; - - /// - /// Gets a value from form POST data - /// - /// The name. - /// Task{System.String}. - protected async Task GetFormValue(string name) - { - if (_formValues == null) - { - _formValues = await GetFormValues(HttpListenerContext.Request).ConfigureAwait(false); - } - - if (_formValues.ContainsKey(name)) - { - return _formValues[name].ToString(); - } - - return null; - } - - /// - /// Extracts form POST data from a request - /// - /// The request. - /// Task{Hashtable}. - private async Task GetFormValues(HttpListenerRequest request) - { - var formVars = new Hashtable(); - - if (request.HasEntityBody) - { - if (request.ContentType.IndexOf("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) != -1) - { - using (var requestBody = request.InputStream) - { - using (var reader = new StreamReader(requestBody, request.ContentEncoding)) - { - var s = await reader.ReadToEndAsync().ConfigureAwait(false); - - var pairs = s.Split('&'); - - foreach (var pair in pairs) - { - var index = pair.IndexOf('='); - - if (index != -1) - { - var name = pair.Substring(0, index); - var value = pair.Substring(index + 1); - formVars.Add(name, value); - } - } - } - } - } - } - - return formVars; - } - } - - /// - /// Class ResponseInfo - /// - public class ResponseInfo - { - /// - /// Gets or sets the type of the content. - /// - /// The type of the content. - public string ContentType { get; set; } - /// - /// Gets or sets the etag. - /// - /// The etag. - public Guid? Etag { get; set; } - /// - /// Gets or sets the date last modified. - /// - /// The date last modified. - public DateTime? DateLastModified { get; set; } - /// - /// Gets or sets the duration of the cache. - /// - /// The duration of the cache. - public TimeSpan CacheDuration { get; set; } - /// - /// Gets or sets a value indicating whether [compress response]. - /// - /// true if [compress response]; otherwise, false. - public bool CompressResponse { get; set; } - /// - /// Gets or sets the status code. - /// - /// The status code. - public int StatusCode { get; set; } - /// - /// Gets or sets a value indicating whether [supports byte range requests]. - /// - /// true if [supports byte range requests]; otherwise, false. - public bool SupportsByteRangeRequests { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public ResponseInfo() - { - CacheDuration = TimeSpan.FromTicks(0); - - CompressResponse = true; - - StatusCode = 200; - } - - /// - /// Gets a value indicating whether this instance is response valid. - /// - /// true if this instance is response valid; otherwise, false. - public bool IsResponseValid - { - get - { - return StatusCode >= 200 && StatusCode < 300; - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Net/Handlers/IHttpServerHandler.cs b/MediaBrowser.Common/Net/Handlers/IHttpServerHandler.cs deleted file mode 100644 index dadd614737..0000000000 --- a/MediaBrowser.Common/Net/Handlers/IHttpServerHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MediaBrowser.Common.Kernel; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net.Handlers -{ - /// - /// Interface IHttpServerHandler - /// - public interface IHttpServerHandler - { - /// - /// Initializes the specified kernel. - /// - /// The kernel. - void Initialize(IKernel kernel); - - /// - /// Handleses the request. - /// - /// The request. - /// true if XXXX, false otherwise - bool HandlesRequest(HttpListenerRequest request); - - /// - /// Processes the request. - /// - /// The CTX. - /// Task. - Task ProcessRequest(HttpListenerContext ctx); - } -} diff --git a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs deleted file mode 100644 index 3967d15c35..0000000000 --- a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs +++ /dev/null @@ -1,264 +0,0 @@ -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