diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations/HttpServer')
5 files changed, 232 insertions, 111 deletions
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs b/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs deleted file mode 100644 index 235b62f69..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MediaBrowser.Common; -using ServiceStack.Configuration; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// <summary> - /// Class ContainerAdapter - /// </summary> - class ContainerAdapter : IContainerAdapter - { - /// <summary> - /// The _app host - /// </summary> - private readonly IApplicationHost _appHost; - - /// <summary> - /// Initializes a new instance of the <see cref="ContainerAdapter" /> class. - /// </summary> - /// <param name="appHost">The app host.</param> - public ContainerAdapter(IApplicationHost appHost) - { - _appHost = appHost; - } - /// <summary> - /// Resolves this instance. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <returns>``0.</returns> - public T Resolve<T>() - { - return _appHost.Resolve<T>(); - } - - /// <summary> - /// Tries the resolve. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <returns>``0.</returns> - public T TryResolve<T>() - { - return _appHost.TryResolve<T>(); - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 5d9d05034..d86e4da37 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,13 +1,10 @@ -using Funq; -using MediaBrowser.Common; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; using ServiceStack; using ServiceStack.Host; -using ServiceStack.Host.Handlers; using ServiceStack.Web; using System; using System.Collections.Generic; @@ -17,7 +14,6 @@ using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using Emby.Common.Implementations.Net; using Emby.Server.Implementations.HttpServer; @@ -29,6 +25,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Text; using SocketHttpListener.Net; @@ -47,8 +44,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer private IHttpListener _listener; - private readonly ContainerAdapter _containerAdapter; - public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected; public event EventHandler<WebSocketConnectingEventArgs> WebSocketConnecting; @@ -64,11 +59,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; + private readonly IJsonSerializer _jsonSerializer; + private readonly IXmlSerializer _xmlSerializer; + public HttpListenerHost(IServerApplicationHost applicationHost, ILogManager logManager, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider) + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) : base(serviceName, new Assembly[] { }) { _appHost = applicationHost; @@ -78,11 +76,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer _textEncoding = textEncoding; _socketFactory = socketFactory; _cryptoProvider = cryptoProvider; + _jsonSerializer = jsonSerializer; + _xmlSerializer = xmlSerializer; _config = config; _logger = logManager.GetLogger("HttpServer"); - - _containerAdapter = new ContainerAdapter(applicationHost); } public string GlobalResponse { get; set; } @@ -106,19 +104,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer {typeof (NotSupportedException), 500} }; - // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users - // Custom format allows images - HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.Xml | Feature.CustomFormat; - - Container.Adapter = _containerAdapter; - var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); foreach (var filter in requestFilters) { - HostContext.GlobalRequestFilters.Add(filter.Filter); + GlobalRequestFilters.Add(filter.Filter); } - HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); + GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } protected override ILogger Logger @@ -129,6 +121,21 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } + public override T Resolve<T>() + { + return _appHost.Resolve<T>(); + } + + public override T TryResolve<T>() + { + return _appHost.TryResolve<T>(); + } + + public override object CreateInstance(Type type) + { + return _appHost.CreateInstance(type); + } + public override void OnConfigLoad() { base.OnConfigLoad(); @@ -241,6 +248,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer { try { + _logger.ErrorException("Error processing request", ex); + var httpRes = httpReq.Response; if (httpRes.IsClosed) @@ -248,39 +257,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer return; } - var errorResponse = new ErrorResponse - { - ResponseStatus = new ResponseStatus - { - ErrorCode = ex.GetType().GetOperationName(), - Message = ex.Message, - StackTrace = ex.StackTrace - } - }; - - var contentType = httpReq.ResponseContentType; - - var serializer = ContentTypes.Instance.GetResponseSerializer(contentType); - if (serializer == null) - { - contentType = HostContext.Config.DefaultContentType; - serializer = ContentTypes.Instance.GetResponseSerializer(contentType); - } - - var httpError = ex as IHttpError; - if (httpError != null) - { - httpRes.StatusCode = httpError.Status; - httpRes.StatusDescription = httpError.StatusDescription; - } - else - { - httpRes.StatusCode = 500; - } - - httpRes.ContentType = contentType; + httpRes.StatusCode = 500; - serializer(httpReq, errorResponse, httpRes); + httpRes.ContentType = "text/html"; + httpRes.Write(ex.Message); httpRes.Close(); } @@ -571,7 +551,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer public static void RedirectToUrl(IResponse httpRes, string url) { httpRes.StatusCode = 302; - httpRes.AddHeader(HttpHeaders.Location, url); + httpRes.AddHeader("Location", url); } @@ -622,6 +602,26 @@ namespace MediaBrowser.Server.Implementations.HttpServer return routes.ToArray(); } + public override void SerializeToJson(object o, Stream stream) + { + _jsonSerializer.SerializeToStream(o, stream); + } + + public override void SerializeToXml(object o, Stream stream) + { + _xmlSerializer.SerializeToStream(o, stream); + } + + public override object DeserializeXml(Type type, Stream stream) + { + return _xmlSerializer.DeserializeFromStream(type, stream); + } + + public override object DeserializeJson(Type type, Stream stream) + { + return _jsonSerializer.DeserializeFromStream(stream, type); + } + private string NormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 4c251ba24..b013a0952 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -6,13 +6,17 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Net; +using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using System.Xml; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using ServiceStack; +using ServiceStack.Host; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter; @@ -30,6 +34,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; + private readonly IXmlSerializer _xmlSerializer; /// <summary> /// Initializes a new instance of the <see cref="HttpResultFactory" /> class. @@ -37,10 +42,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="logManager">The log manager.</param> /// <param name="fileSystem">The file system.</param> /// <param name="jsonSerializer">The json serializer.</param> - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; + _xmlSerializer = xmlSerializer; _logger = logManager.GetLogger("HttpResultFactory"); } @@ -130,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw new ArgumentNullException("result"); } - var optimizedResult = requestContext.ToOptimizedResult(result); + var optimizedResult = ToOptimizedResult(requestContext, result); if (responseHeaders == null) { @@ -152,7 +158,99 @@ namespace MediaBrowser.Server.Implementations.HttpServer return optimizedResult; } - + + public static string GetCompressionType(IRequest request) + { + var prefs = new RequestPreferences(request); + + if (prefs.AcceptsDeflate) + return "deflate"; + + if (prefs.AcceptsGzip) + return "gzip"; + + return null; + } + + /// <summary> + /// Returns the optimized result for the IRequestContext. + /// Does not use or store results in any cache. + /// </summary> + /// <param name="request"></param> + /// <param name="dto"></param> + /// <returns></returns> + public object ToOptimizedResult<T>(IRequest request, T dto) + { + request.Response.Dto = dto; + + var compressionType = GetCompressionType(request); + if (compressionType == null) + { + var contentType = request.ResponseContentType; + var contentTypeAttr = ContentFormat.GetEndpointAttributes(contentType); + + switch (contentTypeAttr) + { + case RequestAttributes.Xml: + return SerializeToXmlString(dto); + + case RequestAttributes.Json: + return _jsonSerializer.SerializeToString(dto); + } + } + + using (var ms = new MemoryStream()) + { + using (var compressionStream = GetCompressionStream(ms, compressionType)) + { + ContentTypes.Instance.SerializeToStream(request, dto, compressionStream); + compressionStream.Close(); + + var compressedBytes = ms.ToArray(); + + var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) + { + Status = request.Response.StatusCode + }; + + httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); + httpResult.Headers["Content-Encoding"] = compressionType; + + return httpResult; + } + } + } + + public static string SerializeToXmlString(object from) + { + using (var ms = new MemoryStream()) + { + var xwSettings = new XmlWriterSettings(); + xwSettings.Encoding = new UTF8Encoding(false); + xwSettings.OmitXmlDeclaration = false; + + using (var xw = XmlWriter.Create(ms, xwSettings)) + { + var serializer = new DataContractSerializer(from.GetType()); + serializer.WriteObject(xw, from); + xw.Flush(); + ms.Seek(0, SeekOrigin.Begin); + var reader = new StreamReader(ms); + return reader.ReadToEnd(); + } + } + } + + private static Stream GetCompressionStream(Stream outputStream, string compressionType) + { + if (compressionType == "deflate") + return new DeflateStream(outputStream, CompressionMode.Compress); + if (compressionType == "gzip") + return new GZipStream(outputStream, CompressionMode.Compress); + + throw new NotSupportedException(compressionType); + } + /// <summary> /// Gets the optimized result using cache. /// </summary> @@ -471,7 +569,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var contentType = options.ContentType; var responseHeaders = options.ResponseHeaders; - var requestedCompressionType = requestContext.GetCompressionType(); + var requestedCompressionType = GetCompressionType(requestContext); if (!compress || string.IsNullOrEmpty(requestedCompressionType)) { @@ -513,16 +611,64 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } - var contents = content.Compress(requestedCompressionType); + var contents = Compress(content, requestedCompressionType); responseHeaders["Content-Length"] = contents.Length.ToString(UsCulture); + responseHeaders["Content-Encoding"] = requestedCompressionType; if (isHeadRequest) { return GetHttpResult(new byte[] { }, contentType); } - return new CompressedResult(contents, requestedCompressionType, contentType); + return GetHttpResult(contents, contentType, responseHeaders); + } + + public static byte[] Compress(string text, string compressionType) + { + if (compressionType == "deflate") + return Deflate(text); + + if (compressionType == "gzip") + return GZip(text); + + throw new NotSupportedException(compressionType); + } + + public static byte[] Deflate(string text) + { + return Deflate(Encoding.UTF8.GetBytes(text)); + } + + public static byte[] Deflate(byte[] bytes) + { + // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream + // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream + using (var ms = new MemoryStream()) + using (var zipStream = new DeflateStream(ms, CompressionMode.Compress)) + { + zipStream.Write(bytes, 0, bytes.Length); + zipStream.Close(); + + return ms.ToArray(); + } + } + + public static byte[] GZip(string text) + { + return GZip(Encoding.UTF8.GetBytes(text)); + } + + public static byte[] GZip(byte[] buffer) + { + using (var ms = new MemoryStream()) + using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) + { + zipStream.Write(buffer, 0, buffer.Length); + zipStream.Close(); + + return ms.ToArray(); + } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs index 5da515900..abcf84abd 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Text; namespace MediaBrowser.Server.Implementations.HttpServer @@ -28,9 +29,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer string defaultRedirectpath, ITextEncoding textEncoding, ISocketFactory socketFactory, - ICryptoProvider cryptoProvider) + ICryptoProvider cryptoProvider, + IJsonSerializer json, + IXmlSerializer xml) { - return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, networkmanager, streamProvider, textEncoding, socketFactory, cryptoProvider); + return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, networkmanager, streamProvider, textEncoding, socketFactory, cryptoProvider, json, xml); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 6f44fcce7..95b2ccaba 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Text; using Emby.Server.Implementations.HttpServer.SocketSharp; -using Funq; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; @@ -19,7 +18,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { public partial class WebSocketSharpRequest : IHttpRequest { - public Container Container { get; set; } private readonly HttpListenerRequest request; private readonly IHttpResponse response; private readonly IMemoryStreamFactory _memoryStreamProvider; @@ -239,6 +237,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; private static string GetResponseContentType(IRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); @@ -246,7 +246,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp var acceptContentTypes = httpReq.AcceptTypes; var defaultContentType = httpReq.ContentType; - if (httpReq.HasAnyOfContentTypes(MimeTypes.FormUrlEncoded, MimeTypes.MultiPartFormData)) + if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { defaultContentType = HostContext.Config.DefaultContentType; } @@ -299,15 +299,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } - if (acceptContentTypes == null && httpReq.ContentType == MimeTypes.Soap11) + if (acceptContentTypes == null && httpReq.ContentType == Soap11) { - return MimeTypes.Soap11; + return Soap11; } //We could also send a '406 Not Acceptable', but this is allowed also return HostContext.Config.DefaultContentType; } + public const string Soap11 = "text/xml; charset=utf-8"; + public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) + { + if (contentTypes == null || request.ContentType == null) return false; + foreach (var contentType in contentTypes) + { + if (IsContentType(request, contentType)) return true; + } + return false; + } + + public static bool IsContentType(IRequest request, string contentType) + { + return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); + } + + public const string Xml = "application/xml"; private static string GetQueryStringContentType(IRequest httpReq) { var format = httpReq.QueryString["format"]; @@ -323,7 +340,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp format = format.LeftPart('.').ToLower(); if (format.Contains("json")) return "application/json"; - if (format.Contains("xml")) return MimeTypes.Xml; + if (format.Contains("xml")) return Xml; string contentType; ContentTypes.Instance.ContentTypeFormats.TryGetValue(format, out contentType); @@ -464,8 +481,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return httpMethod - ?? (httpMethod = Param(HttpHeaders.XHttpMethodOverride) - ?? request.HttpMethod); + ?? (httpMethod = request.HttpMethod); } } |
