tag: 개발 이야기

NameValueCollection을 JSON으로 Serialize 하기

2014년 12월 3일

ConfigurationManager.appSettingsSerialize해서 다른 곳에 전송하는 것은 어떨까 하는 아이디어를 듣고 코드를 작성해 Json.NET을 사용해서 SerializeObject를 했다. appSettings는 NameValueCollection 클래스인데 Dictionary와 같이 serialize 될 것이라 예상했지만 결과는 키값만 배열로 반환했다.

var col = new System.Collections.Specialized.NameValueCollection(){
    {"a", "Hello"}, {"a", "World"}
};

Console.WriteLine(JsonConvert.serializeObject(col));
// return "[\"a\"]"

NameValueCollection은 하나의 키에 여러개의 값을 가질 수 있는 컬렉션이기 때문에 Dictionary와는 다른 형태로 serialize되도록 NameObjectCollectionBase에서 구현되어 있는 것으로 보인다.

Console.WriteLine(col["a"]);
// return Hello,World

동일한 키라도 각각의 값이 독립적으로 보장되야 한다면 조금 복잡해지겠지만 내 경우에는 위와 같이 ,로만 구분 되어도 큰 문제가 없는 상황이라서 Dictionary로 변환한 후에 Serialize하는 방법으로 문제를 해결했다.

var dict = col.AllKeys.ToDictionary(k => k, v => col[v]);

Console.WriteLine(dict["a"]);
// return Hello,World

Console.WriteLine(JsonConvert.serializeObject(dict));
// return "{\"a\":\"Hello,World\"}"

동일한 키에 여러개의 값을 가지는 상황이라면 JSON에선 키 아래 배열로 변환되어야 의미론에 더 맞는 것 같다. 위와 같은 방법 말고도 더 아름답고 쉽고 시멘틱한 방법이 있을 것 같은데…


GetValues(key)를 이용하면 string[]으로 반환해준다고 한다. how to convert NameValueCollection to JSONstring의 코드를 참고하면 되겠다.

C# 클래스 소멸자 ~ (물결 문자)

2014년 11월 24일

C#을 쓸 일이 종종 있는데 아직 초보 수준이라서 모르는 문법이 많다. 코드를 읽다가 메서드 선언 앞에 나온 물결 문자를 보게 되었는데 관련된 내용을 찾아봤다. 다음 내용은 함수명 앞에 오는 물결 표시는 무슨 의미인가요?에 나온 답변이다.

C#에서 메소드 선언 앞에 나오는 ~(물결 문자, 틸드)는 소멸자다.

  • 소멸자는 자동으로 동작하며 명시적으로 실행할 수 없다.
  • 소멸자는 오버로드가 불가능하다. 그러므로 클래스는 최대 하나의 소멸자를 가질 수 있다.
  • 소멸자는 상속되지 않는다. 그러므로 클래스는 하나 이외의 소멸자를 가지지 않게 되고 그 소멸자는 해당 클래스에서 선언이 되어야만 한다.
  • 소멸자는 구조체에서는 사용할 수 없다. 클래스에서만 사용 가능하며 어떤 코드에서도 더 이상 인스턴스를 사용하지 못하게 될 때 소멸될 자격이 주어진다.
  • 인스턴스의 소멸자는 인스턴스가 소멸될 자격만 된다면 언제든 실행이 된다.
  • 인스턴스가 소멸될 때 소멸자는 상속 체인을 따라 자식-부모 순으로 순차적으로 호출된다.

Finalize

C#에서 Finalize 메소드는 표준 C++ 소멸자처럼 동작한다. C#에서는 Finalize라고 메소드에 이름 붙이는 것 대신 C++의 소멸자 문법처럼 물결 표시를 메소드명 앞에 붙인다.

Dispose

객체를 정리하기 위해 명시적으로 호출이 가능한 메소드 Close() 또는 Dispose()를 제공하는 것이 바람직하다. 소멸자는 GC에 의해 호출되기 때문이다.

IDisposable 인터페이스는 클래스가 가지고 있는 자원이 정리되어야 한다는 것을 알려주며 실제로 정리될 수 있는 방법을 제공해준다. 만약 소멸자를 직접 구현해야 한다면 그 클래스의 정리(Dispose) 메소드는 GC.SuppressFinalize() 메소드를 꼭 실행해 인스턴스를 강제적으로 제거해야 한다.

What to use?

명시적으로 소멸자를 호출하는 것은 허용되지 않는다. 소멸자는 가비지 컬렉터에 의해 호출된다. 만약 file을 다루는 것과 같이 처리 비용이 비싸 관리되지 않는 자원들을 다루게 되면 가능한한 빠르게 닫고 정리하기를 원할 것이다. 이런 경우에는 IDisposable 인터페이스를 구현해야 한다.


정말 상속되지 않는가

MSDN의 Destructors (C# Programming Guide)의 예제를 보면 상속은 아니지만 상속 순서에 따른 연쇄 호출(chaining)이 발생한다. 호출의 순서는 가장 하위 클래스의 소멸자부터 상위 클래스로 연쇄적으로 호출한다.

class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's destructor is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
    }
}

class TestDestructors
{
    static void Main()
    {
        Third t = new Third();
    }

}
/* Output (to VS Output Window):
    Third's destructor is called.
    Second's destructor is called.
    First's destructor is called.
*/

Scrapbook – 스크랩을 위한 워드프레스 플러그인

2014년 10월 4일

트위터, 페이스북, pocket 전부 관리되지 않는 스크랩 자료로만 가득 차는 기분이 들어서 워드프레스에서 수집할 수 있도록 작은 플러그인을 만들었다.

Screen Shot 2014 10 05 at 1 58 29 am

플러그인은 scrap이라는 포스트 타입을 생성해주며 일반 포스트와 동일하게 category와 tag를 지원한다. 추가적으로 워드프레스에서 post에서 사용할 수 있는 함수를 비슷하게 쓸 수 있도록 이름만 바꿔서 같이 넣었다. 기본적인 widget도 포함하고 있다.

플러그인은 gist 에서 받을 수 있으며 소스코드도 확인 할 수 있다.

2014년 10월 7일 업데이트: 플러그인을 GitHub 리포지터리로 옮기고 bookmarklet을 활용할 수 있도록 Scrap this를 추가했다. 다운로드는 여기에서, 코드는 github에서 확인할 수 있다.


이 블로그와 같이 scrap archive를 만들기 위해서는 해당 테마에 새로운 포스트 타입과 taxonomy를 위한 페이지를 생성해야 하며 생성할 때 the_tag()와 같은 함수들을 the_scrap_tag()와 같이 변경해줘야 한다.

  • archive.php -> archive-scrap.php, taxonomy-scrap_category.php, taxonomy-scrap_tag.php
  • single.php-> single-scrap.php
  • sidebar.php -> sidebar-scrap.php

물론 생성하지 않아도 출력은 되지만 약간 다르게 나오는 부분들이 있다. (원래는 자동으로 뿅 되야 하는데 대충 만들어서… 추후 버전을 기약하며.)

이 플러그인을 위한 특정 Permalink를 사용하려면 Custom Post Type Permalinks 플러그인을 활용할 수 있다.

PHP 클래스 자동으로 불러오기 (Autoloading)

2014년 9월 15일

객체 지향 프로그래밍에 익숙한 개발자라면 하나의 파일에 하나의 클래스를 작성하는 방식에 익숙할 것이다. 다만 php는 다른 언어와 같이 라이브러리를 일괄적으로 불러오는 방법이 없어 위와 같은 접근 방법으로는 require 또는 include를 이용해 수많은 단일 파일을 불러들여야만 했었다.

PHP5에서는 클래스 또는 인터페이스 등을 호출했을 때 해당 파일을 자동으로 불러올 수 있도록 여러 함수를 제공한다. 먼저 __autoload 함수를 이용한 예제다.

<?php
function __autoload($className){
  include $className . '.php';
}

$foo = new Foo();
$bar = new Bar();
?>

위와 같이 함수를 선언하면 new Foo()와 같이 클래스를 사용하는 순간 해당 클래스명으로 __autoload 함수가 실행, Foo.php 파일을 include한다.

다만 __autoload 함수는 spl_autoload_register 함수를 통해 대체될 수 있기 때문에 권장되지 않는다. spl_autoload_register는 다음과 같이 사용한다.

<?php
function my_autoloader($className){
  include 'classes/' . $className . '.class.php';
}

spl_autoload_register('my_autoloader');
?>

PSR-0 Autoloading Standard

위 함수를 통해 모듈화가 가능하도록 PHP Framework Interop Group(PHP-FIG)에서 PSR-0 Autoloading Standard가 제안되었다. 해당 제안은 다음의 규약을 포함하고 있다.

  • 네임스페이스와 클래스명으로의 자격을 갖추기 위해서는 다음의 구조를 따라야 한다. \<Vendor Name>\(<Namespace>\)*<Class Name>
  • 각 네임스페이스는 최상위 네임스페이스를 가져야 한다. (“Vendor Name”)
  • 각 네임스페이스는 필요에 따라 서브 네임스페이스를 가질 수 있다.
  • 각 네임스페이스 구분자는 파일 시스템에서 해당 파일을 불러오기 위한 디렉토리 구분자로 사용된다.
  • 클래스명에 들어있는 _ 글자도 디렉토리 구분자로 사용된다. 네임스페이스에서의 _는 특별한 의미가 없다.(PEAR 구현을 포함)
  • 완전한 네임스페이스와 클래스는 파일 시스템에서 불러올 때 .php를 접미어로 붙여 불러온다.
  • 알파벳으로 구성된 벤더명, 네임스페이스, 클래스명은 대소문자를 구분한다.

위 규약에 따른 예제는 PSR-0 문서에서 제공되고 있으며 SplClassLoader 구현도 해당 문서에서 확인할 수 있다.

다음은 문서에서 제공되는 autoload 함수 예제다.

<?php
function autoload($className) {
  $className = ltrim($className, '\\');
  $fileName = '';
  $namespace = '';
  if($lastNsPos = strpos($className, '\\')) {
    $namespace = substr($className, 0, $lastNsPos);
    $className = substr($className, $lastNsPos + 1);
    $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
  }
  $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
  require $fileName;
  }
}
?>

Composer 활용하기

Composer는 PSR-4 Autoloader 제안과 함께 위에서 살펴본 PSR-0를 준수하고 있어서 간단한 설정으로 PSR-0 방식을 사용할 수 있다. composer.json에 autoload 경로를 등록하면 composer의 ClassLoader와 맵핑되어 자동으로 불러온다.

{
  ...
  "autoload": {
    "psr-0": {"": "<path>/"}
  },
  ...
}

위 내용을 추가한 후 composer update 등을 통해 갱신하면 vendor/composer/autoload_namespaces.php 파일 안에 composer.json에서 작성한 경로가 추가된 것을 확인할 수 있다.

더 읽을 거리

맥에서 여러 파일 이름 일괄 변경하기

2014년 8월 20일

터미널에서 rename을 이용하면 정규표현식으로 한번에 파일 이름을 변경할 수 있다. mac에 기본적으로 없는 것 같으니 homebrew를 이용해서 rename을 설치한다.

brew install rename

rename 정규식 파일조건으로 변경할 수 있다.

rename s/en\-US/en\-AU/ *.html

변경 전에 어떤 식으로 변경이 될지 -n 플래그를 사용해 확인할 수 있다.

rename -n s/en\-US/en\-AU/ *.html

Mono 환경에서 OWIN 시작하기

2014년 7월 31일

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의 전체적인 흐름은 다음과 같다.

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)

create solution

먼저 최신 패키지 설치를 위해 target framework를 Mono / .NET 4.5로 변경해야 한다. 해당 솔루션의 옵션에서 Build > General에서 변경할 수 있다.

options

project options

솔루션에 패키지를 추가한다. 솔루션에 오른쪽 클릭해서 Add > Add Packages... 를 클릭한다.

add packages menu

Show pre-release packages를 체크하고 Owin을 검색한다. 검색 결과에서 OWIN, Microsoft.Owin, Microsoft.Owin.Hosting, Microsoft.Owin.Host.HttpListener를 체크하고 Add Packages 버튼을 누른다.

add packages

설치가 완료되었으면 OWIN에서 엔트리 포인트가 될 Startup.cs 파일을 추가한다.

create file

이 파일의 내용은 다음과 같다. 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를 사용한다.

이제 프로젝트를 빌드하고 실행하면 다음과 같은 콘솔 화면이 나타난다.

console

그리고 해당 주소를 브라우저에서 열면 페이지를 확인할 수 있다.

helloworld

미들웨어 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 클래스를 상속받는 MyFirstMiddlewareInvoke(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()로 각각의 미들웨어를 연결했다. 앞서 예제와 동일하게 구동해보면 다음 결과를 볼 수 있다.

Owin with MiddlewareMiddleware Result

특별한 예제가 딱히 떠오르지 않아서 그냥 html 코드를 넣었는데 GitHub이나 nuget 리포지터리에 OAuth Autentication 같은 멋진 미들웨어가 많이 있어 위와 같이 간편하게 추가만 하면 사용이 가능하다.

여기까지 OWIN에 대해 살펴봤다. 아직 C#는 초보라서 깊이있게 글을 쓰지 못하는게 아쉽다. 하지만 앞으로의 발전이 더 기대되고 꾸준히 Follow-up 할 닷넷 프로젝트가 될 것 같다.

예제 코드

예제는 모두 Xamarin Studio로 작성했고 GitHub 리포지터리에서 내려받을 수 있다.

  • HelloWorldOwin : 위에서 작성한 예제 코드
  • HelloWorldOwinHost : owinhost.exe로 구동하는 예제 코드로 web.config등 Configuration 하는 방식이 조금 다르다.

더 읽을 거리

  • Python에서의 uWSGI와 유사한 접근 방식이다. 
  • OWIN에서 ASP.NET MVC를 구동할 수는 없지만 MVC에서는 Helios라는 프로젝트를 통해 OWIN을 사용할 수 있다. IIS의 파이프라인 중간에서 OWIN을 구동하게 돕는다. 
  • ASP.NET MVC에서 OWIN은 구동 가능하지만(Helios) 아직까지는 OWIN에서 ASP.NET MVC를 쓸 수는 없다. WebAPI는 OWIN 기반이라 현재도 활용 가능. MVC vNext에서는 OWIN도 지원될 예정이라고. 
  • Mono에 내장된 웹서버인 xsp는 아직 OWIN Startup을 인식하지 못해 Web Project로 시작하면 mscorlib 에러를 만나게 된다. 
  • 사실 Flask처럼 Python uWSGI를 기반으로 한 라이브러리/모듈을 사용해봤다면 익숙한 양식이다.