ASP.NET 프레임워크는 웹 요청을 효율적으로 처리하기 위해 파이프라인 기반 아키텍처를 사용합니다. 이 아키텍처의 핵심 요소는 HttpModule과 HttpHandler입니다. 이들은 요청이 클라이언트로부터 들어와 응답이 다시 나갈 때까지 다양한 단계에서 요청을 가로채고 처리하며 수정할 수 있는 확장 가능한 구성 요소입니다.
ASP.NET 요청 처리 흐름 개요
ASP.NET에서 웹 요청이 처리되는 일반적인 과정은 다음과 같습니다:
- 클라이언트가 웹 서버에 HTTP 요청을 보냅니다 (예:
.aspx파일). - IIS (인터넷 정보 서비스)는 요청을 수신하고 파일 확장자(예:
.aspx)를 식별합니다. - IIS는 해당 요청을 ASP.NET ISAPI 확장(
aspnet_isapi.dll)으로 전달합니다. aspnet_isapi.dll은 요청을 ASP.NET 작업자 프로세스(aspnet_wp.exe또는w3wp.exe)로 보냅니다.- 작업자 프로세스 내의
HttpRuntime이 요청을 인스턴스화하고HttpApplication객체를 통해 HTTP 파이프라인으로 보냅니다. - 요청은 일련의
HttpModule을 거치며, 각 모듈은 요청 수명 주기의 특정 단계에서 추가 작업을 수행할 수 있습니다. - 모든
HttpModule을 거친 후, 요청은 최종적으로 적절한HttpHandler에 의해 처리됩니다.HttpHandler는 요청된 리소스(예:.aspx페이지, 웹 서비스)에 대한 핵심 비즈니스 로직을 실행합니다. HttpHandler가 처리를 완료하면, 응답은 다시HttpModule파이프라인을 역순으로 거쳐 클라이언트에게 전송되기 전에 추가적인 수정이 이루어질 수 있습니다.
이 파이프라인 모델의 중요한 특징은 여러 HttpModule이 요청 수명 주기의 여러 지점에서 작동할 수 있지만, 단 하나의 HttpHandler만이 특정 요청을 최종적으로 처리한다는 점입니다.
HttpModule: 요청 파이프라인의 중간자
HttpModule은 System.Web.IHttpModule 인터페이스를 구현하는 클래스입니다. 이 인터페이스는 두 가지 메서드를 정의합니다:
Init(HttpApplication context): 시스템 초기화 시 호출되며,HttpModule이HttpApplication객체의 이벤트에 자체 이벤트 핸들러를 등록할 수 있도록 합니다.Dispose(): 가비지 컬렉션 전에 리소스 정리를 수행할 기회를 제공합니다.
using System.Web;
public interface IHttpModule
{
void Init(HttpApplication context);
void Dispose();
}
주요 HttpApplication 이벤트
HttpModule은 HttpApplication 객체의 다양한 이벤트에 등록하여 요청 처리 흐름에 개입할 수 있습니다. 주요 이벤트는 다음과 같습니다:
BeginRequest: 새로운 HTTP 요청이 수신될 때 발생합니다.AuthenticateRequest: 사용자 인증 준비 시 발생합니다.AuthorizeRequest: 사용자 권한 부여 준비 시 발생합니다.AcquireRequestState: 세션 상태가 준비되었을 때 발생합니다.PreRequestHandlerExecute: HTTP 핸들러 실행 직전에 발생합니다.PostRequestHandlerExecute: HTTP 핸들러 실행 직후에 발생합니다.EndRequest: HTTP 응답이 클라이언트로 전송되기 직전에 발생합니다.Error: 처리되지 않은 예외가 발생했을 때 발생합니다.
HttpModule 활용 예시
1. 모든 페이지에 동적 콘텐츠 추가
모든 웹 페이지의 응답에 특정 주석이나 스크립트를 동적으로 추가해야 하는 경우 HttpModule을 활용할 수 있습니다. 예를 들어, 웹사이트의 저작권 정보나 추적 스크립트를 모든 페이지에 삽입할 수 있습니다. 다음 예제에서는 EndRequest 이벤트에 등록하여 응답 본문에 주석을 추가합니다.
using System;
using System.Web;
namespace CustomModules
{
public class ResponseFooterModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.EndRequest += new EventHandler(OnEndRequest);
}
private void OnEndRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
// 응답 스트림이 이미 플러시되지 않았는지 확인
if (!app.Response.IsRequestBeingRedirected)
{
app.Response.Write("");
}
}
public void Dispose() { /* 리소스 정리 (필요한 경우) */ }
}
}
Web.config 파일에 모듈을 등록하여 활성화합니다:
<configuration>
<system.web>
<httpModules>
<add name="ResponseFooterModule" type="CustomModules.ResponseFooterModule, CustomModules"/>
</httpModules>
</system.web>
</configuration>
2. 중앙 집중식 인증 및 권한 부여 처리
로그인 여부를 확인하고, 로그인되지 않은 사용자에게는 특정 페이지에 대한 접근을 제한하는 로직을 모든 페이지마다 중복해서 작성하는 대신, HttpModule을 사용하여 중앙에서 처리할 수 있습니다. 세션 상태에 접근해야 하므로 AcquireRequestState 또는 PreRequestHandlerExecute 이벤트에 등록하는 것이 적절합니다.
using System;
using System.Web;
namespace CustomModules
{
public class AuthenticationCheckModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(OnAcquireRequestState);
}
private void OnAcquireRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpContext currentContext = app.Context;
string requestPath = currentContext.Request.Path.ToLower();
// 인증이 필요 없는 페이지 (예: 로그인 페이지)
if (requestPath.Contains("login.aspx") || requestPath.Contains("public.aspx"))
{
return;
}
// 세션에 사용자 정보가 없으면 로그인 페이지로 리디렉션
if (currentContext.Session == null || currentContext.Session["LoggedInUser"] == null)
{
string returnUrl = currentContext.Server.UrlEncode(currentContext.Request.Url.PathAndQuery);
currentContext.Response.Redirect($"login.aspx?ReturnUrl={returnUrl}");
currentContext.Response.End(); // 중요: 추가 처리 중단
}
}
public void Dispose() { /* 리소스 정리 (필요한 경우) */ }
}
}
Web.config 파일에 모듈을 등록합니다:
<configuration>
<system.web>
<httpModules>
<add name="AuthenticationCheckModule" type="CustomModules.AuthenticationCheckModule, CustomModules"/>
</httpModules>
</system.web>
</configuration>
HttpHandler: 요청의 최종 처리자
HttpHandler는 System.Web.IHttpHandler 인터페이스를 구현하며, 특정 유형의 HTTP 요청을 처리하는 역할을 합니다. HttpHandler는 HttpModule과 달리, 요청에 대한 핵심 콘텐츠를 생성하고 응답을 직접 구성하는 책임이 있습니다. 하나의 요청에 대해 하나의 HttpHandler만이 호출됩니다.
using System.Web;
public interface IHttpHandler
{
bool IsReusable { get; }
void ProcessRequest(HttpContext context);
}
ProcessRequest(HttpContext context): 실제 요청 처리 로직을 구현하는 메서드입니다.IsReusable: 핸들러 인스턴스를 재사용할 수 있는지 여부를 나타냅니다 (true면 성능 향상에 도움이 될 수 있음).
HttpHandler 활용 예시: 이미지 스트리밍
데이터베이스나 파일 시스템에서 이미지를 읽어와 웹 페이지에 직접 스트리밍하는 커스텀 핸들러를 구현할 수 있습니다. 이는 정적 파일로 이미지를 제공하는 것과 달리, 동적으로 이미지 데이터를 생성하거나 보안 검사를 추가할 때 유용합니다.
using System.IO;
using System.Web;
namespace CustomHandlers
{
public class ImageStreamHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string imagePath = context.Server.MapPath("~/Assets/sample.jpg");
if (File.Exists(imagePath))
{
context.Response.ContentType = "image/jpeg"; // 이미지 타입 설정
using (FileStream fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[4096]; // 4KB 버퍼
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
context.Response.OutputStream.Write(buffer, 0, bytesRead);
}
}
}
else
{
context.Response.StatusCode = 404;
context.Response.ContentType = "text/plain";
context.Response.Write("Image not found.");
}
}
public bool IsReusable => false; // 이 핸들러는 재사용하지 않음
}
}
Web.config에 핸들러를 등록하여 .img 확장자를 가진 요청을 이 핸들러가 처리하도록 지정합니다:
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="*.img" type="CustomHandlers.ImageStreamHandler, CustomHandlers"/>
</httpHandlers>
</system.web>
</configuration>
이제 yourpage.img와 같은 URL로 요청하면, 해당 경로의 sample.jpg 이미지가 스트리밍되어 표시됩니다.
IHttpHandlerFactory: 핸들러 인스턴스 관리
IHttpHandlerFactory 인터페이스는 HttpHandler 인스턴스를 동적으로 생성하고 해제하는 데 사용됩니다. 이는 단일 진입점에서 여러 핸들러를 조건부로 제공해야 할 때 유용합니다. 예를 들어, 요청된 파일 형식이나 URL 매개변수에 따라 다른 핸들러를 반환할 수 있습니다.
using System.Web;
public interface IHttpHandlerFactory
{
IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
void ReleaseHandler(IHttpHandler handler);
}
GetHandler: 주어진 요청 정보에 따라 적절한IHttpHandler인스턴스를 반환합니다.ReleaseHandler:HttpHandler인스턴스를 해제하거나 재사용 풀로 반환할 수 있도록 합니다.
IHttpHandlerFactory 활용 예시: 동적 핸들러 선택
다음은 요청 URL에 따라 이미지 스트리밍 핸들러와 CAPTCHA 생성 핸들러 중 하나를 선택하여 반환하는 팩토리 예제입니다.
CAPTCHA 핸들러 예제
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using System.Web.SessionState; // 세션 상태를 위해 필요
namespace CustomHandlers
{
public class CaptchaHandler : IHttpHandler, IRequiresSessionState // 세션 사용을 위해 인터페이스 구현
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpeg";
using (Bitmap captchaImage = new Bitmap(100, 30))
using (Graphics graphics = Graphics.FromImage(captchaImage))
{
graphics.FillRectangle(Brushes.White, 0, 0, 100, 30); // 배경
string captchaText = GenerateRandomCode(5);
context.Session["CaptchaCode"] = captchaText; // 세션에 CAPTCHA 코드 저장
using (Font font = new Font("Arial", 16, FontStyle.Bold))
using (SolidBrush brush = new SolidBrush(Color.Black))
{
graphics.DrawString(captchaText, font, brush, 5, 5); // 텍스트 그리기
}
captchaImage.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
}
private string GenerateRandomCode(int length)
{
Random random = new Random();
string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char[] code = new char[length];
for (int i = 0; i < length; i++)
{
code[i] = chars[random.Next(chars.Length)];
}
return new string(code);
}
public bool IsReusable => false;
}
}
커스텀 핸들러 팩토리
using System;
using System.Web;
namespace CustomHandlers
{
public class CustomHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
string requestedFileName = System.IO.Path.GetFileName(url).ToLower();
if (requestedFileName == "getimage.custom")
{
return new ImageStreamHandler();
}
else if (requestedFileName == "getcaptcha.custom")
{
return new CaptchaHandler();
}
else
{
// 적합한 핸들러가 없으면 기본 또는 오류 핸들러를 반환하거나 null 반환
context.Response.StatusCode = 404;
context.Response.Write("No handler found for this request.");
return null;
}
}
public void ReleaseHandler(IHttpHandler handler)
{
// 필요한 경우 핸들러 인스턴스 정리 (IDisposable 구현 시)
if (handler is IDisposable disposableHandler)
{
disposableHandler.Dispose();
}
}
}
}
Web.config에 팩토리를 등록합니다:
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="*.custom" type="CustomHandlers.CustomHandlerFactory, CustomHandlers"/>
</httpHandlers>
</system.web>
</configuration>
이제 /getimage.custom으로 요청하면 이미지가 스트리밍되고, /getcaptcha.custom으로 요청하면 CAPTCHA 이미지가 생성됩니다.
HttpHandler에서 세션 상태 사용하기
HttpHandler에서 ASP.NET 세션 상태에 접근하려면, 해당 핸들러 클래스가 System.Web.SessionState.IRequiresSessionState 인터페이스를 구현해야 합니다. 이 인터페이스는 실제 메서드를 포함하지 않는 마커(marker) 인터페이스입니다. 이를 구현하면 ASP.NET 런타임이 핸들러 실행 전에 세션 상태를 로드하고 준비합니다.
using System.Web;
using System.Web.SessionState; // IRequiresSessionState를 위해 필요
namespace CustomHandlers
{
// IRequiresSessionState 인터페이스를 구현하여 세션 상태에 접근 가능
public class SessionAwareHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
// 이제 context.Session에 접근할 수 있습니다.
int? visitCount = context.Session["VisitCount"] as int?;
visitCount = (visitCount ?? 0) + 1;
context.Session["VisitCount"] = visitCount;
context.Response.ContentType = "text/plain";
context.Response.Write($"이 페이지 방문 횟수: {visitCount}");
}
public bool IsReusable => true;
}
}