OWIN과 함께 춤을 – Hello, World

OWIN과 미들웨어


OWIN은 .NET Framework를 이용하여 코드를 실행할 수 있는 서버 환경이면 어디서든 사용이 가능한 이식이 편리한 코어 웹 프레임워크입니다. 기본적으로 OWIN은 웹 요청을 받아들이면 주어진 순서대로 구성된 미들웨어 체인을 따라 응답을 만들어내게 됩니다.


.NET Framework를 이용한 웹 응용프로그램 개발도 요즈음에는 다양한 프레임워크를 결합하여 개발하는 것이 요구 사항으로 자리잡고 있으며, 초창기의 .NET과는 달리 더 이상 System.Web 기반의 기술만으로 모든 것을 구현하지는 않습니다.


 


 


대표적으로, 최근 소개된 비동기 양방향 웹 소켓 호환 통신을 지원하는 SignalR의 경우 버전 2.0부터는 System.Web과 독립적으로 움직일 수 있도록 OWIN 위에서 실행되는 구조를 취하게 되었습니다. SignalR을 도입하는데 뜬금없이 OWIN Startup이라던지 하는 코드를 보면서 생소하다는 느낌을 받으셨다면 그게 바로 이것입니다.


재미있는 것은 OWIN 스택 전체는 기존에 ASP.NET을 실행하던 환경과 독립적인 관계를 가집니다. 기존 ASP.NET 환경 위에서 호스팅하는 경우 ASP.NET 환경보다 가장 먼저 앞서서 실행되는 형태로 되어있습니다. 이전처럼 Visual Studio 도구에 종속적인 방식으로 프로그래밍하는 것이 아니라, 내가 어떤 웹 기술을 사용할 것인지 Startup 클래스에서 정하여 선택적으로 사용할 수 있게 됩니다.


OWIN 기반의 응용프로그램 처음 만들어보기


앞에서 잠시 이야기한 것처럼 OWIN은 흔히 콘솔이나 클라이언트 응용프로그램처럼 시작점이 존재합니다. ASP.NET 기반의 응용프로그램으로 말할 것 같으면 Global.asax 같은 역할을 담당한다고 할 수 있습니다. 이것을 OWIN Startup 클래스라고 하며, OWIN Startup 클래스에서 내가 어떤 미들웨어를 사용하여 웹 요청을 처리할 것인지 프로그래밍할 수 있습니다.


빠르게 예제를 만들어보기 위하여, Visual Studio에서 비어있는 ASP.NET 프로젝트를 하나 만들어보도록 하겠습니다. (MVC나 Web Form 등은 일절 필요하지 않습니다.)


참고로, 이번 아티클에서 사용하는 Visual Studio 버전은 2013 버전이지만 2012로도 큰 차이 없이 작업할 수 있습니다.


새 웹 프로젝트를 만들면서 아래와 같이 One ASP.NET 프로젝트 대화 상자가 나타나는데, 빈 템플릿으로 하나 만들도록 합니다.


 


그러면 아래와 같이 최소 수준으로 구성된 웹 프로젝트가 만들어지게 됩니다.


 


프로젝트 항목 (위 그림 기준으로 OwinExample)을 마우스 오른쪽 버튼으로 클릭하고 NUGET 패키지 관리 메뉴를 클릭하면 아래와 같이 패키지 설치 대화 상자가 나타납니다. 우측 항목들 중 온라인 항목을 선택하고, 검색어에 “system.web owin”이라고 입력하여 검색합니다.


만약 이 기능을 찾을 수 없는 경우 Visual Studio에 NuGet 패키지 관리자가 설치되어있지 않은 것이므로, http://visualstudiogallery.msdn.microsoft.com/27077b70-9dad-4c64-adcf-c7cf6bc9970c 에서 익스텐션을 내려 받아 설치하시면 됩니다.


 


나타나는 검색 결과 항목들 중 Microsoft.Owin.Host.SystemWeb 항목을 클릭하고 Install 버튼을 클릭하면, 종속성 관계에 따라 추가 설치가 필요한 패키지에 대한 정보나 라이선스 동의 등의 추가 확인 대화 상자가 나타날 수 있고, 여기에 모두 승인하시면 ASP.NET 프로젝트에서 OWIN을 사용할 수 있게 준비가 완료됩니다.


설치가 마무리되면 새 클래스를 만듭니다. 아래와 같이 클래스의 내용을 작성하도록 합니다. 혹은 Visual Studio 2013을 사용하는 경우 새 파일 템플릿 중에 OWIN 시작 클래스라는 항목도 있는데 이 항목을 대신 사용해도 됩니다.
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;


[assembly: OwinStartup(typeof(OwinExample.Startup1))]


namespace OwinExample
{
    public class Startup1
    {
        public void Configuration(IAppBuilder app)
        {
        }
    }
}



위에 보시는 것이 처음 OWIN 프로그램을 시작할 때 사용되는 시작 클래스입니다. 여기서 Configuration 부분에 OWIN에서 사용할 미들웨어 등에 대한 구성을 추가하면 됩니다.


OWIN 패키지로 설치되는 라이브러리에 대한 이해


엄격하게 말해서 OWIN 어셈블리는 IAppBuilder라고 불리는 인터페이스 하나만을 가지고 있습니다.


 


보시는 것처럼 정말 IAppBuilder라는 인터페이스를 하나만 가지고 있을 뿐입니다. 그런데 이 인터페이스가 OWIN 기반의 응용프로그램을 만들기 위한 여러 가지 기본 사항들을 정의하고 있습니다. 각 멤버들에 대해서 간단히 살펴보면 다음과 같습니다.


•Build(System.Type): 이 인터페이스를 구현하는 클래스의 재량이며, 주어진 Type 형식에 대응되는 객체의 참조를 반환합니다. Microsoft OWIN 구현체의 경우, Map과 MapWhen 관련 기능을 소화하기 위한 목적으로 이 메서드를 활용합니다.


 


•New(): 역시 인터페이스를 구현하는 클래스의 재량이며, 또 다른 IAppBuilder 인터페이스 형식의 객체의 참조를 반환합니다. Microsoft OWIN 구현체의 경우, Map과 MapWhen 관련 기능을 소화하기 위한 목적으로 이 메서드를 활용합니다.


 


•Use(object, params object[]): OWIN 초기 구성에 있어서 가장 중요한 메서드입니다. 요청을 처리하고자 하는 미들웨어를 필요한 만큼 추가할 수 있으며, Use 메서드를 구성 과정에서 부른 순서대로 내부적으로 배열이나 리스트 안에 Use 메서드를 통해 전달받은 미들웨어 진입점들을 보관하고 순서대로 호출이 이루어질 수 있게 합니다.


 


OWIN 기반으로 프로그램을 만드는 과정에서 가장 첫 단추는 바로 이 IAppBuilder의 Use 메서드를 적절하게 활용하는 것입니다.


하지만 이 인터페이스 만으로 프로그래밍을 한다는 것은 정말 최소한의 수준을 만족하는 프로그래밍 기법을 사용하는 것으로, 실제 우리가 관심을 가져야 할 부분과는 거리가 상당히 멀리 떨어져 있습니다. 호스팅 환경이나 웹 프로그래밍에서 응당 필요한 요청과 응답 과정에서의 파이프라인 처리 등 필요한 것이 많습니다. 이런 부분들을 적절히 제공하는 것이 바로 Microsoft가 제안하는 Katana 프로젝트를 통한 프로그래밍입니다. Microsoft.Owin 이라는 이름으로 시작하는 어셈블리들의 시리즈이며, 이것을 사용하여 좀 더 웹 프로그래밍 다운 웹 프로그래밍을 할 수 있게 됩니다.


지금 여러분이 만든 ASP.NET 프로젝트에서 OWIN을 실행할 수 있도록 해준다는 것은 Katana 프로젝트의 일부인 System.Web Loader 프로젝트의 기능입니다. 어떻게 해서 이 클래스가 별다른 설정도 없이 자동으로 모든 요청을 받아들일 수 있는 시작점이 되는가에 대해서는 지금은 자세히 알지 못해도 괜찮습니다.


Hello World 미들웨어 작성


이제 본격적으로 Hello World 메시지를 출력하는 간단한 미들웨어를 하나 작성해보도록 하겠습니다.


Configuration 메서드 안에 다음과 같이 코드를 작성합니다.
app.Run(async (context) =>
{
    await context.Response.WriteAsync(“Hello, World!”);
});


앞에서 살펴본 Use 메서드를 응용하는 도우미 메서드로 Run 메서드를 Katana에서 제공하고 있습니다. 이 메서드를 사용하면 다른 미들웨어를 실행하지 않고 자기 선에서 요청에 대한 응답을 끝낼 수 있는 미들웨어를 간단한 코드로 쉽게 작성할 수 있게 해줍니다. 여기서는 Hello World라는 문자열을 HTTP 응답으로 내보내도록 하는 코드를 작성해보았습니다.


이 코드를 실행하면 다음과 같이 웹 브라우저에 Hello, World! 라는 문구가 나타날 것입니다.


 


HTTP Query String 받아서 처리하기


Response 속성을 통해 응답을 내보내는 것 말고, 조금 더 나가보기로 하겠습니다. 이번에는 Query String을 입력으로 받아들여 이름을 출력하는 코드를 조금 더 작성해보기로 하겠습니다.


문자열을 다루는 코드를 조금 추가할 것이므로 네임스페이스에 대한 참조가 다음과 같이 추가되어야 합니다.



using Microsoft.Owin;
using Owin;
using System;
using System.Globalization;


 



그리고 앞에서 작성한 Hello, World! 메시지를 내보내는 미들웨어의 코드를 다음과 같이 변경하겠습니다.



var query = context.Request.Query;
var name = query.Get(“name”);


if (name == null)
    name = “Stranger”;


var message = String.Format(
    CultureInfo.InvariantCulture,
    “Hello, {0}!”,
    name);


await context.Response.WriteAsync(message);


 



Query 속성의 Get 메서드를 사용하여 Query String 형태로 전달되는 매개 변수의 값을 가져오도록 할 수 있는데, 만약 값을 가져오지 못한다면 NULL 참조를 대신 반환합니다. 이 경우 기본값으로 Stranger로 설정하도록 코드를 작성하였습니다. 그 다음은 익히 잘 아시는 String.Format 메서드를 사용하여 문구를 완성하는 것이고, 응답에 이를 사용하게 됩니다.


그럼 이제 다시 한 번 코드를 실행해보겠습니다. 이름을 지정하지 않은 상태에서는 다음과 같이 실행될 것입니다.


 


그리고 주소 뒤에 ?name=David 라고 입력해봅니다.


 


그런데 여기서 한 가지 궁금한 점이 생깁니다. 보통 웹 프로그래밍을 할 때 흔히 어떤 파일에 대해서 작업을 하고 그 파일을 열어보면 실행된다는 식인데 지금 이 화면을 띄우기까지 어떤 파일 위에서 작업한 것이 아니라 그냥 프로그래밍을 했을 뿐입니다. 다시 말해, 어떤 URL을 경유해서 들어오든 지금 보는 화면과 동작이 적용됨을 뜻합니다. 임의로 아무렇게나 주소를 한 번 넣어보시면 어떤 의미인지 금방 알 수 있습니다. 예를 들어, http://localhost:37339/askbvkkewr?name=David 라고 없는 주소를 임의로 써봅니다.


 


그렇습니다. 어디서 어떻게 들어오든 모든 웹 요청을 전부 방금 만든 미들웨어가 소화를 하도록 되어있는 것입니다. 전통적인 ASP.NET 또는 웹 프로그래밍 환경과는 다르게, OWIN 안에서는 HTTP 서비스 전체를 자유자재로 통제할 수 있습니다.


HTTP POST URL Encoded Form 다루기


마지막으로 한 가지 더 살펴보도록 하겠습니다. Query String도 쉽게 처리할 수 있었는데, 그렇다면 POST로 보내는 요청들도 Katana에서 쉽게 처리할 수 있을까요?


간단하게 요약하면, Katana가 제공하는 Request에 대한 처리는 Request Body를 실시간으로 읽을 수 있는 System.IO.Stream 구현체를 사용하거나, ReadFormAsync() 메서드를 사용하여 URL Encoded Form을 받아들이는 정도를 우선 활용할 수 있습니다. 그러나 흔히 사용하는 Multipart 데이터는 자체적으로 소화할 수 있는 방법은 따로 없고, ASP.NET MVC Web API v2의 도우미 클래스를 사용하여 처리하는 방법을 사용할 수 있습니다. 일단 여기서는 URL Encoded Form의 형태로 받아서 처리할 수 있는 예를 한 번 다루어보도록 하겠습니다.


URL Encoded Form으로 들어오는 요청을 손쉽게 만들기 위하여, 아래와 같이 간단한 웹 페이지를 하나 만들어보도록 하겠습니다. <FORM> 태그의 ACTION 속성 값에 들어갈 URL은 현재 여러분이 만든 OWIN 응용프로그램의 웹 주소로 적절하게 치환하셔야 정상적으로 전송이 됩니다. 이 주소를 확인하는 방법은 방금 만든 프로젝트의 속성을 연 다음, 웹 탭을 클릭하는 것입니다. 참고로 이 주소는 프로젝트 생성 시점에 동적으로 할당되어 프로젝트 설정으로 저장되는 값으로, 개발 과정 중에는 계속 같은 포트 값을 유지할 수 있습니다.


 


위의 주소를 확인하여 아래와 같이 HTML 페이지를 작성하도록 합니다.


<!DOCTYPE html>


<html lang=”en” xmlns=”http://www.w3.org/1999/xhtml“>
<head>
    <meta charset=”utf-8″ />
    <title>Hello, World!</title>
</head>
<body>
    <form action=”http://localhost:37339/” method=”post”
          enctype=”text/plain”>
        <label for=”name”>Your Name: </label>
        <input type=”text” name=”name” id=”name” />
        <input type=”submit” value=”Hello?” />
    </form>
</body>
</html>


 


여기서 중요한 것은 <FORM> 태그의 METHOD 속성의 값이 POST라는 것과 ENCTYPE 속성이 “application/x-www-form-urlencoded”로 지정된 것입니다. 이렇게 지정해야 OWIN에서 데이터를 가져올 수 있습니다.


그리고 Form 데이터를 받아서 처리할 수 있도록 아래와 같이 수정합니다.


//var query = context.Request.Query;
var form = await context.Request.ReadFormAsync();
var name = form.Get(“name”);


 



특별히 바뀐 것은 없습니다. context.Request.Query 속성 대신 context.Request.ReadFormAsync() 비동기 메서드를 호출하여 얻은 결과로 나오는 객체를 활용하도록 하는 것이고 그 이후는 동일한 로직을 사용합니다.


테스트를 위해서 앞에서 만든 HTML 페이지를 브라우저로 열어봅니다. 텍스트 상자 안에 임의의 이름을 넣고 Hello? 버튼을 클릭하면 다음과 같이 입력한 문자열이 반영된 응답이 나오게 됩니다.


 


만약 HTML 폼에서 “application/x-www-form-urlencoded” 대신 “multipart/form-data”를 지정할 경우 ReadFormAsync() 비동기 메서드는 비어있는 컬렉션을 반환합니다.


다음 아티클에서는


다음 아티클에서는 이 특성을 사용하여 좀 더 세밀하고 다양한 미들웨어 프로그래밍을 다루어보도록 하겠습니다.

댓글 남기기