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 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>
156 lines
4.6 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|