aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations/HttpServer
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations/HttpServer')
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs44
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs102
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs158
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs7
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs32
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);
}
}