Browse Source

Merge pull request #1158 from Bond-009/httpclean

Reduce complexity http routes
Joshua M. Boniface 6 years ago
parent
commit
c3532b92f7

+ 2 - 35
Emby.Server.Implementations/ApplicationHost.cs

@@ -620,8 +620,6 @@ namespace Emby.Server.Implementations
 
             DiscoverTypes();
 
-            SetHttpLimit();
-
             await RegisterResources(serviceCollection).ConfigureAwait(false);
 
             FindParts();
@@ -918,8 +916,7 @@ namespace Emby.Server.Implementations
                 .Distinct();
 
             logger.LogInformation("Arguments: {Args}", commandLineArgs);
-            // FIXME: @bond this logs the kernel version, not the OS version
-            logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version);
+            logger.LogInformation("Operating system: {OS}", OperatingSystem.Name);
             logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
             logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess);
             logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive);
@@ -929,19 +926,6 @@ namespace Emby.Server.Implementations
             logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath);
         }
 
-        private void SetHttpLimit()
-        {
-            try
-            {
-                // Increase the max http request limit
-                ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error setting http limit");
-            }
-        }
-
         private X509Certificate2 GetCertificate(CertificateInfo info)
         {
             var certificateLocation = info?.Path;
@@ -1539,6 +1523,7 @@ namespace Emby.Server.Implementations
             {
                 Logger.LogError(ex, "Error getting WAN Ip address information");
             }
+
             return null;
         }
 
@@ -1841,24 +1826,6 @@ namespace Emby.Server.Implementations
         {
         }
 
-        /// <summary>
-        /// Called when [application updated].
-        /// </summary>
-        /// <param name="package">The package.</param>
-        protected void OnApplicationUpdated(PackageVersionInfo package)
-        {
-            Logger.LogInformation("Application has been updated to version {0}", package.versionStr);
-
-            ApplicationUpdated?.Invoke(
-                this,
-                new GenericEventArgs<PackageVersionInfo>()
-                {
-                    Argument = package
-                });
-
-            NotifyPendingRestart();
-        }
-
         private bool _disposed = false;
 
         /// <summary>

+ 15 - 35
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Net.Sockets;
@@ -11,7 +10,6 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Net;
 using Emby.Server.Implementations.Services;
-using Emby.Server.Implementations.SocketSharp;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
@@ -127,12 +125,12 @@ namespace Emby.Server.Implementations.HttpServer
 
         private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
         {
-            var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList();
+            var attributes = requestDtoType.GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList();
 
             var serviceType = GetServiceTypeByRequest(requestDtoType);
             if (serviceType != null)
             {
-                attributes.AddRange(serviceType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>());
+                attributes.AddRange(serviceType.GetCustomAttributes(true).OfType<IHasRequestFilter>());
             }
 
             attributes.Sort((x, y) => x.Priority - y.Priority);
@@ -154,7 +152,7 @@ namespace Emby.Server.Implementations.HttpServer
                 QueryString = e.QueryString ?? new QueryCollection()
             };
 
-            connection.Closed += Connection_Closed;
+            connection.Closed += OnConnectionClosed;
 
             lock (_webSocketConnections)
             {
@@ -164,7 +162,7 @@ namespace Emby.Server.Implementations.HttpServer
             WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
         }
 
-        private void Connection_Closed(object sender, EventArgs e)
+        private void OnConnectionClosed(object sender, EventArgs e)
         {
             lock (_webSocketConnections)
             {
@@ -322,14 +320,14 @@ namespace Emby.Server.Implementations.HttpServer
 
         private static string NormalizeConfiguredLocalAddress(string address)
         {
-            var index = address.Trim('/').IndexOf('/');
-
+            var add = address.AsSpan().Trim('/');
+            int index = add.IndexOf('/');
             if (index != -1)
             {
-                address = address.Substring(index + 1);
+                add = add.Slice(index + 1);
             }
 
-            return address.Trim('/');
+            return add.TrimStart('/').ToString();
         }
 
         private bool ValidateHost(string host)
@@ -399,8 +397,8 @@ namespace Emby.Server.Implementations.HttpServer
                 if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
                 {
                     // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
-                    if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
+                    if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
+                        || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
                     {
                         return true;
                     }
@@ -572,7 +570,7 @@ namespace Emby.Server.Implementations.HttpServer
 
                 if (handler != null)
                 {
-                    await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false);
+                    await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, cancellationToken).ConfigureAwait(false);
                 }
                 else
                 {
@@ -613,21 +611,11 @@ namespace Emby.Server.Implementations.HttpServer
         {
             var pathInfo = httpReq.PathInfo;
 
-            var pathParts = pathInfo.TrimStart('/').Split('/');
-            if (pathParts.Length == 0)
-            {
-                Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
-                return null;
-            }
-
-            var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType);
+            pathInfo = ServiceHandler.GetSanitizedPathInfo(pathInfo, out string contentType);
+            var restPath = ServiceController.GetRestPathForRequest(httpReq.HttpMethod, pathInfo);
             if (restPath != null)
             {
-                return new ServiceHandler
-                {
-                    RestPath = restPath,
-                    ResponseContentType = contentType
-                };
+                return new ServiceHandler(restPath, contentType);
             }
 
             Logger.LogError("Could not find handler for {PathInfo}", pathInfo);
@@ -655,11 +643,6 @@ namespace Emby.Server.Implementations.HttpServer
             }
             else
             {
-                // TODO what is this?
-                var httpsUrl = url
-                    .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
-                    .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
-
                 RedirectToUrl(httpRes, url);
             }
         }
@@ -684,10 +667,7 @@ namespace Emby.Server.Implementations.HttpServer
             UrlPrefixes = urlPrefixes.ToArray();
             ServiceController = new ServiceController();
 
-            Logger.LogInformation("Calling ServiceStack AppHost.Init");
-
-            var types = services.Select(r => r.GetType()).ToArray();
-
+            var types = services.Select(r => r.GetType());
             ServiceController.Init(this, types);
 
             ResponseFilters = new Action<IRequest, IResponse, object>[]

+ 21 - 33
Emby.Server.Implementations/Services/ServiceController.cs

@@ -1,26 +1,17 @@
 using System;
 using System.Collections.Generic;
-using System.Reflection;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.HttpServer;
 using MediaBrowser.Model.Services;
 
 namespace Emby.Server.Implementations.Services
 {
-    public delegate Task<object> InstanceExecFn(IRequest requestContext, object intance, object request);
     public delegate object ActionInvokerFn(object intance, object request);
     public delegate void VoidActionInvokerFn(object intance, object request);
 
     public class ServiceController
     {
-        public static ServiceController Instance;
-
-        public ServiceController()
-        {
-            Instance = this;
-        }
-
-        public void Init(HttpListenerHost appHost, Type[] serviceTypes)
+        public void Init(HttpListenerHost appHost, IEnumerable<Type> serviceTypes)
         {
             foreach (var serviceType in serviceTypes)
             {
@@ -37,7 +28,11 @@ namespace Emby.Server.Implementations.Services
             foreach (var mi in serviceType.GetActions())
             {
                 var requestType = mi.GetParameters()[0].ParameterType;
-                if (processedReqs.Contains(requestType)) continue;
+                if (processedReqs.Contains(requestType))
+                {
+                    continue;
+                }
+
                 processedReqs.Add(requestType);
 
                 ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
@@ -55,18 +50,6 @@ namespace Emby.Server.Implementations.Services
             }
         }
 
-        public static Type FirstGenericType(Type type)
-        {
-            while (type != null)
-            {
-                if (type.GetTypeInfo().IsGenericType)
-                    return type;
-
-                type = type.GetTypeInfo().BaseType;
-            }
-            return null;
-        }
-
         public readonly RestPath.RestPathMap RestPathMap = new RestPath.RestPathMap();
 
         public void RegisterRestPaths(HttpListenerHost appHost, Type requestType, Type serviceType)
@@ -84,17 +67,24 @@ namespace Emby.Server.Implementations.Services
 
         public void RegisterRestPath(RestPath restPath)
         {
-            if (!restPath.Path.StartsWith("/"))
+            if (restPath.Path[0] != '/')
+            {
                 throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
+            }
+
             if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
+            {
                 throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName()));
+            }
 
-            if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch))
+            if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch))
             {
-                pathsAtFirstMatch = new List<RestPath>();
-                RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
+                pathsAtFirstMatch.Add(restPath);
+            }
+            else
+            {
+                RestPathMap[restPath.FirstMatchHashKey] = new List<RestPath>() { restPath };
             }
-            pathsAtFirstMatch.Add(restPath);
         }
 
         public RestPath GetRestPathForRequest(string httpMethod, string pathInfo)
@@ -155,17 +145,15 @@ namespace Emby.Server.Implementations.Services
             return null;
         }
 
-        public Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req)
+        public Task<object> Execute(HttpListenerHost httpHost, object requestDto, IRequest req)
         {
             req.Dto = requestDto;
             var requestType = requestDto.GetType();
             req.OperationName = requestType.Name;
 
-            var serviceType = appHost.GetServiceTypeByRequest(requestType);
-
-            var service = appHost.CreateInstance(serviceType);
+            var serviceType = httpHost.GetServiceTypeByRequest(requestType);
 
-            //var service = typeFactory.CreateInstance(serviceType);
+            var service = httpHost.CreateInstance(serviceType);
 
             var serviceRequiresContext = service as IRequiresRequest;
             if (serviceRequiresContext != null)

+ 48 - 61
Emby.Server.Implementations/Services/ServiceHandler.cs

@@ -11,31 +11,32 @@ namespace Emby.Server.Implementations.Services
 {
     public class ServiceHandler
     {
+        public RestPath RestPath { get; }
+
+        public string ResponseContentType { get; }
+
+        internal ServiceHandler(RestPath restPath, string responseContentType)
+        {
+            RestPath = restPath;
+            ResponseContentType = responseContentType;
+        }
+
         protected static Task<object> CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType)
         {
             if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0)
             {
                 var deserializer = RequestHelper.GetRequestReader(host, contentType);
-                if (deserializer != null)
-                {
-                    return deserializer(requestType, httpReq.InputStream);
-                }
+                return deserializer?.Invoke(requestType, httpReq.InputStream);
             }
-            return Task.FromResult(host.CreateInstance(requestType));
-        }
-
-        public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType)
-        {
-            pathInfo = GetSanitizedPathInfo(pathInfo, out contentType);
 
-            return ServiceController.Instance.GetRestPathForRequest(httpMethod, pathInfo);
+            return Task.FromResult(host.CreateInstance(requestType));
         }
 
         public static string GetSanitizedPathInfo(string pathInfo, out string contentType)
         {
             contentType = null;
             var pos = pathInfo.LastIndexOf('.');
-            if (pos >= 0)
+            if (pos != -1)
             {
                 var format = pathInfo.Substring(pos + 1);
                 contentType = GetFormatContentType(format);
@@ -44,58 +45,38 @@ namespace Emby.Server.Implementations.Services
                     pathInfo = pathInfo.Substring(0, pos);
                 }
             }
+
             return pathInfo;
         }
 
         private static string GetFormatContentType(string format)
         {
             //built-in formats
-            if (format == "json")
-                return "application/json";
-            if (format == "xml")
-                return "application/xml";
-
-            return null;
-        }
-
-        public RestPath GetRestPath(string httpMethod, string pathInfo)
-        {
-            if (this.RestPath == null)
+            switch (format)
             {
-                this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out string contentType);
-
-                if (contentType != null)
-                    ResponseContentType = contentType;
+                case "json": return "application/json";
+                case "xml": return "application/xml";
+                default: return null;
             }
-            return this.RestPath;
         }
 
-        public RestPath RestPath { get; set; }
-
-        // Set from SSHHF.GetHandlerForPathInfo()
-        public string ResponseContentType { get; set; }
-
-        public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName, CancellationToken cancellationToken)
+        public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken)
         {
-            var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo);
-            if (restPath == null)
-            {
-                throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo);
-            }
-
-            SetRoute(httpReq, restPath);
+            httpReq.Items["__route"] = RestPath;
 
             if (ResponseContentType != null)
+            {
                 httpReq.ResponseContentType = ResponseContentType;
+            }
 
-            var request = httpReq.Dto = await CreateRequest(appHost, httpReq, restPath, logger).ConfigureAwait(false);
+            var request = httpReq.Dto = await CreateRequest(httpHost, httpReq, RestPath, logger).ConfigureAwait(false);
 
-            appHost.ApplyRequestFilters(httpReq, httpRes, request);
+            httpHost.ApplyRequestFilters(httpReq, httpRes, request);
 
-            var response = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false);
+            var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
 
             // Apply response filters
-            foreach (var responseFilter in appHost.ResponseFilters)
+            foreach (var responseFilter in httpHost.ResponseFilters)
             {
                 responseFilter(httpReq, httpRes, response);
             }
@@ -152,7 +133,11 @@ namespace Emby.Server.Implementations.Services
 
             foreach (var name in request.QueryString.Keys)
             {
-                if (name == null) continue; //thank you ASP.NET
+                if (name == null)
+                {
+                    // thank you ASP.NET
+                    continue;
+                }
 
                 var values = request.QueryString[name];
                 if (values.Count == 1)
@@ -175,7 +160,11 @@ namespace Emby.Server.Implementations.Services
                 {
                     foreach (var name in formData.Keys)
                     {
-                        if (name == null) continue; //thank you ASP.NET
+                        if (name == null)
+                        {
+                            // thank you ASP.NET
+                            continue;
+                        }
 
                         var values = formData.GetValues(name);
                         if (values.Count == 1)
@@ -210,7 +199,12 @@ namespace Emby.Server.Implementations.Services
 
             foreach (var name in request.QueryString.Keys)
             {
-                if (name == null) continue; //thank you ASP.NET
+                if (name == null)
+                {
+                    // thank you ASP.NET
+                    continue;
+                }
+
                 map[name] = request.QueryString[name];
             }
 
@@ -221,7 +215,12 @@ namespace Emby.Server.Implementations.Services
                 {
                     foreach (var name in formData.Keys)
                     {
-                        if (name == null) continue; //thank you ASP.NET
+                        if (name == null)
+                        {
+                            // thank you ASP.NET
+                            continue;
+                        }
+
                         map[name] = formData[name];
                     }
                 }
@@ -229,17 +228,5 @@ namespace Emby.Server.Implementations.Services
 
             return map;
         }
-
-        private static void SetRoute(IRequest req, RestPath route)
-        {
-            req.Items["__route"] = route;
-        }
-
-        private static RestPath GetRoute(IRequest req)
-        {
-            req.Items.TryGetValue("__route", out var route);
-            return route as RestPath;
-        }
     }
-
 }

+ 12 - 8
Emby.Server.Implementations/Services/SwaggerService.cs

@@ -1,9 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Services;
+using Emby.Server.Implementations.HttpServer;
 
 namespace Emby.Server.Implementations.Services
 {
@@ -109,10 +109,16 @@ namespace Emby.Server.Implementations.Services
 
     public class SwaggerService : IService, IRequiresRequest
     {
+        private readonly IHttpServer _httpServer;
         private SwaggerSpec _spec;
 
         public IRequest Request { get; set; }
 
+        public SwaggerService(IHttpServer httpServer)
+        {
+            _httpServer = httpServer;
+        }
+
         public object Get(GetSwaggerSpec request)
         {
             return _spec ?? (_spec = GetSpec());
@@ -181,7 +187,8 @@ namespace Emby.Server.Implementations.Services
         {
             var paths = new SortedDictionary<string, Dictionary<string, SwaggerMethod>>();
 
-            var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList();
+            // REVIEW: this can be done better
+            var all = ((HttpListenerHost)_httpServer).ServiceController.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList();
 
             foreach (var current in all)
             {
@@ -192,11 +199,8 @@ namespace Emby.Server.Implementations.Services
                         continue;
                     }
 
-                    if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase))
-                    {
-                        continue;
-                    }
-                    if (info.Path.StartsWith("/jellyfin", StringComparison.OrdinalIgnoreCase))
+                    if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase)
+                        || info.Path.StartsWith("/jellyfin", StringComparison.OrdinalIgnoreCase))
                     {
                         continue;
                     }

+ 4 - 0
Jellyfin.Server/Program.cs

@@ -118,6 +118,10 @@ namespace Jellyfin.Server
 
             SQLitePCL.Batteries_V2.Init();
 
+            // Increase the max http request limit
+            // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
+            ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
+
             // Allow all https requests
             ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });