Files
dv-community-map/CommunityMapLocation/Api/ApiServer.cs
T
TheEvilPenguin 700e06b898 We don't actually need to restart HttpListener when the user changes the port
Changed Settings to implement INotifyPropertyChanged to allow consumers to potentially listen to individual property changes
2025-06-12 08:49:21 +10:00

149 lines
4.8 KiB
C#

using CommunityMapLocation.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using UnityModManagerNet;
namespace CommunityMapLocation.Api
{
public class ApiServer
{
public ApiServer(Settings settings, UnityModManager.ModEntry.ModLogger logger)
{
listener = new HttpListener();
serializerOptions = new JsonSerializerOptions()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
IncludeFields = true,
WriteIndented = true
};
this.settings = settings;
this.logger = logger;
}
private static string GetPrefix(int port) => $"http://localhost:{port}/";
private readonly Settings settings;
private readonly UnityModManager.ModEntry.ModLogger logger;
private readonly HttpListener listener;
private readonly JsonSerializerOptions serializerOptions;
private readonly List<IEndpoint> endpoints = new List<IEndpoint>();
public void AutoRegister(Assembly assembly = null)
{
if (assembly == null)
{
assembly = Assembly.GetExecutingAssembly();
}
foreach (var endpointType in assembly.ExportedTypes.Where(t => !t.IsInterface && !t.IsAbstract && typeof(IEndpoint).IsAssignableFrom(t)))
{
try
{
endpoints.Add((IEndpoint)Activator.CreateInstance(endpointType));
}
catch (Exception ex)
{
logger.Warning($"Could not register endpoint {endpointType.Name}: {ex}");
}
}
}
public void RegisterEndpoint(IEndpoint endpoint)
{
endpoints.Add(endpoint);
}
public void Start()
{
try
{
listener.Prefixes.Add(GetPrefix(settings.Port));
listener.Start();
listener.BeginGetContext(HandleConnection, null);
}
catch (Exception ex)
{
logger.Warning(ex.ToString());
Stop();
}
}
private void HandleConnection(IAsyncResult ar)
{
try
{
var context = listener.EndGetContext(ar);
listener.BeginGetContext(HandleConnection, listener);
var request = context.Request;
var response = context.Response;
var matchedEndpoints = endpoints.Where(e => e.Matches(request.Url) &&
(request.HttpMethod == "OPTIONS" || request.HttpMethod.Equals(e.HttpMethod.ToString(), StringComparison.OrdinalIgnoreCase)));
if (!endpoints.Any())
{
response.StatusCode = (int)HttpStatusCode.NotFound;
}
else
{
var methods = matchedEndpoints.Select(e => e.HttpMethod).Distinct();
response.AppendHeader("Access-Control-Allow-Origin", settings.RequestSourceType == RequestSourceType.All ? "*" : settings.RequestSource);
if (request.HttpMethod == "OPTIONS")
{
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
response.AddHeader("Access-Control-Allow-Methods", String.Join(", ", methods.Select(m => m.ToString().ToUpper())));
response.AddHeader("Access-Control-Max-Age", "1728000");
}
else
{
var responseBody = matchedEndpoints.First().Handle(logger, request);
var buffer = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(responseBody, serializerOptions));
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
}
response.OutputStream.Close();
}
catch (HttpListenerException)
{
// HttpListener is stopping, swallow exception
}
}
public void UpdateSettings()
{
var prefix = GetPrefix(settings.Port);
if (listener.Prefixes.Count != 1 || listener.Prefixes.Single() != prefix)
{
listener.Prefixes.Clear();
listener.Prefixes.Add(prefix);
}
}
public void Stop()
{
listener.Stop();
listener.Prefixes.Clear();
}
}
}