Merge pull request #1158 from Bond-009/httpclean

Reduce complexity http routes
pull/1245/head
Joshua M. Boniface 5 years ago committed by GitHub
commit c3532b92f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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>

@ -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>[]

@ -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)

@ -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;
}
}
}

@ -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;
}

@ -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; });

Loading…
Cancel
Save