http-debug.ts•13 kB
import { z } from 'zod';
import { Logger } from '../server/logger';
import { MetricsCollector } from '../server/metrics';
import { NetMvcAdapter } from '../adapters/netmvc-adapter';
import {
DapHttpRequestDebugRequest,
DapHttpRequestDebugResponse,
} from '../schemas/netmvc-tools.schemas';
/**
* dap.debugHttpRequest tool implementation
*
* Handles HTTP request/response debugging for .NET MVC applications
* with detailed inspection of request lifecycle, routing, and response generation
*/
export class HttpDebugTool {
private logger: Logger;
private metrics: MetricsCollector;
private adapter: NetMvcAdapter;
constructor() {
this.logger = new Logger('dap.debugHttpRequest');
this.metrics = MetricsCollector.getInstance();
this.adapter = new NetMvcAdapter();
}
/**
* Execute HTTP request debugging
*/
async execute(args: any): Promise<DapHttpRequestDebugResponse> {
this.metrics.startTimer('dap.debugHttpRequest.tool');
this.metrics.increment('dap.debugHttpRequest.count');
try {
// Validate input
const validatedArgs = this.validateArgs(args);
this.logger.debug('Debugging HTTP request', {
method: validatedArgs.method,
path: validatedArgs.path,
requestId: validatedArgs.requestId,
});
// Process HTTP request debugging
const debugResult = await this.debugHttpRequest(validatedArgs);
this.logger.info('HTTP request debugging completed', {
requestId: debugResult.requestId,
method: validatedArgs.method,
path: validatedArgs.path,
});
this.metrics.stopTimer('dap.debugHttpRequest.tool');
return this.createSuccessResponse(debugResult);
} catch (error) {
this.logger.error('HTTP request debugging failed:', error);
this.metrics.increment('dap.debugHttpRequest.error.count');
this.metrics.stopTimer('dap.debugHttpRequest.tool');
return this.createErrorResponse((error as Error).message);
}
}
/**
* Validate input arguments
*/
private validateArgs(args: any): DapHttpRequestDebugRequest['arguments'] {
const schema = z.object({
requestId: z.string().optional(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']),
path: z.string(),
queryString: z.string().optional(),
headers: z.record(z.string()).optional(),
body: z.string().optional(),
cookies: z.record(z.string()).optional(),
session: z.record(z.any()).optional(),
user: z.record(z.any()).optional(),
routeData: z.record(z.any()).optional(),
controller: z.string().optional(),
action: z.string().optional(),
modelState: z.record(z.any()).optional(),
form: z.record(z.any()).optional(),
files: z.record(z.any()).optional(),
culture: z.string().optional(),
acceptedLanguages: z.array(z.string()).optional(),
isAjax: z.boolean().optional(),
isLocal: z.boolean().optional(),
protocol: z.string().optional(),
scheme: z.string().optional(),
host: z.string().optional(),
originalUrl: z.string().optional(),
});
return schema.parse(args);
}
/**
* Process HTTP request debugging
*/
private async debugHttpRequest(args: DapHttpRequestDebugRequest['arguments']): Promise<any> {
// Mock HTTP request processing
const httpRequest = {
requestId: args.requestId || `req_${Date.now()}`,
timestamp: Date.now(),
method: args.method,
path: args.path,
fullPath: args.path + (args.queryString ? `?${args.queryString}` : ''),
// Request parsing
headers: this.parseHeaders(args.headers || {}),
cookies: args.cookies || {},
session: args.session || {},
user: args.user || {},
// ASP.NET Core specific
routeData: args.routeData || this.extractRouteData(args.path),
controller: args.controller || this.extractController(args.path),
action: args.action || this.extractAction(args.path),
// Request lifecycle tracking
processingStart: Date.now(),
middleware: [],
filters: [],
modelBinding: {},
modelValidation: args.modelState || {},
// Response generation
viewData: {},
tempData: {},
viewBag: {},
// Response info
response: {
statusCode: 200,
contentType: 'text/html',
headers: {},
body: null,
isRedirect: false,
redirectUrl: null,
},
// Performance metrics
processingTime: 0,
databaseQueries: [],
externalCalls: [],
// Request context
culture: args.culture || 'en-US',
acceptedLanguages: args.acceptedLanguages || ['en-US'],
isAjax: args.isAjax || false,
isLocal: args.isLocal || false,
protocol: args.protocol || 'https',
scheme: args.scheme || 'https',
host: args.host || 'localhost:5001',
originalUrl: args.originalUrl || args.path,
// Security
authentication: {
isAuthenticated: false,
identity: null,
principal: null,
},
// Debug info
debugInfo: {
middlewareApplied: false,
modelBound: false,
validModelState: true,
viewRendered: false,
cacheHit: false,
optimizationApplied: [],
},
};
// Simulate request processing
await this.simulateRequestProcessing(httpRequest);
// Add to adapter tracking
await this.adapter.debugHttpRequest(httpRequest);
return {
requestId: httpRequest.requestId,
httpRequest,
metrics: {
processingTime: httpRequest.processingTime,
databaseQueries: httpRequest.databaseQueries.length,
externalCalls: httpRequest.externalCalls.length,
memoryUsage: Math.random() * 50 + 10, // MB
},
};
}
/**
* Parse HTTP headers
*/
private parseHeaders(headers: Record<string, string>): any {
const parsed: any = {};
// Common headers to parse
if (headers['content-type']) {
parsed.contentType = headers['content-type'];
}
if (headers['content-length']) {
parsed.contentLength = parseInt(headers['content-length']);
}
if (headers['user-agent']) {
parsed.userAgent = headers['user-agent'];
}
if (headers['accept']) {
parsed.accept = headers['accept'];
}
if (headers['accept-language']) {
parsed.acceptLanguage = headers['accept-language'];
}
if (headers['accept-encoding']) {
parsed.acceptEncoding = headers['accept-encoding'];
}
if (headers['authorization']) {
parsed.authorization = '[REDACTED]';
}
if (headers['cookie']) {
parsed.cookies = headers['cookie'];
}
return parsed;
}
/**
* Extract route data from path
*/
private extractRouteData(path: string): any {
// Mock route data extraction for common MVC patterns
const routePatterns = [
{ pattern: '^/Home/Index$', controller: 'Home', action: 'Index' },
{ pattern: '^/Home/About$', controller: 'Home', action: 'About' },
{ pattern: '^/Users/Index$', controller: 'Users', action: 'Index' },
{ pattern: '^/Users/Edit/\\d+$', controller: 'Users', action: 'Edit' },
{ pattern: '^/Api/.*$', controller: 'Api', action: 'Index' },
];
for (const route of routePatterns) {
const regex = new RegExp(route.pattern);
if (regex.test(path)) {
return {
controller: route.controller,
action: route.action,
area: null,
routeName: `${route.controller}_${route.action}`,
template: `/{controller}/{action}`,
constraints: {},
dataTokens: {},
rawUrl: path,
};
}
}
return {
controller: 'Unknown',
action: 'Unknown',
area: null,
routeName: 'Default',
template: '/{controller}/{action}/{id?}',
constraints: {},
dataTokens: {},
rawUrl: path,
};
}
/**
* Extract controller from path
*/
private extractController(path: string): string {
const route = this.extractRouteData(path);
return route.controller;
}
/**
* Extract action from path
*/
private extractAction(path: string): string {
const route = this.extractRouteData(path);
return route.action;
}
/**
* Simulate ASP.NET Core request processing
*/
private async simulateRequestProcessing(httpRequest: any): Promise<void> {
const startTime = Date.now();
// 1. Middleware pipeline simulation
const middleware = [
{ name: 'UseExceptionHandler', executed: true, duration: 5 },
{ name: 'UseHsts', executed: true, duration: 2 },
{ name: 'UseHttpsRedirection', executed: true, duration: 3 },
{ name: 'UseStaticFiles', executed: true, duration: 10 },
{ name: 'UseRouting', executed: true, duration: 8 },
{ name: 'UseAuthentication', executed: true, duration: 15 },
{ name: 'UseAuthorization', executed: true, duration: 12 },
{ name: 'UseEndpoints', executed: true, duration: 20 },
];
httpRequest.middleware = middleware;
// 2. Filter pipeline simulation
const filters = [
{ name: 'ActionFilter', executed: true, duration: 5 },
{ name: 'ResultFilter', executed: true, duration: 3 },
{ name: 'ExceptionFilter', executed: false, duration: 0 },
];
httpRequest.filters = filters;
// 3. Model binding simulation
httpRequest.modelBinding = {
bound: true,
model: {
id: 123,
name: 'John Doe',
email: 'john.doe@example.com',
createdAt: '2023-01-01T00:00:00Z',
},
};
// 4. Model validation simulation
httpRequest.modelValidation = {
isValid: true,
errors: [],
warnings: [],
};
// 5. Database queries simulation
const databaseQueries = [
{
id: `db_query_${Date.now()}_1`,
text: 'SELECT * FROM Users WHERE Id = @p0',
parameters: { '@p0': 123 },
executionTime: Math.random() * 10 + 1,
connection: 'DefaultConnection',
isAsync: true,
},
{
id: `db_query_${Date.now()}_2`,
text: 'SELECT * FROM Products WHERE UserId = @p0',
parameters: { '@p0': 123 },
executionTime: Math.random() * 15 + 5,
connection: 'DefaultConnection',
isAsync: true,
},
];
httpRequest.databaseQueries = databaseQueries;
// 6. External calls simulation
const externalCalls = [
{
id: 'ext_call_1',
service: 'UserService',
operation: 'GetUser',
url: 'https://api.example.com/users/123',
method: 'GET',
duration: Math.random() * 50 + 20,
success: true,
},
];
httpRequest.externalCalls = externalCalls;
// 7. Response generation simulation
httpRequest.viewData = {
pageTitle: 'User Profile',
user: httpRequest.modelBinding.model,
products: [],
stats: {
totalOrders: 5,
totalSpent: 250.0,
},
};
httpRequest.tempData = {
successMessage: 'User profile updated successfully',
notification: 'Welcome back, John!',
};
httpRequest.viewBag = {
metaTitle: 'User Profile - My Application',
metaDescription: 'View and manage user profile information',
};
// 8. Response setup
httpRequest.response = {
statusCode: 200,
contentType: 'text/html',
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Content-Length': '4096',
'X-Powered-By': 'ASP.NET',
'X-Response-Time': `${Math.random() * 100 + 50}ms`,
},
body: '<html><body>User profile page content...</body></html>',
isRedirect: false,
redirectUrl: null,
};
// Calculate processing time
httpRequest.processingTime = Date.now() - startTime;
}
/**
* Create success response
*/
private createSuccessResponse(body: any): DapHttpRequestDebugResponse {
return {
type: 'response',
seq: 1,
command: 'debugHttpRequest',
request_seq: 1,
success: true,
body: {
success: true,
requestId: body.requestId,
message: 'HTTP request debug completed successfully',
httpRequest: body.httpRequest,
},
};
}
/**
* Create error response
*/
private createErrorResponse(message: string): DapHttpRequestDebugResponse {
return {
type: 'response',
seq: 1,
command: 'debugHttpRequest',
request_seq: 1,
success: false,
message,
body: {
success: false,
requestId: 'req_error_' + Date.now(),
message: 'Failed to debug HTTP request',
},
};
}
}
// Singleton instance
export const httpDebugTool = new HttpDebugTool();
// Tool execution function
export async function executeHttpDebug(args: any): Promise<DapHttpRequestDebugResponse> {
return await httpDebugTool.execute(args);
}