mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-13 09:50:43 -05:00
![Merijn Hendriks](/assets/img/avatar_default.png)
This PR won't affect modders who have already updated their mods to 3.8.0. They just need to re-compile. This is a resubmission of my previous PR (SPT-AKI/Modules#99) with additional code cleanup and fixes. Instead of outright removing the functionality, this time I deprecate it instead (marked for removal in next release). Requires SPT-AKI/Server#274 to function. ## Overview - HTTP modernization - Adds `Aki.Common.Http.Client`, a replacement for `Aki.Common.Http.Request` and builds on top of `System.Net.Http.HttpClient` - Implements failsafe retries when requesting during busy connections - Improved debugging - Improved performance - Deprecades old request code - Bundle system - Fixes remote downloaded bundles using external IP - Implements functional bundle caching from remote sources - Implements multi-threaded bundle downloads - Implements Unity-compatible bundle format support - Extensive cleanup - Deprecated unneccecary models ## Why? In it's current state, the bundle system is ducktaped together in 2021, fundumentally broken and in desperate need of a cleanup and fixes. The HTTP code hasn't been updated since 2021, and `HttpWebRequest` has been deprecated by Microsoft for a while now. There was also a lot of opportunity left for simple performance gains that even reduces the complexity of the code. As for why not two separate PRs (HTTP modernization, bundle rework): both were deeply interconnected. A change in one requires modification in the other. Hence the current approach. ## Testing The code has been validated and tested by @TheSparta and me. A large section of the code has been implemented and tested extensively by modders. ### Local 1. Start the game from 127.0.0.1 2. The game starts loading bundles from the mods path ### Remote, full re-aquire 1. Start the server from LAN IP (http.json, set host to `cmd > ipconfig` address, example: `192.168.178.32`) 2. Start the game from LAN IP 3. A folder named `user/cache/bundles` is created ### Remote, partial-aquire (deleted) 1. Ensure all bundles are cached 2. Delete one of the aquired bundles from cache 3. Start the server from LAN IP (http.json, set host to `cmd > ipconfig` address, example: `192.168.178.32`) 4. Start the game from LAN IP 5. The bundle is redownloaded ### Remote, partial-aquire (invalid crc) 1. Ensure all bundles are cached 2. Update a bundle mod with a new bundle on the same path 3. Start the server from LAN IP (http.json, set host to `cmd > ipconfig` address, example: `192.168.178.32`) 4. Start the game from LAN IP 5. The bundle is redownloaded ### Remote, use cache 1. Ensure all bundles are cached 2. Start the server from LAN IP (http.json, set host to `cmd > ipconfig` address, example: `192.168.178.32`) 3. Start the game from LAN IP 4. The game starts loading bundles from the cache path ## Risk assessment In order to reduce friction between releases, this PR introduces a deprecation system. Obsolete classes and methods have been marked deprecated. These will remain available for the current release and continue to function as normal for this release. A warning will be displayed during build when a modder relies on the deprecated functionality. The marked classes and methods are to be removed in the next release. The server-side changes have no impact on modders. ## Deprecation The following classes are affected: - `Aki.Common.Http.Request`: Replaced by `Aki.Common.Http.Request` - `Aki.Common.Http.WebConstants`: Replaced by functionality from `System.Net.Http` - `Aki.Custom.Models.BundleInfo`: Replaced by `Aki.Custom.Models.BundleItem` The following methods are affected: - `Aki.Common.Http.RequestHandler.GetData(path, hasHost)`: `hasHost` enables connection outside intended host. - `Aki.Common.Http.RequestHandler.GetJson(path, hasHost)`: `hasHost` enables connection outside intended host. - `Aki.Common.Http.RequestHandler.PostJson(path, json, hasHost)`: `hasHost` enables connection outside intended host. - `Aki.Common.Http.RequestHandler.PutJson(path, json, hasHost)`: `hasHost` enables connection outside intended host. The deprecated methods and `Aki.Custom.Models.BundleInfo` are self-contained and can be removed independently. The deprecated classes require removal of all deprecated code at once. Reviewed-on: SPT-AKI/Modules#104 Reviewed-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com> Co-authored-by: Merijn Hendriks <merijn.d.hendriks@gmail.com> Co-committed-by: Merijn Hendriks <merijn.d.hendriks@gmail.com>
192 lines
6.0 KiB
C#
192 lines
6.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using BepInEx.Logging;
|
|
using Aki.Common.Utils;
|
|
|
|
namespace Aki.Common.Http
|
|
{
|
|
public static class RequestHandler
|
|
{
|
|
private static ManualLogSource _logger;
|
|
public static readonly Client HttpClient;
|
|
public static readonly string Host;
|
|
public static readonly string SessionId;
|
|
public static readonly bool IsLocal;
|
|
|
|
static RequestHandler()
|
|
{
|
|
_logger = Logger.CreateLogSource(nameof(RequestHandler));
|
|
|
|
// grab required info from command args
|
|
var args = Environment.GetCommandLineArgs();
|
|
|
|
foreach (var arg in args)
|
|
{
|
|
if (arg.Contains("BackendUrl"))
|
|
{
|
|
var json = arg.Replace("-config=", string.Empty);
|
|
Host = Json.Deserialize<ServerConfig>(json).BackendUrl;
|
|
}
|
|
|
|
if (arg.Contains("-token="))
|
|
{
|
|
SessionId = arg.Replace("-token=", string.Empty);
|
|
}
|
|
}
|
|
|
|
IsLocal = Host.Contains("127.0.0.1")
|
|
|| Host.Contains("localhost");
|
|
|
|
// initialize http client
|
|
HttpClient = new Client(Host, SessionId);
|
|
}
|
|
|
|
private static void ValidateData(byte[] data)
|
|
{
|
|
if (data == null)
|
|
{
|
|
_logger.LogError($"Request failed, body is null");
|
|
}
|
|
|
|
_logger.LogInfo($"Request was successful");
|
|
}
|
|
|
|
private static void ValidateJson(string json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
_logger.LogError($"Request failed, body is null");
|
|
}
|
|
|
|
_logger.LogInfo($"Request was successful");
|
|
}
|
|
|
|
public static byte[] GetData(string path)
|
|
{
|
|
_logger.LogInfo($"Request GET data: {SessionId}:{path}");
|
|
|
|
var data = HttpClient.Get(path);
|
|
|
|
ValidateData(data);
|
|
return data;
|
|
}
|
|
|
|
public static string GetJson(string path)
|
|
{
|
|
_logger.LogInfo($"Request GET json: {SessionId}:{path}");
|
|
|
|
var payload = HttpClient.Get(path);
|
|
var body = Encoding.UTF8.GetString(payload);
|
|
|
|
ValidateJson(body);
|
|
return body;
|
|
}
|
|
|
|
public static string PostJson(string path, string json)
|
|
{
|
|
_logger.LogInfo($"Request POST json: {SessionId}:{path}");
|
|
|
|
var payload = Encoding.UTF8.GetBytes(json);
|
|
var data = HttpClient.Post(path, payload);
|
|
var body = Encoding.UTF8.GetString(data);
|
|
|
|
ValidateJson(body);
|
|
return body;
|
|
}
|
|
|
|
public static void PutJson(string path, string json)
|
|
{
|
|
_logger.LogInfo($"Request PUT json: {SessionId}:{path}");
|
|
|
|
var payload = Encoding.UTF8.GetBytes(json);
|
|
HttpClient.Put(path, payload);
|
|
}
|
|
|
|
#region DEPRECATED, REMOVE IN 3.8.1
|
|
[Obsolete("GetData(path, isHost) is deprecated, please use GetData(path) instead.")]
|
|
public static byte[] GetData(string path, bool hasHost)
|
|
{
|
|
var url = (hasHost) ? path : Host + path;
|
|
_logger.LogInfo($"Request GET data: {SessionId}:{url}");
|
|
|
|
var headers = new Dictionary<string, string>()
|
|
{
|
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
|
{ "SessionId", SessionId }
|
|
};
|
|
|
|
var request = new Request();
|
|
var data = request.Send(url, "GET", null, headers: headers);
|
|
|
|
ValidateData(data);
|
|
return data;
|
|
|
|
}
|
|
|
|
[Obsolete("GetJson(path, isHost) is deprecated, please use GetJson(path) instead.")]
|
|
public static string GetJson(string path, bool hasHost)
|
|
{
|
|
var url = (hasHost) ? path : Host + path;
|
|
_logger.LogInfo($"Request GET json: {SessionId}:{url}");
|
|
|
|
var headers = new Dictionary<string, string>()
|
|
{
|
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
|
{ "SessionId", SessionId }
|
|
};
|
|
|
|
var request = new Request();
|
|
var data = request.Send(url, "GET", headers: headers);
|
|
var body = Encoding.UTF8.GetString(data);
|
|
|
|
ValidateJson(body);
|
|
return body;
|
|
|
|
}
|
|
|
|
[Obsolete("PostJson(path, json, isHost) is deprecated, please use PostJson(path, json) instead.")]
|
|
public static string PostJson(string path, string json, bool hasHost)
|
|
{
|
|
var url = (hasHost) ? path : Host + path;
|
|
_logger.LogInfo($"Request POST json: {SessionId}:{url}");
|
|
|
|
var payload = Encoding.UTF8.GetBytes(json);
|
|
var mime = WebConstants.Mime[".json"];
|
|
var headers = new Dictionary<string, string>()
|
|
{
|
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
|
{ "SessionId", SessionId }
|
|
};
|
|
|
|
var request = new Request();
|
|
var data = request.Send(url, "POST", payload, true, mime, headers);
|
|
var body = Encoding.UTF8.GetString(data);
|
|
|
|
ValidateJson(body);
|
|
return body;
|
|
|
|
}
|
|
|
|
[Obsolete("PutJson(path, json, isHost) is deprecated, please use PutJson(path, json) instead.")]
|
|
public static void PutJson(string path, string json, bool hasHost)
|
|
{
|
|
var url = (hasHost) ? path : Host + path;
|
|
_logger.LogInfo($"Request PUT json: {SessionId}:{url}");
|
|
|
|
var payload = Encoding.UTF8.GetBytes(json);
|
|
var mime = WebConstants.Mime[".json"];
|
|
var headers = new Dictionary<string, string>()
|
|
{
|
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
|
{ "SessionId", SessionId }
|
|
};
|
|
|
|
var request = new Request();
|
|
request.Send(url, "PUT", payload, true, mime, headers);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|