diff --git a/lstWrapper/Program.cs b/lstWrapper/Program.cs index 279dff0..50f31f1 100644 --- a/lstWrapper/Program.cs +++ b/lstWrapper/Program.cs @@ -1,26 +1,32 @@ using System; using System.IO; using System.Net; +using System.Net.Http; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddHttpClient("NodeApp", client => -{ - client.BaseAddress = new Uri("http://localhost:4000"); - -}); + +// Register HttpClient so we can proxy HTTP traffic +builder.Services.AddHttpClient(); var app = builder.Build(); // Enable WebSocket support app.UseWebSockets(); -// Logging method +// Forwarded headers (important if behind IIS or another proxy) +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, + ForwardLimit = 2 +}); + +// Simple file logger void LogToFile(string message) { try @@ -28,44 +34,91 @@ void LogToFile(string message) string logDir = Path.Combine(AppContext.BaseDirectory, "logs"); Directory.CreateDirectory(logDir); string logFilePath = Path.Combine(logDir, "proxy_log.txt"); - File.AppendAllText(logFilePath, $"{DateTime.UtcNow}: {message}{Environment.NewLine}"); + File.AppendAllText(logFilePath, $"{DateTime.UtcNow:u}: {message}{Environment.NewLine}"); } catch (Exception ex) { - // Handle potential errors writing to log file Console.WriteLine($"Logging error: {ex.Message}"); } } -app.UseForwardedHeaders(new ForwardedHeadersOptions - { - ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, - // Increase the limit if you have multiple proxies - ForwardLimit = 2 - }); -// Middleware to handle WebSocket requests app.Use(async (context, next) => { - if (context.WebSockets.IsWebSocketRequest && context.Request.Path.StartsWithSegments("/ws")) + if (context.Request.Headers.ContainsKey("Origin")) { - // LogToFile($"WebSocket request received for path: {context.Request.Path}"); + var origin = context.Request.Headers["Origin"].ToString(); + context.Response.Headers["Access-Control-Allow-Origin"] = origin; + context.Response.Headers["Vary"] = "Origin"; + context.Response.Headers["Access-Control-Allow-Credentials"] = "true"; + context.Response.Headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,PATCH,DELETE,OPTIONS"; + context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Requested-With"; + } + if (string.Equals(context.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + context.Response.StatusCode = StatusCodes.Status204NoContent; + return; + } + + await next(); +}); + +// Single terminal middleware (Run instead of Use → no ambiguity) +app.Run(async (HttpContext context) => +{ + var rawPath = context.Request.Path.Value ?? ""; + + string backendHttpBase; + string backendWsBase; + + if (rawPath.StartsWith("/socket.io", StringComparison.OrdinalIgnoreCase) || + rawPath.StartsWith("/lst/socket.io", StringComparison.OrdinalIgnoreCase)) + { + backendHttpBase = "http://localhost:8080"; + backendWsBase = "ws://localhost:8080"; + } + else if (rawPath.StartsWith("/lst/api/controller", StringComparison.OrdinalIgnoreCase) || + rawPath.StartsWith("/api/controller", StringComparison.OrdinalIgnoreCase)) + { + backendHttpBase = "http://localhost:8080"; + backendWsBase = "ws://localhost:8080"; + + // Now strip only once + // var newPath = rawPath.Substring("/lst".Length); + // context.Request.Path = new PathString(newPath); + } + else + { + backendHttpBase = "http://localhost:4000"; + backendWsBase = "ws://localhost:4000"; + } + + // Handle WebSocket requests + if (context.WebSockets.IsWebSocketRequest) + { try { - var backendUri = new UriBuilder("ws", "localhost", 4000) + var backendUri = new UriBuilder(backendWsBase) { Path = context.Request.Path, Query = context.Request.QueryString.ToString() }.Uri; using var backendSocket = new ClientWebSocket(); + + // Forward incoming headers + foreach (var header in context.Request.Headers) + { + try { backendSocket.Options.SetRequestHeader(header.Key, header.Value); } + catch { /* ignore headers WS client doesn't like */ } + } + await backendSocket.ConnectAsync(backendUri, context.RequestAborted); using var frontendSocket = await context.WebSockets.AcceptWebSocketAsync(); var cts = new CancellationTokenSource(); - // WebSocket forwarding tasks - var forwardToBackend = ForwardWebSocketAsync(frontendSocket, backendSocket, cts.Token); + var forwardToBackend = ForwardWebSocketAsync(frontendSocket, backendSocket, cts.Token); var forwardToFrontend = ForwardWebSocketAsync(backendSocket, frontendSocket, cts.Token); await Task.WhenAny(forwardToBackend, forwardToFrontend); @@ -73,33 +126,22 @@ app.Use(async (context, next) => } catch (Exception ex) { - //LogToFile($"WebSocket proxy error: {ex.Message}"); + LogToFile($"WebSocket proxy error: {ex.Message}"); context.Response.StatusCode = (int)HttpStatusCode.BadGateway; await context.Response.WriteAsync($"WebSocket proxy error: {ex.Message}"); } - } - else - { - await next(); - } -}); - -// Middleware to handle HTTP requests -app.Use(async (context, next) => -{ - if (context.WebSockets.IsWebSocketRequest) - { - await next(); return; } - var client = context.RequestServices.GetRequiredService().CreateClient("NodeApp"); + // Otherwise: normal HTTP request + var client = context.RequestServices.GetRequiredService().CreateClient(); + var targetUri = backendHttpBase + context.Request.Path + context.Request.QueryString; try { - var request = new HttpRequestMessage(new HttpMethod(context.Request.Method), - context.Request.Path + context.Request.QueryString); + var request = new HttpRequestMessage(new HttpMethod(context.Request.Method), targetUri); + // Copy headers foreach (var header in context.Request.Headers) { if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray())) @@ -114,21 +156,45 @@ app.Use(async (context, next) => request.Content = new StreamContent(context.Request.Body); } - var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted); + var response = await client.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, + context.RequestAborted); + context.Response.StatusCode = (int)response.StatusCode; - + // Copy backend 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(); + context.Response.Headers.Remove("transfer-encoding"); + + // ✅ NOW inject/override CORS + if (context.Request.Headers.ContainsKey("Origin")) + { + var origin = context.Request.Headers["Origin"].ToString(); + context.Response.Headers["Access-Control-Allow-Origin"] = origin; + context.Response.Headers["Vary"] = "Origin"; + context.Response.Headers["Access-Control-Allow-Credentials"] = "true"; + context.Response.Headers["Access-Control-Allow-Methods"] = + "GET,POST,PUT,PATCH,DELETE,OPTIONS"; + context.Response.Headers["Access-Control-Allow-Headers"] = + "Content-Type, Authorization, X-Requested-With"; } - context.Response.Headers.Remove("transfer-encoding"); - await response.Content.CopyToAsync(context.Response.Body); + //await response.Content.CopyToAsync(context.Response.Body); + + // changes to manage the SSE so we get it all at once + var stream = await response.Content.ReadAsStreamAsync(context.RequestAborted); + var buffer = new byte[8192]; + int bytesRead; + + while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), context.RequestAborted)) > 0) + { + await context.Response.Body.WriteAsync(buffer.AsMemory(0, bytesRead), context.RequestAborted); + + // 🔑 Force flush so chunks go straight to client + await context.Response.Body.FlushAsync(context.RequestAborted); + } } catch (HttpRequestException ex) { @@ -138,9 +204,10 @@ app.Use(async (context, next) => } }); -async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, CancellationToken cancellationToken) +// Helper to forward WS frames in both directions +static async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, CancellationToken cancellationToken) { - var buffer = new byte[4 * 1024]; + var buffer = new byte[8192]; try { while (source.State == WebSocketState.Open && @@ -153,13 +220,22 @@ async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, Cancel await destination.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); break; } - await destination.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken); + + await destination.SendAsync( + new ArraySegment(buffer, 0, result.Count), + result.MessageType, + result.EndOfMessage, + cancellationToken); } } catch (WebSocketException ex) { - LogToFile($"WebSocket forwarding error: {ex.Message}"); - await destination.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, "Error", cancellationToken); + Console.WriteLine($"WebSocket forwarding error: {ex.Message}"); + try + { + await destination.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, "Error", cancellationToken); + } + catch { } } } diff --git a/lstWrapper/web.config b/lstWrapper/web.config index 3155e88..2a0c2a2 100644 --- a/lstWrapper/web.config +++ b/lstWrapper/web.config @@ -1,25 +1,9 @@ - + - - - - - - - - - - - - - - - - - + @@ -38,7 +22,7 @@