diff --git a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs
index c61768a6d..0427a2d06 100644
--- a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs
@@ -46,18 +46,15 @@ namespace MediaBrowser.Api.HttpHandlers
}
}
- public override DateTime? LastDateModified
+ protected override DateTime? GetLastDateModified()
{
- get
+ try
{
- try
- {
- return File.GetLastWriteTime(ImagePath);
- }
- catch
- {
- return null;
- }
+ return File.GetLastWriteTime(ImagePath);
+ }
+ catch
+ {
+ return base.GetLastDateModified();
}
}
diff --git a/MediaBrowser.Api/Plugin.cs b/MediaBrowser.Api/Plugin.cs
index b6b1c8095..fe061a48b 100644
--- a/MediaBrowser.Api/Plugin.cs
+++ b/MediaBrowser.Api/Plugin.cs
@@ -1,8 +1,8 @@
using System;
using System.ComponentModel.Composition;
+using System.Net;
using System.Reactive.Linq;
using MediaBrowser.Api.HttpHandlers;
-using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
@@ -22,78 +22,75 @@ namespace MediaBrowser.Api
{
var httpServer = Kernel.Instance.HttpServer;
- httpServer.Where(ctx => ctx.LocalPath.IndexOf("/api/", StringComparison.OrdinalIgnoreCase) != -1).Subscribe(ctx =>
+ httpServer.Where(ctx => ctx.Request.Url.LocalPath.IndexOf("/api/", StringComparison.OrdinalIgnoreCase) != -1).Subscribe(ctx =>
{
BaseHandler handler = GetHandler(ctx);
if (handler != null)
{
- ctx.Respond(handler);
+ handler.ProcessRequest(ctx);
}
});
}
- private BaseHandler GetHandler(RequestContext ctx)
+ private BaseHandler GetHandler(HttpListenerContext ctx)
{
- BaseHandler handler = null;
-
- string localPath = ctx.LocalPath;
+ string localPath = ctx.Request.Url.LocalPath;
if (localPath.EndsWith("/api/item", StringComparison.OrdinalIgnoreCase))
{
- handler = new ItemHandler();
+ return new ItemHandler();
}
else if (localPath.EndsWith("/api/image", StringComparison.OrdinalIgnoreCase))
{
- handler = new ImageHandler();
+ return new ImageHandler();
}
else if (localPath.EndsWith("/api/users", StringComparison.OrdinalIgnoreCase))
{
- handler = new UsersHandler();
+ return new UsersHandler();
}
else if (localPath.EndsWith("/api/genre", StringComparison.OrdinalIgnoreCase))
{
- handler = new GenreHandler();
+ return new GenreHandler();
}
else if (localPath.EndsWith("/api/genres", StringComparison.OrdinalIgnoreCase))
{
- handler = new GenresHandler();
+ return new GenresHandler();
}
else if (localPath.EndsWith("/api/studio", StringComparison.OrdinalIgnoreCase))
{
- handler = new StudioHandler();
+ return new StudioHandler();
}
else if (localPath.EndsWith("/api/studios", StringComparison.OrdinalIgnoreCase))
{
- handler = new StudiosHandler();
+ return new StudiosHandler();
}
else if (localPath.EndsWith("/api/recentlyaddeditems", StringComparison.OrdinalIgnoreCase))
{
- handler = new RecentlyAddedItemsHandler();
+ return new RecentlyAddedItemsHandler();
}
else if (localPath.EndsWith("/api/inprogressitems", StringComparison.OrdinalIgnoreCase))
{
- handler = new InProgressItemsHandler();
+ return new InProgressItemsHandler();
}
else if (localPath.EndsWith("/api/userconfiguration", StringComparison.OrdinalIgnoreCase))
{
- handler = new UserConfigurationHandler();
+ return new UserConfigurationHandler();
}
else if (localPath.EndsWith("/api/plugins", StringComparison.OrdinalIgnoreCase))
{
- handler = new PluginsHandler();
+ return new PluginsHandler();
}
else if (localPath.EndsWith("/api/pluginconfiguration", StringComparison.OrdinalIgnoreCase))
{
- handler = new PluginConfigurationHandler();
+ return new PluginConfigurationHandler();
}
-
- if (handler != null)
+ else if (localPath.EndsWith("/api/static", StringComparison.OrdinalIgnoreCase))
{
- handler.RequestContext = ctx;
+ return new StaticFileHandler();
}
- return handler;
+ return null;
}
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 7f6c66748..d5f6230e1 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -58,6 +58,7 @@
+
@@ -73,7 +74,6 @@
-
diff --git a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs
index 42d2f8190..5cb476e02 100644
--- a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs
+++ b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs
@@ -3,28 +3,17 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.Compression;
+using System.Linq;
using System.Net;
+using MediaBrowser.Common.Logging;
namespace MediaBrowser.Common.Net.Handlers
{
public abstract class BaseHandler
{
- ///
- /// Response headers
- ///
- public IDictionary Headers = new Dictionary();
-
private Stream CompressedStream { get; set; }
- public virtual bool UseChunkedEncoding
- {
- get
- {
- return true;
- }
- }
-
- public virtual long? ContentLength
+ public virtual bool? UseChunkedEncoding
{
get
{
@@ -32,6 +21,21 @@ namespace MediaBrowser.Common.Net.Handlers
}
}
+ private bool _TotalContentLengthDiscovered = false;
+ private long? _TotalContentLength = null;
+ public long? TotalContentLength
+ {
+ get
+ {
+ if (!_TotalContentLengthDiscovered)
+ {
+ _TotalContentLength = GetTotalContentLength();
+ }
+
+ return _TotalContentLength;
+ }
+ }
+
///
/// Returns true or false indicating if the handler writes to the stream asynchronously.
/// If so the subclass will be responsible for disposing the stream when complete.
@@ -44,29 +48,18 @@ namespace MediaBrowser.Common.Net.Handlers
}
}
- ///
- /// The action to write the response to the output stream
- ///
- public Action WriteStream
+ protected virtual bool SupportsByteRangeRequests
{
get
{
- return s =>
- {
- WriteReponse(s);
-
- if (!IsAsyncHandler)
- {
- DisposeResponseStream();
- }
- };
+ return false;
}
}
///
- /// The original RequestContext
+ /// The original HttpListenerContext
///
- public RequestContext RequestContext { get; set; }
+ protected HttpListenerContext HttpListenerContext { get; private set; }
///
/// The original QueryString
@@ -75,7 +68,54 @@ namespace MediaBrowser.Common.Net.Handlers
{
get
{
- return RequestContext.Request.QueryString;
+ return HttpListenerContext.Request.QueryString;
+ }
+ }
+
+ protected List> _RequestedRanges = null;
+ protected IEnumerable> RequestedRanges
+ {
+ get
+ {
+ if (_RequestedRanges == null)
+ {
+ _RequestedRanges = new List>();
+
+ if (IsRangeRequest)
+ {
+ // Example: bytes=0-,32-63
+ string[] ranges = HttpListenerContext.Request.Headers["Range"].Split('=')[1].Split(',');
+
+ foreach (string range in ranges)
+ {
+ string[] 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;
+ }
+ }
+
+ protected bool IsRangeRequest
+ {
+ get
+ {
+ return HttpListenerContext.Request.Headers.AllKeys.Contains("Range");
}
}
@@ -87,13 +127,7 @@ namespace MediaBrowser.Common.Net.Handlers
///
/// Gets the status code to include in the response headers
///
- public virtual int StatusCode
- {
- get
- {
- return 200;
- }
- }
+ protected int StatusCode { get; set; }
///
/// Gets the cache duration to include in the response headers
@@ -106,18 +140,25 @@ namespace MediaBrowser.Common.Net.Handlers
}
}
+ private bool _LastDateModifiedDiscovered = false;
+ private DateTime? _LastDateModified = null;
///
/// Gets the last date modified of the content being returned, if this can be determined.
/// This will be used to invalidate the cache, so it's not needed if CacheDuration is 0.
///
- public virtual DateTime? LastDateModified
+ public DateTime? LastDateModified
{
get
{
- return null;
+ if (!_LastDateModifiedDiscovered)
+ {
+ _LastDateModified = GetLastDateModified();
+ }
+
+ return _LastDateModified;
}
}
-
+
public virtual bool CompressResponse
{
get
@@ -130,7 +171,7 @@ namespace MediaBrowser.Common.Net.Handlers
{
get
{
- string enc = RequestContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
+ string enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
return enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1;
}
@@ -140,7 +181,7 @@ namespace MediaBrowser.Common.Net.Handlers
{
get
{
- string enc = RequestContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
+ string enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
if (enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -155,30 +196,127 @@ namespace MediaBrowser.Common.Net.Handlers
}
}
- protected virtual void PrepareResponseBeforeWriteOutput(HttpListenerResponse response)
+ public void ProcessRequest(HttpListenerContext ctx)
{
- // Don't force this to true. HttpListener will default it to true if supported by the client.
- if (!UseChunkedEncoding)
- {
- response.SendChunked = false;
- }
+ HttpListenerContext = ctx;
- if (ContentLength.HasValue)
- {
- response.ContentLength64 = ContentLength.Value;
- }
+ Logger.LogInfo("Http Server received request at: " + ctx.Request.Url.ToString());
+ Logger.LogInfo("Http Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
- if (CompressResponse && ClientSupportsCompression)
+ ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
+
+ ctx.Response.KeepAlive = true;
+
+ if (SupportsByteRangeRequests && IsRangeRequest)
{
- response.AddHeader("Content-Encoding", CompressionMethod);
+ ctx.Response.Headers["Accept-Ranges"] = "bytes";
}
+
+ // Set the initial status code
+ // When serving a range request, we need to return status code 206 to indicate a partial response body
+ StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
+
+ ctx.Response.ContentType = ContentType;
TimeSpan cacheDuration = CacheDuration;
-
+
+ if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
+ {
+ DateTime ifModifiedSince;
+
+ if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince))
+ {
+ // If the cache hasn't expired yet just return a 304
+ if (IsCacheValid(ifModifiedSince, cacheDuration, LastDateModified))
+ {
+ StatusCode = 304;
+ }
+ }
+ }
+
+ if (StatusCode == 200 || StatusCode == 206)
+ {
+ ProcessUncachedResponse(ctx, cacheDuration);
+ }
+ else
+ {
+ ctx.Response.StatusCode = StatusCode;
+ ctx.Response.SendChunked = false;
+ DisposeResponseStream();
+ }
+ }
+
+ private void ProcessUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
+ {
+ long? totalContentLength = TotalContentLength;
+
+ // By default, use chunked encoding if we don't know the content length
+ bool 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;
+ }
+
+ // Add the compression header
+ if (CompressResponse && ClientSupportsCompression)
+ {
+ ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
+ }
+
+ // Add caching headers
if (cacheDuration.Ticks > 0)
{
- CacheResponse(response, cacheDuration, LastDateModified);
+ CacheResponse(ctx.Response, cacheDuration, LastDateModified);
}
+
+ PrepareUncachedResponse(ctx, cacheDuration);
+
+ // Set the status code
+ ctx.Response.StatusCode = StatusCode;
+
+ if (StatusCode == 200 || StatusCode == 206)
+ {
+ // Finally, write the response data
+ Stream outputStream = ctx.Response.OutputStream;
+
+ if (CompressResponse && ClientSupportsCompression)
+ {
+ if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
+ {
+ CompressedStream = new DeflateStream(outputStream, CompressionLevel.Fastest, false);
+ }
+ else
+ {
+ CompressedStream = new GZipStream(outputStream, CompressionLevel.Fastest, false);
+ }
+
+ outputStream = CompressedStream;
+ }
+
+ WriteResponseToOutputStream(outputStream);
+
+ if (!IsAsyncHandler)
+ {
+ DisposeResponseStream();
+ }
+ }
+ else
+ {
+ ctx.Response.SendChunked = false;
+ DisposeResponseStream();
+ }
+ }
+
+ protected virtual void PrepareUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
+ {
}
private void CacheResponse(HttpListenerResponse response, TimeSpan duration, DateTime? dateModified)
@@ -190,29 +328,6 @@ namespace MediaBrowser.Common.Net.Handlers
response.Headers[HttpResponseHeader.LastModified] = lastModified.ToString("r");
}
- private void WriteReponse(Stream stream)
- {
- PrepareResponseBeforeWriteOutput(RequestContext.Response);
-
- if (CompressResponse && ClientSupportsCompression)
- {
- if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
- {
- CompressedStream = new DeflateStream(stream, CompressionLevel.Fastest, false);
- }
- else
- {
- CompressedStream = new GZipStream(stream, CompressionLevel.Fastest, false);
- }
-
- WriteResponseToOutputStream(CompressedStream);
- }
- else
- {
- WriteResponseToOutputStream(stream);
- }
- }
-
protected abstract void WriteResponseToOutputStream(Stream stream);
protected void DisposeResponseStream()
@@ -222,7 +337,45 @@ namespace MediaBrowser.Common.Net.Handlers
CompressedStream.Dispose();
}
- RequestContext.Response.OutputStream.Dispose();
+ HttpListenerContext.Response.OutputStream.Dispose();
+ }
+
+ 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.Now < cacheExpirationDate)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
+ ///
+ private DateTime NormalizeDateForComparison(DateTime date)
+ {
+ return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second);
+ }
+
+ protected virtual long? GetTotalContentLength()
+ {
+ return null;
+ }
+
+ protected virtual DateTime? GetLastDateModified()
+ {
+ return null;
}
}
}
\ No newline at end of file
diff --git a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
new file mode 100644
index 000000000..9c9912152
--- /dev/null
+++ b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Logging;
+
+namespace MediaBrowser.Common.Net.Handlers
+{
+ public class StaticFileHandler : BaseHandler
+ {
+ public string Path
+ {
+ get
+ {
+ return QueryString["path"];
+ }
+ }
+
+ private bool FileStreamDiscovered = false;
+ private FileStream _FileStream = null;
+ private FileStream FileStream
+ {
+ get
+ {
+ if (!FileStreamDiscovered)
+ {
+ try
+ {
+ _FileStream = File.OpenRead(Path);
+ }
+ catch (FileNotFoundException)
+ {
+ StatusCode = 404;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ StatusCode = 404;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ StatusCode = 403;
+ }
+ finally
+ {
+ FileStreamDiscovered = true;
+ }
+ }
+
+ return _FileStream;
+ }
+ }
+
+ protected override bool SupportsByteRangeRequests
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override bool CompressResponse
+ {
+ get
+ {
+ string contentType = 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()
+ {
+ try
+ {
+ return FileStream.Length;
+ }
+ catch
+ {
+ return base.GetTotalContentLength();
+ }
+ }
+
+ protected override DateTime? GetLastDateModified()
+ {
+ try
+ {
+ return File.GetLastWriteTime(Path);
+ }
+ catch
+ {
+ return base.GetLastDateModified();
+ }
+ }
+
+ protected override bool IsAsyncHandler
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override string ContentType
+ {
+ get
+ {
+ return MimeTypes.GetMimeType(Path);
+ }
+ }
+
+ protected async override void WriteResponseToOutputStream(Stream stream)
+ {
+ try
+ {
+ if (FileStream != null)
+ {
+ 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)
+ {
+ await ServeCompleteRangeRequest(requestedRange, stream);
+ }
+ else if (TotalContentLength.HasValue)
+ {
+ // This will have to buffer a portion of the content into memory
+ await ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream);
+ }
+ else
+ {
+ // This will have to buffer the entire content into memory
+ await ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream);
+ }
+ }
+ else
+ {
+ await FileStream.CopyToAsync(stream);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogException("WriteResponseToOutputStream", ex);
+ }
+ finally
+ {
+ if (FileStream != null)
+ {
+ FileStream.Dispose();
+ }
+
+ DisposeResponseStream();
+ }
+ }
+
+ ///
+ /// Handles a range request of "bytes=0-"
+ /// This will serve the complete content and add the content-range header
+ ///
+ private async 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)
+ {
+ FileStream.Position = rangeStart;
+ }
+
+ await FileStream.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(FileStream, 0, null);
+
+ 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));
+ }
+
+ ///
+ /// 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(FileStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength));
+
+ // 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));
+ }
+
+ ///
+ /// 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)
+ {
+ byte[] buffer = new byte[16 * 1024];
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ int read;
+ while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ await ms.WriteAsync(buffer, 0, read);
+ }
+ return ms.ToArray();
+ }
+ }
+ else
+ {
+ byte[] buffer = new byte[count.Value];
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ int read = await input.ReadAsync(buffer, 0, buffer.Length);
+
+ await ms.WriteAsync(buffer, 0, read);
+
+ return ms.ToArray();
+ }
+ }
+
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Net/HttpServer.cs b/MediaBrowser.Common/Net/HttpServer.cs
index fad8d13eb..9acccbf2d 100644
--- a/MediaBrowser.Common/Net/HttpServer.cs
+++ b/MediaBrowser.Common/Net/HttpServer.cs
@@ -4,10 +4,10 @@ using System.Reactive.Linq;
namespace MediaBrowser.Common.Net
{
- public class HttpServer : IObservable, IDisposable
+ public class HttpServer : IObservable, IDisposable
{
private readonly HttpListener listener;
- private readonly IObservable stream;
+ private readonly IObservable stream;
public HttpServer(string url)
{
@@ -17,12 +17,11 @@ namespace MediaBrowser.Common.Net
stream = ObservableHttpContext();
}
- private IObservable ObservableHttpContext()
+ private IObservable ObservableHttpContext()
{
- return Observable.Create(obs =>
+ return Observable.Create(obs =>
Observable.FromAsyncPattern(listener.BeginGetContext,
listener.EndGetContext)()
- .Select(c => new RequestContext(c))
.Subscribe(obs))
.Repeat()
.Retry()
@@ -34,7 +33,7 @@ namespace MediaBrowser.Common.Net
listener.Stop();
}
- public IDisposable Subscribe(IObserver observer)
+ public IDisposable Subscribe(IObserver observer)
{
return stream.Subscribe(observer);
}
diff --git a/MediaBrowser.Common/Net/RequestContext.cs b/MediaBrowser.Common/Net/RequestContext.cs
deleted file mode 100644
index 461f28601..000000000
--- a/MediaBrowser.Common/Net/RequestContext.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using System;
-using System.Linq;
-using System.Net;
-using MediaBrowser.Common.Logging;
-using MediaBrowser.Common.Net.Handlers;
-
-namespace MediaBrowser.Common.Net
-{
- public class RequestContext
- {
- public HttpListenerRequest Request { get; private set; }
- public HttpListenerResponse Response { get; private set; }
-
- public string LocalPath
- {
- get
- {
- return Request.Url.LocalPath;
- }
- }
-
- public RequestContext(HttpListenerContext context)
- {
- Response = context.Response;
- Request = context.Request;
- }
-
- public void Respond(BaseHandler handler)
- {
- Logger.LogInfo("Http Server received request at: " + Request.Url.ToString());
- Logger.LogInfo("Http Headers: " + string.Join(",", Request.Headers.AllKeys.Select(k => k + "=" + Request.Headers[k])));
-
- Response.AddHeader("Access-Control-Allow-Origin", "*");
-
- Response.KeepAlive = true;
-
- foreach (var header in handler.Headers)
- {
- Response.AddHeader(header.Key, header.Value);
- }
-
- int statusCode = handler.StatusCode;
- Response.ContentType = handler.ContentType;
-
- TimeSpan cacheDuration = handler.CacheDuration;
-
- if (Request.Headers.AllKeys.Contains("If-Modified-Since"))
- {
- DateTime ifModifiedSince;
-
- if (DateTime.TryParse(Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince))
- {
- // If the cache hasn't expired yet just return a 304
- if (IsCacheValid(ifModifiedSince, cacheDuration, handler.LastDateModified))
- {
- statusCode = 304;
- }
- }
- }
-
- Response.StatusCode = statusCode;
-
- if (statusCode == 200 || statusCode == 206)
- {
- handler.WriteStream(Response.OutputStream);
- }
- else
- {
- Response.SendChunked = false;
- Response.OutputStream.Dispose();
- }
- }
-
- 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.Now < cacheExpirationDate)
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
- ///
- private DateTime NormalizeDateForComparison(DateTime date)
- {
- return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second);
- }
-
- }
-}
\ No newline at end of file