OWIN은 Open Web Interface for .NET의 약어로 요즘 MS 진영에서 핫한(?) 오픈소스 프로젝트다. 다음은 OWIN 공식 사이트에 나와 있는 프로젝트의 목표.
The goal of the OWIN interface is to decouple server and application, encourage the development of simple modules for .NET web development, and, by being an open standard, stimulate the open source ecosystem of .NET web development tools.
MS에서 제공하는 ASP.NET MVC framework는 System.Web
에, 특히 HttpContext
에 깊은 의존성이 있어서 IIS 이외에는 구동이 불가능하다. 하지만 OWIN 구현을 통해 웹서버와 어플리케이션의 의존성이 분리1되어 IIS 이외의 웹서버에서도 C#으로 작성된 웹 어플리케이션을 구동할 수 있게 되었다.2 OWIN은 인스턴스로 처리하기 간편한 구조라 횡적으로 확장이 쉬운데다 서버의 유연성까지 보장하고 있다. OWIN을 이용해 개발한다면 ASP.NET MVC을 기반으로 작업할 수 없지만3 Nancy나 Simple.Web 등 다양한 프레임워크/라이브러리를 활용할 수 있다.
OWIN의 구조
OWIN 명세에서는 각각 서버, 웹프레임워크, 웹 어플리케이션, 미들웨어, 호스트로 정의했다. 발번역(…)하면 다음과 같다.
- 서버: HTTP 서버로 클라이언트와 직접 소통. 요청을 처리하는데 OWIN 문맥을 사용. 서버는 OWIN 문맥을 이해할 수 있도록 돕는 어뎁터 레이어가 필요.
- 웹 프레임워크: 요청을 처리하는데 사용하는 어플리케이션을 OWIN에서 원활하게 구동할 수 있도록 돕는 독립적 컴포넌트. OWIN 문맥을 이해할 수 있는 어뎁터 레이어 필요.
- 웹 어플리케이션: 웹프레임워크 위에서 개발 가능하며, OWIN 호환 서버에서 구동이 가능한 특정 어플리케이션.
- 미들웨어: 서버와 어플리케이션 사이의 파이프라인을 통과해 구동되는 컴포넌트로 특정의 목적에 따라 응답과 요청을 점검, 라우팅 또는 수정할 수 있음.
- 호스트: 서버 내에서 어플리케이션을 실행하기 위해 우선적으로 응답하는 어플리케이션 실행부. 특정 서버는 호스트의 역할도 수행함.
OWIN의 전체적인 흐름은 다음과 같다.
서버를 통해 요청이 들어오면 호스트는 Startup
클래스에 정렬된 파이프라인을 따라 미들웨어를 실행한 후 어플리케이션을 실행한다. 어플리케이션 실행 후 응답 또한 미들웨어를 거쳐 빠져나간다. 미들웨어는 Task
를 이용해 다음 파이프라인으로 컨텍스트를 넘겨준다. 각각의 미들웨어는 앞서의 정의처럼 응답과 요청을 조작할 수 있다.
OWIN 명세에서는 AppFunc
라고 불리는 어플리케이션 대리자(delegate)를 통해 환경을 주고 받는다. 명세에서는 다음과 같이 환경과 Task로 구성되어 있다.
using AppFunc = Func<
IDictionary<string, object>, // Environment
Task>; // Done
환경은 크게 요청, 응답, 기타 데이터로 구분되는데 자세한 내용은 OWIN 스펙에서 찾아볼 수 있다. MS에서는 OWIN 명세를 기반으로 Katana라는 이름으로 프로젝트를 진행하고 있는데(Microsoft.Owin) 여기에서는 IOwinContext
인터페이스를 활용할 수 있다. 이 포스트의 예제에서도 IOwinContext
를 사용하고 있다.
맛보기 코드
OWIN 어플리케이션
이 포스트에서는 누구나 따라해볼 수 있도록 Mono 환경을 기준으로 작성했다. IDE로는 Xamarin Studio를 사용했다.
Xamarin Studio을 실행해 새 솔루션을 생성한다. Console에서 self-host로 구동하는 예제이므로 Console Project를 선택한다. (Visual Studio를 이용하는 예제에서는 Web Empty Project를 사용해도 된다.4)
먼저 최신 패키지 설치를 위해 target framework를 Mono / .NET 4.5
로 변경해야 한다. 해당 솔루션의 옵션에서 Build > General
에서 변경할 수 있다.
솔루션에 패키지를 추가한다. 솔루션에 오른쪽 클릭해서 Add > Add Packages...
를 클릭한다.
Show pre-release packages를 체크하고 Owin을 검색한다. 검색 결과에서 OWIN
, Microsoft.Owin
, Microsoft.Owin.Hosting
, Microsoft.Owin.Host.HttpListener
를 체크하고 Add Packages
버튼을 누른다.
설치가 완료되었으면 OWIN에서 엔트리 포인트가 될 Startup.cs
파일을 추가한다.
이 파일의 내용은 다음과 같다. 5
using System;
using Owin;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(OwinHelloWorld.Startup))]
namespace OwinHelloWorld
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Run (context => {
context.Response.ContentType = "text/html";
return context.Response.WriteAsync("<h1>It Works</h1>");
});
}
}
}
이 Startup
클래스의 Configuration(IappBuilder app)
메소드를 통해서 어떤 미들웨어에 어떤 순서로 접근하며(app.Use()
) 최종적으로 어떤 어플리케이션이 실행되는지(app.Run()
) 작성하게 된다.
[assembly: OwinStartup(typeof(OwinHelloWorld.Startup))]
부분은 이 Owin 코드가 컨테이너 형태로 실행될 때 엔트리 포인트를 지정해주는 부분이다. (이외에도 appsettings에 추가해주거나 owinhost.exe
를 실행할 때 미리 선언한 identifier를 사용하는 방법이 있다.)
이제 이 인터페이스를 실질적으로 접근 가능하게 만들어줄 콘솔 어플리케이션을 작성한다. Program.cs
를 열어 다음의 코드를 입력한다.
using System;
using Microsoft.Owin.Hosting;
namespace OwinHelloWorld
{
class MainClass
{
public static void Main (string[] args)
{
var url = "http://localhost:9000";
using (WebApp.Start<Startup> (url)) {
Console.WriteLine (url);
Console.WriteLine ("Press enter to quit.");
Console.ReadLine ();
}
}
}
}
Microsoft.Owin.Hosting.WebApp
으로 Startup
클래스를 구동한다. 이 과정에서 내부적으로 Microsoft.Owin.Host.HttpListener
를 사용한다.
이제 프로젝트를 빌드하고 실행하면 다음과 같은 콘솔 화면이 나타난다.
그리고 해당 주소를 브라우저에서 열면 페이지를 확인할 수 있다.
미들웨어 Middleware
미들웨어 간의 소통은 앞에서 말한 appFunc
를 사용해 환경과 컨텍스트를 넘겨주는데 여기 예제에서는 Katana에서 제공하는 OwinMiddleware
클래스와 IOwinContext
인터페이스를 활용해 미들웨어를 작성할 것이다.
다음은 MyFirstMiddleware.cs
의 코드다.
using System;
using System.Threading.Tasks;
using Owin;
using Microsoft.Owin;
namespace HelloWorldOwin
{
public class MyFirstMiddleware : OwinMiddleware
{
public MyFirstMiddleware (OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context){
context.Response.Write ("<!doctype html><html><body>");
await Next.Invoke (context);
context.Response.Write ("</body></html>");
}
}
}
OwinMiddleware
클래스를 상속받는 MyFirstMiddleware
로 Invoke(IOwinContext)
메소드를 사용해 해당 미들웨어에서 데이터를 핸들링 하거나 응답/요청을 변경하는 등의 코드를 작성할 수 있다.
다음은 MySecondMiddleware.cs
의 코드다.
using System;
using System.Threading.Tasks;
using Owin;
using Microsoft.Owin;
namespace HelloWorldOwin
{
public class MySecondMiddleware : OwinMiddleware
{
public MySecondMiddleware (OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context){
context.Response.Write ("<header><h1>It works</h1></header>");
await Next.Invoke (context);
context.Response.Write ("<footer><a href=\"http://localhost:9000\">http://localhost:9000</a></footer>");
}
}
}
파이프라인의 흐름을 보여주기 위해서 그냥 예시 코드를 넣은 두번째 미들웨어다. 마지막으로 Startup.cs
로 돌아가서 해당 미들웨어를 파이프라인으로 집어 넣는다.
using System;
using Owin;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(HelloWorldOwin.Startup))]
namespace HelloWorldOwin
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use (typeof(HelloWorldOwin.MyFirstMiddleware));
app.Use (typeof(HelloWorldOwin.MySecondMiddleware));
app.Run (context => {
context.Response.ContentType = "text/html";
return context.Response.WriteAsync("<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>");
});
}
}
}
app.Use()
로 각각의 미들웨어를 연결했다. 앞서 예제와 동일하게 구동해보면 다음 결과를 볼 수 있다.
특별한 예제가 딱히 떠오르지 않아서 그냥 html 코드를 넣었는데 GitHub이나 nuget 리포지터리에 OAuth Autentication 같은 멋진 미들웨어가 많이 있어 위와 같이 간편하게 추가만 하면 사용이 가능하다.
여기까지 OWIN에 대해 살펴봤다. 아직 C#는 초보라서 깊이있게 글을 쓰지 못하는게 아쉽다. 하지만 앞으로의 발전이 더 기대되고 꾸준히 Follow-up 할 닷넷 프로젝트가 될 것 같다.
예제 코드
예제는 모두 Xamarin Studio로 작성했고 GitHub 리포지터리에서 내려받을 수 있다.
- HelloWorldOwin : 위에서 작성한 예제 코드
- HelloWorldOwinHost :
owinhost.exe
로 구동하는 예제 코드로web.config
등 Configuration 하는 방식이 조금 다르다.
더 읽을 거리
- OWIN 공식 사이트
- Nancy
- Getting Start OWIN, Katana and VS2013
- Use Owin to self-host Web API
- How to write owin middleware in 5 different steps
mscorlib
에러를 만나게 된다. ↩