ASP.NET Core 미들웨어 파이프라인 심화
ASP.NET Core의 미들웨어 파이프라인은 HTTP 요청을 처리하는 컴포넌트 체인입니다. 각 미들웨어는 요청을 처리하고 다음 미들웨어를 호출하거나 파이프라인을 단락(short-circuit)시킵니다.
1. 파이프라인 실행 순서
섹션 제목: “1. 파이프라인 실행 순서”요청 → MW1 → MW2 → MW3 → 엔드포인트응답 ← MW1 ← MW2 ← MW3 ←// 실행 순서 확인app.Use(async (ctx, next) =>{ Console.WriteLine("MW1 진입"); await next(ctx); Console.WriteLine("MW1 반환");});
app.Use(async (ctx, next) =>{ Console.WriteLine("MW2 진입"); await next(ctx); Console.WriteLine("MW2 반환");});
app.Run(ctx =>{ Console.WriteLine("엔드포인트"); return ctx.Response.WriteAsync("OK");});
// 출력 순서:// MW1 진입 → MW2 진입 → 엔드포인트 → MW2 반환 → MW1 반환2. Use / Run / Map 비교
섹션 제목: “2. Use / Run / Map 비교”// Use — 다음 미들웨어 호출 가능app.Use(async (ctx, next) =>{ // 전처리 await next(ctx); // 다음으로 전달 // 후처리});
// Run — 파이프라인 종단 (next 없음)app.Run(async ctx =>{ await ctx.Response.WriteAsync("종료"); // 이후 미들웨어 실행 안됨});
// Map — 경로 분기app.Map("/api", apiApp =>{ apiApp.Run(ctx => ctx.Response.WriteAsync("API"));});
// MapWhen — 조건 분기app.MapWhen( ctx => ctx.Request.Headers.ContainsKey("X-API-KEY"), branch => branch.UseMiddleware<ApiKeyMiddleware>());3. 커스텀 미들웨어 — 규약 기반
섹션 제목: “3. 커스텀 미들웨어 — 규약 기반”public class RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger){ public async Task InvokeAsync(HttpContext context) { var sw = System.Diagnostics.Stopwatch.StartNew();
await next(context); // 다음 미들웨어
sw.Stop(); logger.LogInformation( "{Method} {Path} → {Status} ({Elapsed}ms)", context.Request.Method, context.Request.Path, context.Response.StatusCode, sw.ElapsedMilliseconds); }}
// 등록app.UseMiddleware<RequestTimingMiddleware>();
// 또는 확장 메서드로public static class MiddlewareExtensions{ public static IApplicationBuilder UseRequestTiming( this IApplicationBuilder app) => app.UseMiddleware<RequestTimingMiddleware>();}
app.UseRequestTiming();4. IMiddleware — DI 완전 통합
섹션 제목: “4. IMiddleware — DI 완전 통합”// IMiddleware: DI에서 매 요청마다 생성 (Scoped 가능)public class AuthMiddleware(IAuthService authService) : IMiddleware{ public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var token = context.Request.Headers["Authorization"] .FirstOrDefault()?.Split(" ").Last();
if (token is null || !await authService.ValidateAsync(token)) { context.Response.StatusCode = 401; return; // 단락 }
await next(context); }}
// 등록 (DI에 명시적 등록 필요)builder.Services.AddScoped<AuthMiddleware>();app.UseMiddleware<AuthMiddleware>();5. 단락(Short-circuit) 패턴
섹션 제목: “5. 단락(Short-circuit) 패턴”// 헬스체크 — 이후 미들웨어 건너뜀app.Use(async (ctx, next) =>{ if (ctx.Request.Path == "/health") { ctx.Response.StatusCode = 200; await ctx.Response.WriteAsync("OK"); return; // next 호출 안함 → 단락 } await next(ctx);});6. 예외 처리 미들웨어
섹션 제목: “6. 예외 처리 미들웨어”public class GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger){ public async Task InvokeAsync(HttpContext context) { try { await next(context); } catch (NotFoundException ex) { logger.LogWarning(ex, "리소스 없음"); context.Response.StatusCode = 404; await context.Response.WriteAsJsonAsync(new { error = ex.Message }); } catch (Exception ex) { logger.LogError(ex, "처리되지 않은 예외"); context.Response.StatusCode = 500; await context.Response.WriteAsJsonAsync(new { error = "서버 오류" }); } }}
// 파이프라인 맨 앞에 등록app.UseMiddleware<GlobalExceptionMiddleware>();7. 미들웨어 등록 권장 순서
섹션 제목: “7. 미들웨어 등록 권장 순서”app.UseExceptionHandler(); // 1. 예외 처리 (가장 먼저)app.UseHttpsRedirection(); // 2. HTTPS 리다이렉트app.UseStaticFiles(); // 3. 정적 파일 (단락 가능)app.UseRouting(); // 4. 라우팅app.UseAuthentication(); // 5. 인증app.UseAuthorization(); // 6. 인가app.UseRateLimiter(); // 7. 속도 제한app.MapControllers(); // 8. 엔드포인트미들웨어 파이프라인은 등록 순서가 곧 실행 순서입니다. 예외 처리는 가장 바깥에, 인증/인가는 라우팅 다음에 배치하세요. IMiddleware를 사용하면 Scoped 서비스를 안전하게 주입할 수 있으며, 단락 패턴으로 불필요한 처리를 조기에 종료해 성능을 높일 수 있습니다.