Compare commits
4 Commits
5f4d9043c3
...
412c5f6ba5
| Author | SHA1 | Date | |
|---|---|---|---|
| 412c5f6ba5 | |||
| c78fca4316 | |||
| e0be95978d | |||
| 38a6b0c0b3 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -184,3 +184,5 @@ go.work.sum
|
||||
|
||||
|
||||
|
||||
lstWrapper/Program_working_node_ws.txt
|
||||
lstWrapper/web.config.txt
|
||||
|
||||
@@ -4,8 +4,8 @@ meta {
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:8080/build
|
||||
get {
|
||||
url: http://localhost:8080/check
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ const main = async () => {
|
||||
if (process.env.NODE_ENV?.trim() !== "production") {
|
||||
app.use(morgan("tiny"));
|
||||
basePath = "/lst";
|
||||
|
||||
app.use(
|
||||
basePath + "/test",
|
||||
express.static(join(__dirname, "../../controller"))
|
||||
);
|
||||
}
|
||||
|
||||
// docs and api stuff
|
||||
@@ -78,6 +83,11 @@ const main = async () => {
|
||||
express.static(join(__dirname, "../../frontend/dist"))
|
||||
);
|
||||
|
||||
app.use(
|
||||
basePath + "/test",
|
||||
express.static(join(__dirname, "../../frontend/dist"))
|
||||
);
|
||||
|
||||
// register app
|
||||
setupRoutes(app, basePath);
|
||||
|
||||
|
||||
@@ -13,16 +13,6 @@ import (
|
||||
func registerBuildChannel(server *socketio.Server) {
|
||||
// Example: When clients join "build" namespace or room
|
||||
server.OnEvent("/", "subscribe:build", func(s socketio.Conn) {
|
||||
s.Join("build")
|
||||
s.Emit("buildlogs", "👋 Connected to build channel") // this is where all the messages are actually sent to
|
||||
|
||||
})
|
||||
|
||||
// update channel events
|
||||
server.OnEvent("/", "build", func(s socketio.Conn, target string) {
|
||||
fmt.Println("🔨 Build triggered:", target)
|
||||
|
||||
go func() {
|
||||
host, err := os.Hostname()
|
||||
if err != nil {
|
||||
server.BroadcastToRoom("/", "build", "buildlogs", "Could not retrieve hostname")
|
||||
@@ -34,6 +24,17 @@ func registerBuildChannel(server *socketio.Server) {
|
||||
return
|
||||
}
|
||||
|
||||
s.Join("build")
|
||||
s.Emit("buildlogs", "👋 Connected to build channel") // this is where all the messages are actually sent to
|
||||
|
||||
})
|
||||
|
||||
// update channel events
|
||||
server.OnEvent("/", "build", func(s socketio.Conn, target string) {
|
||||
fmt.Println("🔨 Build triggered:", target)
|
||||
|
||||
go func() {
|
||||
|
||||
server.BroadcastToRoom("/", "build", "buildlogs", "🔨 Starting build: Old App")
|
||||
if err := runNpmV2Build(server); err != nil {
|
||||
server.BroadcastToRoom("/", "build", "buildlogs", fmt.Sprintf("❌ Build failed: %v", err))
|
||||
|
||||
@@ -58,13 +58,23 @@
|
||||
<button type="button" id="btnCopyLatest">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
<button id="btnUpdateServer">Server Update</button>
|
||||
</div>
|
||||
|
||||
<div id="logWindow"></div>
|
||||
|
||||
<script>
|
||||
// ✅ Define socket in global scope
|
||||
const socket = io("http://localhost:8000");
|
||||
|
||||
// const socket = io("https://usmcd1vms036.alpla.net", {
|
||||
// path: "/lst/socket.io/",
|
||||
// transports: ["polling"],
|
||||
// });
|
||||
|
||||
const socket = io("http://localhost:8080", {
|
||||
path: "/socket.io/",
|
||||
transports: ["polling"],
|
||||
});
|
||||
|
||||
// log window reference
|
||||
const logWindow = document.getElementById("logWindow");
|
||||
@@ -140,7 +150,10 @@
|
||||
if (!fromMyInput) return;
|
||||
|
||||
// Emit to backend (adjust event name as required)
|
||||
socket.emit("update", fromMyInput);
|
||||
socket.emit("update", {
|
||||
action: "copy",
|
||||
target: fromMyInput,
|
||||
});
|
||||
|
||||
// You can define your own logMessage function
|
||||
logMessage("info", `Copying to ${fromMyInput}`);
|
||||
@@ -151,6 +164,14 @@
|
||||
};
|
||||
};
|
||||
|
||||
document.getElementById("btnUpdateServer").onclick = () => {
|
||||
socket.emit("update", {
|
||||
action: "update",
|
||||
target: "usmcd1vms036",
|
||||
}); // "frontend" = payload target
|
||||
logMessage("update", "Update The server");
|
||||
};
|
||||
|
||||
socket.on("logs", (msg) => logMessage("logs", msg));
|
||||
socket.on("errors", (msg) => logMessage("errors", msg));
|
||||
socket.on("buildlogs", (msg) => logMessage("build", msg));
|
||||
|
||||
68
controller/internal/route_handler/router.go
Normal file
68
controller/internal/route_handler/router.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UpdatePayload struct {
|
||||
Action string `json:"action"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func Setup(basePath string) *gin.Engine {
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
if os.Getenv("NODE") == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
r.GET(basePath+"/check", func(c *gin.Context) {
|
||||
c.JSON(http.StatusAccepted, gin.H{"check": "good"})
|
||||
})
|
||||
|
||||
//logger.RegisterLoggerRoutes(r, basePath, db)
|
||||
r.POST(basePath+"/update", func(c *gin.Context) {
|
||||
var payload UpdatePayload
|
||||
if err := c.BindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
|
||||
flusher, ok := c.Writer.(http.Flusher)
|
||||
if !ok {
|
||||
c.String(http.StatusInternalServerError, "Streaming not supported")
|
||||
return
|
||||
}
|
||||
|
||||
steps := []string{"🚀 Starting...", "📂 Copying...", "🔧 Migrating...", "✅ Done!"}
|
||||
for _, step := range steps {
|
||||
fmt.Fprintf(c.Writer, "event: log\ndata: %s\n\n", step)
|
||||
flusher.Flush() // 🔑 actually push chunk
|
||||
time.Sleep(1 * time.Second) // simulate work
|
||||
}
|
||||
})
|
||||
|
||||
r.Any(basePath+"/", func(c *gin.Context) { errorApiLoc(c) })
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func errorApiLoc(c *gin.Context) {
|
||||
|
||||
// log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{
|
||||
// "endpoint": c.Request.URL.Path,
|
||||
// "client_ip": c.ClientIP(),
|
||||
// "user_agent": c.Request.UserAgent(),
|
||||
// })
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered a route that dose not exist"})
|
||||
}
|
||||
@@ -4,19 +4,33 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
socketio "github.com/googollee/go-socket.io"
|
||||
"github.com/joho/godotenv"
|
||||
router "lst.net/internal/route_handler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load("../.env")
|
||||
if err != nil {
|
||||
//log := logger.New()
|
||||
//log.Info("Warning: .env file not found (ok in Docker/production)", "system", map[string]interface{}{})
|
||||
fmt.Println("Warning: .env file not found")
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
// gin stuff
|
||||
basePath := "/api/controller"
|
||||
if os.Getenv("NODE_ENV") != "production" {
|
||||
basePath = "/lst/api/controller"
|
||||
}
|
||||
r := router.Setup(basePath) // returns *gin.Engine
|
||||
|
||||
server := socketio.NewServer(nil)
|
||||
|
||||
server.OnConnect("/", func(s socketio.Conn) error {
|
||||
@@ -91,12 +105,16 @@ func main() {
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
// Enable CORS wrapper for Socket.IO route
|
||||
http.Handle("/socket.io/", withCORS(server))
|
||||
http.Handle("/", http.FileServer(http.Dir("./static")))
|
||||
// mount socket.io on /socket.io/*
|
||||
r.Any("/socket.io/*any", gin.WrapH(withCORS(server)))
|
||||
|
||||
fmt.Println("🚀 Socket.IO server running on :8000")
|
||||
log.Fatal(http.ListenAndServe(":8000", nil))
|
||||
// mount a static dir (like http.FileServer)
|
||||
//r.Static("/", "./static")
|
||||
|
||||
fmt.Println("🚀 Server running on :" + port)
|
||||
if err := r.Run(":" + port); err != nil {
|
||||
log.Fatal("Server failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse your proper CORS handler
|
||||
@@ -104,12 +122,15 @@ func withCORS(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
// 🔑 Echo the request Origin, not "*"
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Vary", "Origin")
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
socketio "github.com/googollee/go-socket.io"
|
||||
)
|
||||
|
||||
type UpdatePayload struct {
|
||||
Action string `json:"action"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func registerUpdateChannel(server *socketio.Server) {
|
||||
|
||||
server.OnEvent("/", "subscribe:update", func(s socketio.Conn) {
|
||||
@@ -15,21 +28,91 @@ func registerUpdateChannel(server *socketio.Server) {
|
||||
})
|
||||
|
||||
// copy files based on the target passed over
|
||||
server.OnEvent("/", "update", func(s socketio.Conn, target string) {
|
||||
server.OnEvent("/", "update", func(s socketio.Conn, payload UpdatePayload) {
|
||||
switch strings.ToLower(payload.Action) {
|
||||
case "copy":
|
||||
server.BroadcastToRoom("/", "update", "updateLogs",
|
||||
fmt.Sprintf("🚀 Copying latest build to %v", target))
|
||||
copyBuild(server, target)
|
||||
fmt.Sprintf("🚀 Copying latest build to %v", payload.Target))
|
||||
copyLatestBuild(server, payload.Target)
|
||||
|
||||
case "update":
|
||||
updateServer(server, payload.Target)
|
||||
|
||||
default:
|
||||
server.BroadcastToRoom("/", "update", "updateLogs",
|
||||
fmt.Sprintf("❓ Unknown action: %s", payload.Action))
|
||||
}
|
||||
})
|
||||
|
||||
// update the server
|
||||
// server.OnEvent("/", "update", func(s socketio.Conn, target string) {
|
||||
// msg := fmt.Sprintf("🔧 Running updateServer on: %s", target)
|
||||
// server.BroadcastToRoom("/update", "update", "updateLogs", msg)
|
||||
|
||||
// })
|
||||
|
||||
server.OnEvent("/", "unsubscribe:update", func(s socketio.Conn) {
|
||||
s.Leave("update")
|
||||
s.Emit("updateLogs", "👋 Unsubscribed from update logs")
|
||||
})
|
||||
}
|
||||
|
||||
func updateServer(server *socketio.Server, target string) {
|
||||
host, err := os.Hostname()
|
||||
if err != nil {
|
||||
server.BroadcastToRoom("/", "update", "updateLogs", "Could not retrieve hostname")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(host, "VMS") || strings.Contains(host, "vms") {
|
||||
server.BroadcastToRoom("/", "update", "updateLogs", "Your are about to check for a new build and then update the server.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
server.BroadcastToRoom("/", "update", "updateLogs", "You seem to be on a dev server or not an actual production server, you MUST pass a server over. I.E. USMCD1VMS036")
|
||||
return
|
||||
} else {
|
||||
server.BroadcastToRoom("/", "update", "updateLogs", fmt.Sprintf("Running the update on: %v", target))
|
||||
go triggerRemoteUpdate(server, target, UpdatePayload{Action: "update", Target: target})
|
||||
}
|
||||
}
|
||||
|
||||
func copyLatestBuild(server *socketio.Server, target string) {
|
||||
server.BroadcastToRoom("/", "update", "updateLogs",
|
||||
fmt.Sprintf("🚀 Copying latest build to %v", target))
|
||||
copyBuild(server, target)
|
||||
}
|
||||
|
||||
func triggerRemoteUpdate(server *socketio.Server, remoteURL string, payload UpdatePayload) {
|
||||
|
||||
basePath := "/api/controller"
|
||||
if os.Getenv("NODE_ENV") != "production" {
|
||||
basePath = "/lst/api/controller"
|
||||
}
|
||||
|
||||
// send POST request with JSON, expect SSE / streaming text back
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
url := fmt.Sprintf("https://%v.alpla.net%v/update", remoteURL, basePath)
|
||||
//url := fmt.Sprintf("http://localhost:8080%v/update", basePath)
|
||||
fmt.Println(url)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
log.Println("❌ Cannot connect remote:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
decoder := bufio.NewReader(resp.Body)
|
||||
for {
|
||||
line, err := decoder.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Println("❌ Error reading stream:", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
//fmt.Println(line)
|
||||
parsed := strings.TrimSpace(line)
|
||||
if parsed != "" {
|
||||
log.Println("📡 Remote log:", parsed)
|
||||
server.BroadcastToRoom("/", "update", "updateLogs", parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,43 +34,90 @@ 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 forwardToFrontend = ForwardWebSocketAsync(backendSocket, frontendSocket, cts.Token);
|
||||
|
||||
@@ -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<IHttpClientFactory>().CreateClient("NodeApp");
|
||||
// Otherwise: normal HTTP request
|
||||
var client = context.RequestServices.GetRequiredService<IHttpClientFactory>().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,14 +220,23 @@ async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, Cancel
|
||||
await destination.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
|
||||
break;
|
||||
}
|
||||
await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
|
||||
|
||||
await destination.SendAsync(
|
||||
new ArraySegment<byte>(buffer, 0, result.Count),
|
||||
result.MessageType,
|
||||
result.EndOfMessage,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
LogToFile($"WebSocket forwarding error: {ex.Message}");
|
||||
Console.WriteLine($"WebSocket forwarding error: {ex.Message}");
|
||||
try
|
||||
{
|
||||
await destination.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, "Error", cancellationToken);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
app.Run();
|
||||
@@ -1,25 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<!-- Enable WebSockets (may require unlocking at host level) -->
|
||||
<!-- Enable WebSockets -->
|
||||
<webSocket enabled="true" receiveBufferLimit="4194304" pingInterval="00:01:00" />
|
||||
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Proxy to Wrapper" stopProcessing="true">
|
||||
<match url="^lst/(.*)" />
|
||||
<conditions>
|
||||
<add input="{HTTP_UPGRADE}" pattern="^WebSocket$" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="http://localhost:4000/{R:1}" />
|
||||
<serverVariables>
|
||||
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
|
||||
<set name="HTTP_X_REAL_IP" value="{REMOTE_ADDR}" />
|
||||
</serverVariables>
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
|
||||
<staticContent>
|
||||
<remove fileExtension=".js" />
|
||||
<mimeMap fileExtension=".js" mimeType="application/javascript" />
|
||||
@@ -38,7 +22,7 @@
|
||||
|
||||
<aspNetCore processPath="dotnet"
|
||||
arguments=".\lstWrapper.dll"
|
||||
stdoutLogEnabled="false"
|
||||
stdoutLogEnabled="true"
|
||||
stdoutLogFile=".\logs\stdout"
|
||||
hostingModel="inprocess" />
|
||||
</system.webServer>
|
||||
|
||||
Reference in New Issue
Block a user