feat(logging): added in db and logging with websocket

This commit is contained in:
2025-07-22 19:59:06 -05:00
parent 623e19f028
commit 52ef39fd5c
9 changed files with 555 additions and 96 deletions

View File

@@ -1,65 +1,149 @@
using System.Net;
using System.Net.WebSockets;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Configure clients
builder.Services.AddHttpClient("GoBackend", client => {
builder.Services.AddHttpClient("GoBackend", client =>
{
client.BaseAddress = new Uri("http://localhost:8080");
});
var app = builder.Build();
// Handle trailing slash redirects
app.Use(async (context, next) => {
if (context.Request.Path.Equals("/lst", StringComparison.OrdinalIgnoreCase)) {
context.Response.Redirect("/lst/", permanent: true);
return;
// Enable WebSocket support
app.UseWebSockets();
app.Use(async (context, next) =>
{
// Proxy WebSocket requests for /lst/api/logger/logs (adjust path as needed)
if (context.WebSockets.IsWebSocketRequest &&
context.Request.Path.StartsWithSegments("/lst/api/logger/logs"))
{
try
{
var backendUri = new UriBuilder("ws", "localhost", 8080)
{
Path = context.Request.Path,
Query = context.Request.QueryString.ToString()
}.Uri;
using var backendSocket = new ClientWebSocket();
// Forward most headers except those managed by WebSocket protocol
foreach (var header in context.Request.Headers)
{
if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase) &&
!header.Key.Equals("Upgrade", StringComparison.OrdinalIgnoreCase) &&
!header.Key.Equals("Connection", StringComparison.OrdinalIgnoreCase) &&
!header.Key.Equals("Sec-WebSocket-Key", StringComparison.OrdinalIgnoreCase) &&
!header.Key.Equals("Sec-WebSocket-Version", StringComparison.OrdinalIgnoreCase))
{
backendSocket.Options.SetRequestHeader(header.Key, header.Value);
}
}
await backendSocket.ConnectAsync(backendUri, context.RequestAborted);
using var frontendSocket = await context.WebSockets.AcceptWebSocketAsync();
var cts = new CancellationTokenSource();
// Bidirectional forwarding tasks
var forwardToBackend = ForwardWebSocketAsync(frontendSocket, backendSocket, cts.Token);
var forwardToFrontend = ForwardWebSocketAsync(backendSocket, frontendSocket, cts.Token);
await Task.WhenAny(forwardToBackend, forwardToFrontend);
cts.Cancel();
return;
}
catch (Exception ex)
{
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
await context.Response.WriteAsync($"WebSocket proxy error: {ex.Message}");
return;
}
}
await next();
});
// Proxy all requests to Go backend
app.Use(async (context, next) => {
// Skip special paths
if (context.Request.Path.StartsWithSegments("/.well-known")) {
// Proxy normal HTTP requests
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
await next();
return;
}
var client = context.RequestServices.GetRequiredService<IHttpClientFactory>()
.CreateClient("GoBackend");
try {
var request = new HttpRequestMessage(
new HttpMethod(context.Request.Method),
var client = context.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient("GoBackend");
try
{
var request = new HttpRequestMessage(new HttpMethod(context.Request.Method),
context.Request.Path + context.Request.QueryString);
// Copy headers
foreach (var header in context.Request.Headers) {
if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray())) {
foreach (var header in context.Request.Headers)
{
if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()))
{
request.Content ??= new StreamContent(context.Request.Body);
request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
if (context.Request.ContentLength > 0) {
if (context.Request.ContentLength > 0 && request.Content == null)
{
request.Content = new StreamContent(context.Request.Body);
}
var response = await client.SendAsync(request);
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
context.Response.StatusCode = (int)response.StatusCode;
foreach (var header in response.Headers) {
foreach (var header in response.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in response.Content.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
if (response.Content.Headers.ContentType != null) {
context.Response.ContentType = response.Content.Headers.ContentType.ToString();
}
context.Response.Headers.Remove("transfer-encoding");
await response.Content.CopyToAsync(context.Response.Body);
}
catch (HttpRequestException) {
context.Response.StatusCode = 502;
catch (HttpRequestException ex)
{
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
await context.Response.WriteAsync($"Backend request failed: {ex.Message}");
}
});
app.Run();
async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, CancellationToken cancellationToken)
{
var buffer = new byte[4 * 1024];
try
{
while (source.State == WebSocketState.Open &&
destination.State == WebSocketState.Open &&
!cancellationToken.IsCancellationRequested)
{
var result = await source.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
await destination.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
break;
}
await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
}
}
catch (WebSocketException)
{
// Normal close or network error
}
}
app.Run();

View File

@@ -1,36 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<!-- Enable WebSockets -->
<webSocket enabled="true" receiveBufferLimit="4194304" pingInterval="00:01:00" />
<rewrite>
<rules>
<!-- Redirect root to /lst/ -->
<rule name="Root Redirect" stopProcessing="true">
<match url="^$" />
<action type="Redirect" url="/lst/" redirectType="Permanent" />
</rule>
<!-- Proxy static assets -->
<rule name="Static Assets" stopProcessing="true">
<match url="^lst/assets/(.*)" />
<action type="Rewrite" url="http://localhost:8080/lst/assets/{R:1}" />
</rule>
<!-- Proxy API requests -->
<rule name="API Routes" stopProcessing="true">
<match url="^lst/api/(.*)" />
<action type="Rewrite" url="http://localhost:8080/lst/api/{R:1}" />
</rule>
<!-- Proxy all other requests -->
<rule name="Frontend Routes" stopProcessing="true">
<!-- Proxy all requests starting with /lst/ to the .NET wrapper (port 4000) -->
<rule name="Proxy to Wrapper" stopProcessing="true">
<match url="^lst/(.*)" />
<action type="Rewrite" url="http://localhost:8080/lst/{R:1}" />
<action type="Rewrite" url="http://localhost:8080/{R:1}" />
</rule>
</rules>
</rewrite>
<staticContent>
<clear />
<mimeMap fileExtension=".js" mimeType="application/javascript" />
<mimeMap fileExtension=".mjs" mimeType="application/javascript" />
<mimeMap fileExtension=".css" mimeType="text/css" />
@@ -38,9 +22,11 @@
</staticContent>
<handlers>
<!-- Let AspNetCoreModule handle all requests -->
<remove name="WebSocketHandler" />
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\LstWrapper.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</configuration>
</configuration>