partial request working

This commit is contained in:
Alex
2025-01-09 23:11:57 +00:00
parent 589a5644d2
commit fa22bc8019
19 changed files with 766 additions and 36 deletions
+7
View File
@@ -0,0 +1,7 @@
namespace Core.DI;
public interface ISerializer
{
public void Serialize(string sessionID, HttpRequest req, HttpResponse resp, object? body);
public bool CanHandle(string something);
}
+107
View File
@@ -0,0 +1,107 @@
using Core.Models.Eft.Common;
using Core.Models.Eft.ItemEvent;
using Core.Models.Eft.Profile;
using Core.Models.Utils;
namespace Core.DI;
public abstract class Router
{
protected List<HandledRoute> handledRoutes = [];
public string GetTopLevelRoute()
{
return "spt";
}
protected abstract List<HandledRoute> GetHandledRoutes();
protected List<HandledRoute> GetInternalHandledRoutes()
{
if (handledRoutes.Count == 0)
{
handledRoutes = GetHandledRoutes();
}
return handledRoutes;
}
public bool CanHandle(string url, bool partialMatch = false)
{
if (partialMatch)
{
return GetInternalHandledRoutes()
.Where((r) => r.dynamic)
.Any((r) => url.Contains(r.route));
}
return GetInternalHandledRoutes()
.Where((r) => !r.dynamic)
.Any((r) => r.route == url);
}
public abstract Type? GetBodyDeserializationType();
}
public abstract class StaticRouter : Router
{
private List<RouteAction<IRequestData>> actions;
public StaticRouter(List<RouteAction<IRequestData>> routes) : base()
{
actions = routes;
}
public object HandleStatic(string url, IRequestData? info, string sessionID, string output)
{
return actions.Single(route => route.url == url).action(url, info, sessionID, output);
}
protected override List<HandledRoute> GetHandledRoutes()
{
return actions.Select((route) => new HandledRoute(route.url, false)).ToList();
}
}
public abstract class DynamicRouter : Router
{
private List<RouteAction<IRequestData>> actions;
public DynamicRouter(List<RouteAction<IRequestData>> routes) : base()
{
actions = routes;
}
public object HandleDynamic(string url, IRequestData? info, string sessionID, string output)
{
return actions.First(r => url.Contains(r.url)).action(url, info, sessionID, output);
}
protected override List<HandledRoute> GetHandledRoutes()
{
return actions.Select((route) => new HandledRoute(route.url, true)).ToList();
}
}
// The name of this class should be ItemEventRouter, but that name is taken,
// So instead I added the definition
public abstract class ItemEventRouterDefinition : Router
{
public abstract object HandleItemEvent(
string url,
PmcData pmcData,
object body,
string sessionID,
ItemEventRouterResponse output
);
}
public abstract class SaveLoadRouter : Router
{
public abstract SptProfile HandleLoad(SptProfile profile);
}
public record HandledRoute(string route, bool dynamic);
public record RouteAction<T>(string url, Func<string, T?, string?, string?, object> action) where T : IRequestData;
//public action: (url: string, info: any, sessionID: string, output: string) => Promise<any>,
+1 -1
View File
@@ -60,7 +60,7 @@ public class HttpServerHelper
public void SendTextJson(HttpResponse resp, object output)
{
resp.Headers.Add("Content-Type", mime["json"]);
resp.Headers.Append("Content-Type", mime["json"]);
resp.StatusCode = 200;
/* TODO: figure this one out
resp.writeHead(200, "OK", {
+5 -3
View File
@@ -1,5 +1,7 @@
namespace Core.Models.Eft.Common;
using Core.Models.Utils;
public class EmptyRequestData
namespace Core.Models.Eft.Common;
public class EmptyRequestData : IRequestData
{
}
}
+90
View File
@@ -0,0 +1,90 @@
namespace Core.Models.Enums;
public enum BackendErrorCodes
{
NONE = 0,
UNKNOWN_ERROR = 200,
NOT_AUTHORIZED = 201,
NEED_AUTHORIZATION_CODE = 209,
WRONG_AUTHORIZATION_CODE = 211,
NEED_CAPTCHA = 214,
NO_NEED_CAPTCHA = 215,
CAPTCHA_INVALID_ANSWER = 216,
CAPTCHA_FAILED = 218,
CAPTCHA_BRUTE_FORCED = 219,
NO_ROOM_IN_STASH = 223,
NICKNAME_NOT_UNIQUE = 225,
NICKNAME_NOT_VALID = 226,
UNSUPPORTED_CLIENT_VERSION = 232,
REPORT_NOT_ALLOWED = 238,
NICKNAME_IS_ABUSIVE = 241,
NICKNAME_CHANGE_TIMEOUT = 242,
NOT_ENOUGH_SPACE_TO_UNPACK = 257,
NOT_MODIFIED = 304,
HTTP_BAD_REQUEST = 400,
HTTP_NOT_AUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405,
UNKNOWN_TRADING_ERROR = 500,
HTTPNOTIMPLEMENTED = 501,
HTTPBADGATEWAY = 502,
HTTPSERVICEUNAVAILABLE = 503,
HTTPGATEWAYTIMEOUT = 504,
TRADEROUTOFMONEY = 505,
HTTPVARIANTALSONEGOTIATES = 506,
PRICECHANGED = 509,
TRADERDISABLED = 512,
ITEMHASBEENSOLD = 513,
NOTENOUGHSPACEFORMONEY = 518,
HTTPINVALIDSSLCERTIFICATE = 526,
UNKNOWNRAGFAIRERROR = 550,
UNKNOWNRAGFAIRERROR2 = 551,
UNKNOWNMATCHMAKERERROR = 600,
SESSIONPARAMETERSERROR = 601,
SESSIONLOST = 602,
SERVERNOTREGISTERED = 604,
UNKNOWNQUESTERROR = 700,
QUESTBADPARAM = 702,
QUESTNOTFOUND = 703,
QUESTISUNAVAILABLE = 704,
NOFREESPACEFORREWARDS = 705,
WRONGQUESTSTATUS = 706,
CANTCOMPLETEQUEST = 707,
UNKNOWNMAILERROR = 900,
TOOMANYFRIENDREQUESTS = 925,
UNKNOWNSCRIPTEXECUTIONERROR = 1000,
UNKNOWNREPAIRINGERROR = 1200,
UNKNOWNINSURANCEERROR = 1300,
UNKNOWNCURRENCYEXCHANGEERROR = 1400,
OFFERNOTFOUND = 1503,
NOTENOUGHSPACE = 1505,
OFFEROUTOFSTOCK = 1506,
OFFERSOLD = 1507,
RAGFAIRUNAVAILABLE = 1511,
BANNEDERRORCODE = 1513,
INSUFFICIENTNUMBERINSTOCK = 1516,
TOOMANYITEMSTOSELL = 1517,
INCORRECTCLIENTPRICE = 1519,
EXAMINATIONFAILED = 22001,
ITEMALREADYEXAMINED = 22002,
UNKNOWNNGINXERROR = 9000,
PARSERESPONSEERROR = 9001,
UNKNOWNMATCHMAKERERROR2 = 503000, // They have two of these...why :/
UNKNOWNGROUPERROR = 502000,
GROUPREQUESTNOTFOUND = 502002,
GROUPFULL = 502004,
PLAYERALREADYINGROUP = 502005,
PLAYERNOTINGROUP = 502006,
PLAYERNOTLEADER = 502007,
CANTCHANGEREADYSTATE = 502010,
PLAYERFORBIDDENGROUPINVITES = 502011,
LEADERALREADYREADY = 502012,
GROUPSENDINVITEERROR = 502013,
PLAYERISOFFLINE = 502014,
PLAYERISNOTSEARCHINGFORGROUP = 502018,
PLAYERALREADYLOOKINGFORGAME = 503001,
PLAYERINRAID = 503002,
LIMITFORPRESETSREACHED = 504001,
PLAYERPROFILENOTFOUND = 505001,
}
+5
View File
@@ -0,0 +1,5 @@
namespace Core.Models.Utils;
public interface IRequestData
{
}
+23
View File
@@ -0,0 +1,23 @@
using Core.Annotations;
using Core.DI;
namespace Core.Routers.Dynamic;
[Injectable(InjectableTypeOverride = typeof(DynamicRouter))]
public class HttpDynamicRouter : DynamicRouter
{
public HttpDynamicRouter(ImageRouter imageRouter) : base(
[
new(".jpg", (_, _, _, _) => imageRouter.GetImage()),
new(".png", (_, _, _, _) => imageRouter.GetImage()),
new(".ico", (_, _, _, _) => imageRouter.GetImage())
]
)
{
}
public override Type? GetBodyDeserializationType()
{
return null;
}
}
+94
View File
@@ -0,0 +1,94 @@
using System.Text.Json;
using Core.Annotations;
using Core.DI;
namespace Core.Routers;
[Injectable]
public class HttpRouter
{
private readonly IEnumerable<StaticRouter> _staticRouters;
private readonly IEnumerable<DynamicRouter> _dynamicRoutes;
public HttpRouter(
IEnumerable<StaticRouter> staticRouters,
IEnumerable<DynamicRouter> dynamicRoutes
)
{
_staticRouters = staticRouters;
_dynamicRoutes = dynamicRoutes;
}
/*
protected groupBy<T>(list: T[], keyGetter: (t: T) => string): Map<string, T[]> {
const map: Map<string, T[]> = new Map();
for (const item of list) {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
}
return map;
}
*/
public string? GetResponse(HttpRequest req, string sessionID, string? body, out object deserializedObject)
{
var wrapper = new ResponseWrapper("");
var handled = HandleRoute(req, sessionID, wrapper, _staticRouters, false, body, out deserializedObject);
if (!handled) {
HandleRoute(req, sessionID, wrapper, _dynamicRoutes, true, body, out deserializedObject);
}
// TODO: Temporary hack to change ItemEventRouter response sessionID binding to what client expects
if (wrapper.Output?.Contains("\"profileChanges\":{") ?? false) {
wrapper.Output = wrapper.Output.Replace(sessionID, sessionID);
}
return wrapper.Output;
}
protected bool HandleRoute(
HttpRequest request,
string sessionID,
ResponseWrapper wrapper,
IEnumerable<Router> routers,
bool dynamic,
string? body,
out object deserializedObject
)
{
var url = request.Path.Value;
deserializedObject = null;
// remove retry from url
if (url?.Contains("?retry=") ?? false) {
url = url.Split("?retry=")[0];
}
var matched = false;
foreach (var route in routers)
{
if (route.CanHandle(url, dynamic)) {
var type = route.GetBodyDeserializationType();
if (type != null && !string.IsNullOrEmpty(body))
deserializedObject = JsonSerializer.Deserialize(body, type);
if (dynamic) {
wrapper.Output = (route as DynamicRouter).HandleDynamic(url, deserializedObject, sessionID, wrapper.Output) as string;
} else {
wrapper.Output = (route as StaticRouter).HandleStatic(url, deserializedObject, sessionID, wrapper.Output) as string;
}
matched = true;
}
}
return matched;
}
protected class ResponseWrapper(string? output)
{
public string? Output { get; set; } = output;
}
}
+8 -7
View File
@@ -25,22 +25,23 @@ public class ImageRouter
public void AddRoute(string key, string valueToAdd)
{
_imageRouterService.addRoute(key, valueToAdd);
_imageRouterService.AddRoute(key, valueToAdd);
}
public Task SendImage(string sessionID, HttpRequest req, HttpResponse resp, object body)
public void SendImage(string sessionID, HttpRequest req, HttpResponse resp, object body)
{
// remove file extension
var url = _fileUtil.StripExtension(req.Path);
var url = _fileUtil.StripExtension(req.Path, true);
// send image
if (_imageRouterService.ExistsByKey(url)) {
return _httpFileUtil.SendFileAsync(resp, _imageRouterService.getByKey(url));
if (_imageRouterService.ExistsByKey(url))
{
_httpFileUtil.SendFile(resp, _imageRouterService.GetByKey(url));
}
return Task.CompletedTask;
}
public string GetImage() {
public string GetImage()
{
return "IMAGE";
}
}
@@ -0,0 +1,25 @@
using Core.Annotations;
using Core.DI;
namespace Core.Routers.Serializers;
[Injectable]
public class ImageSerializer : ISerializer
{
protected ImageRouter _imageRouter;
public ImageSerializer(ImageRouter imageRouter)
{
_imageRouter = imageRouter;
}
public void Serialize(string sessionID, HttpRequest req, HttpResponse resp, object? body)
{
_imageRouter.SendImage(sessionID, req, resp, body);
}
public bool CanHandle(string route)
{
return route == "IMAGE";
}
}
@@ -0,0 +1,60 @@
using Core.Annotations;
using Core.Callbacks;
using Core.DI;
using Core.Models.Eft.Common;
namespace Core.Routers.Static;
[Injectable(InjectableTypeOverride = typeof(StaticRouter))]
public class LauncherStaticRouter : StaticRouter {
public LauncherStaticRouter(LauncherCallbacks launcherCallbacks) : base([
new RouteAction<EmptyRequestData?>(
"/launcher/ping",
(url, _, sessionID, _) => launcherCallbacks.Ping(url, null, sessionID)),
new RouteAction<EmptyRequestData?>(
"/launcher/server/connect",
(_, _, _, _) => launcherCallbacks.Connect()),
new RouteAction(
"/launcher/profile/login",
(url, info, sessionID, _) => launcherCallbacks.Login(url, info, sessionID)),
new RouteAction(
"/launcher/profile/register",
(url, info, sessionID, _) => launcherCallbacks.Register(url, info, sessionID)),
new RouteAction(
"/launcher/profile/get",
(url, info, sessionID, _) => launcherCallbacks.Get(url, info, sessionID)),
new RouteAction(
"/launcher/profile/change/username",
(url, info, sessionID, _) => launcherCallbacks.ChangeUsername(url, info, sessionID)),
new RouteAction(
"/launcher/profile/change/password",
(url, info, sessionID, _) => launcherCallbacks.ChangePassword(url, info, sessionID)),
new RouteAction(
"/launcher/profile/change/wipe",
(url, info, sessionID, _) => launcherCallbacks.Wipe(url, info, sessionID)),
new RouteAction(
"/launcher/profile/remove",
(url, info, sessionID, _) => launcherCallbacks.RemoveProfile(url, info, sessionID)),
new RouteAction(
"/launcher/profile/compatibleTarkovVersion",
(_, _, _, _) => launcherCallbacks.GetCompatibleTarkovVersion()),
new RouteAction(
"/launcher/server/version",
(_, _, _, _) => launcherCallbacks.GetServerVersion()),
new RouteAction(
"/launcher/server/loadedServerMods",
(_, _, _, _) => launcherCallbacks.GetLoadedServerMods()),
new RouteAction(
"/launcher/server/serverModsUsedByProfile",
(url, info, sessionID, _) => launcherCallbacks.GetServerModsProfileUsed(url, info, sessionID)),
])
{
}
public override Type? GetBodyDeserializationType()
{
throw new NotImplementedException();
}
}
}
+3 -3
View File
@@ -2,6 +2,6 @@
public interface IHttpListener
{
bool CanHandle(string sessionId, object req);
Task Handle(string sessionId, object req, object resp);
}
bool CanHandle(string sessionId, HttpRequest req);
void Handle(string sessionId, HttpRequest req, HttpResponse resp);
}
+188
View File
@@ -0,0 +1,188 @@
using System.Collections.Immutable;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
using Core.Annotations;
using Core.DI;
using Core.Routers;
using Core.Services;
using Core.Utils;
using ILogger = Core.Models.Utils.ILogger;
namespace Core.Servers.Http;
[Injectable]
public class SptHttpListener : IHttpListener
{
protected readonly HttpRouter _router;
protected readonly IEnumerable<ISerializer> _serializers;
protected readonly ILogger _logger;
protected readonly HttpResponseUtil _httpResponseUtil;
protected readonly LocalisationService _localisationService;
public SptHttpListener(
HttpRouter httpRouter, // TODO: delay required
IEnumerable<ISerializer> serializers,
ILogger logger,
// TODO: requestsLogger: ILogger,
// TODO: JsonUtil jsonUtil,
HttpResponseUtil httpHttpResponseUtil,
LocalisationService localisationService
)
{
_router = httpRouter;
_serializers = serializers;
_logger = logger;
_httpResponseUtil = httpHttpResponseUtil;
_localisationService = localisationService;
}
private static readonly ImmutableHashSet<string> SupportedMethods = ["GET", "PUT", "POST"];
public bool CanHandle(string _, HttpRequest req)
{
return SupportedMethods.Contains(req.Method);
}
public void Handle(string sessionId, HttpRequest req, HttpResponse resp)
{
switch (req.Method) {
case "GET": {
var response = GetResponse(sessionId, req, null);
SendResponse(sessionId, req, resp, null, response);
break;
}
// these are handled almost identically.
case "POST":
case "PUT": {
// Contrary to reasonable expectations, the content-encoding is _not_ actually used to
// determine if the payload is compressed. All PUT requests are, and POST requests without
// debug = 1 are as well. This should be fixed.
// let compressed = req.headers["content-encoding"] === "deflate";
var requestIsCompressed = req.Headers.TryGetValue("requestcompressed", out var compressHeader) &&
compressHeader != "0";
var requestCompressed = req.Method == "PUT" || requestIsCompressed;
var fullTextBody = new StreamReader(req.Body).ReadToEnd();
;
var value = requestCompressed ? new StreamReader(new ZLibStream(req.Body, CompressionLevel.SmallestSize, false)).ReadToEnd() : fullTextBody;
if (!requestIsCompressed) {
_logger.Debug(value, true);
}
var response = GetResponse(sessionId, req, value);
SendResponse(sessionId, req, resp, value, response);
break;
}
default: {
_logger.Warning($"{_localisationService.GetText("unknown_request")}: {req.Method}");
break;
}
}
}
/**
* Send HTTP response back to sender
* @param sessionID Player id making request
* @param req Incoming request
* @param resp Outgoing response
* @param body Buffer
* @param output Server generated response data
*/
public void SendResponse(
string sessionID,
HttpRequest req,
HttpResponse resp,
object? body,
string output
)
{
if (body == null)
body = "{}";
var bodyInfo = JsonSerializer.Serialize(body);
if (IsDebugRequest(req)) {
// Send only raw response without transformation
SendJson(resp, output, sessionID);
// TODO: this.logRequest(req, output);
return;
}
// Not debug, minority of requests need a serializer to do the job (IMAGE/BUNDLE/NOTIFY)
var serialiser = _serializers.FirstOrDefault((x) => x.CanHandle(output));
if (serialiser != null) {
serialiser.Serialize(sessionID, req, resp, bodyInfo);
} else {
// No serializer can handle the request (majority of requests dont), zlib the output and send response back
SendZlibJson(resp, output, sessionID);
}
// TODO: this.LogRequest(req, output);
}
/**
* Is request flagged as debug enabled
* @param req Incoming request
* @returns True if request is flagged as debug
*/
protected bool IsDebugRequest(HttpRequest req) {
return req.Headers.TryGetValue("responsecompressed", out var value) && value == "0";
}
/**
* Log request if enabled
* @param req Incoming message request
* @param output Output string
*/
/* TODO: log requests
protected logRequest(req: IncomingMessage, output: string): void {
//
if (ProgramStatics.ENTRY_TYPE !== EntryType.RELEASE) {
const log = new Response(req.method, output);
this.requestsLogger.info(`RESPONSE=${this.jsonUtil.serialize(log)}`);
}
}
*/
public string GetResponse(string sessionID, HttpRequest req, string? body)
{
/* TODO: REQUEST LOGGER
if (ProgramStatics.ENTRY_TYPE !== EntryType.RELEASE) {
// Parse quest info into object
const data = typeof info === "object" ? info : this.jsonUtil.deserialize(info);
const log = new Request(req.method, new RequestData(req.url, req.headers, data));
this.requestsLogger.info(`REQUEST=${this.jsonUtil.serialize(log)}`);
}
*/
var output = _router.GetResponse(req, sessionID, body, out var deserializedObject);
/* route doesn't exist or response is not properly set up */
if (string.IsNullOrEmpty(output)) {
_logger.Error(_localisationService.GetText("unhandled_response", req.Path));
_logger.Info(JsonSerializer.Serialize(deserializedObject));
output = _httpResponseUtil.GetBody<object?>(null, 404, $"UNHANDLED RESPONSE: {req.Path}");
}
return output;
}
public void SendJson(HttpResponse resp, string? output, string sessionID)
{
if (!string.IsNullOrEmpty(output))
resp.Body = new MemoryStream(Encoding.UTF8.GetBytes(output));
resp.StatusCode = 200;
resp.ContentType = "application/json";
resp.Headers.Append("Set-Cookie", $"PHPSESSID={sessionID}");
resp.StartAsync().Wait();
resp.CompleteAsync().Wait();
}
public void SendZlibJson(HttpResponse resp, string? output, string sessionID)
{
if (!string.IsNullOrEmpty(output))
new ZLibStream(resp.Body, CompressionLevel.SmallestSize, false).WriteAsync(Encoding.UTF8.GetBytes(output)).AsTask().Wait();
resp.StartAsync().Wait();
resp.CompleteAsync().Wait();
}
}
+7 -3
View File
@@ -53,7 +53,11 @@ public class HttpServer
app.UseWebSockets();
app.UseRouting();
app.UseEndpoints(endpointBuilder => { endpointBuilder.MapFallback(HandleFallback); });
app.Use((HttpContext req, RequestDelegate _) =>
{
return Task.Factory.StartNew(() => HandleFallback(req));
});
// app.UseEndpoints(endpointBuilder => { endpointBuilder.MapFallback(HandleFallback); });
started = true;
app.Run($"http://{httpConfig.Ip}:{httpConfig.Port}");
}
@@ -101,7 +105,7 @@ public class HttpServer
}
//_httpListeners.Single()
_httpListeners.SingleOrDefault(l => l.CanHandle(sessionId, context.Request))?.Handle(sessionId, context.Request, context.Response);
// This http request would be passed through the SPT Router and handled by an ICallback
}
@@ -158,4 +162,4 @@ public class HttpServer
{
return started;
}
}
}
@@ -7,12 +7,12 @@ public class ImageRouterService
{
protected Dictionary<string, string> routes = new();
public void addRoute(string urlKey, string route)
public void AddRoute(string urlKey, string route)
{
routes[urlKey] = route;
}
public string getByKey(string urlKey)
public string GetByKey(string urlKey)
{
return routes[urlKey];
}
+16 -12
View File
@@ -51,6 +51,7 @@ public class DatabaseImporter : OnLoad
_importerUtil = importerUtil;
_configServer = configServer;
_fileUtil = fileUtil;
_imageRouter = imageRouter;
httpConfig = _configServer.GetConfig<HttpConfig>(ConfigTypes.HTTP);
}
@@ -91,7 +92,7 @@ public class DatabaseImporter : OnLoad
await HydrateDatabase(filepath);
var imageFilePath = $"${filepath}images/";
var imageFilePath = $"{filepath}images/";
//var directories = this.vfs.getDirs(imageFilePath);
LoadImages(imageFilePath, _fileUtil.GetDirectories(imageFilePath), [
"/files/achievement/",
@@ -175,25 +176,26 @@ public class DatabaseImporter : OnLoad
{
// Get all files in directory
var filesInDirectory = _fileUtil.GetFiles(directories[i]);
foreach (var file in filesInDirectory) {
foreach (var file in filesInDirectory)
{
var imagePath = file;
// Register each file in image router
var filename = _fileUtil.StripExtension(file);
var routeKey = $"{routes[i]}{filename}";
var imagePath = $"{filepath}{directories[i]}/{file}";
/*
const pathOverride = this.getImagePathOverride(imagePath);
if (pathOverride) {
this.logger.debug(`overrode route: ${routeKey} endpoint: ${imagePath} with ${pathOverride}`);
//var imagePath = $"{filepath}{directories[i]}/{file}";
var pathOverride = GetImagePathOverride(imagePath);
if (!string.IsNullOrEmpty(pathOverride)) {
_logger.Debug($"overrode route: {routeKey} endpoint: {imagePath} with {pathOverride}");
imagePath = pathOverride;
}
this.imageRouter.addRoute(routeKey, imagePath);
*/
_imageRouter.AddRoute(routeKey, imagePath);
}
}
// Map icon file separately
//this.imageRouter.addRoute("/favicon.ico", `${filepath}icon.ico`);
_imageRouter.AddRoute("/favicon.ico", $"{filepath}icon.ico");
}
/**
@@ -201,9 +203,11 @@ public class DatabaseImporter : OnLoad
* @param imagePath Key
* @returns override for key
*/
protected string GetImagePathOverride(string imagePath)
protected string? GetImagePathOverride(string imagePath)
{
return httpConfig.ServerImagePathOverride[imagePath];
if (httpConfig.ServerImagePathOverride.TryGetValue(imagePath, out var value))
return value;
return null;
}
}
+2 -2
View File
@@ -25,8 +25,8 @@ public class FileUtil
return Path.GetExtension(path).Replace(".", "");
}
public string StripExtension(string path)
public string StripExtension(string path, bool keepPath = false)
{
return Path.GetFileNameWithoutExtension(path);
return keepPath ? path.Split('.').First() : Path.GetFileNameWithoutExtension(path);
}
}
+3 -3
View File
@@ -13,12 +13,12 @@ public class HttpFileUtil
_httpServerHelper = httpServerHelper;
}
public Task SendFileAsync(HttpResponse resp,string filePath) {
public void SendFile(HttpResponse resp,string filePath) {
var pathSlice = filePath.Split("/");
var mimePath = _httpServerHelper.GetMimeText(pathSlice[^1].Split(".")[^1]);
var type = string.IsNullOrWhiteSpace(mimePath) ? _httpServerHelper.GetMimeText("txt") : mimePath;
resp.Headers.Add("Content-Type", type);
return resp.SendFileAsync(filePath, CancellationToken.None);
resp.Headers.Append("Content-Type", type);
resp.SendFileAsync(filePath, CancellationToken.None).Wait();
// maybe the above is correct?
// await pipeline(fs.createReadStream(filePath), resp);
}
+120
View File
@@ -0,0 +1,120 @@
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.RegularExpressions;
using Core.Annotations;
using Core.Models.Eft.HttpResponse;
using Core.Models.Eft.ItemEvent;
using Core.Models.Enums;
using Core.Services;
namespace Core.Utils;
[Injectable]
public class HttpResponseUtil
{
protected readonly LocalisationService _localisationService;
public HttpResponseUtil(
// JsonUtil jsonUtil,
LocalisationService localisationService
)
{
_localisationService = localisationService;
}
private readonly ImmutableList<Regex> _cleanupRegexList =
[
new("[\\b]"),
new("[\\f]"),
new("[\\n]"),
new("[\\r]"),
new("[\\t]")
];
protected string ClearString(string s)
{
var value = s;
foreach (var regex in _cleanupRegexList)
{
value = regex.Replace(value, string.Empty);
}
return value;
}
/**
* Return passed in data as JSON string
* @param data
* @returns
*/
public string NoBody<T>(T data)
{
return ClearString(JsonSerializer.Serialize(data));
}
/**
* Game client needs server responses in a particular format
* @param data
* @param err
* @param errmsg
* @returns
*/
public string GetBody<T>(T data, int err = 0, string? errmsg = null, bool sanitize = true)
{
return sanitize
? ClearString(GetUnclearedBody(data, err, errmsg))
: GetUnclearedBody(data, err, errmsg);
}
public string GetUnclearedBody<T>(T? data, int err = 0, string? errmsg = null)
{
return JsonSerializer.Serialize(new GetBodyResponseData<T> { Err = err, ErrMsg = errmsg, Data = data });
}
public string EmptyResponse()
{
return GetBody("", 0, "");
}
public string NullResponse()
{
return ClearString(GetUnclearedBody<object>(null, 0, null));
}
public string EmptyArrayResponse()
{
return GetBody(new List<object>());
}
/**
* Add an error into the 'warnings' array of the client response message
* @param output IItemEventRouterResponse
* @param message Error message
* @param errorCode Error code
* @returns IItemEventRouterResponse
*/
public ItemEventRouterResponse AppendErrorToOutput(
ItemEventRouterResponse output,
string? message = null,
BackendErrorCodes errorCode = BackendErrorCodes.NONE
)
{
if (string.IsNullOrEmpty(message))
message = _localisationService.GetText("http-unknown_error");
if (output.Warnings?.Count > 0)
{
output.Warnings.Add(new Warning
{
Index = output.Warnings?.Count - 1,
ErrorMessage = message,
Code = errorCode.ToString()
});
}
else
{
output.Warnings = [new Warning { Index = 0, ErrorMessage = message, Code = errorCode.ToString() }];
}
return output;
}
}