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 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;
namespace MediaBrowser.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 IXmlSerializer _xmlSerializer;
///
/// Initializes a new instance of the class.
///
/// The log manager.
/// The file system.
/// The json serializer.
public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer)
{
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
_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, responseHeaders);
}
///
/// Gets the HTTP result.
///
/// The content.
/// Type of the content.
/// The response headers.
/// IHasHeaders.
private IHasHeaders GetHttpResult(object content, string contentType, 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);
}
}
}
if (responseHeaders == null)
{
responseHeaders = new Dictionary();
}
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 prefs = new RequestPreferences(request);
if (prefs.AcceptsDeflate)
return "deflate";
if (prefs.AcceptsGzip)
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)
{
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);
}
///
/// 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;
}
IHasHeaders httpResult;
var stream = result as Stream;
if (stream != null)
{
httpResult = new StreamWriter(stream, contentType, _logger);
}
else
{
// Otherwise wrap into an HttpResult
httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified);
}
AddResponseHeaders(httpResult, responseHeaders);
return httpResult;
}
///
/// 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