diff options
Diffstat (limited to 'Emby.Server.Implementations/HttpServer')
7 files changed, 234 insertions, 85 deletions
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs new file mode 100644 index 000000000..dbaf97b1e --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class FileWriter : IHttpResult + { + private ILogger Logger { get; set; } + + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } + + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } + + public Action OnComplete { get; set; } + public Action OnError { get; set; } + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + public List<Cookie> Cookies { get; private set; } + + public FileShareMode FileShare { get; set; } + + /// <summary> + /// The _options + /// </summary> + private readonly IDictionary<string, string> _options = new Dictionary<string, string>(); + /// <summary> + /// Gets the options. + /// </summary> + /// <value>The options.</value> + public IDictionary<string, string> Headers + { + get { return _options; } + } + + public string Path { get; set; } + + public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem) + { + if (string.IsNullOrEmpty(contentType)) + { + throw new ArgumentNullException("contentType"); + } + + Path = path; + Logger = logger; + RangeHeader = rangeHeader; + + Headers["Content-Type"] = contentType; + + TotalContentLength = fileSystem.GetFileInfo(path).Length; + + if (string.IsNullOrWhiteSpace(rangeHeader)) + { + Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + StatusCode = HttpStatusCode.OK; + } + else + { + Headers["Accept-Ranges"] = "bytes"; + StatusCode = HttpStatusCode.PartialContent; + SetRangeValues(); + } + + FileShare = FileShareMode.Read; + Cookies = new List<Cookie>(); + } + + /// <summary> + /// Sets the range values. + /// </summary> + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + // Content-Length is the length of what we're serving, not the original content + Headers["Content-Length"] = RangeLength.ToString(UsCulture); + Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + } + + /// <summary> + /// The _requested ranges + /// </summary> + private List<KeyValuePair<long, long?>> _requestedRanges; + /// <summary> + /// Gets the requested ranges. + /// </summary> + /// <value>The requested ranges.</value> + protected List<KeyValuePair<long, long?>> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List<KeyValuePair<long, long?>>(); + + // Example: bytes=0-,32-63 + var ranges = RangeHeader.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], UsCulture); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1], UsCulture); + } + + _requestedRanges.Add(new KeyValuePair<long, long?>(start, end)); + } + } + + return _requestedRanges; + } + } + + public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) + { + Logger.Info("Transmit file {0}", Path); + await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); + return; + } + + await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + + } +} diff --git a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs b/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs deleted file mode 100644 index 819ede1ab..000000000 --- a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.HttpServer -{ - /// <summary> - /// Class GetDashboardResource - /// </summary> - [Route("/swagger-ui/{ResourceName*}", "GET")] - public class GetSwaggerResource - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string ResourceName { get; set; } - } -}
\ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b5a3c2992..6d15cc619 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; + private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; private readonly ICertificate _certificate; @@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets) - : base() + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) { Instance = this; @@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; + _fileSystem = fileSystem; _config = config; _logger = logger; @@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer _cryptoProvider, _streamFactory, _enableDualModeSockets, - GetRequest); + GetRequest, + _fileSystem); } private IHttpRequest GetRequest(HttpListenerContext httpContext) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 6bfd83110..310161d41 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer { throw new ArgumentNullException("cacheKey"); } - if (options.ContentFactory == null) - { - throw new ArgumentNullException("factoryFn"); - } var key = cacheKey.ToString("N"); @@ -560,30 +556,44 @@ namespace Emby.Server.Implementations.HttpServer { var rangeHeader = requestContext.Headers.Get("Range"); - var stream = await factoryFn().ConfigureAwait(false); + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) + { + return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + { + OnComplete = options.OnComplete, + OnError = options.OnError, + FileShare = options.FileShare + }; + } if (!string.IsNullOrEmpty(rangeHeader)) { + var stream = await factoryFn().ConfigureAwait(false); + return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; } + else + { + var stream = await factoryFn().ConfigureAwait(false); - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - if (isHeadRequest) - { - stream.Dispose(); + if (isHeadRequest) + { + stream.Dispose(); - return GetHttpResult(new byte[] { }, contentType, true); - } + return GetHttpResult(new byte[] { }, contentType, true); + } - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; + return new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; + } } using (var stream = await factoryFn().ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 652fc4f83..b11b2fe88 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; private readonly IStreamFactory _streamFactory; + private readonly IFileSystem _fileSystem; private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; private readonly bool _enableDualMode; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory) + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem) { _logger = logger; _certificate = certificate; @@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _streamFactory = streamFactory; _enableDualMode = enableDualMode; _httpRequestFactory = httpRequestFactory; + _fileSystem = fileSystem; } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } @@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable<string> urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider); + _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem); _listener.EnableDualMode = _enableDualMode; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 36f795411..fd30b227f 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using SocketHttpListener.Net; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; @@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void ClearCookies() { } + + public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) + { + return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); + } } } diff --git a/Emby.Server.Implementations/HttpServer/SwaggerService.cs b/Emby.Server.Implementations/HttpServer/SwaggerService.cs deleted file mode 100644 index d41946645..000000000 --- a/Emby.Server.Implementations/HttpServer/SwaggerService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using System.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.HttpServer -{ - public class SwaggerService : IService, IRequiresRequest - { - private readonly IServerApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - public SwaggerService(IServerApplicationPaths appPaths, IFileSystem fileSystem, IHttpResultFactory resultFactory) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - _resultFactory = resultFactory; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSwaggerResource request) - { - var swaggerDirectory = Path.Combine(_appPaths.ApplicationResourcesPath, "swagger-ui"); - - var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', _fileSystem.DirectorySeparatorChar)); - - return _resultFactory.GetStaticFileResult(Request, requestedFile).Result; - } - - /// <summary> - /// Gets or sets the result factory. - /// </summary> - /// <value>The result factory.</value> - private readonly IHttpResultFactory _resultFactory; - - /// <summary> - /// Gets or sets the request context. - /// </summary> - /// <value>The request context.</value> - public IRequest Request { get; set; } - } -} |
