0
0
mirror of https://github.com/sp-tarkov/modules.git synced 2025-02-13 09:50:43 -05:00
Merijn Hendriks 4b401e7449 async-bundles (!117)
This patch contains the following:

- Initial async VFS code (for reading / writing files)
- Simplified Http Client code
- Added async support to Http Client, RequestHandler
- Improved RequestHandler logging
- Deferred bundle loading to EasyAssetPatch
- Make GetManifestJson run async

This comes with a number of benefits:

- When downloading bundles, it will mention which files succeeded or failed to download
- Bundle loading happens in the initial screen, not the white screen
- Fixed the issue where bundle loading could break bepinex loading (too long load time)
- Modders can now make async http request and read/write files async

I removed logging of sessionid inside the RequestHandler for each request, sessionid is already visible from bepinex log startup parameters.

At last, sorry for the amount of commits it took. I initially wanted to target the 3.9.0 branch, but decided to use 3.8.1 instead as async request can really help out some mods.

Reviewed-on: SPT-AKI/Modules#117
Co-authored-by: Merijn Hendriks <merijn.d.hendriks@gmail.com>
Co-committed-by: Merijn Hendriks <merijn.d.hendriks@gmail.com>
2024-05-06 10:28:51 +00:00

156 lines
4.6 KiB
C#

using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Aki.Common.Utils;
namespace Aki.Common.Http
{
// NOTE: Don't dispose this, keep a reference for the lifetime of the
// application.
public class Client : IDisposable
{
protected readonly HttpClient _httpv;
protected readonly string _address;
protected readonly string _accountId;
protected readonly int _retries;
public Client(string address, string accountId, int retries = 3)
{
_address = address;
_accountId = accountId;
_retries = retries;
var handler = new HttpClientHandler
{
// set cookies in header instead
UseCookies = false
};
_httpv = new HttpClient(handler);
}
private HttpRequestMessage GetNewRequest(HttpMethod method, string path)
{
return new HttpRequestMessage()
{
Method = method,
RequestUri = new Uri(_address + path),
Headers = {
{ "Cookie", $"PHPSESSID={_accountId}" }
}
};
}
protected async Task<byte[]> SendAsync(HttpMethod method, string path, byte[] data, bool zipped = true)
{
HttpResponseMessage response = null;
using (var request = GetNewRequest(method, path))
{
if (data != null)
{
// add payload to request
if (zipped)
{
data = Zlib.Compress(data, ZlibCompression.Maximum);
}
request.Content = new ByteArrayContent(data);
}
// send request
response = await _httpv.SendAsync(request);
}
if (!response.IsSuccessStatusCode)
{
// response error
throw new Exception($"Code {response.StatusCode}");
}
using (var ms = new MemoryStream())
{
using (var stream = await response.Content.ReadAsStreamAsync())
{
// grap response payload
await stream.CopyToAsync(ms);
var body = ms.ToArray();
if (Zlib.IsCompressed(body))
{
body = Zlib.Decompress(body);
}
if (body == null)
{
// payload doesn't contains data
var code = response.StatusCode.ToString();
body = Encoding.UTF8.GetBytes(code);
}
return body;
}
}
}
protected async Task<byte[]> SendWithRetriesAsync(HttpMethod method, string path, byte[] data, bool compress = true)
{
var error = new Exception("Internal error");
// NOTE: <= is intentional. 0 is send, 1/2/3 is retry
for (var i = 0; i <= _retries; ++i)
{
try
{
return await SendAsync(method, path, data, compress);
}
catch (Exception ex)
{
error = ex;
}
}
throw error;
}
public async Task<byte[]> GetAsync(string path)
{
return await SendWithRetriesAsync(HttpMethod.Get, path, null);
}
public byte[] Get(string path)
{
return Task.Run(() => GetAsync(path)).Result;
}
public async Task<byte[]> PostAsync(string path, byte[] data, bool compress = true)
{
return await SendWithRetriesAsync(HttpMethod.Post, path, data, compress);
}
public byte[] Post(string path, byte[] data, bool compress = true)
{
return Task.Run(() => PostAsync(path, data, compress)).Result;
}
// NOTE: returns status code as bytes
public async Task<byte[]> PutAsync(string path, byte[] data, bool compress = true)
{
return await SendWithRetriesAsync(HttpMethod.Post, path, data, compress);
}
// NOTE: returns status code as bytes
public byte[] Put(string path, byte[] data, bool compress = true)
{
return Task.Run(() => PutAsync(path, data, compress)).Result;
}
public void Dispose()
{
_httpv.Dispose();
}
}
}