rework result factory

This commit is contained in:
Luke Pulverenti
2016-11-10 09:41:24 -05:00
parent c1ae3ec2ce
commit 227dd0a42d
83 changed files with 715 additions and 811 deletions

View File

@@ -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>();
}
}
}

View File

@@ -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
}
};
httpRes.StatusCode = 500;
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;
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))

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}