forked from SoDOff-Project/sodoff
assets server "partial" mode improvements
* add support for cache/store assets in partial mode * use stream approach in assets proxy mode * reduce memory usage on proxy big files * reduce risk of game client timeouts on slow connection
This commit is contained in:
parent
7888445eee
commit
bd6df27c92
@ -6,6 +6,7 @@ public class AssetServerConfig {
|
|||||||
public AssetServerMode Mode { get; set; }
|
public AssetServerMode Mode { get; set; }
|
||||||
public string ProviderURL { get; set; } = string.Empty;
|
public string ProviderURL { get; set; } = string.Empty;
|
||||||
public bool SubstituteMissingLocalAssets { get; set; } = false;
|
public bool SubstituteMissingLocalAssets { get; set; } = false;
|
||||||
|
public bool UseCache { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AssetServerMode {
|
public enum AssetServerMode {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Net;
|
||||||
|
using System.IO;
|
||||||
using sodoff.Configuration;
|
using sodoff.Configuration;
|
||||||
|
|
||||||
namespace sodoff.Middleware;
|
namespace sodoff.Middleware;
|
||||||
@ -34,6 +36,9 @@ public class AssetMiddleware
|
|||||||
|
|
||||||
string localPath = GetLocalPath("assets/" + assetPath);
|
string localPath = GetLocalPath("assets/" + assetPath);
|
||||||
|
|
||||||
|
if (localPath == string.Empty && config.Value.Mode == AssetServerMode.Partial && config.Value.UseCache)
|
||||||
|
localPath = GetLocalPath("assets-cache/" + assetPath);
|
||||||
|
|
||||||
if (localPath == string.Empty) {
|
if (localPath == string.Empty) {
|
||||||
if (config.Value.Mode == AssetServerMode.Partial)
|
if (config.Value.Mode == AssetServerMode.Partial)
|
||||||
await GetRemoteAsset(context, assetPath);
|
await GetRemoteAsset(context, assetPath);
|
||||||
@ -49,24 +54,58 @@ public class AssetMiddleware
|
|||||||
private async Task GetRemoteAsset(HttpContext context, string path)
|
private async Task GetRemoteAsset(HttpContext context, string path)
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
|
string filePath = Path.GetFullPath("assets-cache/" + path);
|
||||||
|
string filePathTmp = filePath + Path.GetRandomFileName().Substring(0, 8);
|
||||||
try {
|
try {
|
||||||
var response = await client.GetAsync(config.Value.ProviderURL + path);
|
using (var response = await client.GetAsync(
|
||||||
string? contentType = response.Content.Headers.ContentType?.MediaType;
|
config.Value.ProviderURL + path,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead
|
||||||
|
)) {
|
||||||
|
if (response.IsSuccessStatusCode) {
|
||||||
|
if (response.Content.Headers.ContentType?.MediaType != null)
|
||||||
|
context.Response.Headers["Content-Type"] = response.Content.Headers.ContentType?.MediaType;
|
||||||
|
|
||||||
if (contentType is null) {
|
if (response.Content.Headers.ContentLength != null)
|
||||||
context.Response.StatusCode = 404;
|
context.Response.Headers["Content-Length"] = response.Content.Headers.ContentLength.ToString();
|
||||||
}
|
|
||||||
else if (contentType.StartsWith("application/octet-stream") || contentType.StartsWith("image/jpeg")) {
|
using (var inputStream = await response.Content.ReadAsStreamAsync()) {
|
||||||
context.Response.Headers["Content-Type"] = contentType;
|
if (config.Value.UseCache) {
|
||||||
await response.Content.CopyToAsync(context.Response.Body);
|
string dirPath = Path.GetDirectoryName(filePath);
|
||||||
}
|
if (!Directory.Exists(dirPath)) {
|
||||||
else {
|
Directory.CreateDirectory(dirPath);
|
||||||
context.Response.ContentType = contentType;
|
}
|
||||||
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
|
|
||||||
|
// copy data retrieved from upstream server to file and to response for game client
|
||||||
|
using (var fileStream = File.Open(filePathTmp, FileMode.Create)) {
|
||||||
|
// read response from upstream server
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
|
||||||
|
// write to temporary file
|
||||||
|
var task1 = fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||||
|
// send to client
|
||||||
|
var task2 = context.Response.Body.WriteAsync(buffer, 0, bytesRead);
|
||||||
|
// wait for finish both writes
|
||||||
|
await Task.WhenAll(task1, task2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// after successfully write data to temporary file, rename it to proper asset filename
|
||||||
|
File.Move(filePathTmp, filePath);
|
||||||
|
} else {
|
||||||
|
await inputStream.CopyToAsync(context.Response.Body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.Response.StatusCode = 404;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception) {
|
catch (Exception) {
|
||||||
context.Response.StatusCode = 404;
|
if (File.Exists(filePathTmp))
|
||||||
|
File.Delete(filePathTmp);
|
||||||
|
if (!context.Response.HasStarted)
|
||||||
|
context.Response.StatusCode = 502;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user