Files
2025-11-30 08:35:03 +02:00

261 lines
7.7 KiB
C#

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
namespace Needle.Engine.Utils
{
public static class WebHelper
{
internal static bool HttpConnectionsAllowed
{
#if UNITY_2022_3_OR_NEWER && UNITY_EDITOR
get => PlayerSettings.insecureHttpOption != InsecureHttpOption.NotAllowed;
#else
get => true;
#endif
}
private static DateTime _lastTimeInvalidHttpRequest = DateTime.MinValue;
public static bool CanMakeRequest(string url)
{
#if UNITY_EDITOR
if (url.StartsWith("http:") && !HttpConnectionsAllowed)
{
NeedleDebug.LogWarning(TracingScenario.NetworkRequests,
"HTTP requests are not allowed - will not request url: " + url);
var now = DateTime.Now;
if (now - _lastTimeInvalidHttpRequest > TimeSpan.FromSeconds(30))
{
_lastTimeInvalidHttpRequest = now;
/*
Debug.LogWarning(
"HTTP requests are disabled in your Unity project. You can change this in the Player Settings under \"Other Settings\" -> \"Configuration\" -> \"Allow 'unsafe' HTTP requests\"");
*/
}
return false;
}
#endif
return true;
}
public static async Task<UnityWebRequest> MakeHeaderOnlyRequest(string url,
int timeout = 1000,
CancellationToken cancel = default)
{
// Seems we may need to sanitize // in URLs since otherwise requests can fail on Mac
// url = url.Replace("//", "/").Replace("//", "/").Replace(":/", "://");
NeedleDebug.Log(TracingScenario.NetworkRequests, "Making header only request to " + url);
var req = new UnityWebRequest(url, "HEAD");
// TODO: this is in seconds but we get curl errors that we can not suppress otherwise
req.timeout = timeout;
req.certificateHandler = new AllowAllCertHandler();
#if UNITY_2023_1_OR_NEWER
await req.SendWebRequest();
#else
req.SendWebRequest();
#endif
while (!req.isDone && !cancel.IsCancellationRequested)
{
NeedleDebug.Log(TracingScenario.NetworkRequests, "Waiting for header only request response...");
await Task.Delay(30, cancel);
}
return req;
}
public static async Task<bool> IsRespondingUnityWebRequest(string url, CancellationToken cancel = default)
{
try
{
if (!CanMakeRequest(url)) return false;
var req = new UnityWebRequest(url, "HEAD");
// TODO: this is in seconds but we get curl errors that we can not suppress otherwise
req.timeout = 1000;
if (url.StartsWith("https"))
{
req.certificateHandler = new AllowAllCertHandler();
}
#if UNITY_2023_1_OR_NEWER
await req.SendWebRequest();
#else
req.SendWebRequest();
#endif
while (!req.isDone && !cancel.IsCancellationRequested) await Task.Delay(30, cancel);
// it is responding so at least the website exists
if (req.responseCode == 404) return true;
return req.result == UnityWebRequest.Result.Success;
}
catch (InvalidOperationException)
{
return false;
}
catch (SocketException)
{
return false;
}
catch (Exception)
{
// Debug.LogException();
return false;
}
}
public static async Task<bool> IsResponding(string url, CancellationToken cancel = default)
{
#if UNITY_EDITOR
while (EditorApplication.isCompiling) await Task.Delay(1000);
#endif
// TODO: test on OSX
return await IsRespondingUnityWebRequest(url, cancel);
// var previousCallback = ServicePointManager.ServerCertificateValidationCallback;
// try
// {
// ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// using var client = new HttpClient();
// var res = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url), cancel);
// var isOk = res.StatusCode == HttpStatusCode.OK ||
// res.StatusCode == HttpStatusCode.NotFound ||
// res.StatusCode == HttpStatusCode.Unauthorized ||
// res.StatusCode == HttpStatusCode.Forbidden;
// return isOk;
// }
// catch (InvalidOperationException)
// {
// return false;
// }
// catch (SocketException)
// {
// return false;
// }
// catch (Exception ex)
// {
// // Debug.LogException(ex);
// return false;
// }
// finally
// {
// ServicePointManager.ServerCertificateValidationCallback = previousCallback;
// }
}
public static async
Task<(bool success, UnityWebRequest.Result result, string error, bool isCertificateError, long code)>
IsRespondingWithStatus(string url, CancellationToken cancel = default)
{
#if UNITY_EDITOR
while (EditorApplication.isCompiling) await Task.Delay(1000);
#endif
if (string.IsNullOrWhiteSpace(url))
return (false, UnityWebRequest.Result.ConnectionError, "No url", false, -1);
var req = new UnityWebRequest(url);
// TODO: this is in seconds but we get curl errors that we can not suppress otherwise
req.timeout = 1000;
req.certificateHandler = new AllowAllCertHandler();
#if UNITY_2023_1_OR_NEWER
await req.SendWebRequest();
#else
req.SendWebRequest();
#endif
while (!req.isDone && !cancel.IsCancellationRequested) await Task.Delay(30, cancel);
// it is responding so at least the website exists
var isCertificateError = req.error?.Contains("certificate error") ?? false;
if (req.responseCode == 404)
return (true, UnityWebRequest.Result.Success, req.error, isCertificateError, req.responseCode);
return (req.result == UnityWebRequest.Result.Success, req.result, req.error, isCertificateError,
req.responseCode);
}
public class AllowAllCertHandler : CertificateHandler
{
protected override bool ValidateCertificate(byte[] certificateData)
{
return true;
}
}
// public static async Task<bool> IsRespondingHeaderOnly(string url, CancellationToken token = default)
// {
// var client = WebRequest.Create(new Uri(url));
// client.Timeout = 1000;
// client.Method = "HEAD";
// var res = await client.GetResponseAsync();
// return res != null;
// }
public static async Task<string> GetLocalServerUrl(bool tryGetIp = true, int port = 3000)
{
#if UNITY_EDITOR
while (EditorApplication.isCompiling) await Task.Delay(1000);
#endif
var ip = default(IPAddress);
if (tryGetIp)
{
ip = await Task.Run(() =>
{
if (TryGetIp(out var _ip))
return _ip;
return null;
});
}
var url = "https://localhost:" + port;
if (tryGetIp) url = "https://" + ip + ":" + port;
return url;
}
public static bool TryGetIp(out IPAddress address)
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up))
{
var isSuitable = IsSuitableNetworkInterface(ni);
if (!isSuitable) continue;
foreach (var ip in ni.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
address = ip.Address;
return true;
}
}
}
bool IsSuitableNetworkInterface(NetworkInterface ni)
{
// ignore all but Ethernet / Wifi (e.g. Bluetooth)
if (ni.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 &&
ni.NetworkInterfaceType != NetworkInterfaceType.Ethernet)
return false;
// filter out known virtual adapters (e.g. VMWare)
if (ni.Name.IndexOf("vEthernet", StringComparison.InvariantCultureIgnoreCase) >= 0 ||
ni.Name.IndexOf("VMware", StringComparison.InvariantCultureIgnoreCase) >= 0)
return false;
var hasInterNetworkAddress = ni
.GetIPProperties()
.UnicastAddresses
.Any(ip => ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
return hasInterNetworkAddress;
}
address = null;
return false;
}
}
}