diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index aa188dd..cd82071 100644
--- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs
+++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -5,7 +5,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
+using System.Net.Sockets;
using System.Reflection;
+using System.Security.Authentication;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -55,8 +57,18 @@ public class MatrixHttpClient {
public Dictionary<string, string> AdditionalQueryParameters { get; set; } = new();
public Uri? BaseAddress { get; set; }
- public bool RetryOnNetworkError { get; set; } = true;
- public bool RetryOnMatrixError { get; set; } = true;
+ public static bool DefaultRetryOnNetworkError { get; set; } = true;
+ public static bool DefaultRetryOnMatrixError { get; set; } = true;
+ public bool RetryOnNetworkError { get; set; } = DefaultRetryOnNetworkError;
+ public bool RetryOnMatrixError { get; set; } = DefaultRetryOnMatrixError;
+
+ public static int DefaultMinRetryIntervalMs { get; set; } = 1000;
+ public static int DefaultMaxRetryIntervalMs { get; set; } = 2000;
+ public static int DefaultMaxRetries { get; set; } = 20;
+
+ public int MinRetryIntervalMs { get; set; } = DefaultMinRetryIntervalMs;
+ public int MaxRetryIntervalMs { get; set; } = DefaultMaxRetryIntervalMs;
+ public int MaxRetries { get; set; } = DefaultMaxRetries;
private Dictionary<HttpRequestMessage, int> _retries = [];
@@ -148,14 +160,23 @@ public class MatrixHttpClient {
"X-Content-Security-Policy",
"Referrer-Policy",
"X-Robots-Tag",
- "Content-Security-Policy"
+ "Content-Security-Policy",
+ "Alt-Svc",
+ // evil
+ "CF-Cache-Status",
+ "CF-Ray",
+ "x-amz-request-id",
+ "x-do-app-origin",
+ "x-do-orig-status",
+ "x-rgw-object-type",
+ "Report-To"
]));
return responseMessage;
}
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) {
- _retries.TryAdd(request, 10);
+ _retries.TryAdd(request, MaxRetries);
HttpResponseMessage responseMessage;
try {
responseMessage = await SendUnhandledAsync(request, cancellationToken);
@@ -163,8 +184,27 @@ public class MatrixHttpClient {
catch (HttpRequestException ex) {
if (RetryOnNetworkError) {
if (_retries[request]-- <= 0) throw;
+ // browser exceptions
if (ex.InnerException?.GetType().FullName == "System.Runtime.InteropServices.JavaScript.JSException")
- Console.WriteLine("Got JSException, likely a CORS error due to a reverse proxy misconfiguration and error, retrying...");
+ Console.WriteLine($"Got JSException, likely a CORS error due to a reverse proxy misconfiguration and error, retrying ({_retries[request]} left)...");
+ // native exceptions
+ else if (ex.InnerException is SocketException sockEx)
+ if (sockEx.SocketErrorCode == SocketError.HostNotFound) {
+ throw new LibMatrixNetworkException(ex) {
+ Error = $"Host {request.RequestUri?.Host ?? "(null)"} not found",
+ ErrorCode = LibMatrixNetworkException.ErrorCodes.RLM_NET_UNKNOWN_HOST
+ };
+ }
+ else { } // empty
+ else if (ex.InnerException is AuthenticationException authEx)
+ if (authEx.Message.Contains("The remote certificate is invalid")) {
+ throw new LibMatrixNetworkException(ex) {
+ Error = ex.Message,
+ ErrorCode = LibMatrixNetworkException.ErrorCodes.RLM_NET_INVALID_REMOTE_CERTIFICATE
+ };
+ }
+ else { } // empty
+
else
Console.WriteLine(new {
ex.HttpRequestError,
@@ -175,10 +215,11 @@ public class MatrixHttpClient {
InnerExceptionType = ex.InnerException?.GetType().FullName
}.ToJson());
- await Task.Delay(Random.Shared.Next(1000, 2000), cancellationToken);
+ await Task.Delay(Random.Shared.Next(MinRetryIntervalMs, MaxRetryIntervalMs), cancellationToken);
request.ResetSendStatus();
return await SendAsync(request, cancellationToken);
}
+
throw;
}
@@ -221,7 +262,7 @@ public class MatrixHttpClient {
if (ex.ErrorCode == MatrixException.ErrorCodes.M_LIMIT_EXCEEDED) {
// if (ex.RetryAfterMs is null) throw ex!;
//we have a ratelimit error
- await Task.Delay(ex.RetryAfterMs ?? responseMessage.Headers.RetryAfter?.Delta?.Milliseconds ?? 500, cancellationToken);
+ await Task.Delay(ex.RetryAfterMs ?? responseMessage.Headers.RetryAfter?.Delta?.Milliseconds ?? MinRetryIntervalMs, cancellationToken);
request.ResetSendStatus();
return await SendAsync(request, cancellationToken);
}
@@ -233,8 +274,8 @@ public class MatrixHttpClient {
// spread out retries
if (RetryOnNetworkError) {
if (_retries[request]-- <= 0) throw new InvalidDataException("Encountered invalid data:\n" + content);
- Console.WriteLine("Got 502 Bad Gateway, retrying...");
- await Task.Delay(Random.Shared.Next(1000, 2000), cancellationToken);
+ Console.WriteLine($"Got 502 Bad Gateway, retrying ({_retries[request]} left)...");
+ await Task.Delay(Random.Shared.Next(MinRetryIntervalMs, MaxRetryIntervalMs), cancellationToken);
request.ResetSendStatus();
return await SendAsync(request, cancellationToken);
}
diff --git a/LibMatrix/LibMatrixNetworkException.cs b/LibMatrix/LibMatrixNetworkException.cs
new file mode 100644
index 0000000..7be0f4e
--- /dev/null
+++ b/LibMatrix/LibMatrixNetworkException.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using ArcaneLibs.Extensions;
+// ReSharper disable MemberCanBePrivate.Global
+
+namespace LibMatrix;
+
+public class LibMatrixNetworkException : Exception {
+ public LibMatrixNetworkException() : base() { }
+ public LibMatrixNetworkException(Exception httpRequestException) : base("A network error occurred", httpRequestException) { }
+
+ [JsonPropertyName("errcode")]
+ public required string ErrorCode { get; set; }
+
+ [JsonPropertyName("error")]
+ public required string Error { get; set; }
+
+ public object GetAsObject() => new { errcode = ErrorCode, error = Error };
+ public string GetAsJson() => GetAsObject().ToJson(ignoreNull: true);
+
+ public override string Message =>
+ $"{ErrorCode}: {ErrorCode switch {
+ ErrorCodes.RLM_NET_UNKNOWN_HOST => "The specified host could not be found.",
+ ErrorCodes.RLM_NET_INVALID_REMOTE_CERTIFICATE => "The remote server's TLS certificate is invalid or could not be verified.",
+ _ => $"Unknown error: {GetAsObject().ToJson(ignoreNull: true)}"
+ }}\nError: {Error}";
+
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")]
+ public static class ErrorCodes {
+ public const string RLM_NET_UNKNOWN_HOST = "RLM_NET_UNKNOWN_HOST";
+ public const string RLM_NET_INVALID_REMOTE_CERTIFICATE = "RLM_NET_INVALID_REMOTE_CERTIFICATE";
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
index 4c78347..c5e9d9c 100644
--- a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
@@ -34,17 +34,13 @@ public class WellKnownResolverService {
WellKnownResolverConfiguration? config = null) {
WellKnownRecords records = new();
_logger.LogDebug($"Resolving well-knowns for {homeserver}");
- if (includeClient && await _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } clientResult) {
- records.ClientWellKnown = clientResult;
- }
-
- if (includeServer && await _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } serverResult) {
- records.ServerWellKnown = serverResult;
- }
-
- if (includeSupport && await _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } supportResult) {
- records.SupportWellKnown = supportResult;
- }
+ var clientTask = _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration);
+ var serverTask = _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration);
+ var supportTask = _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration);
+
+ if (includeClient && await clientTask is { } clientResult) records.ClientWellKnown = clientResult;
+ if (includeServer && await serverTask is { } serverResult) records.ServerWellKnown = serverResult;
+ if (includeSupport && await supportTask is { } supportResult) records.SupportWellKnown = supportResult;
return records;
}
@@ -75,8 +71,10 @@ public class WellKnownResolverService {
public struct WellKnownResolutionWarning {
public WellKnownResolutionWarningType Type { get; set; }
public string Message { get; set; }
+
[JsonIgnore]
public Exception? Exception { get; set; }
+
public string? ExceptionMessage => Exception?.Message;
[JsonConverter(typeof(JsonStringEnumConverter))]
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
index f8de38d..f52b217 100644
--- a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
@@ -14,8 +14,6 @@ public class ClientWellKnownResolver(ILogger<ClientWellKnownResolver> logger, We
StoreNulls = false
};
- private static readonly MatrixHttpClient HttpClient = new();
-
public Task<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) {
config ??= configuration;
return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => {
diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
index a99185c..a48d846 100644
--- a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
+++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
@@ -1,6 +1,5 @@
using System.Text.Json.Serialization;
using ArcaneLibs.Collections;
-using LibMatrix.Extensions;
using Microsoft.Extensions.Logging;
using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown;
using ResultType =
@@ -14,8 +13,6 @@ public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, We
StoreNulls = false
};
- private static readonly MatrixHttpClient HttpClient = new();
-
public Task<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) {
config ??= configuration;
return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => {
@@ -25,13 +22,11 @@ public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, We
await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/server", WellKnownResolverService.WellKnownSource.Https);
if (result.Content != null) return result;
-
return result;
});
}
}
-
public class ServerWellKnown {
[JsonPropertyName("m.server")]
public string Homeserver { get; set; }
|