This commit is contained in:
2026-06-15 18:18:16 +08:00
parent 97c9fba14e
commit 2b9f134e5f
4164 changed files with 386922 additions and 79 deletions

View File

@@ -0,0 +1,67 @@
#if UNITY_ANDROID && !UNITY_EDITOR
using System;
using Best.HTTP.Shared;
using UnityEngine;
namespace Best.HTTP.Proxies.Autodetect
{
public sealed class AndroidProxyDetector : IProxyDetector
{
private const string ClassPath = "com.Best.HTTP.proxy.ProxyFinder";
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
try
{
var proxyUrl = FindFor(request.CurrentUri.ToString());
HTTPManager.Logger.Information(nameof(AndroidProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with proxyUrl: '{proxyUrl}'", request.Context);
if (proxyUrl == null)
return null;
if (proxyUrl.StartsWith("socks://", StringComparison.OrdinalIgnoreCase))
{
return new SOCKSProxy(new Uri(proxyUrl), null);
}
else if (proxyUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
return new HTTPProxy(new Uri(proxyUrl));
}
else
{
HTTPManager.Logger.Warning(nameof(AndroidProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with unknown format. proxyUrl: '{proxyUrl}'", request.Context);
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(AndroidProxyDetector), nameof(IProxyDetector.GetProxy), ex, request.Context);
}
return null;
}
private string FindFor(string uriStr) => Call<string>("FindFor", uriStr);
private static T Call<T>(string methodName, params object[] args)
{
bool isMainThread = HTTPUpdateDelegator.Instance.IsMainThread();
try
{
if (!isMainThread)
AndroidJNI.AttachCurrentThread();
using (var javaClass = new AndroidJavaClass(ClassPath))
return javaClass.CallStatic<T>(methodName, args);
}
finally
{
if (!isMainThread)
AndroidJNI.DetachCurrentThread();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa9c1e7ca4f4ea54cba5a91100ffa10b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using System.Linq;
using Best.HTTP.Hosts.Connections;
using Best.HTTP.Shared;
// Examples on proxy strings:
// https://gist.github.com/yougg/5d2b3353fc5e197a0917aae0b3287d64
namespace Best.HTTP.Proxies.Autodetect
{
/// <summary>
/// Based on <see href="https://curl.se/docs/manual.html"/>'s "Environment Variables" section.
/// </summary>
public sealed class EnvironmentProxyDetector : IProxyDetector
{
private Proxy _cachedProxy;
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
if (this._cachedProxy != null)
return this._cachedProxy;
string proxyUrl = null;
if (HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri))
{
proxyUrl = GetEnv("HTTPS_PROXY");
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - HTTPS_PROXY: '{proxyUrl}'", request.Context);
}
else
{
proxyUrl = GetEnv("HTTP_PROXY");
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - HTTP_PROXY: '{proxyUrl}'", request.Context);
}
if (proxyUrl == null)
{
proxyUrl = GetEnv("ALL_PROXY");
}
else
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - ALL_PROXY: '{proxyUrl}'", request.Context);
if (string.IsNullOrEmpty(proxyUrl))
return null;
// if the url is just a host[:port], add the http:// part too. Checking for :// should keep and treat the socks:// scheme too.
if (proxyUrl.IndexOf("://") == -1 && !proxyUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
proxyUrl = "http://" + proxyUrl;
string exceptionList = null;
try
{
var proxyUri = new Uri(proxyUrl);
Proxy proxy = null;
if (proxyUri.Scheme.StartsWith("socks", StringComparison.OrdinalIgnoreCase))
proxy = new SOCKSProxy(proxyUri, null);
else
proxy = new HTTPProxy(proxyUri);
// A comma-separated list of host names that should not go through any proxy is set in (only an asterisk, * matches all hosts)
exceptionList = GetEnv("NO_PROXY");
if (!string.IsNullOrEmpty(exceptionList))
proxy.Exceptions = exceptionList.Split(';').ToList<string>();
return this._cachedProxy = proxy;
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(EnvironmentProxyDetector), $"GetProxy - proxyUrl: '{proxyUrl}', exceptionList: '{exceptionList}'", ex, request.Context);
}
return null;
}
string GetEnv(string key) => System.Environment.GetEnvironmentVariable(key) ?? System.Environment.GetEnvironmentVariable(key.ToLowerInvariant());
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c43a242bd76482c4b9d41d7093047658
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using System;
using Best.HTTP.Shared;
namespace Best.HTTP.Proxies.Autodetect
{
/// <summary>
/// This is a detector using the .net framework's implementation. It might work not just under Windows but MacOS and Linux too.
/// </summary>
/// <remarks>
/// More details can be found here:
/// <list type="bullet">
/// <item><description><see href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy?view=net-6.0">HttpClient.DefaultProxy Property</see></description></item>
/// </list>
/// </remarks>
public sealed class FrameworkProxyDetector : IProxyDetector
{
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
var detectedProxy = System.Net.WebRequest.GetSystemWebProxy() as System.Net.WebProxy;
if (detectedProxy != null && detectedProxy.Address != null)
{
var proxyUri = detectedProxy.GetProxy(request.CurrentUri);
if (proxyUri != null && !proxyUri.Equals(request.CurrentUri))
{
if (proxyUri.Scheme.StartsWith("socks", StringComparison.OrdinalIgnoreCase))
{
return SetExceptionList(new SOCKSProxy(proxyUri, null), detectedProxy);
}
else if (proxyUri.Scheme.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return SetExceptionList(new HTTPProxy(proxyUri), detectedProxy);
}
else
{
HTTPManager.Logger.Warning(nameof(FrameworkProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with unknown format. proxyUri: '{proxyUri}'", request.Context);
}
}
}
return null;
}
private Proxy SetExceptionList(Proxy proxy, System.Net.WebProxy detectedProxy)
{
if (detectedProxy.BypassProxyOnLocal)
{
proxy.Exceptions = proxy.Exceptions ?? new System.Collections.Generic.List<string>();
proxy.Exceptions.Add("localhost");
proxy.Exceptions.Add("127.0.0.1");
}
// TODO: use BypassList to put more entries to the Exceptions list.
// But because BypassList contains regex strings, we either
// 1.) store and use regex strings in the Exception list (not backward compatible)
// 2.) store non-regex strings but create a new list for regex
// 3.) detect if the stored entry in the Exceptions list is regex or not and use it accordingly
// "^.*\\.httpbin\\.org$"
// https://github.com/Benedicht/BestHTTP-Issues/issues/141
return proxy;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9dcbe5fe0e354414e8e3c134836f4986
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using Best.HTTP.Shared;
namespace Best.HTTP.Proxies.Autodetect
{
/// <summary>
/// This one just returns with HTTPManager.Proxy,
/// so when ProgrammaticallyAddedProxyDetector is used in the first place for the ProxyDetector,
/// HTTPManager.Proxy gets the highest priority.
/// </summary>
public sealed class ProgrammaticallyAddedProxyDetector : IProxyDetector
{
Proxy IProxyDetector.GetProxy(HTTPRequest request) => HTTPManager.Proxy;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd7524aaa58af91418ca03146bd55dbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,184 @@
#if !UNITY_WEBGL || UNITY_EDITOR
using Best.HTTP.Hosts.Connections;
using Best.HTTP.Request.Timings;
using Best.HTTP.Shared;
using System;
namespace Best.HTTP.Proxies.Autodetect
{
/// <summary>
/// Interface for custom proxy-detection logic.
/// </summary>
public interface IProxyDetector
{
/// <summary>
/// Receives the <see cref="HTTPRequest"/> instance this detector has to try to find a proxy.
/// </summary>
/// <param name="request"><see cref="HTTPRequest"/>instance to find a proxy for</param>
/// <returns>A concrete <see cref="Proxy"/> implementation, or <c>null</c> if no proxy could be found.</returns>
Proxy GetProxy(HTTPRequest request);
}
/// <summary>
/// Possible detection modes the <see cref="ProxyDetector"/> can be in.
/// </summary>
public enum ProxyDetectionMode
{
/// <summary>
/// In Continuous mode the ProxyDetector will check for a proxy for every request.
/// </summary>
Continuous,
/// <summary>
/// This mode will cache the first Proxy found and use it for consecutive requests.
/// </summary>
CacheFirstFound
}
/// <summary>
/// Helper class to contain, manage and execute logic to detect available proxy on the network. It's a wrapper class to execute the various <see cref="IProxyDetector"/>s.
/// </summary>
public sealed class ProxyDetector
{
public static IProxyDetector[] GetDefaultDetectors() => new IProxyDetector[] {
// HTTPManager.Proxy has the highest priority
new ProgrammaticallyAddedProxyDetector(),
// then comes the environment set
new EnvironmentProxyDetector(),
// .net framework's detector
new FrameworkProxyDetector(),
#if UNITY_ANDROID && !UNITY_EDITOR
new AndroidProxyDetector(),
#endif
};
private IProxyDetector[] _proxyDetectors;
private ProxyDetectionMode _detectionMode;
private bool _attached;
public ProxyDetector()
: this(ProxyDetectionMode.CacheFirstFound, GetDefaultDetectors())
{ }
public ProxyDetector(ProxyDetectionMode detectionMode)
: this(detectionMode, GetDefaultDetectors())
{ }
public ProxyDetector(ProxyDetectionMode detectionMode, IProxyDetector[] proxyDetectors)
{
this._detectionMode = detectionMode;
this._proxyDetectors = proxyDetectors;
if (this._proxyDetectors != null)
Reattach();
}
public void Reattach()
{
HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Reattach)}({this._attached})");
if (!this._attached)
{
RequestEventHelper.OnEvent += OnRequestEvent;
this._attached = true;
}
}
/// <summary>
/// Call Detach() to disable ProxyDetector's logic to find and set a proxy.
/// </summary>
public void Detach()
{
HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Detach)}({this._attached})");
if (this._attached)
{
RequestEventHelper.OnEvent -= OnRequestEvent;
this._attached = false;
}
}
private void OnRequestEvent(RequestEventInfo @event)
{
// The Resend event is raised for every request when it's queued up (sent or redirected).
if (@event.Event == RequestEvents.QueuedResend &&
@event.SourceRequest.ProxySettings != null &&
@event.SourceRequest.ProxySettings.Proxy == null)
{
Uri uri = @event.SourceRequest.CurrentUri;
if (uri.Scheme.Equals("file"))
return;
@event.SourceRequest.Timing.StartNext(TimingEventNames.ProxyDetection);
try
{
for (int i = 0; i < this._proxyDetectors.Length; i++)
{
var detector = this._proxyDetectors[i];
if (detector == null)
continue;
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Calling {detector.GetType().Name}'s GetProxy", @event.SourceRequest.Context);
Proxy proxy = null;
#if ENABLE_PROFILER
using (var _ = new Unity.Profiling.ProfilerMarker($"{detector.GetType().Name}.GetProxy").Auto())
#endif
proxy = detector.GetProxy(@event.SourceRequest);
#if ENABLE_PROFILER
using (var _ = new Unity.Profiling.ProfilerMarker($"{detector.GetType().Name}.UseProxyForAddress").Auto())
#endif
if (proxy != null && proxy.UseProxyForAddress(uri))
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"[{detector.GetType().Name}] Proxy found: {proxy.Address} ", @event.SourceRequest.Context);
switch (this._detectionMode)
{
case ProxyDetectionMode.Continuous:
@event.SourceRequest.ProxySettings.Proxy = proxy;
break;
case ProxyDetectionMode.CacheFirstFound:
HTTPManager.Proxy = @event.SourceRequest.ProxySettings.Proxy = proxy;
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Proxy cached in HTTPManager.Proxy!", @event.SourceRequest.Context);
Detach();
break;
}
return;
}
}
if (this._detectionMode == ProxyDetectionMode.CacheFirstFound)
Detach();
HTTPManager.Logger.Information(nameof(ProxyDetector), $"No Proxy for '{uri}'.", @event.SourceRequest.Context);
}
catch (Exception ex)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Exception(nameof(ProxyDetector), $"GetProxyFor({@event.SourceRequest.CurrentUri})", ex, @event.SourceRequest.Context);
}
finally
{
@event.SourceRequest.Timing.StartNext(TimingEventNames.Queued);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 168a0e891e4343544b01417cd5fbdad7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: