using Amib.Threading; using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.HttpServer.NetListener; using ServiceStack; using ServiceStack.Api.Swagger; 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 class HttpListenerHost : ServiceStackHost, IHttpServer { private string HandlerPath { get; set; } private string DefaultRedirectPath { get; set; } private readonly ILogger _logger; public IEnumerable UrlPrefixes { get; private set; } private readonly List _restServices = new List(); private IHttpListener _listener; private readonly SmartThreadPool _threadPoolManager; private const int IdleTimeout = 300; private readonly ContainerAdapter _containerAdapter; private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); public event EventHandler WebSocketConnected; /// /// Gets the local end points. /// /// The local end points. public IEnumerable LocalEndPoints { get { return _listener == null ? new List() : _listener.LocalEndPoints; } } public HttpListenerHost(IApplicationHost applicationHost, ILogManager logManager, string serviceName, string handlerPath, string defaultRedirectPath, params Assembly[] assembliesWithServices) : base(serviceName, assembliesWithServices) { DefaultRedirectPath = defaultRedirectPath; HandlerPath = handlerPath; _logger = logManager.GetLogger("HttpServer"); _containerAdapter = new ContainerAdapter(applicationHost); _threadPoolManager = new SmartThreadPool(IdleTimeout, maxWorkerThreads: Math.Max(16, Environment.ProcessorCount * 2)); } public override void Configure(Container container) { HostConfig.Instance.DefaultRedirectPath = DefaultRedirectPath; HostConfig.Instance.MapExceptionToStatusCode = new Dictionary { {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(allowedHeaders: "Content-Type, Authorization")); //Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { // new SessionAuthProvider(_containerAdapter.Resolve()), //})); 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(); return this; } /// /// Starts the Web Service /// private void StartListener() { HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); _listener = new HttpListenerServer(_logger, _threadPoolManager) { WebSocketHandler = WebSocketHandler, ErrorHandler = ErrorHandler, RequestHandler = RequestHandler }; _listener.Start(UrlPrefixes); } private void WebSocketHandler(WebSocketConnectEventArgs args) { if (WebSocketConnected != null) { WebSocketConnected(this, args); } } /// /// Logs the HTTP request. /// /// The request. private void LogHttpRequest(HttpListenerRequest request) { var endpoint = request.LocalEndPoint; if (endpoint != null) { var address = endpoint.ToString(); _localEndPoints.GetOrAdd(address, address); } LoggerUtils.LogRequest(_logger, request); } private void ErrorHandler(Exception ex, IRequest httpReq) { try { var httpRes = httpReq.Response; if (httpRes.IsClosed) { return; } var errorResponse = new ErrorResponse { ResponseStatus = new ResponseStatus { ErrorCode = ex.GetType().GetOperationName(), Message = ex.Message, StackTrace = ex.StackTrace, } }; 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); } } /// /// Shut down the Web Service /// public void Stop() { if (_listener != null) { _listener.Stop(); } } /// /// Overridable method that can be used to implement a custom hnandler /// /// The HTTP req. /// Task. protected Task RequestHandler(IHttpRequest httpReq, Uri url) { var date = DateTime.Now; var httpRes = httpReq.Response; var operationName = httpReq.OperationName; var localPath = url.LocalPath; if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); return Task.FromResult(true); } if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } if (string.IsNullOrEmpty(localPath)) { httpRes.RedirectToUrl("/" + HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } var handler = HttpHandlerFactory.GetHandler(httpReq); var remoteIp = httpReq.RemoteIp; 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(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); //Matches Exceptions handled in HttpListenerBase.InitTask() var statusCode = httpRes.StatusCode; var urlString = url.ToString(); task.ContinueWith(x => { var duration = DateTime.Now - date; LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration); }, TaskContinuationOptions.None); return task; } return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo) .AsTaskException(); } /// /// Adds the rest handlers. /// /// The services. public void Init(IEnumerable services) { _restServices.AddRange(services); ServiceController = CreateServiceController(); _logger.Info("Calling ServiceStack AppHost.Init"); base.Init(); } //public override RouteAttribute[] GetRouteAttributes(System.Type requestType) //{ // var routes = base.GetRouteAttributes(requestType); // routes.Each(x => x.Path = "/api" + x.Path); // return routes; //} /// /// Releases the specified instance. /// /// The instance. 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) { _threadPoolManager.Dispose(); Stop(); } //release unmanaged resources here... _disposed = true; } } public override void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void StartServer(IEnumerable urlPrefixes) { UrlPrefixes = urlPrefixes.ToList(); Start(UrlPrefixes.First()); } public bool SupportsWebSockets { get { return NativeWebSocket.IsSupported; } } } }