IIS 통합 파이프라인과 OWIN 미들웨어

등록일시: 2015-08-10 08:00,  수정일시: 2016-09-02 08:59
조회수: 5,164
이 문서는 OWINKatana 프로젝트를 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

본 자습서에서는 먼저 IIS 통합 파이프라인에서 OWIN 미들웨어 구성 요소(OMCs, OWIN Middleware Components)가 실행되는 방식에 관해서 살펴본 다음, OWIN 미들웨어 구성 요소가 실행될 파이프라인 이벤트를 개발자가 직접 설정하는 방법을 알아봅니다. 그리고 본문을 읽어보기 전에 우선 Katana 프로젝트 기사와 OWIN 시작 클래스 감지방식 기사를 한 번 살펴보실 것을 권해드립니다. 본 자습서는 Rick Anderson (@RickAndMSFT), Chris Ross, Praburaj Thiagarajan, 그리고 Howard Dierking (@howard_dierking)에 의해서 작성되었습니다.

기본적으로 OWIN 미들웨어 구성 요소는 서버의 파이프라인에 구애받지 않고 실행될 수 있도록 설계되었기 때문에, IIS 통합 파이프라인에서도 OWIN 미들웨어 구성 요소들을 정상적으로 실행할 수 있습니다 (단, 클래식 모드는 지원하지 않습니다). OWIN 미들웨어 구성 요소들을 IIS 통합 파이프라인에서 동작할 수 있게 설정하려면 패키지 관리자 콘솔(Package Manager Console)에서 다음의 패키지를 설치하기만 하면 됩니다:

Install-Package Microsoft.Owin.Host.SystemWeb

따라서 아직까지 IIS와 System.Web 외부에서 실행할 수 없는 모든 응용 프로그램 프레임워크들도 기존의 OWIN 미들웨어 구성 요소들을 활용할 수 있습니다.

노트: Visual Studio 2013에 새로운 Identity 시스템으로 포함된 모든 Microsoft.Owin.Security.* 패키지들 역시 OWIN 미들웨어 구성 요소의 형태로 작성되었으며, 이 구성 요소들은 자체 호스트 시나리오와 IIS 호스트 시나리오 모두에서 사용할 수 있습니다. (예: Cookies, Microsoft 계정, Google, Facebook, Twitter, 전달 토큰(Bearer Token), OAuth, Authorization server, JWT, Azure Active directory, 그리고 Active directory federation services 등)

OWIN 미들웨어가 IIS 통합 파이프라인에서 실행되는 방식

콘솔 형태의 OWIN 응용 프로그램에서는 OWIN 시작 클래스에 작성된 구성 코드에서 IAppBuilder.Use 메서드를 호출해서 구성 요소들을 추가한 순서대로 응용 프로그램의 파이프라인이 구성됩니다. 다시 말해서 Katana 런타임의 OWIN 파이프라인은 IAppBuilder.Use 메서드를 통해서 등록된 순서 그대로 OWIN 미들웨어 구성 요소들을 처리한다는 뜻입니다. 그러나 IIS 통합 파이프라인의 요청 파이프라인은 BeginRequest, AuthenticateRequest, AuthorizeRequest 등과 같이 사전에 미리 정의된 일련의 파이프라인 이벤트들을 구독하는 HttpModules들의 모음으로 구성됩니다.

따라서 특정 OWIN 미들웨어 구성 요소를 그에 대응하는 ASP.NET 환경의 HttpModule과 비교해보려면 OWIN 미들웨어 구성 요소 역시 미리 정의된 파이프라인 이벤트들 중 올바른 이벤트에 등록되어야만 합니다. 가령, MyModule이라는 이름을 가진 다음의 HttpModule은 파이프라인에서 요청이 AuthenticateRequest 단계에 도달했을 때 호출됩니다:

public class MyModule : IHttpModule
{
    public void Dispose()
    {
        // clean-up code here.
    }

    public void Init(HttpApplication context)
    {
        // An example of how you can handle AuthenticateRequest events.
        context.AuthenticateRequest += ctx_AuthRequest;
    }

    void ctx_AuthRequest(object sender, EventArgs e)
    {
        // Handle event.
    }
}

Katana 런타임 코드는 이와 동일한 이벤트 기반의 실행 순서에 따라 OWIN 미들웨어 구성 요소를 참여시키기 위해서 OWIN 시작 클래스에 구성된 코드 내역을 검토한 다음, 각각의 미들웨어 구성 요소들을 통합 파이프라인 이벤트에 구독시킵니다. 예를 들어서, 다음의 OWIN 미들웨어 구성 요소 및 등록 코드를 살펴보면 미들웨어 구성 요소들이 등록되는 기본적인 방식을 확인할 수 있습니다. (OWIN 시작 클래스를 생성하는 더 자세한 방법은 OWIN 시작 클래스 감지방식 기사를 참고하시기 바랍니다.)

  1. Empty 템플릿을 사용해서 owin2라는 이름으로 ASP.NET 웹 응용 프로그램(ASP.NET Web Application) 프로젝트를 생성합니다.
  2. 그리고 패키지 관리자 콘솔(Package Manager Console) 창에 다음 명령을 입력합니다:

    Install-Package Microsoft.Owin.Host.SystemWeb

  3. 그런 다음, Startup이라는 이름으로 OWIN 시작 클래스(OWIN Startup Class)를 추가하고, 자동으로 생성된 코드를 다음과 같이 변경합니다 (코드에 변경해야 할 부분들이 강조되어 있습니다):
    using System;
    using System.Threading.Tasks;
    using Microsoft.Owin;
    using Owin;
    using System.Web;
    using System.IO;
    using Microsoft.Owin.Extensions;
    
    [assembly: OwinStartup(typeof(owin2.Startup))]
    
    namespace owin2
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                app.Use((context, next) =>
                {
                    PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
                    return next.Invoke();
                });
    
                app.Use((context, next) =>
                {
                    PrintCurrentIntegratedPipelineStage(context, "2nd MW");
                    return next.Invoke();
                });
    
                app.Run(context =>
                {
                    PrintCurrentIntegratedPipelineStage(context, "3rd MW");
                    return context.Response.WriteAsync("Hello world");
                });
            }
    
            private void PrintCurrentIntegratedPipelineStage(IOwinContext context, string msg)
            {
                var currentIntegratedpipelineStage = HttpContext.Current.CurrentNotification;
                context.Get<TextWriter>("host.TraceOutput").WriteLine(
                    "Current IIS event: " + currentIntegratedpipelineStage
                    + " Msg: " + msg);
            }
        }
    }
  4. F5 키를 눌러서 응용 프로그램을 실행합니다.

이 시작 클래스의 구성 코드에서는 세 가지 미들웨어 구성 요소들을 설정하고 있습니다. 첫 번째와 두 번째 구성 요소는 진단 정보만 출력하고, 마지막 구성 요소는 진단 정보를 출력한 다음 이벤트에 응답합니다. 그리고 PrintCurrentIntegratedPipelineStage 메서드는 미들웨어가 호출된 통합 파이프라인의 이벤트 정보와 메시지를 함께 출력해줍니다. 이제 출력 창을 살펴보면 다음과 같은 결과를 확인할 수 있을 것입니다:

Current IIS event: PreExecuteRequestHandler Msg: Middleware 1
Current IIS event: PreExecuteRequestHandler Msg: 2nd MW
Current IIS event: PreExecuteRequestHandler Msg: 3rd MW

이 결과에서 알 수 있는 것처럼 Katana 런타임은 기본적으로 각각의 OWIN 미들웨어 구성 요소들을 IIS의 PreRequestHandlerExecute 파이프라인 이벤트에 대응하는 PreExecuteRequestHandler에 매핑시킵니다.

단계 표시자(Stage Markers)

단계 표시자 역할을 하는 IAppBuilder.UseStageMarker 확장 메서드를 사용하면 OWIN 미들웨어 구성 요소가 실행될 파이프라인 상의 특정 단계를 지정할 수 있습니다. 특정 단계에서 일련의 미들웨어 구성 요소들을 실행하려면, 해당 구성 요소들 중 마지막 구성 요소를 등록한 직후 단계 표시자를 호출하면 됩니다. 이 때, 파이프라인의 어떤 단계에서 미들웨어를 실행할 수 있는지, 또 어떤 순서로 구성 요소들이 실행되어야 하는지에 관한 규칙들이 존재합니다 (이 규칙들에 관해서는 잠시 뒤에 살펴봅니다). 다음과 같이 Configuration 메서드 코드에 UseStageMarker 메서드 호출을 추가해보겠습니다:

public void Configuration(IAppBuilder app)
{
    app.Use((context, next) =>
    {
        PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
        return next.Invoke();
    });

    app.Use((context, next) =>
    {
        PrintCurrentIntegratedPipelineStage(context, "2nd MW");
        return next.Invoke();
    });

    app.UseStageMarker(PipelineStage.Authenticate);

    app.Run(context =>
    {
        PrintCurrentIntegratedPipelineStage(context, "3rd MW");
        return context.Response.WriteAsync("Hello world");
    });

    app.UseStageMarker(PipelineStage.ResolveCache);
}

이 코드에서는 app.UseStageMarker(PipelineStage.Authenticate) 메서드 호출로 인해서 그전까지 등록된 모든 미들웨어 구성 요소들이 (즉, 처음의 두 진단 구성 요소들이) 파이프라인의 Authenticate 단계에서 실행되도록 구성됩니다. 반면 마지막 미들웨어 구성 요소는 (진단 정보를 출력하고 요청에 응답하는 구성 요소는) ResolveCache 단계에서 실행될 것입니다 (ResolveRequestCache 이벤트).

다시 F5 키를 눌러서 응용 프로그램을 실행시켜보면 이번에는 출력 창에서 다음과 같은 결과를 확인할 수 있을 것입니다:

Current IIS event: AuthenticateRequest Msg: Middleware 1
Current IIS event: AuthenticateRequest Msg: 2nd MW
Current IIS event: ResolveRequestCache Msg: 3rd MW

단계 표시자 규칙

OWIN 미들웨어 구성 요소들은 다음과 같은 OWIN 파이프라인 단계 이벤트들에서 실행될 수 있습니다:

public enum PipelineStage
{
    Authenticate = 0,
    PostAuthenticate = 1,
    Authorize = 2,
    PostAuthorize = 3,
    ResolveCache = 4,
    PostResolveCache = 5,
    MapHandler = 6,
    PostMapHandler = 7,
    AcquireState = 8,
    PostAcquireState = 9,
    PreHandlerExecute = 10
}
  1. 기본적으로 OWIN 미들웨어 구성 요소들은 가장 마지막 이벤트인 PreHandlerExecute에서 실행됩니다. 바로 그래서 본문의 첫 번째 예제 코드의 진단 정보에서 "PreExecuteRequestHandler"가 출력됐던 것입니다.
  2. 그러나 app.UseStageMarker 메서드를 사용하면 PipelineStage 열거형 목록에 존재하는 OWIN 파이프라인의 단계들 중 특정 단계에서 OWIN 미들웨어 구성 요소가 실행되도록 등록할 수 있습니다.
  3. 다만, OWIN 파이프라인이나 IIS 파이프라인의 순서가 미리 정해져있기 때문에, app.UseStageMarker 메서드를 호출할 때도 순서를 지켜야만 합니다. 다시 말해서 app.UseStageMarker 메서드를 호출해서 등록한 마지막 이벤트보다 순서적으로 앞선 이벤트에 대한 이벤트 헨들러를 그 뒤에 다시 설정할 수는 없습니다. 가령 다음과 같이 메서드를 호출한 다음:

    app.UseStageMarker(PipelineStage.Authorize);

    그 뒤에 다시 AuthenticatePostAuthenticate 열거형을 전달해서 app.UseStageMarker 메서드를 호출한다면 예외가 던져지거나 하지는 않지만, 그렇다고 의도하는 대로 동작하지도 않을 것입니다. 방금 설명한 것처럼 OWIN 미들웨어 구성 요소들은 기본적으로 가장 마지막 단계인 PreHandlerExecute 단계에 실행됩니다. 그리고 단계 표시자는 미들웨어 구성 요소들이 특정 단계 이전에 실행되도록 설정하기 위한 용도로 사용됩니다. 따라서 순서를 지키지 않고 단계 표시자를 지정할 경우, 그 중 가장 선행 이벤트의 표시자가 적용됩니다. 다시 말해서 단계 표시자를 추가한다는 것은 "지금 지정하는 이 단계 이후에는 지금까지 등록한 미들웨어 구성 요소들을 실행하면 안된다"고 지시하는 것과 같은 뜻인 것입니다. 결국 OWIN 미들웨어 구성 요소들은 자신이 OWIN 파이프라인에 추가된 이후에 지정된 가장 빠른 단계의 단계 표시자에서 실행될 것입니다.
  4. 결론적으로 가장 빠른 단계가 지정된 app.UseStageMarker 메서드 호출의 우선순위가 가장 높습니다. 이를 테면, 이전 절의 예제에서 app.UseStageMarker 메서드의 호출 순서를 다음과 같이 바꿔봅니다:
    public void Configuration(IAppBuilder app)
    {
        app.Use((context, next) =>
        {
            PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
            return next.Invoke();
        });
    
        app.Use((context, next) =>
        {
            PrintCurrentIntegratedPipelineStage(context, "2nd MW");
            return next.Invoke();
        });
    
        app.UseStageMarker(PipelineStage.ResolveCache);
    
        app.Run(context =>
        {
            PrintCurrentIntegratedPipelineStage(context, "3rd MW");
            return context.Response.WriteAsync("Hello world");
        });
    
        app.UseStageMarker(PipelineStage.Authenticate);
    }
    그러면 출력 창에 다음과 같은 결과가 나타날 것입니다:

    Current IIS event: AuthenticateRequest Msg: Middleware 1
    Current IIS event: AuthenticateRequest Msg: 2nd MW
    Current IIS event: AuthenticateRequest Msg: 3rd MW


    이 결과를 살펴보면 모든 OWIN 미들웨어 구성 요소들이 AuthenticateRequest 단계에서 실행된 것을 확인할 수 있는데, 그 이유는 마지막 OWIN 미들웨어 구성 요소가 Authenticate 이벤트에 등록되었고, 이 Authenticate 이벤트가 다른 모든 이벤트들 보다 선행하는 이벤트이기 때문입니다.

이 기사는 2013년 11월 7일에 최초 작성되었습니다.