mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
108 lines
3.8 KiB
C#
108 lines
3.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Reflection;
|
|
using BestHTTP;
|
|
using HarmonyLib;
|
|
|
|
/// <summary>
|
|
/// Fabric-level repairs for two things the game cannot survive as-is:
|
|
/// a chunked response the server truncated, and two responses whose shape the
|
|
/// client's reader rejects.
|
|
/// </summary>
|
|
internal static class HttpTolerancePatches
|
|
{
|
|
private static long _healed;
|
|
private static int _lastHealReport;
|
|
|
|
internal static void Apply(Harmony harmony)
|
|
{
|
|
try
|
|
{
|
|
var target = TargetMethod();
|
|
if (target == null)
|
|
{
|
|
Diag.Warn("[Http] BestHTTP.HTTPResponse.ReadChunkLength(Stream) was not found: " +
|
|
"a truncated chunked body will keep failing its request.");
|
|
return;
|
|
}
|
|
|
|
harmony.Patch(target,
|
|
finalizer: new HarmonyMethod(AccessTools.Method(typeof(HttpTolerancePatches), nameof(Finalizer))));
|
|
Diag.Info("[Http] truncated chunked responses are completed instead of failing their request.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diag.Warn("[Http] could not arm the tolerant chunked reader (" + ex.GetType().Name + ": " + ex.Message + ").");
|
|
}
|
|
}
|
|
|
|
internal static MethodBase TargetMethod() =>
|
|
AccessTools.Method(typeof(HTTPResponse), "ReadChunkLength", new[] { typeof(Stream) });
|
|
|
|
/// <summary>
|
|
/// "Can't parse '' as a hex number" is BestHTTP reading a chunk-size line from a
|
|
/// connection the server had already closed - the pooled keep-alive connection
|
|
/// it picked was dead. Ending the body there (chunk length 0) delivers the
|
|
/// response that did arrive instead of failing the request, which is what the
|
|
/// heartbeats need: their ack body is empty anyway.
|
|
/// </summary>
|
|
private static Exception Finalizer(Exception __exception, ref int __result)
|
|
{
|
|
try
|
|
{
|
|
if (__exception == null) return null;
|
|
if (!ResponseRepair.IsTruncatedChunkFailure(__exception.Message)) return __exception;
|
|
|
|
ReportHealed();
|
|
__result = 0;
|
|
__exception = null;
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return __exception;
|
|
}
|
|
}
|
|
|
|
private static void ReportHealed()
|
|
{
|
|
var count = System.Threading.Interlocked.Increment(ref _healed);
|
|
var now = Environment.TickCount;
|
|
if (count != 1 && unchecked(now - _lastHealReport) < 60000) return;
|
|
_lastHealReport = now;
|
|
Diag.Warn("[Http] a chunked response ended early (dead pooled connection); the request was completed "
|
|
+ "with the bytes that arrived instead of failing. Occurrences so far: " + count + ".");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies <see cref="ResponseRepair"/> to a live response before the client's own
|
|
/// callback reads it. Called from the request funnel, always on the transport
|
|
/// thread that owns the response.
|
|
/// </summary>
|
|
internal static void RepairResponse(HTTPResponse response, string path, string query)
|
|
{
|
|
try
|
|
{
|
|
if (response == null || string.IsNullOrEmpty(path)) return;
|
|
if (response.StatusCode != 200) return;
|
|
|
|
string body;
|
|
try { body = response.DataAsText; }
|
|
catch { return; }
|
|
|
|
var repaired = ResponseRepair.RepairBody(path, query, body, out var note);
|
|
if (repaired == null) return;
|
|
|
|
response.Data = Encoding.UTF8.GetBytes(repaired);
|
|
|
|
if (Diag.FirstTime("repair:" + path))
|
|
Diag.Warn("[Repair] " + path + ": " + note + ".");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diag.Warn("[Repair] could not repair a " + path + " response: " + ex.GetType().Name + ": " + ex.Message);
|
|
}
|
|
}
|
|
}
|