mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-03 06:18:28 +01:00
Reduce complexity http routes
This commit is contained in:
@@ -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("/"))
|
||||
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 (restPath.Path[0] != '/')
|
||||
{
|
||||
pathsAtFirstMatch = new List<RestPath>();
|
||||
RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
|
||||
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))
|
||||
{
|
||||
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 serviceType = httpHost.GetServiceTypeByRequest(requestType);
|
||||
|
||||
var service = appHost.CreateInstance(serviceType);
|
||||
|
||||
//var service = typeFactory.CreateInstance(serviceType);
|
||||
var service = httpHost.CreateInstance(serviceType);
|
||||
|
||||
var serviceRequiresContext = service as IRequiresRequest;
|
||||
if (serviceRequiresContext != null)
|
||||
|
||||
@@ -11,6 +11,18 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
public class ServiceHandler
|
||||
{
|
||||
private readonly ServiceController _serviceController;
|
||||
|
||||
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)
|
||||
@@ -21,21 +33,22 @@ namespace Emby.Server.Implementations.Services
|
||||
return deserializer(requestType, httpReq.InputStream);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(host.CreateInstance(requestType));
|
||||
}
|
||||
|
||||
public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType)
|
||||
public RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType)
|
||||
{
|
||||
pathInfo = GetSanitizedPathInfo(pathInfo, out contentType);
|
||||
|
||||
return ServiceController.Instance.GetRestPathForRequest(httpMethod, pathInfo);
|
||||
return _serviceController.GetRestPathForRequest(httpMethod, pathInfo);
|
||||
}
|
||||
|
||||
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 +57,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;
|
||||
switch (format)
|
||||
{
|
||||
case "json": return "application/json";
|
||||
case "xml": return "application/xml";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public RestPath GetRestPath(string httpMethod, string pathInfo)
|
||||
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.RestPath == null)
|
||||
{
|
||||
this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out string contentType);
|
||||
|
||||
if (contentType != null)
|
||||
ResponseContentType = contentType;
|
||||
}
|
||||
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)
|
||||
{
|
||||
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 +145,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 +172,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 +211,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 +227,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 +240,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user