Add WebAPI middleware for multi-instance Roslyn Bridge routing
Add RoslynBridge.WebApi - ASP.NET Core 8.0 middleware that: - Provides a centralized REST API for accessing multiple VS instances - Manages instance registry with discovery by port, solution, or PID - Proxies requests to the appropriate VS instance - Tracks request/response history for debugging - Auto-cleanup of stale instances via background service Features: - Health endpoints: /api/health, /api/health/ping - Roslyn endpoints: /api/roslyn/projects, /api/roslyn/diagnostics, etc. - Instance management: /api/instances (register, heartbeat, unregister) - History tracking: /api/history, /api/history/stats - Swagger UI at root (/) for API documentation - CORS enabled for web applications Services: - InstanceRegistryService: Thread-safe registry of VS instances - HistoryService: In-memory request/response history (max 1000 entries) - InstanceCleanupService: Background service to remove stale instances - RoslynBridgeClient: HTTP client for proxying to VS instances Update RoslynBridge.sln to include RoslynBridge.WebApi project. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
132
RoslynBridge.WebApi/Middleware/HistoryMiddleware.cs
Normal file
132
RoslynBridge.WebApi/Middleware/HistoryMiddleware.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Middleware to capture and log all Roslyn API requests and responses
|
||||
/// </summary>
|
||||
public class HistoryMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<HistoryMiddleware> _logger;
|
||||
|
||||
public HistoryMiddleware(RequestDelegate next, ILogger<HistoryMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IHistoryService historyService)
|
||||
{
|
||||
// Only track Roslyn API endpoints
|
||||
if (!context.Request.Path.StartsWithSegments("/api/roslyn"))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var historyEntry = new QueryHistoryEntry
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Path = context.Request.Path,
|
||||
Method = context.Request.Method,
|
||||
ClientIp = context.Connection.RemoteIpAddress?.ToString()
|
||||
};
|
||||
|
||||
// Capture request
|
||||
RoslynQueryRequest? request = null;
|
||||
if (context.Request.Method == "POST" && context.Request.ContentLength > 0)
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true);
|
||||
var requestBody = await reader.ReadToEndAsync();
|
||||
context.Request.Body.Position = 0;
|
||||
|
||||
try
|
||||
{
|
||||
request = JsonSerializer.Deserialize<RoslynQueryRequest>(requestBody, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
historyEntry.Request = request;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to deserialize request for history");
|
||||
}
|
||||
}
|
||||
|
||||
// Capture response
|
||||
var originalBodyStream = context.Response.Body;
|
||||
using var responseBody = new MemoryStream();
|
||||
context.Response.Body = responseBody;
|
||||
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
|
||||
stopwatch.Stop();
|
||||
historyEntry.DurationMs = stopwatch.ElapsedMilliseconds;
|
||||
historyEntry.Success = context.Response.StatusCode < 400;
|
||||
|
||||
// Read response
|
||||
responseBody.Seek(0, SeekOrigin.Begin);
|
||||
var responseText = await new StreamReader(responseBody).ReadToEndAsync();
|
||||
responseBody.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
try
|
||||
{
|
||||
var response = JsonSerializer.Deserialize<RoslynQueryResponse>(responseText, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
historyEntry.Response = response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to deserialize response for history");
|
||||
}
|
||||
|
||||
// Copy response back
|
||||
await responseBody.CopyToAsync(originalBodyStream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
historyEntry.DurationMs = stopwatch.ElapsedMilliseconds;
|
||||
historyEntry.Success = false;
|
||||
_logger.LogError(ex, "Error in request processing");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Response.Body = originalBodyStream;
|
||||
|
||||
// Add to history
|
||||
try
|
||||
{
|
||||
historyService.Add(historyEntry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to add entry to history");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for adding history middleware
|
||||
/// </summary>
|
||||
public static class HistoryMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseHistoryTracking(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<HistoryMiddleware>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user