using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
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 Emby.Server.Implementations.Services;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using ServiceStack;
using IRequest = MediaBrowser.Model.Services.IRequest;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter;
namespace Emby.Server.Implementations.HttpServer
{
///
/// Class HttpResultFactory
///
public class HttpResultFactory : IHttpResultFactory
{
///
/// The _logger
///
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IMemoryStreamFactory _memoryStreamFactory;
///
/// Initializes a new instance of the class.
///
public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory)
{
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_memoryStreamFactory = memoryStreamFactory;
_logger = logManager.GetLogger("HttpResultFactory");
}
///
/// Gets the result.
///
/// The content.
/// Type of the content.
/// The response headers.
/// System.Object.
public object GetResult(object content, string contentType, IDictionary responseHeaders = null)
{
return GetHttpResult(content, contentType, true, responseHeaders);
}
///
/// Gets the HTTP result.
///
private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null)
{
IHasHeaders result;
var stream = content as Stream;
if (stream != null)
{
result = new StreamWriter(stream, contentType, _logger);
}
else
{
var bytes = content as byte[];
if (bytes != null)
{
result = new StreamWriter(bytes, contentType, _logger);
}
else
{
var text = content as string;
if (text != null)
{
result = new StreamWriter(Encoding.UTF8.GetBytes(text), contentType, _logger);
}
else
{
result = new HttpResult(content, contentType, HttpStatusCode.OK);
}
}
}
if (responseHeaders == null)
{
responseHeaders = new Dictionary();
}
string expires;
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires))
{
responseHeaders["Expires"] = "-1";
}
AddResponseHeaders(result, responseHeaders);
return result;
}
///
/// Gets the optimized result.
///
///
/// The request context.
/// The result.
/// The response headers.
/// System.Object.
/// result
public object GetOptimizedResult(IRequest requestContext, T result, IDictionary responseHeaders = null)
where T : class
{
return GetOptimizedResultInternal(requestContext, result, true, responseHeaders);
}
private object GetOptimizedResultInternal(IRequest requestContext, T result, bool addCachePrevention, IDictionary responseHeaders = null)
where T : class
{
if (result == null)
{
throw new ArgumentNullException("result");
}
var optimizedResult = ToOptimizedResult(requestContext, result);
if (responseHeaders == null)
{
responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
if (addCachePrevention)
{
responseHeaders["Expires"] = "-1";
}
// Apply headers
var hasHeaders = optimizedResult as IHasHeaders;
if (hasHeaders != null)
{
AddResponseHeaders(hasHeaders, responseHeaders);
}
return optimizedResult;
}
public static string GetCompressionType(IRequest request)
{
var acceptEncoding = request.Headers["Accept-Encoding"];
if (!string.IsNullOrWhiteSpace(acceptEncoding))
{
if (acceptEncoding.Contains("deflate"))
return "deflate";
if (acceptEncoding.Contains("gzip"))
return "gzip";
}
return null;
}
///
/// Returns the optimized result for the IRequestContext.
/// Does not use or store results in any cache.
///
///
///
///
public object ToOptimizedResult(IRequest request, T dto)
{
var compressionType = GetCompressionType(request);
if (compressionType == null)
{
var contentType = request.ResponseContentType;
switch (GetRealContentType(contentType))
{
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
return SerializeToXmlString(dto);
case "application/json":
case "text/json":
return _jsonSerializer.SerializeToString(dto);
}
}
// Do not use the memoryStreamFactory here, they don't place nice with compression
using (var ms = new MemoryStream())
{
var contentType = request.ResponseContentType;
var writerFn = RequestHelper.GetResponseWriter(contentType);
writerFn(dto, ms);
ms.Position = 0;
var responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
return GetCompressedResult(ms, compressionType, responseHeaders, false, request.ResponseContentType).Result;
}
}
private static Stream GetCompressionStream(Stream outputStream, string compressionType)
{
if (compressionType == "deflate")
return new DeflateStream(outputStream, CompressionMode.Compress, true);
if (compressionType == "gzip")
return new GZipStream(outputStream, CompressionMode.Compress, true);
throw new NotSupportedException(compressionType);
}
public static string GetRealContentType(string contentType)
{
return contentType == null
? null
: contentType.Split(';')[0].ToLower().Trim();
}
private 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();
}
}
}
///
/// Gets the optimized result using cache.
///
///
/// The request context.
/// The cache key.
/// The last date modified.
/// Duration of the cache.
/// The factory fn.
/// The response headers.
/// System.Object.
/// cacheKey
/// or
/// factoryFn
public object GetOptimizedResultUsingCache(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, IDictionary responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
if (responseHeaders == null)
{
responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null);
if (result != null)
{
return result;
}
return GetOptimizedResultInternal(requestContext, factoryFn(), false, responseHeaders);
}
///
/// To the cached result.
///
///
/// The request context.
/// The cache key.
/// The last date modified.
/// Duration of the cache.
/// The factory fn.
/// Type of the content.
/// The response headers.
/// System.Object.
/// cacheKey
public object GetCachedResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType, IDictionary responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
{
throw new ArgumentNullException("cacheKey");
}
if (factoryFn == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N");
if (responseHeaders == null)
{
responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType);
if (result != null)
{
return result;
}
result = factoryFn();
// Apply caching headers
var hasHeaders = result as IHasHeaders;
if (hasHeaders != null)
{
AddResponseHeaders(hasHeaders, responseHeaders);
return hasHeaders;
}
return GetHttpResult(result, contentType, false, responseHeaders);
}
///
/// Pres the process optimized result.
///
/// The request context.
/// The responseHeaders.
/// The cache key.
/// The cache key string.
/// The last date modified.
/// Duration of the cache.
/// Type of the content.
/// System.Object.
private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
{
responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
{
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified);
AddResponseHeaders(result, responseHeaders);
return result;
}
AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
return null;
}
public Task