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>
133 lines
4.1 KiB
C#
133 lines
4.1 KiB
C#
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>();
|
|
}
|
|
}
|