Исправление: том ./mods:/lampac/mods в compose и копии 10 модулей с enable:true; конфиг без SkipModules
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
using Shared.Services;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Music;
|
||||
|
||||
public static class MusicHttp
|
||||
{
|
||||
public static HttpClient CreateClient(string providerId, MusicProxyPurpose purpose = MusicProxyPurpose.Api)
|
||||
=> new(new RoutingHandler(providerId, purpose));
|
||||
|
||||
public static HttpClient GetTransport(MusicProxyLease lease)
|
||||
{
|
||||
HttpClientHandler handler = null;
|
||||
|
||||
if (lease?.Enabled == true)
|
||||
{
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = true,
|
||||
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||
Proxy = lease.Proxy,
|
||||
UseProxy = true,
|
||||
ServerCertificateCustomValidationCallback = Http.AlwaysAllowCertificate
|
||||
};
|
||||
}
|
||||
|
||||
return FriendlyHttp.MessageClient(
|
||||
"base",
|
||||
handler,
|
||||
out _,
|
||||
allowAutoRedirect: true,
|
||||
findNoRedirectClient: true);
|
||||
}
|
||||
|
||||
public static bool IsProxyFailureStatus(HttpStatusCode statusCode)
|
||||
=> statusCode is HttpStatusCode.ProxyAuthenticationRequired
|
||||
or HttpStatusCode.BadGateway
|
||||
or HttpStatusCode.ServiceUnavailable
|
||||
or HttpStatusCode.GatewayTimeout;
|
||||
|
||||
public static bool IsTransientFailureStatus(HttpStatusCode statusCode)
|
||||
=> statusCode is HttpStatusCode.Unauthorized
|
||||
or HttpStatusCode.Forbidden
|
||||
or HttpStatusCode.RequestTimeout
|
||||
or HttpStatusCode.TooManyRequests
|
||||
|| (int)statusCode >= 500;
|
||||
|
||||
public static bool IsProxyFailure(Exception exception)
|
||||
{
|
||||
for (var current = exception; current != null; current = current.InnerException)
|
||||
{
|
||||
if (current is HttpRequestException)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
sealed class RoutingHandler : HttpMessageHandler
|
||||
{
|
||||
readonly string providerId;
|
||||
readonly MusicProxyPurpose purpose;
|
||||
|
||||
public RoutingHandler(string providerId, MusicProxyPurpose purpose)
|
||||
{
|
||||
this.providerId = providerId;
|
||||
this.purpose = purpose;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var lease = MusicProxyService.Acquire(providerId, purpose);
|
||||
var transport = GetTransport(lease);
|
||||
using var routedRequest = await CloneRequestAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await transport.SendAsync(routedRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (lease.Enabled && IsProxyFailureStatus(response.StatusCode))
|
||||
lease.Failure();
|
||||
else
|
||||
lease.Success();
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (lease.Enabled && IsProxyFailure(ex))
|
||||
lease.Failure();
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<HttpRequestMessage> CloneRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
|
||||
{
|
||||
Version = request.Version,
|
||||
VersionPolicy = request.VersionPolicy
|
||||
};
|
||||
|
||||
foreach (var header in request.Headers)
|
||||
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
|
||||
foreach (var option in request.Options)
|
||||
clone.Options.Set(new HttpRequestOptionsKey<object>(option.Key), option.Value);
|
||||
|
||||
if (request.Content != null)
|
||||
{
|
||||
clone.Content = new ByteArrayContent(await request.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false));
|
||||
foreach (var header in request.Content.Headers)
|
||||
clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Shared.Services;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
|
||||
namespace Music;
|
||||
|
||||
public enum MusicProxyPurpose
|
||||
{
|
||||
Api,
|
||||
Stream
|
||||
}
|
||||
|
||||
public sealed class MusicProxyLease
|
||||
{
|
||||
readonly ProxyManager manager;
|
||||
|
||||
internal MusicProxyLease(string scope, ProxyManager manager, WebProxy proxy, (string ip, string username, string password) data)
|
||||
{
|
||||
Scope = scope;
|
||||
this.manager = manager;
|
||||
Proxy = proxy;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public string Scope { get; }
|
||||
public WebProxy Proxy { get; }
|
||||
public (string ip, string username, string password) Data { get; }
|
||||
public bool Enabled => Proxy != null;
|
||||
public string RouteKey => MusicProxyService.BuildRouteKey(
|
||||
Scope,
|
||||
Data.ip ?? Proxy?.Address?.ToString(),
|
||||
Data.username,
|
||||
Data.password
|
||||
);
|
||||
|
||||
public void Success() => manager?.Success();
|
||||
public void Failure() => manager?.Refresh();
|
||||
|
||||
public void ApplyTo(MusicPlaybackSource source, bool overwrite = false)
|
||||
{
|
||||
if (source == null || !Enabled || (!overwrite && !string.IsNullOrWhiteSpace(source.proxy_url)))
|
||||
return;
|
||||
|
||||
source.proxy_url = Data.ip ?? Proxy.Address?.ToString();
|
||||
source.proxy_username = Data.username;
|
||||
source.proxy_password = Data.password;
|
||||
source.proxy_scope = Scope;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MusicProxyService
|
||||
{
|
||||
sealed record ManagerEntry(ModuleConf Conf, ProxyManager Manager);
|
||||
|
||||
static readonly ConcurrentDictionary<string, ManagerEntry> managers = new(StringComparer.OrdinalIgnoreCase);
|
||||
static long configurationVersion;
|
||||
|
||||
public static long ConfigurationVersion => Interlocked.Read(ref configurationVersion);
|
||||
|
||||
public static void ConfigurationChanged()
|
||||
{
|
||||
Interlocked.Increment(ref configurationVersion);
|
||||
managers.Clear();
|
||||
}
|
||||
|
||||
public static MusicProxyLease Acquire(string providerId, MusicProxyPurpose purpose)
|
||||
{
|
||||
var conf = ModInit.conf;
|
||||
bool enabled = purpose == MusicProxyPurpose.Api
|
||||
? conf?.useproxy == true
|
||||
: conf?.useproxystream == true;
|
||||
|
||||
if (!enabled || conf == null)
|
||||
return new MusicProxyLease(null, null, null, default);
|
||||
|
||||
string provider = string.IsNullOrWhiteSpace(providerId) ? "shared" : providerId.Trim().ToLowerInvariant();
|
||||
string scope = $"Music:{provider}:{purpose.ToString().ToLowerInvariant()}";
|
||||
var entry = managers.AddOrUpdate(
|
||||
scope,
|
||||
_ => new ManagerEntry(conf, new ProxyManager(scope, conf)),
|
||||
(_, current) => ReferenceEquals(current.Conf, conf)
|
||||
? current
|
||||
: new ManagerEntry(conf, new ProxyManager(scope, conf)));
|
||||
|
||||
var selected = entry.Manager.BaseGet();
|
||||
return new MusicProxyLease(scope, entry.Manager, selected.proxy, selected.data);
|
||||
}
|
||||
|
||||
public static void ApplyStreamProxy(string providerId, IEnumerable<MusicPlaybackSource> sources)
|
||||
{
|
||||
if (sources == null)
|
||||
return;
|
||||
|
||||
MusicProxyLease lease = null;
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
if (source == null || !string.IsNullOrWhiteSpace(source.proxy_url))
|
||||
continue;
|
||||
|
||||
lease ??= Acquire(providerId, MusicProxyPurpose.Stream);
|
||||
lease.ApplyTo(source);
|
||||
}
|
||||
}
|
||||
|
||||
public static string CurrentRouteKey(string providerId, MusicProxyPurpose purpose)
|
||||
=> Acquire(providerId, purpose).RouteKey;
|
||||
|
||||
public static string SourceRouteKey(MusicPlaybackSource source)
|
||||
=> source == null
|
||||
? "direct"
|
||||
: BuildRouteKey(source.proxy_scope, source.proxy_url, source.proxy_username, source.proxy_password);
|
||||
|
||||
internal static string BuildRouteKey(string scope, string proxyUrl, string username, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(proxyUrl))
|
||||
return "direct";
|
||||
|
||||
return $"{scope ?? string.Empty}|{proxyUrl.Trim()}|{username ?? string.Empty}|{password ?? string.Empty}";
|
||||
}
|
||||
|
||||
public static void ReportSuccess(string scope)
|
||||
=> GetCurrentManager(scope)?.Success();
|
||||
|
||||
public static void ReportFailure(string scope)
|
||||
=> GetCurrentManager(scope)?.Refresh();
|
||||
|
||||
static ProxyManager GetCurrentManager(string scope)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(scope))
|
||||
return null;
|
||||
|
||||
return managers.TryGetValue(scope, out var entry) ? entry.Manager : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user