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.cs53
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs533
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs32
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs642
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs48
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs36
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs5
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs102
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs7
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs3
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs9
11 files changed, 801 insertions, 669 deletions
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs b/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs
new file mode 100644
index 000000000..93d224b8d
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs
@@ -0,0 +1,53 @@
+using MediaBrowser.Common;
+using ServiceStack.Configuration;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+ /// <summary>
+ /// Class ContainerAdapter
+ /// </summary>
+ class ContainerAdapter : IContainerAdapter, IRelease
+ {
+ /// <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>();
+ }
+
+ /// <summary>
+ /// Releases the specified instance.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
+ public void Release(object instance)
+ {
+ // Leave this empty so SS doesn't try to dispose our objects
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
new file mode 100644
index 000000000..c50588f95
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -0,0 +1,533 @@
+using Funq;
+using MediaBrowser.Common;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Logging;
+using ServiceStack;
+using ServiceStack.Configuration;
+using ServiceStack.Host;
+using ServiceStack.Host.Handlers;
+using ServiceStack.Host.HttpListener;
+using ServiceStack.Logging;
+using ServiceStack.Web;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+ public delegate void DelReceiveWebRequest(HttpListenerContext context);
+
+ public class HttpListenerHost : ServiceStackHost, IHttpServer
+ {
+ private string ServerName { get; set; }
+ private string HandlerPath { get; set; }
+ private string DefaultRedirectPath { get; set; }
+
+ private readonly ILogger _logger;
+ public string UrlPrefix { get; private set; }
+
+ private readonly List<IRestfulService> _restServices = new List<IRestfulService>();
+
+ private HttpListener Listener { get; set; }
+ protected bool IsStarted = false;
+
+ private readonly List<AutoResetEvent> _autoResetEvents = new List<AutoResetEvent>();
+
+ private readonly ContainerAdapter _containerAdapter;
+
+ private readonly ConcurrentDictionary<string, string> _localEndPoints = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
+
+ /// <summary>
+ /// Gets the local end points.
+ /// </summary>
+ /// <value>The local end points.</value>
+ public IEnumerable<string> LocalEndPoints
+ {
+ get { return _localEndPoints.Keys.ToList(); }
+ }
+
+ public HttpListenerHost(IApplicationHost applicationHost, ILogManager logManager, string serviceName, string handlerPath, string defaultRedirectPath, params Assembly[] assembliesWithServices)
+ : base(serviceName, assembliesWithServices)
+ {
+ // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4
+ Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0=");
+
+ DefaultRedirectPath = defaultRedirectPath;
+ ServerName = serviceName;
+ HandlerPath = handlerPath;
+
+ _logger = logManager.GetLogger("HttpServer");
+
+ LogManager.LogFactory = new ServerLogFactory(logManager);
+
+ _containerAdapter = new ContainerAdapter(applicationHost);
+
+ for (var i = 0; i < 2; i++)
+ {
+ _autoResetEvents.Add(new AutoResetEvent(false));
+ }
+ }
+
+ public override void Configure(Container container)
+ {
+ HostConfig.Instance.DefaultRedirectPath = DefaultRedirectPath;
+
+ HostConfig.Instance.MapExceptionToStatusCode = new Dictionary<Type, int>
+ {
+ {typeof (InvalidOperationException), 422},
+ {typeof (ResourceNotFoundException), 404},
+ {typeof (FileNotFoundException), 404},
+ {typeof (DirectoryNotFoundException), 404}
+ };
+
+ HostConfig.Instance.DebugMode = true;
+
+ HostConfig.Instance.LogFactory = LogManager.LogFactory;
+
+ // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
+ // Custom format allows images
+ HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat;
+
+ container.Adapter = _containerAdapter;
+
+ //Plugins.Add(new SwaggerFeature());
+ Plugins.Add(new CorsFeature());
+ HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
+ }
+
+ public override void OnAfterInit()
+ {
+ SetAppDomainData();
+
+ base.OnAfterInit();
+ }
+
+ public override void OnConfigLoad()
+ {
+ base.OnConfigLoad();
+
+ Config.HandlerFactoryPath = string.IsNullOrEmpty(HandlerPath)
+ ? null
+ : HandlerPath;
+
+ Config.MetadataRedirectPath = string.IsNullOrEmpty(HandlerPath)
+ ? "metadata"
+ : PathUtils.CombinePaths(HandlerPath, "metadata");
+ }
+
+ protected override ServiceController CreateServiceController(params Assembly[] assembliesWithServices)
+ {
+ var types = _restServices.Select(r => r.GetType()).ToArray();
+
+ return new ServiceController(this, () => types);
+ }
+
+ public virtual void SetAppDomainData()
+ {
+ //Required for Mono to resolve VirtualPathUtility and Url.Content urls
+ var domain = Thread.GetDomain(); // or AppDomain.Current
+ domain.SetData(".appDomain", "1");
+ domain.SetData(".appVPath", "/");
+ domain.SetData(".appPath", domain.BaseDirectory);
+ if (string.IsNullOrEmpty(domain.GetData(".appId") as string))
+ {
+ domain.SetData(".appId", "1");
+ }
+ if (string.IsNullOrEmpty(domain.GetData(".domainId") as string))
+ {
+ domain.SetData(".domainId", "1");
+ }
+ }
+
+ public override ServiceStackHost Start(string listeningAtUrlBase)
+ {
+ StartListener(listeningAtUrlBase);
+ return this;
+ }
+
+ /// <summary>
+ /// Starts the Web Service
+ /// </summary>
+ /// <param name="listeningAtUrlBase">
+ /// A Uri that acts as the base that the server is listening on.
+ /// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/
+ /// Note: the trailing slash is required! For more info see the
+ /// HttpListener.Prefixes property on MSDN.
+ /// </param>
+ protected void StartListener(string listeningAtUrlBase)
+ {
+ // *** Already running - just leave it in place
+ if (IsStarted)
+ return;
+
+ if (Listener == null)
+ Listener = new HttpListener();
+
+ HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(listeningAtUrlBase);
+
+ UrlPrefix = listeningAtUrlBase;
+
+ Listener.Prefixes.Add(listeningAtUrlBase);
+
+ _logger.Info("Adding HttpListener Prefixes");
+ Listener.Prefixes.Add(listeningAtUrlBase);
+
+ IsStarted = true;
+ _logger.Info("Starting HttpListner");
+ Listener.Start();
+
+ for (var i = 0; i < _autoResetEvents.Count; i++)
+ {
+ var index = i;
+ ThreadPool.QueueUserWorkItem(o => Listen(o, index));
+ }
+ }
+
+ private bool IsListening
+ {
+ get { return this.IsStarted && this.Listener != null && this.Listener.IsListening; }
+ }
+
+ // Loop here to begin processing of new requests.
+ private void Listen(object state, int index)
+ {
+ while (IsListening)
+ {
+ if (Listener == null) return;
+
+ try
+ {
+ Listener.BeginGetContext(c => ListenerCallback(c, index), Listener);
+
+ _autoResetEvents[index].WaitOne();
+ }
+ catch (Exception ex)
+ {
+ _logger.Error("Listen()", ex);
+ return;
+ }
+ if (Listener == null) return;
+ }
+ }
+
+ // Handle the processing of a request in here.
+ private void ListenerCallback(IAsyncResult asyncResult, int index)
+ {
+ var listener = asyncResult.AsyncState as HttpListener;
+ HttpListenerContext context = null;
+
+ if (listener == null) return;
+
+ try
+ {
+ if (!IsListening)
+ {
+ _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening");
+ return;
+ }
+ // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework,
+ // blocks until there is a request to be processed or some type of data is available.
+ context = listener.EndGetContext(asyncResult);
+ }
+ catch (Exception ex)
+ {
+ // You will get an exception when httpListener.Stop() is called
+ // because there will be a thread stopped waiting on the .EndGetContext()
+ // method, and again, that is just the way most Begin/End asynchronous
+ // methods of the .NET Framework work.
+ var errMsg = ex + ": " + IsListening;
+ _logger.Warn(errMsg);
+ return;
+ }
+ finally
+ {
+ // Once we know we have a request (or exception), we signal the other thread
+ // so that it calls the BeginGetContext() (or possibly exits if we're not
+ // listening any more) method to start handling the next incoming request
+ // while we continue to process this request on a different thread.
+ _autoResetEvents[index].Set();
+ }
+
+ if (context == null) return;
+
+ var date = DateTime.Now;
+
+ Task.Factory.StartNew(async () =>
+ {
+ try
+ {
+ LogHttpRequest(context, index);
+
+ if (context.Request.IsWebSocketRequest)
+ {
+ ProcessWebSocketRequest(context);
+ return;
+ }
+
+ var localPath = context.Request.Url.LocalPath;
+
+ if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
+ {
+ context.Response.Redirect(DefaultRedirectPath);
+ context.Response.Close();
+ return;
+ }
+ if (string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
+ {
+ context.Response.Redirect("mediabrowser/" + DefaultRedirectPath);
+ context.Response.Close();
+ return;
+ }
+ if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
+ {
+ context.Response.Redirect("mediabrowser/" + DefaultRedirectPath);
+ context.Response.Close();
+ return;
+ }
+ if (string.IsNullOrEmpty(localPath))
+ {
+ context.Response.Redirect("/mediabrowser/" + DefaultRedirectPath);
+ context.Response.Close();
+ return;
+ }
+
+ var url = context.Request.Url.ToString();
+ var endPoint = context.Request.RemoteEndPoint;
+
+ await ProcessRequestAsync(context).ConfigureAwait(false);
+
+ var duration = DateTime.Now - date;
+
+ if (EnableHttpRequestLogging)
+ {
+ LoggerUtils.LogResponse(_logger, context, url, endPoint, duration);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("ProcessRequest failure", ex);
+
+ HandleError(ex, context, _logger);
+ }
+
+ });
+ }
+
+ /// <summary>
+ /// Logs the HTTP request.
+ /// </summary>
+ /// <param name="ctx">The CTX.</param>
+ private void LogHttpRequest(HttpListenerContext ctx, int index)
+ {
+ var endpoint = ctx.Request.LocalEndPoint;
+
+ if (endpoint != null)
+ {
+ var address = endpoint.ToString();
+
+ _localEndPoints.GetOrAdd(address, address);
+ }
+
+ if (EnableHttpRequestLogging)
+ {
+ LoggerUtils.LogRequest(_logger, ctx, index);
+ }
+ }
+
+ /// <summary>
+ /// Processes the web socket request.
+ /// </summary>
+ /// <param name="ctx">The CTX.</param>
+ /// <returns>Task.</returns>
+ private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
+ {
+#if !__MonoCS__
+ try
+ {
+ var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
+ if (WebSocketConnected != null)
+ {
+ WebSocketConnected(this, new WebSocketConnectEventArgs { WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), Endpoint = ctx.Request.RemoteEndPoint.ToString() });
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("AcceptWebSocketAsync error", ex);
+ ctx.Response.StatusCode = 500;
+ ctx.Response.Close();
+ }
+#endif
+ }
+
+ public static void HandleError(Exception ex, HttpListenerContext context, ILogger logger)
+ {
+ try
+ {
+ var errorResponse = new ErrorResponse
+ {
+ ResponseStatus = new ResponseStatus
+ {
+ ErrorCode = ex.GetType().GetOperationName(),
+ Message = ex.Message,
+ StackTrace = ex.StackTrace,
+ }
+ };
+
+ var operationName = context.Request.GetOperationName();
+ var httpReq = context.ToRequest(operationName);
+ var httpRes = httpReq.Response;
+ var contentType = httpReq.ResponseContentType;
+
+ var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType);
+ if (serializer == null)
+ {
+ contentType = HostContext.Config.DefaultContentType;
+ serializer = HostContext.ContentTypes.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.Close();
+ }
+ catch (Exception errorEx)
+ {
+ logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx);
+ }
+ }
+
+ /// <summary>
+ /// Shut down the Web Service
+ /// </summary>
+ public void Stop()
+ {
+ if (Listener != null)
+ {
+ Listener.Prefixes.Remove(UrlPrefix);
+
+ Listener.Close();
+ }
+ }
+
+ /// <summary>
+ /// Overridable method that can be used to implement a custom hnandler
+ /// </summary>
+ /// <param name="context"></param>
+ protected Task ProcessRequestAsync(HttpListenerContext context)
+ {
+ if (string.IsNullOrEmpty(context.Request.RawUrl))
+ return ((object)null).AsTaskResult();
+
+ var operationName = context.Request.GetOperationName();
+
+ var httpReq = context.ToRequest(operationName);
+ var httpRes = httpReq.Response;
+ var handler = HttpHandlerFactory.GetHandler(httpReq);
+
+ var serviceStackHandler = handler as IServiceStackHandler;
+ if (serviceStackHandler != null)
+ {
+ var restHandler = serviceStackHandler as RestHandler;
+ if (restHandler != null)
+ {
+ httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
+ }
+
+ var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
+ task.ContinueWith(x => httpRes.Close());
+
+ return task;
+ }
+
+ return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
+ .AsTaskException();
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [enable HTTP request logging].
+ /// </summary>
+ /// <value><c>true</c> if [enable HTTP request logging]; otherwise, <c>false</c>.</value>
+ public bool EnableHttpRequestLogging { get; set; }
+
+ /// <summary>
+ /// Adds the rest handlers.
+ /// </summary>
+ /// <param name="services">The services.</param>
+ public void Init(IEnumerable<IRestfulService> services)
+ {
+ _restServices.AddRange(services);
+
+ ServiceController = CreateServiceController();
+
+ _logger.Info("Calling ServiceStack AppHost.Init");
+ Init();
+ }
+
+ /// <summary>
+ /// Releases the specified instance.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
+ public override void Release(object instance)
+ {
+ // Leave this empty so SS doesn't try to dispose our objects
+ }
+
+ private bool _disposed;
+ private readonly object _disposeLock = new object();
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed) return;
+ base.Dispose();
+
+ lock (_disposeLock)
+ {
+ if (_disposed) return;
+
+ if (disposing)
+ {
+ Stop();
+ }
+
+ //release unmanaged resources here...
+ _disposed = true;
+ }
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void StartServer(string urlPrefix)
+ {
+ Start(urlPrefix);
+ }
+
+ public bool SupportsWebSockets
+ {
+ get { return NativeWebSocket.IsSupported; }
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index 5a5a2dd04..798632af7 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -2,10 +2,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
-using ServiceStack.Common;
-using ServiceStack.Common.Web;
-using ServiceStack.ServiceHost;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -13,6 +11,8 @@ using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
+using ServiceStack;
+using ServiceStack.Web;
using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">result</exception>
- public object GetOptimizedResult<T>(IRequestContext requestContext, T result, IDictionary<string, string> responseHeaders = null)
+ public object GetOptimizedResult<T>(IRequest requestContext, T result, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (result == null)
@@ -156,7 +156,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// or
/// factoryFn
/// </exception>
- public object GetOptimizedResultUsingCache<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, IDictionary<string, string> responseHeaders = null)
+ public object GetOptimizedResultUsingCache<T>(IRequest requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
@@ -199,7 +199,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
- public object GetCachedResult<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string, string> responseHeaders = null)
+ public object GetCachedResult<T>(IRequest requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string, string> responseHeaders = null)
where T : class
{
if (cacheKey == Guid.Empty)
@@ -256,7 +256,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="cacheDuration">Duration of the cache.</param>
/// <param name="contentType">Type of the content.</param>
/// <returns>System.Object.</returns>
- private object GetCachedResult(IRequestContext requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
+ private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
{
responseHeaders["ETag"] = cacheKeyString;
@@ -287,7 +287,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">path</exception>
- public object GetStaticFileResult(IRequestContext requestContext, string path, FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
+ public object GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
{
if (string.IsNullOrEmpty(path))
{
@@ -332,7 +332,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <exception cref="System.ArgumentNullException">cacheKey
/// or
/// factoryFn</exception>
- public object GetStaticResult(IRequestContext requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
+ public object GetStaticResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
{
if (cacheKey == Guid.Empty)
{
@@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="requestContext">The request context.</param>
/// <param name="contentType">Type of the content.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- private bool ShouldCompressResponse(IRequestContext requestContext, string contentType)
+ private bool ShouldCompressResponse(IRequest requestContext, string contentType)
{
// It will take some work to support compression with byte range requests
if (!string.IsNullOrEmpty(requestContext.GetHeader("Range")))
@@ -428,9 +428,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="compress">if set to <c>true</c> [compress].</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task{IHasOptions}.</returns>
- private async Task<IHasOptions> GetStaticResult(IRequestContext requestContext, IDictionary<string, string> responseHeaders, string contentType, Func<Task<Stream>> factoryFn, bool compress, bool isHeadRequest)
+ private async Task<IHasOptions> GetStaticResult(IRequest requestContext, IDictionary<string, string> responseHeaders, string contentType, Func<Task<Stream>> factoryFn, bool compress, bool isHeadRequest)
{
- if (!compress || string.IsNullOrEmpty(requestContext.CompressionType))
+ var requestedCompressionType = requestContext.GetCompressionType();
+
+ if (!compress || string.IsNullOrEmpty(requestedCompressionType))
{
var stream = await factoryFn().ConfigureAwait(false);
@@ -471,9 +473,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return new HttpResult(content, contentType);
}
- var contents = content.Compress(requestContext.CompressionType);
+ var contents = content.Compress(requestedCompressionType);
- return new CompressedResult(contents, requestContext.CompressionType, contentType);
+ return new CompressedResult(contents, requestedCompressionType, contentType);
}
/// <summary>
@@ -548,7 +550,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
- private bool IsNotModified(IRequestContext requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
+ private bool IsNotModified(IRequest requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
{
var isNotModified = true;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
deleted file mode 100644
index 7d049549b..000000000
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
+++ /dev/null
@@ -1,642 +0,0 @@
-using Funq;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Logging;
-using ServiceStack.Api.Swagger;
-using ServiceStack.Common.Web;
-using ServiceStack.Configuration;
-using ServiceStack.Logging;
-using ServiceStack.ServiceHost;
-using ServiceStack.ServiceInterface.Cors;
-using ServiceStack.Text;
-using ServiceStack.WebHost.Endpoints;
-using ServiceStack.WebHost.Endpoints.Extensions;
-using ServiceStack.WebHost.Endpoints.Support;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.WebSockets;
-using System.Reactive.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.HttpServer
-{
- /// <summary>
- /// Class HttpServer
- /// </summary>
- public class HttpServer : HttpListenerBase, IHttpServer
- {
- /// <summary>
- /// The logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// Gets the URL prefix.
- /// </summary>
- /// <value>The URL prefix.</value>
- public string UrlPrefix { get; private set; }
-
- /// <summary>
- /// The _rest services
- /// </summary>
- private readonly List<IRestfulService> _restServices = new List<IRestfulService>();
-
- /// <summary>
- /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it
- /// </summary>
- /// <value>The HTTP listener.</value>
- private IDisposable HttpListener { get; set; }
-
- /// <summary>
- /// Occurs when [web socket connected].
- /// </summary>
- public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
-
- /// <summary>
- /// Gets the default redirect path.
- /// </summary>
- /// <value>The default redirect path.</value>
- private string DefaultRedirectPath { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the server.
- /// </summary>
- /// <value>The name of the server.</value>
- private string ServerName { get; set; }
-
- /// <summary>
- /// The _container adapter
- /// </summary>
- private readonly ContainerAdapter _containerAdapter;
-
- private readonly ConcurrentDictionary<string, string> _localEndPoints = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- /// <summary>
- /// Gets the local end points.
- /// </summary>
- /// <value>The local end points.</value>
- public IEnumerable<string> LocalEndPoints
- {
- get { return _localEndPoints.Keys.ToList(); }
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpServer" /> class.
- /// </summary>
- /// <param name="applicationHost">The application host.</param>
- /// <param name="logManager">The log manager.</param>
- /// <param name="serverName">Name of the server.</param>
- /// <param name="defaultRedirectpath">The default redirectpath.</param>
- /// <exception cref="System.ArgumentNullException">urlPrefix</exception>
- public HttpServer(IApplicationHost applicationHost, ILogManager logManager, string serverName, string defaultRedirectpath)
- : base()
- {
- if (logManager == null)
- {
- throw new ArgumentNullException("logManager");
- }
- if (applicationHost == null)
- {
- throw new ArgumentNullException("applicationHost");
- }
- if (string.IsNullOrEmpty(serverName))
- {
- throw new ArgumentNullException("serverName");
- }
- if (string.IsNullOrEmpty(defaultRedirectpath))
- {
- throw new ArgumentNullException("defaultRedirectpath");
- }
-
- ServerName = serverName;
- DefaultRedirectPath = defaultRedirectpath;
- _logger = logManager.GetLogger("HttpServer");
-
- LogManager.LogFactory = new ServerLogFactory(logManager);
-
- EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath = null;
- EndpointHostConfig.Instance.MetadataRedirectPath = "metadata";
-
- _containerAdapter = new ContainerAdapter(applicationHost);
- }
-
- /// <summary>
- /// The us culture
- /// </summary>
- protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
- /// Configures the specified container.
- /// </summary>
- /// <param name="container">The container.</param>
- public override void Configure(Container container)
- {
- JsConfig.DateHandler = JsonDateHandler.ISO8601;
- JsConfig.ExcludeTypeInfo = true;
- JsConfig.IncludeNullValues = false;
-
- SetConfig(new EndpointHostConfig
- {
- DefaultRedirectPath = DefaultRedirectPath,
-
- MapExceptionToStatusCode = {
- { typeof(InvalidOperationException), 422 },
- { typeof(ResourceNotFoundException), 404 },
- { typeof(FileNotFoundException), 404 },
- { typeof(DirectoryNotFoundException), 404 }
- },
-
- DebugMode = true,
-
- ServiceName = ServerName,
-
- LogFactory = LogManager.LogFactory,
-
- // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
- // Custom format allows images
- EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat
- });
-
- container.Adapter = _containerAdapter;
-
- Plugins.Add(new SwaggerFeature());
- Plugins.Add(new CorsFeature());
-
- ResponseFilters.Add(FilterResponse);
- }
-
- /// <summary>
- /// Filters the response.
- /// </summary>
- /// <param name="req">The req.</param>
- /// <param name="res">The res.</param>
- /// <param name="dto">The dto.</param>
- private void FilterResponse(IHttpRequest req, IHttpResponse res, object dto)
- {
- // Try to prevent compatibility view
- res.AddHeader("X-UA-Compatible", "IE=Edge");
-
- var exception = dto as Exception;
-
- if (exception != null)
- {
- _logger.ErrorException("Error processing request for {0}", exception, req.RawUrl);
-
- if (!string.IsNullOrEmpty(exception.Message))
- {
- var error = exception.Message.Replace(Environment.NewLine, " ");
- error = RemoveControlCharacters(error);
-
- res.AddHeader("X-Application-Error-Code", error);
- }
- }
-
- if (dto is CompressedResult)
- {
- // Per Google PageSpeed
- // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed.
- // The correct version of the resource is delivered based on the client request header.
- // This is a good choice for applications that are singly homed and depend on public proxies for user locality.
- res.AddHeader("Vary", "Accept-Encoding");
- }
-
- var hasOptions = dto as IHasOptions;
-
- if (hasOptions != null)
- {
- // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy
- string contentLength;
-
- if (hasOptions.Options.TryGetValue("Content-Length", out contentLength) && !string.IsNullOrEmpty(contentLength))
- {
- var length = long.Parse(contentLength, UsCulture);
-
- if (length > 0)
- {
- var response = (HttpListenerResponse)res.OriginalResponse;
-
- response.ContentLength64 = length;
-
- // Disable chunked encoding. Technically this is only needed when using Content-Range, but
- // anytime we know the content length there's no need for it
- response.SendChunked = false;
- }
- }
- }
- }
-
- /// <summary>
- /// Removes the control characters.
- /// </summary>
- /// <param name="inString">The in string.</param>
- /// <returns>System.String.</returns>
- private static string RemoveControlCharacters(string inString)
- {
- if (inString == null) return null;
-
- var newString = new StringBuilder();
-
- foreach (var ch in inString)
- {
- if (!char.IsControl(ch))
- {
- newString.Append(ch);
- }
- }
- return newString.ToString();
- }
-
- /// <summary>
- /// Starts the Web Service
- /// </summary>
- /// <param name="urlBase">A Uri that acts as the base that the server is listening on.
- /// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/
- /// Note: the trailing slash is required! For more info see the
- /// HttpListener.Prefixes property on MSDN.</param>
- /// <exception cref="System.ArgumentNullException">urlBase</exception>
- public override void Start(string urlBase)
- {
- if (string.IsNullOrEmpty(urlBase))
- {
- throw new ArgumentNullException("urlBase");
- }
-
- // *** Already running - just leave it in place
- if (IsStarted)
- {
- return;
- }
-
- if (Listener == null)
- {
- _logger.Info("Creating HttpListner");
- Listener = new HttpListener();
- }
-
- EndpointHost.Config.ServiceStackHandlerFactoryPath = HttpListenerRequestWrapper.GetHandlerPathIfAny(urlBase);
-
- UrlPrefix = urlBase;
-
- _logger.Info("Adding HttpListener Prefixes");
- Listener.Prefixes.Add(urlBase);
-
- IsStarted = true;
- _logger.Info("Starting HttpListner");
- Listener.Start();
-
- _logger.Info("Creating HttpListner observable stream");
- HttpListener = CreateObservableStream().Subscribe(ProcessHttpRequestAsync);
- }
-
- /// <summary>
- /// Creates the observable stream.
- /// </summary>
- /// <returns>IObservable{HttpListenerContext}.</returns>
- private IObservable<HttpListenerContext> CreateObservableStream()
- {
- return Observable.Create<HttpListenerContext>(obs =>
- Observable.FromAsync(() => Listener.GetContextAsync())
- .Subscribe(obs))
- .Repeat()
- .Retry()
- .Publish()
- .RefCount();
- }
-
- /// <summary>
- /// Processes incoming http requests by routing them to the appropiate handler
- /// </summary>
- /// <param name="context">The CTX.</param>
- private async void ProcessHttpRequestAsync(HttpListenerContext context)
- {
- var date = DateTime.Now;
-
- LogHttpRequest(context);
-
- if (context.Request.IsWebSocketRequest)
- {
- await ProcessWebSocketRequest(context).ConfigureAwait(false);
- return;
- }
-
- var localPath = context.Request.Url.LocalPath;
-
- if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
- {
- context.Response.Redirect(DefaultRedirectPath);
- context.Response.Close();
- return;
- }
- if (string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
- {
- context.Response.Redirect("mediabrowser/" + DefaultRedirectPath);
- context.Response.Close();
- return;
- }
- if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
- {
- context.Response.Redirect("mediabrowser/" + DefaultRedirectPath);
- context.Response.Close();
- return;
- }
- if (string.IsNullOrEmpty(localPath))
- {
- context.Response.Redirect("/mediabrowser/" + DefaultRedirectPath);
- context.Response.Close();
- return;
- }
-
- RaiseReceiveWebRequest(context);
-
- await Task.Factory.StartNew(() =>
- {
- try
- {
- var url = context.Request.Url.ToString();
- var endPoint = context.Request.RemoteEndPoint;
-
- ProcessRequest(context);
-
- var duration = DateTime.Now - date;
-
- LogResponse(context, url, endPoint, duration);
-
- }
- catch (Exception ex)
- {
- _logger.ErrorException("ProcessRequest failure", ex);
- }
-
- }).ConfigureAwait(false);
- }
-
- /// <summary>
- /// Processes the web socket request.
- /// </summary>
- /// <param name="ctx">The CTX.</param>
- /// <returns>Task.</returns>
- private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
- {
- #if __MonoCS__
- #else
- try
- {
- var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
- if (WebSocketConnected != null)
- {
- WebSocketConnected(this, new WebSocketConnectEventArgs { WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), Endpoint = ctx.Request.RemoteEndPoint.ToString() });
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("AcceptWebSocketAsync error", ex);
- ctx.Response.StatusCode = 500;
- ctx.Response.Close();
- }
- #endif
- }
-
- /// <summary>
- /// Logs the HTTP request.
- /// </summary>
- /// <param name="ctx">The CTX.</param>
- private void LogHttpRequest(HttpListenerContext ctx)
- {
- var endpoint = ctx.Request.LocalEndPoint;
-
- if (endpoint != null)
- {
- var address = endpoint.ToString();
-
- _localEndPoints.GetOrAdd(address, address);
- }
-
- if (EnableHttpRequestLogging)
- {
- var log = new StringBuilder();
-
- log.AppendLine("Url: " + ctx.Request.Url);
- log.AppendLine("Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
-
- var type = ctx.Request.IsWebSocketRequest ? "Web Socket" : "HTTP " + ctx.Request.HttpMethod;
-
- _logger.LogMultiline(type + " request received from " + ctx.Request.RemoteEndPoint, LogSeverity.Debug, log);
- }
- }
-
- /// <summary>
- /// Overridable method that can be used to implement a custom hnandler
- /// </summary>
- /// <param name="context">The context.</param>
- /// <exception cref="System.NotImplementedException">Cannot execute handler: + handler + at PathInfo: + httpReq.PathInfo</exception>
- protected override void ProcessRequest(HttpListenerContext context)
- {
- if (string.IsNullOrEmpty(context.Request.RawUrl)) return;
-
- var operationName = context.Request.GetOperationName();
-
- var httpReq = new HttpListenerRequestWrapper(operationName, context.Request);
- var httpRes = new HttpListenerResponseWrapper(context.Response);
- var handler = ServiceStackHttpHandlerFactory.GetHandler(httpReq);
-
- var serviceStackHandler = handler as IServiceStackHttpHandler;
-
- if (serviceStackHandler != null)
- {
- var restHandler = serviceStackHandler as RestHandler;
- if (restHandler != null)
- {
- httpReq.OperationName = operationName = restHandler.RestPath.RequestType.Name;
- }
- serviceStackHandler.ProcessRequest(httpReq, httpRes, operationName);
- return;
- }
-
- throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
- }
-
- /// <summary>
- /// Logs the response.
- /// </summary>
- /// <param name="ctx">The CTX.</param>
- /// <param name="url">The URL.</param>
- /// <param name="endPoint">The end point.</param>
- /// <param name="duration">The duration.</param>
- private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration)
- {
- if (!EnableHttpRequestLogging)
- {
- return;
- }
-
- var statusCode = ctx.Response.StatusCode;
-
- var log = new StringBuilder();
-
- log.AppendLine(string.Format("Url: {0}", url));
-
- log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
-
- var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds);
-
- var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime;
-
- _logger.LogMultiline(msg, LogSeverity.Debug, log);
- }
-
- /// <summary>
- /// Creates the service manager.
- /// </summary>
- /// <param name="assembliesWithServices">The assemblies with services.</param>
- /// <returns>ServiceManager.</returns>
- protected override ServiceManager CreateServiceManager(params Assembly[] assembliesWithServices)
- {
- var types = _restServices.Select(r => r.GetType()).ToArray();
-
- return new ServiceManager(new Container(), new ServiceController(() => types));
- }
-
- /// <summary>
- /// Shut down the Web Service
- /// </summary>
- public override void Stop()
- {
- if (HttpListener != null)
- {
- HttpListener.Dispose();
- HttpListener = null;
- }
-
- if (Listener != null)
- {
- Listener.Prefixes.Remove(UrlPrefix);
- }
-
- base.Stop();
- }
-
- /// <summary>
- /// The _supports native web socket
- /// </summary>
- private bool? _supportsNativeWebSocket;
-
- /// <summary>
- /// Gets a value indicating whether [supports web sockets].
- /// </summary>
- /// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
- public bool SupportsWebSockets
- {
- get
- {
- #if __MonoCS__
- return false;
- #else
- #endif
-
- if (!_supportsNativeWebSocket.HasValue)
- {
- try
- {
- new ClientWebSocket();
-
- _supportsNativeWebSocket = true;
- }
- catch (PlatformNotSupportedException)
- {
- _supportsNativeWebSocket = false;
- }
- }
-
- return _supportsNativeWebSocket.Value;
- }
- }
-
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable HTTP request logging].
- /// </summary>
- /// <value><c>true</c> if [enable HTTP request logging]; otherwise, <c>false</c>.</value>
- public bool EnableHttpRequestLogging { get; set; }
-
- /// <summary>
- /// Adds the rest handlers.
- /// </summary>
- /// <param name="services">The services.</param>
- public void Init(IEnumerable<IRestfulService> services)
- {
- _restServices.AddRange(services);
-
- _logger.Info("Calling EndpointHost.ConfigureHost");
-
- EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager());
-
- _logger.Info("Calling ServiceStack AppHost.Init");
- Init();
- }
-
- /// <summary>
- /// Releases the specified instance.
- /// </summary>
- /// <param name="instance">The instance.</param>
- public override void Release(object instance)
- {
- // Leave this empty so SS doesn't try to dispose our objects
- }
- }
-
- /// <summary>
- /// Class ContainerAdapter
- /// </summary>
- class ContainerAdapter : IContainerAdapter, IRelease
- {
- /// <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>();
- }
-
- /// <summary>
- /// Releases the specified instance.
- /// </summary>
- /// <param name="instance">The instance.</param>
- public void Release(object instance)
- {
- // Leave this empty so SS doesn't try to dispose our objects
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
new file mode 100644
index 000000000..8fe1297c7
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
@@ -0,0 +1,48 @@
+using MediaBrowser.Model.Logging;
+using System;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+ public static class LoggerUtils
+ {
+ public static void LogRequest(ILogger logger, HttpListenerContext ctx, int workerIndex)
+ {
+ var log = new StringBuilder();
+
+ log.AppendLine("Url: " + ctx.Request.Url);
+ log.AppendLine("Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
+
+ var type = ctx.Request.IsWebSocketRequest ? "Web Socket" : "HTTP " + ctx.Request.HttpMethod;
+
+ logger.LogMultiline(type + " request received on worker " + workerIndex + " from " + ctx.Request.RemoteEndPoint, LogSeverity.Debug, log);
+ }
+
+ /// <summary>
+ /// Logs the response.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="ctx">The CTX.</param>
+ /// <param name="url">The URL.</param>
+ /// <param name="endPoint">The end point.</param>
+ /// <param name="duration">The duration.</param>
+ public static void LogResponse(ILogger logger, HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration)
+ {
+ var statusCode = ctx.Response.StatusCode;
+
+ var log = new StringBuilder();
+
+ log.AppendLine(string.Format("Url: {0}", url));
+
+ log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
+
+ var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds);
+
+ var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime;
+
+ logger.LogMultiline(msg, LogSeverity.Debug, log);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
index a40dff5a4..ff822a4e6 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
@@ -184,5 +184,41 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
+
+ /// <summary>
+ /// The _supports native web socket
+ /// </summary>
+ private static bool? _supportsNativeWebSocket;
+
+ /// <summary>
+ /// Gets a value indicating whether [supports web sockets].
+ /// </summary>
+ /// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
+ public static bool IsSupported
+ {
+ get
+ {
+#if __MonoCS__
+ return false;
+#else
+#endif
+
+ if (!_supportsNativeWebSocket.HasValue)
+ {
+ try
+ {
+ new ClientWebSocket();
+
+ _supportsNativeWebSocket = true;
+ }
+ catch (PlatformNotSupportedException)
+ {
+ _supportsNativeWebSocket = false;
+ }
+ }
+
+ return _supportsNativeWebSocket.Value;
+ }
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 3956eb69d..312e718e1 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -1,5 +1,4 @@
-using ServiceStack.Service;
-using ServiceStack.ServiceHost;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -197,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public string ContentType { get; set; }
- public IRequestContext RequestContext { get; set; }
+ public IRequest RequestContext { get; set; }
public object Response { get; set; }
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
new file mode 100644
index 000000000..520f03561
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -0,0 +1,102 @@
+using MediaBrowser.Model.Logging;
+using ServiceStack;
+using ServiceStack.Web;
+using System;
+using System.Globalization;
+using System.Net;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+ public class ResponseFilter
+ {
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ private readonly ILogger _logger;
+
+ public ResponseFilter(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ /// <summary>
+ /// Filters the response.
+ /// </summary>
+ /// <param name="req">The req.</param>
+ /// <param name="res">The res.</param>
+ /// <param name="dto">The dto.</param>
+ public void FilterResponse(IRequest req, IResponse res, object dto)
+ {
+ // Try to prevent compatibility view
+ res.AddHeader("X-UA-Compatible", "IE=Edge");
+
+ var exception = dto as Exception;
+
+ if (exception != null)
+ {
+ _logger.ErrorException("Error processing request for {0}", exception, req.RawUrl);
+
+ if (!string.IsNullOrEmpty(exception.Message))
+ {
+ var error = exception.Message.Replace(Environment.NewLine, " ");
+ error = RemoveControlCharacters(error);
+
+ res.AddHeader("X-Application-Error-Code", error);
+ }
+ }
+
+ if (dto is CompressedResult)
+ {
+ // Per Google PageSpeed
+ // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed.
+ // The correct version of the resource is delivered based on the client request header.
+ // This is a good choice for applications that are singly homed and depend on public proxies for user locality.
+ res.AddHeader("Vary", "Accept-Encoding");
+ }
+
+ var hasOptions = dto as IHasOptions;
+
+ if (hasOptions != null)
+ {
+ // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy
+ string contentLength;
+
+ if (hasOptions.Options.TryGetValue("Content-Length", out contentLength) && !string.IsNullOrEmpty(contentLength))
+ {
+ var length = long.Parse(contentLength, UsCulture);
+
+ if (length > 0)
+ {
+ var response = (HttpListenerResponse)res.OriginalResponse;
+
+ response.ContentLength64 = length;
+
+ // Disable chunked encoding. Technically this is only needed when using Content-Range, but
+ // anytime we know the content length there's no need for it
+ response.SendChunked = false;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes the control characters.
+ /// </summary>
+ /// <param name="inString">The in string.</param>
+ /// <returns>System.String.</returns>
+ private static string RemoveControlCharacters(string inString)
+ {
+ if (inString == null) return null;
+
+ var newString = new StringBuilder();
+
+ foreach (var ch in inString)
+ {
+ if (!char.IsControl(ch))
+ {
+ newString.Append(ch);
+ }
+ }
+ return newString.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
index e953a3c6d..57acddc43 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
@@ -1,5 +1,5 @@
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -15,11 +15,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="applicationHost">The application host.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="serverName">Name of the server.</param>
+ /// <param name="handlerPath">The handler path.</param>
/// <param name="defaultRedirectpath">The default redirectpath.</param>
/// <returns>IHttpServer.</returns>
- public static IHttpServer CreateServer(IApplicationHost applicationHost, ILogManager logManager, string serverName, string defaultRedirectpath)
+ public static IHttpServer CreateServer(IApplicationHost applicationHost, ILogManager logManager, string serverName, string handlerPath, string defaultRedirectpath)
{
- return new HttpServer(applicationHost, logManager, serverName, defaultRedirectpath);
+ return new HttpListenerHost(applicationHost, logManager, serverName, handlerPath, defaultRedirectpath);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
index 46d38ad14..a4e6f18bb 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Model.Logging;
-using ServiceStack.Service;
-using ServiceStack.ServiceHost;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
index c7c6ad706..8f8505933 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using ServiceStack.ServiceHost;
+using MediaBrowser.Controller.Net;
+using ServiceStack;
+using ServiceStack.Web;
using System.IO;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -40,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', Path.DirectorySeparatorChar));
- return ResultFactory.GetStaticFileResult(RequestContext, requestedFile);
+ return ResultFactory.GetStaticFileResult(Request, requestedFile);
}
/// <summary>
@@ -53,6 +54,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// Gets or sets the request context.
/// </summary>
/// <value>The request context.</value>
- public IRequestContext RequestContext { get; set; }
+ public IRequest Request { get; set; }
}
}