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