'남정현'에 해당되는 글 503건

  1. 2014.09.26 구관이 명관 – 어느 위치에서 실행하든 경로를 유지하는 배치 파일 만들기
  2. 2014.09.15 ubuntu 14.04에서 asp.net vnext 설치하고 사용하기, mono 3.8 업데이트
  3. 2014.08.24 Project.json 파일
  4. 2014.08.17 .NET에서의 String과 Null Character에 대한 이야기
  5. 2014.08.11 ubuntu 14.04에서 asp.net vnext 설치하고 사용하기
  6. 2014.08.03 OWIN과 함께 춤을 – Hello, World
  7. 2014.07.15 OWIN과 함께 춤을 – 시작하기
  8. 2014.07.07 Azure VHD 디스크 생성 시 캐시 지정에 관하여
  9. 2014.06.07 Visual Studio 테스트, 어렵지 않아요
  10. 2014.04.11 C#에서 HTML 문서 분석 때문에 고민하지 마세요 – HtmlAgilityPack
  11. 2014.04.05 블로그 이사 공지
  12. 2014.04.05 System.Net.Http.HttpClient에 대한 경험담
  13. 2014.02.28 Microsoft MVP Community Camp 2014
  14. 2013.11.16 Visual Studio 2013에서 ASP.NET 프로젝트가 만들어지지 않는 경우
  15. 2013.10.25 NUnit Runner를 대신하는 간편한 Self Runner 구현하기
  16. 2013.10.21 Windows Forms용 Async/Await 프로그래밍 보조 라이브러리
  17. 2013.10.20 Windows Azure Linux Virtual Machine과 docker를 이용한 Linux 기반의 C# 개발 환경 구축
  18. 2013.10.18 Practical Code Writing Tips in C#
  19. 2013.10.13 ZIP 파일을 이용한 Windows Azure Storage의 저장 공간 효율 극대화하기
  20. 2013.10.12 ASP.NET을 이용한 이식성 높은 클라우드 서비스 개발하기
  21. 2013.10.05 SendGrid E-MAIL 서비스로 E-MAIL 보내기
  22. 2013.09.14 Microsoft TechDays 2013 Korea
  23. 2013.09.13 2013년 9월 Windows Azure 커뮤니티 캠프
  24. 2013.08.06 메서드 하나로 .NET 비동기 패턴 연습하기
  25. 2013.07.16 NuGet 패키지 관리자를 이용한 TDD 초기 환경 구축하기
  26. 2013.07.11 Windows Azure 커뮤니티 온라인 캠프 (2013년 7월)
  27. 2013.07.04 Internet Explorer 11의 Breaking Changes
  28. 2013.04.20 IronPython으로 Windows NT 서비스 만들어서 띄우기
  29. 2013.04.16 Windows Azure Lounge - Global Windows Azure Bootcamp in Korea
  30. 2013.04.01 Windows Azure Virtual Machine을 위한 FileZilla Server 방화벽 설정법
Useful Solutions2014. 9. 26. 09:39

오랫만에 구관이 명관 시리즈를 업데이트해봅니다. 이번 아티클은 작지만 확실히 유용한 팁 하나를 소개해볼까 합니다. 배치 파일을 작성하고 배포할 때 가장 큰 문제가 되는 것이, 배치 파일이 의도하지 않은 디렉터리 경로 상에서 실행된다는 점일 것입니다.

 

 

예를 들면 배치 파일을 명령어나 다른 프로그램을 이용하여 실행하면 배치 파일이 있는 경로가 아닌 호출한 프로그램의 현재 디렉터리 경로 위에서 실행되어서 문제가 될 때가 있습니다. 물론 배치 파일의 이런 특성을 활용해야 하는 경우도 있겠습니다만, 보통은 배치 파일을 실행할 때 정확히 해당 경로 상에서 어떤 작업을 하도록 의도한 것이 많으므로 이런 특성은 불편할 수 있습니다.

이 문제를 수정하려면 배치 파일의 경로를 항상 정확하게 알 수 있어야 하는데, 이럴 때 %~dp0 환경 변수를 사용하면 쉽게 문제를 해결할 수 있습니다. 하지만 무작정 cd 명령을 사용하면 배치파일이 끝나고 난 다음에 디렉터리 위치가 원래대로 돌아오지 않는 문제가 남습니다.

이런 문제를 해결하기 위하여, 새로 작성하는 배치 파일의 시작과 끝을 아래의 코드처럼 작성하도록 하면 편리할 것입니다.
@echo off
pushd "%~dp0"

rem 여기에 배치 파일 본문을 작성합니다.

:exit
popd
@echo on


 

내용은 단순합니다. 실행하는 명령줄이 보이지 않게 @echo off로 감추고, pushd 명령을 사용하여 스택에 현재 디렉터리 경로를 push하고 새 위치로 이동한 다음 원하는 일을 하는 것입니다. 그리고 나중에 종료할 때에는 popd 명령을 사용하여 직전에 저장한 디렉터리 경로를 꺼내와 그 위치로 다시 이동하고 @echo on으로 (심미성을 위하여 @echo on이라고 썼습니다.) echo 상태를 복원합니다. 여기서 :exit 라벨은 프로그램 내에서 탈출 조건을 만났을 때 편리하게 활용할 수 있도록 하기 위한 것입니다.

위의 예제를 활용하면 다음과 같이 응용할 수 있습니다.

@echo off
pushd "%~dp0"


if not exist %windir%\system32\runas.exe. (
    echo RUNAS.EXE 프로그램을 찾을 수 없습니다.
    pause
    goto exit
)

for /r "%~dp0" %%v in (test.cmd) do (
    if exist "%%v" (
        echo %%v 진행 중...
        call "%%v"
        rem 다른 배치 파일을 호출하고나면 echo on 상태가 되므로 다시 @echo off로 꺼줍니다.
        @echo off
    )
)

:exit
popd
@echo on


 

위와 같이 활용한다면 배치 파일을 어디서 실행하든 원하는 경로로 들어갔다가 다시 쉽게 원래 위치로 되돌아올 수 있으므로 일반 프로그램처럼 배치 파일을 작성하는 것이 좀 더 쉬워집니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2014. 9. 15. 09:38

NOTE: 지난 번에 올렸던 글 이후로 mono 3.8이 빠르게 추석 연휴를 사이로 업데이트가 되었는데, mono의 최신 버전을 설치하는 과정이 무척 복잡하고 까다로웠던 점이었던게 상당히 아쉬웠습니다. 그런데 이 부분이 잘 해결된 듯 하여 업데이트된 내용을 더해 글을 더 보강하여 다시 게시합니다.

 

 

다시 말씀드리면, 이 블로그 포스트는 MS Azure Virtual Machine과 Ubuntu Server 14.04 버전을 최초 설치했을 때의 상태를 기준으로 작성된 것이며, 이 블로그 글을 작성하는 2014년 9월 현재 ASP.NET vNext가 정식 출시 전임을 말씀드립니다.

주의: 실제 배포 환경에서 이 블로그 포스트의 내용을 활용하시는 것은 매우 위험합니다.

사전 준비 작업

중요: 이 아티클에서 소개하는 내용은 Ubuntu 14.04에서 작동하며, Ubuntu 12 버전에서는 패키지 버전 불일치 등으로 인하여 mono 3.8이 정상적으로 설치가 되지 않아 실행할 수 없습니다.

최근들어 변경된 사항으로, Mono의 최신 버전 릴리즈는 Xamarin이 독자적으로 운영하는 패키지 리포지터리를 통하여 좀 더 빠르게 받아보실 수 있습니다. 하지만 2014년 9월 현재 모든 배포판에 대해 완전하게 설치를 보장하는 것이 아닌 것으로 보입니다.

Mono 3.8 및 그 이후 버전을 설치하기 위하여 우선 해야 할 일은 Xamarin 리포지터리를 시스템에 추가하는 일입니다.

단순한 설명을 위하여, 현재 로그인한 사용자의 홈 디렉터리로 일단 디렉터리 변경을 하겠습니다.


cd ~

Xamarin 리포지터리의 GPG Key를 내려받도록 합니다.


wget http://download.mono-project.com/repo/xamarin.gpg

그리고 내려받은 GPG 키를 저장합니다.


sudo apt-key add xamarin.gpg

/etc/apt/sources.list 파일을 편리한 텍스트 편집기로 열고, 가장 마지막에 다음의 줄을 추가한 후 저장하고 닫습니다.


deb http://origin-download.mono-project.com/repo/debian/ wheezy main

패키지 캐시를 업데이트하기 위하여 아래 명령어를 실행합니다.


sudo apt-get update

이제 Mono 3.8을 설치할 준비가 다 되었습니다. 아래 명령어만 실행하면 됩니다.


sudo apt-get -y install mono-complete

설치가 다 끝나면 mono의 버전을 확인해봅니다.


mono –version

K Runtime과 ASP.NET vNext 설치하기

Mono 3.8이 9월에 릴리즈하고 나서 K Runtime과 ASP.NET vNext를 테스트하는 방법에도 조금 변화가 생겨서 그 내용을 같이 말씀드립니다.

HTTPS/SSL 인증서들을 추가하고 Mono에서 사용할 수 있도록 동기화하는 작업을 반드시 실행합니다.


sudo certmgr -ssl -m https://go.microsoft.com
 sudo certmgr -ssl -m https://nugetgallery.blob.core.windows.net
 sudo certmgr -ssl -m https://nuget.org
 sudo certmgr -ssl -m https://myget.org
 mozroots –import –sync

ASP.NET vNext를 실행하기 위해서는 unzip 패키지가 필요합니다.


sudo apt-get install unzip

그리고 ASP.NET vNext 설치 스크립트를 내려 받습니다.


curl https://raw.githubusercontent.com/aspnet/Home/master/kvminstall.sh | sh && source ~/.kre/kvm/kvm.sh

source 명령을 사용하여 K runtime을 쉽게 실행할 수 있도록 설정합니다.


source ~/.kre/kvm/kvm.sh

2014년 9월 23일 현재 최신 버전은 1.0.0-alpha4-10353입니다. 이 버전을 내려 받기 위하여 KRE_FEED 환경 변수에 Feed URL을 설정하고 해당 버전을 설치합니다.


export KRE_FEED=https://www.myget.org/F/aspnetvnext/api/v2
 kvm install 1.0.0-alpha4-10353

예제 소스 받아서 테스트해보기

이전 아티클에서 이야기했던 내용을 좀 더 보강하면, 현재 ASP.NET vNext의 k web 명령은 아직 Windows 환경에서만 실행이 가능한 상태입니다. Mono를 통하여 웹 서버를 시작하고 ASP.NET vNext를 호스팅할 수 있게 하려면 project.json에 별도의 명령어를 추가해주어야 합니다. 물론 이는 추후에 정식 버전이 릴리즈가 될 때 당연히 해결될 문제이므로 걱정하지 않으셔도 됩니다.

git 패키지를 설치하도록 합니다.


sudo apt-get install git

ASP.NET vNext Home 리포지터리에서 예제 소스를 체크아웃합니다.


git clone https://github.com/aspnet/Home.git

늘 그렇듯이, 콘솔 프로젝트를 시작점으로 잡아봅니다. :-)


cd ~/Home/samples/ConsoleApp/
 kpm restore
 k run

그리고 현재 알파 버전의 ASP.NET vNext 기준으로 리눅스에서 웹 서비스를 실행해보기 위해서는 NOWIN 팩토리 패키지를 구성해야 할 필요가 있습니다.


cd ~/Home/samples/
 mkdir Nowin.vNext
 cd Nowin.vNext
 wget https://github.com/davidfowl/HelloWorldVNext/raw/master/src/Nowin.vNext/NowinServerFactory.cs
 wget https://github.com/davidfowl/HelloWorldVNext/raw/master/src/Nowin.vNext/project.json

위와 같이 준비되면, HelloWeb 프로젝트의 project.json에 들어있는 dependencies 섹션과 commands 섹션을 편집기로 조금 수정하여 앞서 만든 NOWIN 팩토리로 서버를 띄울 수 있게 해야 합니다. project.json에 대한 자세한 내용은 Project.json 파일 을 참고하십시오.


cd ~/Home/samples/HelloWeb/

project.json 파일을 편집기로 열고 각각 다음의 사항을 반영하도록 합니다.
•dependencies에 다음을 추가합니다.
“Nowin.vNext”: “”,
•commands에 다음을 추가합니다.
“nowin”: “Microsoft.AspNet.Hosting –server Nowin.vNext”,

project.json 파일을 저장하고, 다음의 명령어를 실행합니다.


kpm restore
 k nowin

앞에서 다운로드 한 Nowin Factory 프로젝트의 코드를 보면 TCP/5000 포트를 웹 리스너 포트로 사용하고 있습니다. 밖에서 호스트 이름과 함께 5000번 포트로 접속하면 웹 페이지가 나타나는 것을 볼 수 있습니다. 그리고 서버를 종료하려면 콘솔에서 아무 키나 누르면 종료가 됩니다.

만약에 원격에서 좀 더 지속적으로 서버의 성능을 측정해보고 싶으시다면 screen 유틸리티를 사용하여 세션을 분리하신 상태에서 위의 명령어를 입력하고, 서버가 떠 있을 때 Ctrl 키를 누른 상태에서 빠르게 a, a, d 키를 누르면 세션이 분리되어 계속 살아있는 서버가 만들어집니다. 이 상태에서 Apache Bench (AB)등의 유틸리티를 사용하여 부하 테스트 등을 해보시는 것도 의미가 있을 것입니다.

참고로, NAT 환경이나 퍼블릭 클라우드 환경에서는 대표 IP 주소에 대한 외부 방화벽 설정을 열어주셔야 밖에서도 접속이 가능합니다.

마무리

ASP.NET vNext는 계속 업데이트가 이루어지고 있는 상태이며, Mono 런타임의 개선에 따라 리눅스와 맥 OS X에서 ASP.NET vNext를 사용하는 것이 좀 더 쉬워지고 간편해질 전망입니다. 더 많은 기대를 해도 좋지 않을까 생각합니다. :-)

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2014. 8. 24. 09:37

번역: 남정현


날짜: 2014-08-24


이 문서에 대하여

이 문서는 2014년 8월 19일에 작성된 https://github.com/aspnet/Home/wiki/Project.json-file 의 내용을 한국어로 번역한 것으로, 원본 문서 작성자는 ASP.NET vNext 팀입니다. 오역, 어색한 부분, 매끄럽지 않은 부분이 있을 경우 알려주시면 적극적으로 반영하도록 하겠습니다.

스키마

http://json.schemastore.org/project

의존성

 

 

의존성 섹션은 여러분의 애플리케이션이 사용하는 모든 의존성들을 열거합니다. 이름과 버전으로 정의할 수 있으며, 런타임 로더가 어떤 것을 반드시 로드해야 할지 결정합니다. NuGet 패키지, 소스 코드 등이 될 수 있습니다.

[json]
 {

“dependencies”: {

“Microsoft.AspNet.ConfigurationModel”: “0.1-alpha-*”,

“SomeProject”: “”

}

}
 [/json]

여기서 사용할 수 있는 또 다른 기능으로 아래와 같이 더 구체적으로 의존성 설정을 지정하는 것이 가능합니다.

[json]
 {

“dependencies”: {

“Microsoft.AspNet.ConfigurationModel”: { “version”: “0.1-alpha-*”, “options”: “private” },

“FakeToolingPackage” : {“version”: “0.1”, “options”: “dev”},

“SomeProject”: “”

}

}
 [/json]

참조에는 여러 가지 다른 종류들이 있을 수 있습니다.

•Private – 의존성을 인텔리센스나 혹은 컴필레이션에 노출하지 않도록 할 수 있습니다.

 

•Bago (Build and go away) – 이 참조를 컴파일한 후에 대상 프로젝트 안으로 같이 컴파일됩니다.

 

어떻게 의존성 버전이 선택되는지에 대한 더 상세한 정보는 https://github.com/aspnet/Home/wiki/Dependency-Resolution 에서 자세히 확인할 수 있습니다.

Configurations 섹션

Configurations는 컴필레이션 설정에 대한 이름이 붙여진 그룹 항목들입니다. 실행 시점에는 기본으로 주어지는 설정 두 가지가 있는데, 바로 Debug와 Release입니다. 이들 설정을 project.json에 필요에 따라 다시 설정하거나 새로운 설정을 더 추가하는 것도 가능합니다.

[json]
 {

“configurations”: {

“Debug”: {

“compilationOptions”: {

“define”: ["DEBUG", "TRACE"],

“debugSymbols”: “full”

}

},

“Release”: {

“compilationOptions”: {

“define”: ["RELEASE", "TRACE"],

“optimize”: true,

“debugSymbols”: “pdbOnly”

}

}

}

}
 [/json]

Frameworks 섹션

어느 프레임워크를 대상으로 개발된 프로그램인지 정의하고, 해당 구성에서 참조하는 의존성들을 dependencies에서 정의할 수 있습니다. 아래 코드 조각에서는 데스크톱 (net45) 또는 Core CLR (k10) 중 하나를 사용할 것입니다. Core CLR은 BCL을 만들기 위해서 더 많은 참조들에 대한 의존성을 가집니다.

[json]
 {

“frameworks”: {

“net45″: {},

“k10″: {

“dependencies”: {

“System.Collections”: “4.0.0.0”,

“System.Collections.Concurrent”: “4.0.0.0”,

“System.ComponentModel”: “4.0.0.0”,

“System.Linq”: “4.0.0.0”,

“System.Reflection”: “4.0.10.0”,

“System.Runtime”: “4.0.20.0”,

“System.Runtime.InteropServices”: “4.0.10.0”,

“System.Threading”: “4.0.0.0”,

“System.Threading.Tasks”: “4.0.0.0”

}

}

}

}
 [/json]

Sources 섹션

sources 섹션은 컴파일해야 할 소스 코드들을 지정합니다.

[json]
 {

“code”: “*.cs”,

“exclude”: “buggy/**/*.cs”,

“resources”: “embed/**/*.*”

}
 [/json]

공유 파일 섹션

여러 프로젝트에서 의존하는 코드를 공유할 수 있습니다. 공유 어셈블리 정보 같은 내용을 담고 있는 코드를 공유하기 위해서, 공통 프로젝트를 만들고, 이 프로젝트에서 공유 파일 섹션을 포함하도록 공유할 코드를 참조하게 하면 됩니다. 그 후에는 새로 만든 공통 프로젝트를 참조하는 프로젝트라면 항상 프로젝트에 해당 파일들이 같이 포함되어 컴파일이 이루어지게 됩니다.

[json]
 {

“shared”: “*.cs”

}
 [/json]

컴파일 옵션

컴파일 옵션에서는 Roslyn으로 전달할 설정을 지정할 수 있습니다. 여기서 언어의 버전을 선택할 수 있습니다.

[json]
 {

“compilationOptions”: {

“define”: ["SOMETHING"],

“allowUnsafe”: true,

“warningsAsErrors” : true,

“languageVersion”: “experimental”

}

}
 [/json]

명령

K.cmd를 실행할 때에는 실행하려는 명령의 이름을 지정할 수 있습니다. 아래 코드 조각은 K web이라는 명령어를 실행할 때 셀프 호스트를 시작하도록 하고 있고, K test라는 명령어를 실행할 때에는 모든 단위 테스트를 실행하도록 하고 있습니다.

[json]
 {

“commands”: {

“web”: “Microsoft.AspNet.Hosting server.name=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001″,

“test”: “Xunit.KRunner”

}

}
 [/json]

스크립트

KPM에서 어떤 일을 수행하기 전과 후에 발생하는 이벤트에 간섭하여 추가 작업을 지시할 수 있습니다.

[json]
 {

“scripts”: {

“prebuild”: “echo before building”,

“postbuild”: “echo after building”,

“prepack”: “echo before packing”,

“postpack”: “echo after packing”,

“prerestore”: “echo before restoring packages”,

“postrestore”: “echo after restoring packages”

}

}
 [/json]

그리고 사용 가능한 변수들은 다음과 같습니다.
</p><p>%project:Directory% - 프로젝트 디렉터리 경로
</p><p>%project:Name% - 프로젝트 이름
</p><p>%project:Version% - 프로젝트 버전<br />

메타데이터

프로젝트에 대한 메타데이터를 기록할 수 있습니다.

[json]
 {

“version”: “0.1-alpha”,

“authors”: ["John Doe"],

“description”: “A wonderful library that does nice stuff”

}
 [/json]

Entity Framework 프로젝트에서 가져온 project.json 파일의 한 예시

[json]
 {

“version”: “0.1-alpha-*”,

“compilationOptions”: {

“warningsAsErrors”: true

},

“dependencies”: {

“Microsoft.Bcl.Immutable”: “1.1.18-beta-*”,

“Microsoft.AspNet.ConfigurationModel”: “0.1-alpha-*”,

“Microsoft.AspNet.DependencyInjection”: “0.1-alpha-*”,

“Microsoft.AspNet.Logging”: “0.1-alpha-*”,

“System.Data.Common”: “0.1-alpha-*”

},

“code”: “**\\*.cs;..\\Shared\\*.cs”,

“frameworks”: {

“net45″: {

“dependencies”: {

“System.Runtime”: “”,

“System.Collections”: “”

}

},

“k10″: {

“dependencies”: {

“System.Collections”: “4.0.0.0”,

“System.Collections.Concurrent”: “4.0.0.0”,

“System.ComponentModel”: “4.0.0.0”,

“System.Console”: “4.0.0.0”,

“System.Diagnostics.Contracts”: “4.0.0.0”,

“System.Diagnostics.Debug”: “4.0.10.0”,

“System.Globalization”: “4.0.10.0”,

“System.Linq”: “4.0.0.0”,

“System.Linq.Expressions”: “4.0.0.0”,

“System.Linq.Queryable”: “4.0.0.0”,

“System.Reflection”: “4.0.10.0”,

“System.Reflection.Extensions”: “4.0.0.0”,

“System.Resources.ResourceManager”: “4.0.0.0”,

“System.Runtime”: “4.0.20.0”,

“System.Runtime.Extensions”: “4.0.10.0”,

“System.Threading”: “4.0.0.0”,

“System.Threading.Tasks”: “4.0.10.0”

}

}

}

}
 [/json]

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2014. 8. 17. 09:36

.NET은 문자열을 다루는 데 있어서 C, C++, 혹은 파스칼과 비슷한 듯 다른 면이 있습니다. 그리고 이번 아티클에서는 사소하지만 큰 오류를 내포하게 될 가능성이 있는 부분을 잠시 소개하려고 합니다.

http://msdn.microsoft.com/ko-kr/library/ms228362.aspx 에서는 .NET의 String에 대해 이렇게 소개하고 있습니다.

 

 


문자열은 값이 텍스트인 String 형식의 개체입니다. 내부적으로 텍스트는 Char 개체의 순차적 읽기 전용 컬렉션으로 저장됩니다. C# 문자열 끝에는 null 종결 문자가 없습니다. 따라서 C# 문자열은 포함된 null 문자(‘\0′)를 제한 없이 포함할 수 있습니다. 문자열의 Length 속성은 유니코드 문자의 수가 아니라 포함된 Char 개체의 수를 나타냅니다. 문자열에서 개별 유니코드 코드 포인트에 액세스하려면 StringInfo 개체를 사용합니다.

굵게 강조 표시한 부분의 내용에 오늘 아티클의 핵심 내용이 모두 들어있습니다. 하지만 꼼꼼하게 기억해두지 않으면 허술하게 다루어질 가능성도 있는 부분이라고 생각합니다.

위의 내용을 상기하면서, 아래의 코드들이 각각 어떻게 실행될지 예상해보면 흥미롭습니다.
string a = "'abc'".Replace('\'', default(char));
Console.WriteLine("a: {0} (Length: {1})", a, a.Length);
string b = "'abc'".Replace('\'', Char.MinValue);
Console.WriteLine("b: {0} (Length: {1})", b, b.Length);
string c = "'abc'".Replace('\'', (char)0);
Console.WriteLine("c: {0} (Length: {1})", c, c.Length);
string d = "'abc'".Replace('\'', '\0');
Console.WriteLine("d: {0} (Length: {1})", d, d.Length);


 

 

Replace로 한 글자만 제거하고 싶어서 위와 같은 코드를 작성하기 쉬운데, 위의 결과에서 원래 의도는 ‘abc’ 라는 다섯 글자를 abc라는 세 글자로 만드는 것이지만, 실제로는 여전히 다섯 글자가 됩니다. 그런데 여기서 한 가지 더 중요한 것은, Trim() 메서드가 앞 뒤로 붙는 null character를 제거해 주지는 않는다는 점입니다.
string a = "'abc'".Replace('\'', default(char)).Trim();
Console.WriteLine("a: {0} (Length: {1})", a, a.Length);
string b = "'abc'".Replace('\'', Char.MinValue).Trim();
Console.WriteLine("b: {0} (Length: {1})", b, b.Length);
string c = "'abc'".Replace('\'', (char)0).Trim();
Console.WriteLine("c: {0} (Length: {1})", c, c.Length);
string d = "'abc'".Replace('\'', '\0').Trim();
Console.WriteLine("d: {0} (Length: {1})", d, d.Length);


 

앞/뒤로 붙은 null character를 제거하려면 null character를 명시하는 작업이 필요합니다. 그리고 이것은 Replace 메서드에 대해서도 동일하게 적용됩니다.
</pre>
<pre>string a = "'abc'".Replace('\'', default(char)).Trim('\0');
Console.WriteLine("a: {0} (Length: {1})", a, a.Length);
string b = "'abc'".Replace('\'', Char.MinValue).Trim('\0');
Console.WriteLine("b: {0} (Length: {1})", b, b.Length);
string c = "'abc'".Replace('\'', (char)0).Trim('\0');
Console.WriteLine("c: {0} (Length: {1})", c, c.Length);
string d = "'abc'".Replace('\'', '\0').Trim('\0');
Console.WriteLine("d: {0} (Length: {1})", d, d.Length);


이런 맥락에서 보았을 때, 외부로부터 들어오는 입력 문자열에 대해 엄격하게 이야기하자면, null character에 대한 것을 String.Empty로 치환하는 작업도 필요할 수 있다고 볼 수 있겠습니다.

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Linux + .NET2014. 8. 11. 09:35

업데이트: mono 3.8이 9월 초에 새로 릴리즈되었으며 이 내용을 기초로 새로 업데이트한 아티클을 올렸습니다.

이 블로그 포스트의 내용은 아래 두 블로그 포스트의 내용을 기초로 작성한 것임을 말씀드립니다.
•http://graemechristie.github.io/graemechristie/blog/2014/05/26/asp-dot-net-vnext-on-osx-and-linux/
•http://www.rocko.me/install-mono-3-4-ubuntu/

또한 이 블로그 포스트는 MS Azure Virtual Machine과 Ubuntu Server 14.04 버전을 최초 설치했을 때의 상태를 기준으로 작성된 것이며, 이 블로그 글을 작성하는 2014년 8월 현재 ASP.NET vNext가 정식 출시 전임을 말씀드립니다.

주의: 실제 배포 환경에서 이 블로그 포스트의 내용을 활용하시는 것은 매우 위험합니다.

 

 

ASP.NET vNext는 기존의 System.Web 기반의 레거시 웹 개발 프레임워크에서 탈피하고자 하는 MS의 강력한 의지의 결과물인듯 합니다. 이전에는 상상하기 어려웠고, MS의 손이 아닌 오픈 소스 그룹 (Mono의 System.Web 구현)이나 써드 파티 회사 (Grasshoper 같은)에 의한 제한적인 수준의 작업 결과물일 뿐이었던 ASP.NET의 이식성이 이제서야 완벽함을 기할 수 있게 되었습니다.

이 블로그 포스트에서는 ASP.NET vNext를 우분투 서버 14.04에서 설치해본 과정을 기록하여 그것을 토대로 작성하였습니다. ASP.NET vNext의 발전 가능성을 살펴보시고, 여러 이야기를 나눌 수 있지 않을까 하여 기록해봅니다.

사전 준비 작업

ASP.NET vNext는 Windows 서버 환경에서는 손수 기존에 설치된 .NET Framework를 대체하는 K Runtime을 사용하여, 어느 버전의 K Runtime을 사용할 것인지 패키지 레벨에서 정의할 수 있는 것이 특징이었는데, 리눅스의 경우 기본 실행 엔진은 현재는 Mono를 기반으로 하고 있는 것이 특징입니다. 그럼에도 불구하고 K Runtime이 가지는 영역이 엄연히 있고, 아마 핵심 실행 엔진만 현재는 Mono를 기반으로 실행되는 것 같습니다.

그런 이유로 Mono의 최신 버전을 시스템에 설치해야 하는데, 안타깝게도 Ubuntu 14.04에 등록된 Mono 패키지의 최신 버전은 ASP.NET vNext를 실행하기 위해 필요한 버전과 격차가 상당히 크고, 또한 지원되지 않습니다. 그래서 제일 먼저 해야 할 일은 github에 올라와있는 Mono 소스 코드를 내려 받아 컴파일하고 새 버전으로 바꾸는 작업입니다.

우선은 기존에 Mono 런타임을 설치했던 이력이 있을 경우를 고려하여 Mono와 관련된 모든 패키지를 제거해야 하는데, 아래 명령어로 간단히 제거할 수 있습니다.


sudo apt-get -y purge mono-*

그 다음, Mono를 설치하기 위하여 필요한 이미징 라이브러리 관련 종속성을 해결해주어야 하는데, 필요한 패키지들중 상당수는 Ubuntu 14.04에서 직접 지원하지 않거나 오래된 버전으로 취급하여 apt-get으로 직접 설치가 어려운 패키지들입니다. 따라서, 이들 패키지들을 수동으로 내려 받아 설치하는 작업이 필요한데, 아래 명령어를 복사하여 하나씩 실행하시면 되겠습니다.


wget http://security.ubuntu.com/ubuntu/pool/main/j/jbigkit/libjbig0_2.0-2ubuntu1.13.10.1_amd64.deb
 wget http://security.ubuntu.com/ubuntu/pool/main/libj/libjpeg-turbo/libjpeg-turbo8_1.3.0-0ubuntu1.1_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/main/libj/libjpeg8-empty/libjpeg8_8c-2ubuntu8_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/universe/t/tiff3/libtiff4_3.9.7-2ubuntu1_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/universe/t/tiff3/libtiffxx0c2_3.9.7-2ubuntu1_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/main/libj/libjpeg8-empty/libjpeg-dev_8c-2ubuntu8_amd64.deb
 wget http://security.ubuntu.com/ubuntu/pool/main/j/jbigkit/libjbig-dev_2.0-2ubuntu1.13.10.1_amd64.deb
 wget http://security.ubuntu.com/ubuntu/pool/main/libj/libjpeg-turbo/libjpeg-turbo8-dev_1.3.0-0ubuntu1.1_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/main/libj/libjpeg8-empty/libjpeg8-dev_8c-2ubuntu8_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/main/libj/libjpeg8-empty/libjpeg-dev_8c-2ubuntu8_amd64.deb
 wget http://mirrors.kernel.org/ubuntu/pool/universe/t/tiff3/libtiff4-dev_3.9.7-2ubuntu1_amd64.deb

sudo dpkg -i libjbig0_2.0-2ubuntu1.13.10.1_amd64.deb
 sudo dpkg -i libjpeg-turbo8_1.3.0-0ubuntu1.1_amd64.deb
 sudo dpkg -i libjpeg8_8c-2ubuntu8_amd64.deb
 sudo dpkg -i libtiff4_3.9.7-2ubuntu1_amd64.deb
 sudo dpkg -i libtiffxx0c2_3.9.7-2ubuntu1_amd64.deb
 sudo dpkg -i libjbig-dev_2.0-2ubuntu1.13.10.1_amd64.deb
 sudo dpkg -i libjpeg-turbo8-dev_1.3.0-0ubuntu1.1_amd64.deb
 sudo dpkg -i libjpeg8-dev_8c-2ubuntu8_amd64.deb
 sudo dpkg -i libjpeg-dev_8c-2ubuntu8_amd64.deb
 sudo dpkg -i libtiff4-dev_3.9.7-2ubuntu1_amd64.deb

Mono 최신 버전 설치하기

이제 기본 준비 작업은 끝났고, 필요한 패키지들을 한꺼번에 설치할 차례입니다. 아래 명령어를 입력하도록 합니다.


sudo apt-get -y install libpng3 libpng3-dev libtool libexif12 libexif-dev libgif4 libgif-dev libpango1.0-dev libatk1.0-dev libgtk-3-0 libgtk-3-dev bison automake autoconf make gcc gtk-sharp2 build-essential xorg-dev libfreetype6 libfontconfig libfontconfig-dev gettext libglib2.0-dev git libjpeg-dev libjpeg8-dev libjpeg-turbo8-dev g++ unzip

쉬운 설명을 위하여, 사용자 프로필 디렉터리에서 설치를 진행한다고 가정하겠습니다.


cd ~

설치가 모두 되고 나면, mono git 리포지터리에서 libgdiplus 소스를 복사합니다.


git clone https://github.com/mono/libgdiplus.git

받은 소스 디렉터리로 이동합니다.


cd ~/libgdiplus

그리고 각종 설정 검사 및 헤더 구성을 진행합니다. 주의할 것은 공식 가이드에서는 –prefix=/usr/local로 소개하고 있으나 우분투의 경우 아래와 같이 /usr을 기준으로 잡아야 합니다.


./autogen.sh –prefix=/usr

구성이 끝나면 컴파일을 하도록 합니다.


make

컴파일 중 특별한 오류 메시지가 없었다면 시스템에 설치하도록 합니다.


sudo make install

이제 다시 홈 디렉터리로 이동합니다.


cd ~

mono 소스를 컴파일하는 과정 중에는 재귀적으로 mcs 컴파일러가 필요합니다. 이를 위하여 mono-gmcs 패키지를 구 버전이지만 우선 설치해야 합니다.


sudo apt-get -y install mono-gmcs

설치가 끝나면, 이제 mono 소스를 복사하도록 합니다.


git clone git://github.com/mono/mono.git
 cd mono

libgdiplus 때와 마찬가지로 prefix 설정에 유의하여 자동 구성을 진행합니다. 자동 구성 중에 다른 git 리포지터리에서 추가로 관련된 소스를 내려받기도 합니다.


./autogen.sh –prefix=/usr

모든 구성이 끝나면 컴파일하고 설치하도록 합니다.


make
 sudo make install

모든 설치가 다 끝났다면, 새 버전 (2014년 8월 현재 3.8)으로 업데이트가 잘 되었는지 확인해보도록 합니다.


mono –version
 mcs –version

위의 명령어에서 새 버전으로 표시가 된다면 ASP.NET vNext를 설치할 준비가 다 끝난 것입니다. 이제 다시 홈 디렉터리로 이동합니다.


cd ~

계속 하기 전에, 라이브러리 경로에 관련된 환경 변수를 하나 설정해주는 것이 좋습니다. 아래 명령어를 실행하여 LD_LIBRARY_PATH 환경 변수를 설정하도록 합니다.


export LD_LIBRARY_PATH=/usr/lib:/usr/local/lib:$LD_LIBRARY_PATH

K Runtime과 ASP.NET vNext 설치하기

이제 중요한 부분이 남았습니다. K Runtime과 ASP.NET vNext를 설치하는 것이 남았는데, 앞의 과정보다 시간도 짧게 걸리고 비교적 쉽습니다.

ASP.NET vNext의 전체 소스 코드를 복사하지 않고 필요한 셸 스크립트 파일인 kvminstall.sh 파일만 가져오도록 합니다. 아래 명령어를 홈 디렉터리에서 실행합니다.


curl https://raw.githubusercontent.com/aspnet/Home/master/kvminstall.sh | sh

경로 설정을 맞추기 위하여, 아래 명령어를 실행합니다.


source ~/.kre/kvm/kvm.sh

이제 KVM을 사용자 프로필 디렉터리 아래의 .kre 폴더에 설치하기 위해, 다음 명령어를 실행합니다.


kvm upgrade

기본적인 실행 환경이 준비되었고, K Package Manager (달리 표현하면 K Package Manager의 실행을 담당하는 Mono)가 통신해야 할 사이트들의 HTTPS 인증서를 추가한 다음, 시스템에 설치된 루트 인증서를 가져올 수 있도록 하기 위하여 아래 명령어들을 실행합니다. 확인 프롬프트가 나타나면 여러번 yes를 입력하여 모든 필요한 인증서 및 인증서 체인을 가져오도록 합니다.


sudo certmgr -ssl -m https://go.microsoft.com
 sudo certmgr -ssl -m https://nugetgallery.blob.core.windows.net
 sudo certmgr -ssl -m https://nuget.org
 sudo certmgr -ssl -m https://myget.org
 mozroots –import –sync

Hello, World! 찍어보기

모든 설치가 끝났습니다. 예제 소스 코드를 가져와서 실행하기 위하여, David Fowler님의 github 리포지터리에 올라와있는 ASP.NET vNext 샘플을 이용하도록 하겠습니다. 공식 웹 사이트에 있는 샘플은 HTTPAPI를 기반으로 하는 것이어서 Nowin Factory로 교체하여 실행할 수 있지만 쉬운 설명을 위해 David Fowler님의 예제를 가져와서 대신 설명함을 말씀드립니다.

홈 디렉터리로 이동합니다.


cd ~

그리고 아래 명령어를 실행하여 콘솔 프로젝트 샘플 소스를 복사합니다.


git clone https://github.com/davidfowl/HelloWorldVNext.git

해당 디렉터리로 이동하여 다음 순서대로 명령어를 입력하여 Hello World! 메시지가 나타나는지 확인합니다.


cd ~/HelloWorldVNext/src/helloworld
 kpm restore
 k run

여기서 kpm restore 명령은 해당 예제를 실행하기 위하여 필요하다고 project.json에서 명시한 NuGet 패키지들을 전부 시스템에 설치하는 과정을 포함하며, 최초 한 번만 실행하면 됩니다. 그리고 k run 명령은 project.json 또는 그 상위에 정의되어있는 run 명령어를 실행한다는 의미이며, 보통 run 명령어는 재정의하지 않는 한 Main 메서드를 찾아 실행하는 것과 의미가 같습니다.

받은 프로젝트 디렉터리 상의 파일을 보면 흥미로운 것이, 이전처럼 mcs (gmcs)를 호출하여 exe 파일을 만들지 않았는데도 소스 상태에서 바로 k run이라는 명령어를 넣으면 프로그램이 시작된다는 점입니다. 이런 방식의 닷넷 응용프로그램은 웹 환경에서 큰 강점을 발휘하게 될 것입니다.

ASP.NET vNext 샘플 웹 프로젝트 띄워보기

이제 핵심입니다. ASP.NET vNext 샘플 웹 프로젝트를 띄워볼 차례인데, 다음과 같이 명령어를 입력하도록 합니다. 물론, 진행의 편의를 위해 홈 디렉터리에서 실행하는 것이 좋겠습니다.


git clone https://github.com/davidfowl/HelloWorldVNext.git
 cd ~/HelloWorldVNext/src/helloworldweb
 kpm restore
 k web

예제에 같이 들어있는 Nowin Factory 프로젝트의 코드를 보면 TCP/5000 포트를 웹 리스너 포트로 사용하고 있습니다. 밖에서 호스트 이름과 함께 5000번 포트로 접속하면 웹 페이지가 나타나는 것을 볼 수 있습니다. 그리고 서버를 종료하려면 콘솔에서 아무 키나 누르면 종료가 됩니다.

만약에 원격에서 좀 더 지속적으로 서버의 성능을 측정해보고 싶으시다면 screen 유틸리티를 사용하여 세션을 분리하신 상태에서 위의 명령어를 입력하고, 서버가 떠 있을 때 Ctrl 키를 누른 상태에서 빠르게 a, a, d 키를 누르면 세션이 분리되어 계속 살아있는 서버가 만들어집니다. 이 상태에서 Apache Bench (AB)등의 유틸리티를 사용하여 부하 테스트 등을 해보시는 것도 의미가 있을 것입니다.

참고로, NAT 환경이나 퍼블릭 클라우드 환경에서는 대표 IP 주소에 대한 외부 방화벽 설정을 열어주셔야 밖에서도 접속이 가능합니다.

마무리

아직 ASP.NET MVC 6나 다른 기술들이 완전히 준비된 것은 아니지만, 이 정도만 하더라도 ASP.NET은 더 이상 윈도 OS 안에서만 사용 가능한 기술이 아니라는 것을 증명하는데에는 손색이 없을 것입니다. 더 많은 가능성과 잠재력을 포함하는 최신 기술이 곧 나타나게 될 것이 무척 기대가 됩니다.

만약 기존에 ASP.NET 웹 사이트를 개발해 놓은 것이 있다면, ASP.NET vNext로 프로젝트를 마이그레이션하면서 플랫폼에 중립적으로 동작하는 코드로 업데이트하는 프로젝트를 한 번 진행함으로서 그리 어렵지 않게 멀티 플랫폼으로 ASP.NET 웹 응용프로그램을 포팅하실 수 있을 것입니다.

앞으로 더 자세한 정보와 상세한 내용들, 그리고 활용 방안들도 블로그 포스트로 전할 수 있도록 하겠습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2014. 8. 3. 09:33

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) =&gt;
{
    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() 비동기 메서드는 비어있는 컬렉션을 반환합니다.

다음 아티클에서는

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

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2014. 7. 15. 09:32

OWIN 개요

요즈음 .NET 기반 웹 개발에서 주목을 받고 있는 것은 ASP.NET MVC나 ASP.NET Web API가 아니라 사실 OWIN이 아닐까 생각합니다. OWIN은 Open Web Interface for .NET을 줄여서 쓴 말로 .NET Framework 기반의 웹 개발 프레임워크들을 더 다양하게 고르고 조합할 수 있도록 도와주는 표준화된 인터페이스를 말합니다. (http://owin.org/ 에서 자세한 정보를 볼 수 있습니다.)

왜 OWIN인가?

OWIN이 주목을 받는 이유는, 이전에 다루던 웹 응용프로그램 개발 환경과는 달리, 목적과 상황에 맞게 더 정밀하고 더 세세한 웹 프로그래밍이 가능하기 때문입니다. 이전의 ASP.NET 만을 사용하던 개발 환경에서 가장 큰 문제가 되었던 것은 의외로 단순한 부분들로부터 나타나는 것들이 많았는데, 예를 들어 파일 업로드에 대한 것이 가장 큰 문제였습니다.

 

 

인터넷 속도가 빠르지 않은 지역에서의 사용을 기준으로 잡혀있는 파일 업로드 제한과 공간 할당 정책이 기본값이었기 때문에, 대용량 파일 업로드가 필요한 때에는 이것을 처리하기 위해 ASP.NET의 파일 업로드 제한을 다시 정의할 수 밖에 없었고, 사실 이것은 면밀하지 못한 설정이 되어서 보안 리스크가 될 수 밖에 없었습니다. 그리고 .NET Framework 2.0 서비스팩 적용 이전까지는 놀랍게도 파일 업로드를 임시 저장소가 아닌 메모리 공간에 저장하는 방식이어서 대용량 파일 업로드를 할 경우 서버 자원이 고갈되는 (!) 문제까지 있었습니다.

지금은 임시 디스크에 저장한 후에 파일 업로드가 다 되었음을 알려주는 식이 되었지만, 여전히 세밀한 파일 업로드 제어는 하기 어려운 상태입니다. 그리고 제일 큰 문제는, 환경 설정에서 지원하는 최대 파일 업로드 크기가 여전히 부호있는 32비트 정수값 (.NET의 표준은 부호있는 정수 사용을 권합니다.)의 범위에 있어서 최대 업로드가 2GB 밖에 가능하지 않습니다. 이를 해소하기 위해서 청크 방식의 업로드 등을 이용해야 하지만 웹 브라우저가 지원하지 않으면 별도 클라이언트를 이용할 수 밖에 없는 상태입니다. 다시 말해, 간단한 파일 업로드를 필요로 한다면 ASP.NET이 여전히 편리하지만, 복잡해진 요구 사항을 맞추기 위해서는 턱없이 커스터마이징 변수가 부족한 것입니다.

비단 이 문제 뿐 아니라 ASP.NET이 편의를 위하여 제공하는 기능이 요즈음에 들어서는 오히려 복잡한 요구 사항 개발에 방해 요소가 되는 일이 많습니다. 그리고 이러한 요구 사항을 만족하기 위해 ASP.NET의 기본 설정을 변경하는 것 자체가 보안 리스크가 됨은 물론, ASP.NET 자체에 대한 강력한 커플링이 발생하여 유지 보수에 큰 문제가 되곤 합니다. 왜냐하면 커플링이 강력해질수록 ASP.NET에 사소한 업데이트나 패치 하나가 전체 프로그램에 큰 영향을 줄 개연성이나 확률이 더 높아지기 때문입니다. 그래서 기존 ASP.NET의 영역을 유지하면서, 안전하고 더 편리하게 복잡한 요구 사항을 쉽게 맞출 수 있는 방법이 필요해지게 되는데, OWIN이 여기에 대한 확실한 답이 됩니다.

OWIN의 기본 개념

OWIN 그 자체는 HTTP 요청을 받아들이고, 응답을 결정하는 행위를 규정하는 스펙을 .NET 4.0을 기반으로 제공하는 것이 전부이며, 모든 처리 과정은 미들웨어를 통해 이루어지는데, 미들웨어 간의 호출에 사용되는 것은 아주 단순한 Dictionary 자료 구조입니다. (http://owin.org/spec/owin-1.0.0.html)

OWIN-5

OWIN이 하는 일 자체는 위의 그림 (클릭하면 크게 볼 수 있습니다.)이 전부입니다. 여러 개의 미들웨어 구성 요소를 OWIN 프로그램을 시작할 때 등록하고, 미들웨어의 입장에서는 자신이 처리할 수 있는 요청인 경우에만 요청을 가로채고, 그 외에는 다음 미들웨어로 호출을 넘겨주는 것이 전부입니다. 만약 모든 미들웨어를 지나 끝까지 도착했음에도 불구하고 처리할 수 있는 미들웨어가 없다면, OWIN을 어떤 환경에서 사용하는지에 따라 다음 동작이 결정됩니다.
•만약 OWIN 그 자체를 바로 호스팅하는 베어본 서버 환경이라면 익히 알려진대로 404 오류 코드를 내보내거나 빈 응답을 반환할 수 있습니다.
•만약 ASP.NET 환경에서 OWIN을 얹어서 사용하는 형태라면, OWIN 환경 자체가 다른 ASP.NET 모듈보다 먼저 실행되는 ASP.NET 모듈이기 때문에, 다음 ASP.NET 모듈로 넘어가게 됩니다. 기존에 개발한 ASP.NET 웹 프로젝트 위에 OWIN을 추가 장착하게 되면 이런 형태가 됩니다.
•만약 ASP.NET Native Loader 위에서 OWIN을 얹어서 사용하는 형태라면, OWIN 환경 자체를 완전히 분리된 환경에서 실행하는 것을 의미합니다.

각각의 경우를 따로 설명하는 데에는 이유가 있습니다. 이어서 말씀드리면 다음과 같습니다.

세 가지 실행 환경과 가능성

첫 번째는 OWIN과 미들웨어들을 묶어서 어디에서든 웹 서비스를 시작할 수 있음을 말씀드리고 싶어서입니다. 즉, 정상적인 .NET Framework 실행 환경을 갖추고 클라이언트와 서버 사이의 연결을 정확하게 처리하도록 만들어주기만 한다면 서버 OS가 윈도우이든 리눅스이든 맥이든 상관이 없으며 (리눅스와 맥은 Mono 덕분이라고 할 수 있겠습니다.), 전혀 다른 형태로 구현된 웹 서버일지라도 OWIN으로 정확하게 연결을 전달할 수 있다면 이론상으로 .NET 기반이 아닌 서버 플랫폼 환경에서도 얼마든지 .NET 서버 응용프로그램을 호스팅할 수 있는 상태가 됩니다.

두 번째는 기존에 가지고 있던 ASP.NET 웹 프로젝트에 얼마든지 OWIN 응용프로그램을 추가 장착할 수 있음을 의미합니다. System.Web이라고 불리는 레거시 ASP.NET 웹 프레임워크의 종속성에서 완전히 벗어나지는 못하지만, 그 규칙 안에서라면 기존 ASP.NET의 기술과 완전히 무관하게 독자적인 미들웨어를 개발하고 수행하는 것이 가능하므로, 전송 프로토콜 수준에서 HTTP 응답을 재정의하는 것도 가능하고, 실시간으로 파일 업로드에 개입할 수도 있습니다. 기존 ASP.NET 웹 프로젝트를 해치지 않고 얼마든지 복잡한 기능을 쉽게 추가할 수 있음을 뜻합니다.

마지막 세 번째 방법은 기존의 ASP.NET 실행 환경을 사용하지 않고, IIS에 최소 버전의 .NET 실행 환경을 직접 주입하여 실행 속도를 획기적으로 개선하고, 기존의 System.Web 관련 설정을 모두 무시하는 방식입니다. 이것은 Project Helios라고 불리던 것을 제품화한 것으로, 실행 속도와 메모리 점유율을 획기적으로 개선하여 응답 성능을 비약적으로 개선시킬 수 있습니다. 기존 ASP.NET 프로젝트와 독립된 형태로 웹 프로젝트를 개발하면서 IIS 위에서 OWIN 프로그램을 실행하려는 경우 이 방식을 이용하여 성능을 극대화할 수 있습니다.

다음 아티클에서는 OWIN의 내부 구조와 미들웨어를 프로그래밍하는 방법을 몇 가지 살펴보도록 하겠습니다.

이미지 출처: http://byterot.blogspot.kr/2013_08_01_archive.html

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

IaaS2014. 7. 7. 09:30

Microsoft Azure에서 실행되는 VM은 Azure BLOB Storage를 사용하여 VHD 디스크를 동적으로 연결하여 사용합니다. 이것은 광의로 해석하면 Storage Area Network 위에서 실시간으로 호스팅되는 가상 디스크를 마운트하여 사용하는 셈인데, 실제로 Microsoft Azure의 VM이 시험판 단계일 때에는 예기치 않은 문제들이 제법 많았습니다. (물론 지금은 그런 일도 없고, 또 그렇게 되어서는 안되겠죠.)

 

 

Azure BLOB Storage 그 자체도 내부적으로 상당히 많은 추상화가 이루어져있는 복잡한 기술이지만, 이것을 기반으로 하는 VHD를 사용한다는 것은 어떻게 생각하면 추상화의 극을 달린다는 느낌에 가깝습니다. 물론, 이토록 고도화된 시스템을 사용하기 때문에 얻을 수 있는 이점으로는 무중단 상태에서 같은 지역은 물론 여러 지역에 걸쳐 VHD를 실시간으로 미러링하여 백업하거나 VHD 자체를 로드밸런싱하는 등의, 종전의 클라우드 서비스 공급자들도 상상하기 어려운 스케일의 서비스를 제공할 수 있게 된 점은 있습니다. 하지만 흔히 기대하는 베어메탈급의 고성능 서버와는 거리가 멀리 떨어진 듯한 느낌을 지울 수는 없겠지요.

엄밀히 말해서, 정말 빠른 디스크 I/O를 필요로 한다면 오히려 클라우드 환경에 의존하기 보다는 직접 고성능 서버를 구입하여 구축하는 것이 훨씬 더 이치에 맞습니다. 하지만, 단순히 디스크 I/O가 빨라야 하는 요구 사항을 고려하는 것이 아니라면, 대부분의 인프라의 가치는 장애 대비와 적시 복구인 경우가 많습니다. 이런 점을 고려하여, 어느정도 만족할만한 수준의 디스크 성능과 안정성을 동시에 거둘 수 있는 간단한 팁을 하나 올립니다.

Azure에서 VM을 만들고 디스크를 추가로 생성하여 연결할 때, 다음 그림과 같이 질문을 받습니다.

001

여기서 제일 아래쪽의 호스트 캐시 기본 설정이라는 항목이 제가 오늘 말씀드리려는 내용에 대한 것입니다.

호스트 캐시란 앞에서 이야기한 대로 실제 디스크 I/O를 처리하기 위한 과정에서 중간에 관여하는 구성 요소로, 원칙대로라고 한다면 Azure Storage의 VHD에 대해 발생하는 디스크 I/O를 Azure Storage가 완전히 처리해서 응답을 되돌려주기까지의 과정을 운영 체제의 입장에서는 기다려야 하는 셈입니다. (물론 여기서 다 설명하기에는 어려울 정도로 복잡한 메카니즘이 있어서 정말 순진하게 캐시를 사용하지 않는다고해서 무작정 기다리기만 하는 것은 아닙니다.)

그러나 좀 더 적극적으로 개입해서, 실제로 디스크 I/O가 저멀리 있는 Azure Storage에 전달이 되기 이전이라고 하더라도, 확실하게 디스크 I/O에 대한 요청이 접수되었고 처리가 되었음을 기억하여 정확한 응답을 되돌려주면서, 다른 한편으로는 그 내용을 실제 Azure Storage의 VHD에 지속적으로 보내주는 역할을 따로 수행하게 됩니다.

다르게 말하면, 만약 Azure Storage와 연결이 끊어지거나 문제가 발생하는 경우 캐시의 유효성이나 지속성에 문제가 생겨서 서비스 장애로 이어지는 불상사가 벌어질 수 있습니다. 그러나 바꾸어 말하면 캐시를 사용하지 않을 때에는 이것이 직접적으로 VM 위에서 돌아가는 프로그램에 대한 OS의 IO API 호출에 대한 실패로 직격탄이 되어버립니다. 이런 점을 고려해보면, 문제의 전파나 영향 범위를 최소화하는 관점에서 캐시가 미미하지만 어느정도 역할을 수행하는 것도 가능합니다.

이러한 성격의 캐시를 사용할 수 있는 방안은 두 가지가 있는데, 읽기 작업을 위한 캐시만 사용하는 경우와, 읽기/쓰기 작업을 모두 관리하는 캐시를 사용하는 경우입니다. 운영 체제용으로 만들어지는 VHD는 읽기/쓰기 작업을 모두 관리하는 캐시를 기본으로 사용하도록 하고 있습니다. 그리고 추가 연결하는 디스크에 대해서는 디스크 I/O 빈도가 높지 않을 것임을 가정하고 기본적으로는 캐시 없이 직접 Azure BLOB Storage와 연결되도록 하는 것이 기본입니다.

이러한 상황에서, 만약 디스크의 읽기 성능이 중요하다면 읽기 캐시를 사용하도록 해주는 것이 필요합니다. 이렇게 하면 최초로 캐시에서 가지고 있지 않은 데이터 영역에 대한 요청이 들어왔을 때, 이를 캐시에 저장하고 다음 회차 요청 때 좀 더 빠르게 전달할 수 있도록 해주는 식이 됩니다. 물론 쓰기나 다른 동작에 의해 변경이 발생하게 되면 이에 맞추어 당연히 캐시도 다시 형성됩니다.

여기에 쓰기 캐시를 사용하는 것 까지 포함하게 되면, 쓰기 캐시에 들어오는 요청과 읽기 캐시의 내용을 병합하여 중간 버퍼를 형성하고 Azure Storage의 VHD와 동기화하는 작업을 적극적으로 개입하여 수행하게 됩니다.

결론을 이야기하면, 쓰기 작업이 많은 응용프로그램을 위해 디스크를 추가할 때는 읽기/쓰기 캐시 사용을, 웹 사이트나 읽기 작업이 많은 응용프로그램을 위해서는 읽기 캐시 사용을 권할 수 있습니다.

사실 클라우드 서비스를 사용한다는 것이 어떤 의미인지 본질적으로 잘 이해하고 계시다면, 그리고 필연적으로 사람이 하는 일에 있을 수 있는 결함성을 인정하신다면, 이러한 아키텍처 속에서 갑작스럽게 나타날 수 있는 예외 상황은 항상 관리해야 할 리스크가 될 수 있습니다.

적절한 설정을 확인하고 검증하는 단계를 통해 더 성공적으로 클라우드 서비스를 현업에 투입할 수 있게 될 것입니다.

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Useful Solutions2014. 6. 7. 09:29

Visual Studio에서 제공하는 테스트 도구에 NUNIT을 결합해서 사용할 수 있다면 정말 유용할 것입니다. 이번 웹 캐스트는 Visual Studio에서 제공하는 테스트 도구와 NUNIT 간의 연동을 도와주는 NUNIT Test Adapter의 사용 방법과 함께, Windows Forms 기반 응용프로그램에서 Visual Studio와 NUNIT Test Adapter를 결합하여 테스트를 진행할 수 있는 방법을 소개합니다.

(비디오 콘텐츠로 올렸던 게시물이었으나 아티클로 조만간 복원할 예정입니다.)

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2014. 4. 11. 09:28

흔히 소프트웨어 개발 과정에서 나타나는 요구 사항들 중에서, 시간 투자 대비 효율이 가장 떨어지는 요구 사항으로 HTML 문서 분석에 대한 것이 있습니다. 이 요구 사항을 해결하기 위해서, 보통 택하는 방안으로는 Windows 환경에서 Internet Explorer의 MSHTML을 이용하는 방안을 고민하게 됩니다. 그러나 익히 알려져 있는 바, 메모리 누수나 무거운 런타임 크기, 더 나아가서는 제한된 수준의 해석 등 이점보다는 단점이 더 많은 방식이라는 것이 큰 문제입니다. 물론 이 이후로도 오픈 소스 브라우저를 활용하거나 WebKit 바인딩을 이용하는 방법도 흔히 검토가 가능하긴 하지만 전체 브라우저 스택을 완전히 불러들여야 하고 HTML 문서의 계산 과정이 동반된다는 점이 성능 상의 이슈가 됩니다.


HtmlAgilityPack은 순수하게 .NET Framework의 코드 만으로 HTML 문서를 온전하게 분석하는 것은 기본이고, System.Xml 네임스페이스에서 제공하는 XPATH 식 관련 인터페이스와 해석기를 충실하게 지원하고 있어서, 복잡하고 까다로운 HTML 문서 구조 탐색을 매우 손쉽게 처리해 준다는 큰 장점이 있습니다. 무엇보다도 중요한 것은, NuGet에서 쉽게 설치해서 사용할 수 있으므로 HTML 문서 분석에 관한 고민에 시달리고 계시다면 지금 당장 테스트해보시기를 강력히 권할 수 있을 만큼, 훌륭한 기능을 제공합니다. 강력한 기능 덕분에 몇몇 상용 솔루션에서는 이미 절찬리에 채택되어 사용 중에 있습니다.


패키지/프로젝트 소개

HtmlAgilityPack은 2014년 4월 현재 다음과 같이 두 패키지로 구분되어있습니다.

•http://www.nuget.org/packages/HtmlAgilityPack/
•http://www.nuget.org/packages/HtmlAgilityPack-PCL/

 

 

처음의 패키지는 Desktop 버전의 .NET Framework (Mono 포함)에서 이용할 수 있도록 패키징된 버전으로, XPATH 식에 대한 지원을 포함하고 있습니다. 그리고 두 번째 패키지는 Windows Phone, Windows Store App 등의 환경에서도 사용 가능하도록 Portable Class Library 프로필에 맞게 리패키징한 버전입니다. 이 글에서 설명하고자 하는 버전은 첫 번째 버전입니다.


샘플 코드

테스트하려는 프로젝트를 여신 다음, HtmlAgilityPack을 NuGet 패키지 관리자로 프로젝트에 설치하고, 실제로 HTML 페이지 분석이 잘 이루어지는지 확인해보기 위하여, 다음과 같이 코드를 작성해보도록 하겠습니다.
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.XPath;

class Program
{
    static void Main(string[] args)
    {
        Uri targetUri = new Uri("https://www.youtube.com/watch?v=8YkbeycRa2A"); HttpWebRequest webRequest = HttpWebRequest.Create(targetUri) as HttpWebRequest;
        using (HttpWebResponse webResponse = webRequest.GetResponse() as HttpWebResponse)
        using (Stream webResponseStream = webResponse.GetResponseStream())
        {
            HtmlDocument s = new HtmlDocument();
            Encoding targetEncoding = Encoding.UTF8;

            s.Load(webResponseStream, targetEncoding, true);
            IXPathNavigable nav = s;

            string title = WebUtility.HtmlDecode(nav.CreateNavigator().SelectSingleNode("/html/head/meta[@property='og:title']/@content").ToString());
            string description = WebUtility.HtmlDecode(nav.CreateNavigator().SelectSingleNode("/html/head/meta[@property='og:description']/@content").ToString());
            string fullDescription = WebUtility.HtmlDecode(s.GetElementbyId("eow-description").InnerHtml);
            fullDescription = Regex.Replace(fullDescription, @"<(br|hr)[^>]*>", Environment.NewLine);
            fullDescription = Regex.Replace(fullDescription, @"<[^>]*>", String.Empty).Trim();

            Console.WriteLine(title);
            Console.WriteLine(description);
            Console.WriteLine(fullDescription);
        }
    }
}

여기서 설명한 코드는 YouTube의 메타 태그 속성을 이용하여 동영상에 대한 기본적인 정보를 가져오는 코드입니다. 여기서 주목할 것은 XPATH 식을 사용하여 HTML DOM 모델에 정확하게 접근하고 있다는 점입니다. Property 속성이 og:title인 META 태그의 Content 속성을 가져와서 YouTube에 정확하게 등록한 원래 동영상 제목을 추출하거나, 동영상의 설명을 같은 방법으로 가져오고 있습니다. 여기서 그치지 않고, eow-description이라는 ID를 가진 HTML 태그를 찾아 그 태그의 내용을 통째로 가져와서 축약되지 않은 원래 설명도 특별한 API 없이 가져오고 있습니다.


System.Xml.XPath 호환

위의 코드에서 HtmlDocument 클래스를 IXPathNavigable 형식의 변수 s로 캐스팅한 것을 볼 수 있습니다. 이 코드가 의미하는 바는 실로 큰데, IXPathNavigable은 .NET Framework BCL의 System.Xml 안에 들어있는 인터페이스를 실제로 대응하여 구현한 것입니다. 이 기능을 사용하면 다루는 대상이 XML이든 HTML이든 혹은 XHTML이든 일반화하여 다루는 것이 가능하여 의존성 주입의 차원을 고도화하는 것이 가능합니다.


IXPathNavigable 인터페이스는 다른 메서드를 제공하지 않고 오로지 CreateNavigator() 메서드만을 제공하는데, 이 메서드를 이용하여 만드는 객체는 XPathNavigator 클래스입니다. 이 클래스의 인스턴스는 상대적 접근 경로를 인식하여 해당 객체를 만들기 위하여 어떤 DOM 노드 객체를 활용하였는지에 따라 경로가 달라집니다. 이 기능을 사용하면 XPATH 식으로 선택이 어려운 노드를 근처의 형제 노드 (Sibling Node)를 통해서 전/후 탐색 메서드를 사용하여 접근이 가능합니다. XPathNavigator 클래스는 한 번 생성된 이후에는 자신이 스스로 상태를 관리하도록 되어있으므로 일종의 커서처럼 탐색이 가능합니다.


실제 활용 사례

이 즈음되면, 정말 특이한 상황을 제외하고 HTML 페이지를 수집하러 돌아다니는 크롤러를 얼마든지 만들 수도 있겠다는 예상도 해볼 수 있습니다. 실제로 이 라이브러리를 사용하여 크롤링 기술을 구현하고 있는 Arachnode.net이라는 Lucene.net 기반의 상용 검색 엔진 프로젝트도 많은 주목을 받고 있습니다. (http://arachnode.net/)


필자 개인적으로는 HTML 구문 분석이 필요한 프로젝트에서 상당히 큰 도움을 받았으며 해당 프로젝트의 주요 기능으로 적극적으로 채택하는데 큰 도움을 받았습니다.


도입 시 고려할 사항

HTML을 분석할 수 있다는 전무후무한 강력한 장점이 있음에도 불구하고, 한 가지 주의 사항이 있습니다. HtmlAgilityPack으로 처리할 수 있는 HTML은 파일 시스템 상의 고정된 정적 페이지나 네트워크를 통해서 서버가 정적 또는 동적으로 제공하는 HTML 페이지만 정확하게 처리가 가능하며, CSS나 JavaScript, 혹은 다른 Rich Internet Application (Flash나 Silverlight 등)이 나중에 가공하는 HTML 페이지의 변경 사항은 반영되지 않는다는 점입니다. 이것은 라이브러리의 문제가 아니며, 라이브러리가 취하고자 하는 기능의 명확한 한계입니다.


Rich Internet Application에 의한 후 처리를 제외하고 CSS나 JavaScript에 대한 처리가 완료된 웹 페이지에 대한 분석이 필요한 상황인 경우, Headless Browser를 사용하여 계산이 완료된 웹 페이지를 처리하거나 더 복잡한 작업을 수행할 수도 있습니다. 이를 위하여 PhantomJS (http://phantomjs.org/) 를 사용하는 것이 유용합니다. PhantomJS는 NuGet 패키지로도 제공됩니다. (http://www.nuget.org/packages/PhantomJS/)


만약 Rich Internet Application에 의한 후 처리까지 필요한 경우에는 Chrome이나 Internet Explorer를 해석 엔진으로 안정적으로 사용할 수 있도록 도움을 주는 Selenium Web Driver에 의한 간접적 처리도 고려해볼 수 있습니다. (NuGet 패키지: http://www.nuget.org/packages/Selenium.WebDriver/) PhantomJS와 Selenium Web Driver에 관한 이야기는 이 블로그 포스트 범위 밖에 있는 이야기이므로 존재에 대해서만 언급을 하는 것으로 갈무리하겠습니다.

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

남정현2014. 4. 5. 19:20
안녕하세요. Azure MVP 남정현입니다.

2014년 4월 5일부터 Tistory에서 Microsoft Azure와 Textcube 환경으로 블로그를 이사하였습니다. 도메인 주소는 그대로 유지되며 새로운 사이트로 변경되어 표시될 예정입니다.

rkttu.tistory.com으로 접속하신 분들께서는 www.rkttu.com으로 즐겨 찾기 주소를 변경하여 주시면 감사하겠습니다.



Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2014. 4. 5. 09:27

System.Net.Http.HttpClient는 ASP.NET 웹 API의 REST API 호출을 위해서도 요긴하게 사용하지만, 다양한 상황에서 사용 가능한 HTTP 송수신을 담당하는데 특화된 클래스입니다. 최근에 이 클래스를 사용하여 개발한 소프트웨어 프로젝트에서 예기치 않은 문제가 하나 있었는데, TIME_WAIT 상태로 연결 대기가 유지되는 소켓 연결의 수가 급증하여 서버에서 실행 중인 다른 데이터베이스나 웹 서비스의 연결이 WSAENOBUF WinSock 오류 코드를 반환하며 거부되는 일이었습니다.

이 클래스는 기본적으로 IDisposable 인터페이스를 구현하고 있고, 원래의 의도는 사용하지 않을 때 적시에 제거하는 것이 올바른 방식이라고 생각하였습니다. 그러나 실제로 이 클래스를 Dispose() 메서드를 사용하여 소거한다고 하더라도 클라이언트 측 연결이 끊어지지 않고 TIME_WAIT 상태로 변경되는데, 이러한 상황에서 계속 HttpClient 인스턴스를 반복적으로 만들고 연결을 다시 시도하다보면, 접속하는 클라이언트 측의 잔여 TCP 포트의 수가 부족해지는 문제가 발생하게 됩니다.

이 문제를 해결하기 위하여 취한 방법은 해당 인스턴스를 싱글턴 인스턴스로 만들어 사용하는 것이었으며, 실제로 문제를 해결할 수 있었습니다. 하지만 남아있는 문제는 이 인스턴스를 Thread-Safe 인스턴스로 만들어야 하는 것이며 이를 위해서 보강해야 할 것이 있다고 보고 있습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

이벤트2014. 2. 28. 18:09
Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Useful Solutions2013. 11. 16. 02:00

Visual Studio 2013에서는 One ASP.NET이라는 새로운 형태의 프로젝트 생성 방식을 제공하고 있습니다. 이전에는 ASP.NET MVC, Web Form, Web Page, Web API가 각기 다른 프로젝트로 분리되어있어서 구성하기에 불편한 점이 많았습니다만 2013부터는 한 위치에서 동시에 여러 ASP.NET 웹 스택을 선택하고 조합할 수 있게 되었으며, 인증 시나리오도 Windows Identity Foundation, 기본 인증, 소셜 네트워크 기반 인증 등을 지원할 수 있는 옵션이 추가되었습니다.

그런데 이러한 특성 때문에, 처음부터 NuGet 패키지 관리자에 대한 의존성이 필수가 되었습니다. 그래서 간혹 어떤 컴퓨터에서는 Visual Studio 2013으로 ASP.NET 프로젝트를 생성하려 할 때 다음과 같은 오류 메시지가 나타나기도 합니다.

지정된 파일을 찾을 수 없습니다. (예외가 발생한 HRESULT: 0x80070002) 

오류: 이 템플릿에서 구성 요소 어셈블리 'NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'을(를) 로드하려고 했습니다. 이 문제에 대한 자세한 정보 및 이 템플릿을 활성화하는 방법에 대한 정보는 "프로젝트 템플릿 사용자 지정"의 설명서를 참조하십시오. 

위와 같은 오류 메시지가 나타나는 것은 NuGet Package Manager 애드인이 설치되어있지 않기 때문이며, ASP.NET 프로젝트 생성과 동시에 인터넷 상에서 NuGet Package Manager를 이용하여 프로젝트 구성에 필요한 각종 라이브러리들 (jQuery, ANTLRv3 등)을 내려받으려고 시도하기 때문입니다.

문제를 진단하기 위하여, Visual Studio에 현재 설치된 확장 및 업데이트 내역을 확인합니다. 메뉴에서 아래와 같이 실행하면 해당 항목을 찾을 수 있습니다.

도구(T) - 확장 및 업데이트(U) 클릭 

온라인 - Visual Studio 갤러리 선택 - 검색어에 nuget 입력 후 Enter 키 누름 - "NuGet Package Manager for Visual Studio" 선택 - 다운로드 버튼 클릭 

다운로드 진행 - EULA 동의 - 설치 - 아래와 같이 화면이 나타나면 하단의 "지금 다시 시작(R)" 버튼 클릭

이제 ASP.NET 프로젝트를 다시 생성하면 정상적으로 생성이 완료될 것입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2013. 10. 25. 23:00

NUnit의 GUI Runner는 여러 개의 테스트 유닛 프로젝트를 로드하여 동시에 테스트 결과를 시각적으로 확인할 수 있는 매우 유용한 유틸리티입니다. 그러나 한 가지 아쉬운 점이 있다면, Visual Studio와 완벽하게 통합되어있지는 않아서 단위 테스트 도중 변수의 상태를 확인하거나 디버깅을 하기에는 불편한 구조로 제작되어있다는 점입니다. 그래서 개인적으로 자주 애용하는 대안으로 Reflection을 사용하여 Test Fixture와 Test Case를 검색하여 자동으로 호출하는 유틸리티 클래스의 소스 코드를 https://github.com/rkttu/nunit-self-runner 에 게시하였습니다.

이 프로그램 코드는 NUnit Framework 어셈블리 외에 특별한 종속성이 없고 어떤 코드에서든 쉽게 붙여넣어 시작할 수 있습니다. 그러나 기능 상의 제약이 있는데 다음과 같은 유형의 Test Fixture나 Test Case에서는 작동하지 않습니다.

  • Test Fixture 생성 시 별도의 생성자 매개 변수가 필요한 경우
  • Test Method 실행 시 별도의 호출 매개 변수가 필요한 경우
  • private이나 protected, internal 멤버

이 소스 코드를 NUnit 클래스 라이브러리 프로젝트에 추가하고, 해당 NUnit 클래스 라이브러리를 컴파일하여 실행하면 다음과 같은 형태로 단위 테스트가 전개될 것입니다.

테스트에 실패하는 케이스, 즉 Exception이 발생하면 위와 같이 적색의 Test case failed 라는 문구가 나타나고 자세한 Stack Trace 결과가 노란색의 텍스트로 표시되어 시각적으로 구분을 쉽게 해줍니다. 그리고 실패했다는 사실을 알리기 위하여 테스트가 일시 중단되고, Enter 키를 누르면 계속 실행됩니다. 이 메시지를 확인하고 적절한 위치에 중단점을 설정하면 디버거가 해당 위치에서 중지되므로 좀 더 쉽게 문제를 진단할 수 있습니다.

반면 예외 없이 정상적으로 실행되는 테스트 케이스는 초록색의 Test case succeed 메시지를 표시하고 중단없이 계속 다음 테스트를 진행합니다. 그리고 한 Test Fixture의 실행이 완료되면 다시 사용자의 입력을 대기하는 상태로 들어가며, Enter 키를 누르면 다음 Test Fixture로 진행할 수 있으므로 인터랙티브하게 단위 테스트 결과를 확인할 수 있습니다. 

모든 테스트 Fixture의 실행이 끝난 이후에도 한 번 더 사용자의 입력을 기다립니다. 콘솔에 표시된 전체 내용을 리뷰하고 마지막으로 Enter 키를 누르면 프로그램이 완료됩니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

포트폴리오2013. 10. 21. 00:52

Windows Forms는 오랜 시간이 지난 지금 매우 잘 정착하여 성공적으로 여러 시스템과 플랫폼에 걸쳐 사용되고있는 훌륭한 프레임워크입니다. 하지만 최신 프로그래밍 기법과는 잘 어울리지 못한다는 문제점이 있어서 점진적으로 사장되어가고 있다는 것은 개인적으로 참 안타깝게 생각하는 부분입니다.

여러가지 이슈들이 있을 수 있지만, Windows Forms 개발자들에게 있어 가장 근본적이면서도 가장 어려운 문제는 단일 UI 스레드 안에서 모든 코드를 처리하도록 만든 프레임워크의 특성 상 다중 스레드에서의 프로그래밍 기법이 친숙하지 않다는 것입니다. 코드를 잘못 풀어서 정리할 경우 매우 파악하기 어렵고 까다로운 코드가 되기 쉽습니다. 그리고 이러한 문제는 UI 스레드가 아닌 스레드 문맥에서 UI 요소를 제어하려고 할 때 가장 큰 문제가 됩니다.

모든 경우의 수를 다 고려할 수는 없습니다. 그렇지만 빈번하게 사용되는 속성과 메서드들에 대해서만이라도 비동기 프로그래밍이 쉬워진다면 확실한 이점이 있을 것입니다. 이번에 개인적으로 런칭한 라이브러리는 그런 부분에 있어 큰 도움을 줄 수 있을 것이라 기대합니다.

라이브러리 설치 방법

이 라이브러리는 .NET Framework 4.0 Client Profile 이상의 Desktop App을 대상으로 합니다. 따라서 최소한 Visual Studio 2010 이상의 IDE 또는 Visual C#/Basic Express IDE가 필요하며, async/await 프로그래밍을 사용하기 위해서는 Visual Studio 2012 이상의 IDE 또는 Visual Studio 2012 for Desktop Express가 필요합니다. 그리고 이 글을 작성하는 현 시점에서 가장 최신 버전은 Visual Studio 2013 또는 Visual Studio 2013 for Desktop Express입니다. Express 버전의 IDE는 http://www.microsoft.com/express 에서 무료로 다운로드할 수 있습니다.

적절한 버전의 IDE를 다운로드하여 Windows Forms 프로젝트를 열거나 새로 만든 다음, NuGet 패키지 관리자를 열어 온라인에서 검색 키워드로 wfasync를 입력하면 다음과 같이 검색 결과가 나타납니다. 검색 결과 상의 패키지를 설치합니다.

 

기능 시험해보기

이 라이브러리는 System.Windows.Forms를 기본 네임스페이스로 사용하고 있으므로, 자동으로 기존 Windows Forms 프로젝트 상의 주요 컨트롤에 관련된 확장 메서드들이 추가됩니다. Control 클래스를 기준으로 구현되는 거의 모든 속성, 메서드들에 대해서 다음의 사항들에 대한 Wrapper Method가 제공됩니다.

  • Control 클래스의 주요 인스턴스 메서드의 경우 Begin~, End~, ~Async Wrapper 메서드가 추가됩니다. 만약 메서드가 여러 버전의 오버로드를 가지고 있는 경우 정확한 구분을 위하여 Begin과 End 시리즈 메서드에는 접미사로 순번이 붙습니다. 이것은 설계에 따른 기본 설정입니다.
  • Control 클래스의 주요 속성 (인덱서 제외)의 경우 getter/setter 사용 가능 여부에 따라 GetXXX, SetXXX, BeginGetXXX, BeginSetXXX, EndGetXXX, EndSetXXX, GetXXXAsync, SetXXXAsync 확장 메서드가 자동으로 추가됩니다.
  • 해당 Form이나 Control을 Parent Window로 지정하는 MessageBox.Show 메서드에 대한 10가지 오버로드에 대한 확장 메서드가 제공됩니다.

위의 사항들 중 일부를 간단히 시험해볼 수 있는 예제는 다음과 같습니다.

아래 화면과 같이 간단한 Form을 하나 생성하고, 적당한 크기의 RichTextBox 컨트롤을 추가한 다음, 비동기 작업 분리를 위하여 BackgroundWorker 컴포넌트를 하나 추가합니다. 그리고 Form의 Load 이벤트와 BackgroundWorker 컴포넌트의 DoWork 이벤트를 다음과 같이 구현하고 연결합니다.

private void Form1_Load(object sender, EventArgs e)
{
    this.backgroundWorker1.RunWorkerAsync();
}

private async void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    using (System.Net.WebClient wc = new System.Net.WebClient())
    using (System.Threading.AutoResetEvent are = new System.Threading.AutoResetEvent(false))
    {
        wc.Encoding = Encoding.UTF8;
        await this.richTextBox1.SetTextAsync("Performing downloads.");
        string content = await wc.DownloadStringTaskAsync(
            new Uri("http://support.xinics.com/support/index.php?document_srl=23783"));
        await this.ShowMessageBoxAsync(
            String.Format("Downloaded {0} bytes.", content.Length));
        await this.richTextBox1.SetTextAsync("Waiting five seconds.");
        are.WaitOne(5000);
        await this.richTextBox1.SetTextAsync(content);
    }
}

코드의 내용은 단순합니다. UI 스레드가 메시지 처리에만 집중할 수 있도록, 추가 스레드를 쉽게 관리할 수 있도록 해주는 BackgroundWorker 컴포넌트를 Form 시작 시 실행하도록 코드를 추가하였습니다. 그 후, DoWork 이벤트 처리기를 만났을 때 비로소 이 라이브러리의 진가가 발휘됩니다.

결과를 반환하는 메서드가 아니기 때문에, 다행히 async/await 프로그래밍 때문에 이벤트 처리기의 메서드 시그니처와 이벤트 선언 사이의 임피던스 불일치는 일어나지 않습니다. 간편하게 DoWork 이벤트 핸들러 메서드에 async 키워드를 붙여주기만 하면 async/await 프로그래밍 준비는 끝납니다.

그 다음에는 RichTextBox 컨트롤의 텍스트를 변경하도록 요청하고 있고, WebClient 클래스를 초기화하고 using 블록 안에서만 사용하도록 코드를 시작합니다. WebClient의 기본 송수신 인코딩을 UTF8로 정하고, 지정된 URL로부터 문자열을 다운로드하여 해당 인코딩 방식으로 데이터를 디코딩하여 문자열로 가져옵니다.

그리고 다운로드한 문자의 갯수를 보여주기 위하여 ShowMessageBoxAsync 메서드를 사용하였습니다. 이 메서드 안에는 많은 내용이 숨겨져 있는데, 일단은 현재 스레드가 UI 스레드가 아니라는 점을 인지하여 메시지 박스를 UI 스레드를 통해 호출하도록 하고 있으며, 따라서 메시지 박스를 닫기 전까지는 모달 상태이므로 UI는 작업이 중단됩니다. 여기에 한 가지 더, 메시지 박스를 닫을 때까지 이 비동기 스레드 역시 동결 상태가 됩니다. 이것이 이 라이브러리의 역할인데, UI 스레드와 무관하게 전개가 되어야 할 때에는 작업을 독립적으로 수행하다가도, 사용자의 입력이 필요할 때 이와 같이 현재 스레드의 상태를 동결하는 것이 가능하다는 것입니다.

그 다음으로 RichTextBox 컨트롤을 대상으로 UI 스레드가 아닌 위치에서 표시할 문자열 변경을 요청하고 있습니다. 당연히 이 작업도 UI 스레드에 의하여 온전히 처리되도록 관리되며, UI 스레드가 작업을 마무리하기 전까지 현재 스레드는 역시 동결됩니다.

위의 코드를 실행했을 때 처음 얼마간 다운로드 작업이 이루어지는 동안에도 창에 대한 이벤트나 응답이 정상적으로 처리됨을 확인할 수 있습니다. 그리고 메시지 박스를 확인하고 난 다음에 5초의 시간이 지나서 RichTextBox의 내용이 갱신될 때 까지 UI가 전혀 얼어붙지 않음을 쉽게 확인할 수 있습니다.

 

라이선스에 대하여

이 프로젝트의 라이선스는 Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)을 따릅니다. 상업적 이용에 제한을 두지 않으며, 소스 코드 공개 의무를 지니지 않으므로 편리하게 이용할 수 있습니다. 소스 코드에 대한 검토 및 의견 제안은 github를 통해서 받고 있으니 언제든 자유롭게 의견 부탁드리겠습니다.

주요 프로젝트 URL

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

IaaS2013. 10. 20. 22:30

Windows Azure의 Virtual Machine 서비스는 Windows와 Linux를 Guest OS로 지원하는 전형적인 IaaS 플랫폼입니다. 그리고 Linux는 다양한 오픈 소스 소프트웨어와 결합할 수 있는 매우 이상적인 소프트웨어 개발 환경이기도 합니다.

Linux에서 사용할 수 있는 매력적인 소프트웨어들 중 최근 큰 주목을 받고 있는 프로젝트로 docker 프로젝트가 있는데, 이 프로젝트는 기본적으로 Hypervisor 없이 Linux 실행 환경을 가상화하고 서로 격리된 상태로 실행할 수 있도록 도와주는 Linux Container (LXC)를 조금 더 실용적이고 사용하기 편리하게 만들어주는 컴패니언 소프트웨어로, 이미지를 공유하거나, 이미지로부터 생성하는 컨테이너를 만들기 위해 필요한 명령을 자동으로 등록하거나, 컨테이너를 이미지로 변환하는 등의 작업을 단순화합니다.

docker 프로젝트를 설치하고 사용하는 방법에 대한 가이드는 인터넷 상에 이미 다양한 자료로 게시된 적이 있고, 최근에 열린 NAVER 개발자 행사인 DeVIEW 2013에서도 다루어진 적이 있습니다. (http://www.slideshare.net/modestjude/docker-in-deview-2013)

오늘 살펴보려는 내용은 docker 프로젝트를 Windows Azure Virtual Machine 상에서 설치하고 이용하는 방법과 함께, docker Index에 게시되어있는 최신 버전의 Mono 이미지를 다운로드하고 활용하는 방법을 간단히 살펴보려고 합니다. 지금 소개하는 방법을 통해서 쉽고 빠르게 리눅스 기반의 다중 개발 환경을 손쉽고 빠르게 프로토타이핑할 수 있습니다.

시작하기 전에 - Hypervisor 안에서 또다른 가상 환경을 만드는 것은 불가능하지 않습니까?

docker를 일반적인 PC나 서버 환경이 아닌 곳에서 사용할 때 가장 먼저 드는 의문점이 바로 이것입니다. 기본적으로 Hypervisor 위에서 실행하는 OS는 또 다시 가상 환경을 만들어낼 능력이나 여건이 되지 못합니다. 가능하도록 설정했다고 해도 결국 물리적인 한계에 부딪힐 가능성이 커집니다.

사실, docker는 Hypervisor가 아니기 때문에 Hypervisor 고유의 CPU 기능을 활용하는 일은 거의 없고, 호스트가 되는 리눅스의 커널의 재량에 따라 그 안에서 실행되는 독립적인 프로세스일 뿐입니다. 그러나, 제아무리 docker가 유용하다고 해도, 시스템이나 VM에 할당된 자원의 밀도나 품질을 생각해보았을 때 docker가 실제 처리할 수 있는 작업의 양이 항상 효율적이라고는 할 수 없습니다. 따라서, docker를 사용한 시스템 구축은 전적으로 충분한 시나리오 테스트와 QA를 거쳐야만 함을 염두에 두어야 할 것입니다.

docker로 할 수 있는 일

docker는 단순히 프로세스만을 격리하는 것이 아니라 일정 수준의 가상 환경을 다룹니다. 즉, 자체적으로 이용할 수 있는 파일 시스템, 네트워크 어댑터, 가용 메모리 크기 등이 있고, 상황에 따라 이들 자원의 크기가 동적으로 변화하게 됩니다. 그러나 우리가 일반적으로 사용하는 완전한 Hypervisor와는 다르게 자원을 명시적으로 제한할 수 있는 방법이 2013년 10월 현재 기준으로 특별히 없습니다. 그리고 네트워크는 사설 IP로만 할당되기 때문에, docker가 자체적으로 구성하는 NAT를 이용하여 외부로부터 들어오는 네트워크 요청을 특정 컨테이너 앞으로 도착하도록 연결해주는 것이 꼭 필요합니다.

그럼에도 불구하고, docker는 리눅스 기반의 환경에서 항상 있을 수 있는 파일 시스템, 커널, 잘못된 라이브러리나 패키지 설치로 인한 시스템의 중단으로부터 안전하게 지켜줄 수 있고, 신뢰할 수 있을만한 수준을 제공하면서도, 가볍게 다룰 수 있는 가상 환경을 제공한다는 점에서 큰 주목을 받고 있으며, 심지어 Hypervisor 기반의 실행 환경 위에서 전혀 다른 Linux Guest를 추가 실행할 수 있을만큼 유연합니다.

docker Index (https://index.docker.io/)에 게시된 이미지들을 살펴보면 알겠지만 호스트의 Linux OS와 아무 관련이 없는 busybox 같은 응급 이미지도 존재하고, Ubuntu가 호스트인 시스템에서 CentOS를 컨테이너 OS로도 택하는 것이 가능합니다. 즉, docker 환경에 맞추어 개발된 OS이기만하면 docker와의 상호작용을 전제로 호스트 OS와 완전히 분리된 환경에서 실행이 가능하므로 독립적인 환경 구성이 가능함을 뜻합니다.

docker를 Azure Linux VM 위에 설치하기

docker는 LXC 프로젝트를 기반으로 하기 때문에, 커널 버전에 대한 의존성이 크고, 사용할 수 있는 Host OS의 종류에도 제한이 있습니다. 현 시점에서는 Ubuntu Linux 13.04에서의 실행이 가장 안정적이기 때문에, Windows Azure Virtual Machine 갤러리에서 다음 그림과 같이 Ubuntu Linux 13.04 기반의 VM을 하나 추가해서 시작하셔야 합니다. 만약 Ubuntu Linux 12.04나 12.10 버전을 실행 중인 경우 Secure Shell 터미널 환경을 통하여 원격으로 13.04로 업그레이드하는 명령을 실행할 수도 있지만, libcurse 기반의 UI 실행이 일부 필요하기 때문에 putty 등에서는 정상적으로 보이지 않을 수 있고, 또한 잘못 선택할 경우 VM에 원격 접속이 불가능한 상황이 올 수 있으므로 추천하지 않습니다.

http://manage.windowsazure.com/ 으로 접속하여 새 VM을 갤러리로부터 생성하도록 시작합니다. 아래와 같은 대화 상자가 나타나는지 확인한 후 Ubuntu Linux 13.04를 선택합니다.

 

다음 버튼을 클릭한 다음 기본 VM 설정을 입력합니다.

 

PuttyGen을 이용하여 키 체인을 만들어 업로드하거나, 사용자 암호를 지정하고 다음 버튼을 클릭합니다.

 

클라우드 서비스는 네트워크 연결 및 중재를 위한 기본 단위이자 실행 환경을 정의하는 단위 환경입니다. docker 환경과 연결하려는 클라우드 서비스나 다른 VM이 있을 경우 같이 소속되도록 설정해주시고, 가용성 설정 등을 확인한 다음 다음 버튼을 클릭합니다.

 

기본적으로 SSH 포트가 열려있습니다. 그러나 docker container와의 연결을 허용하고 외부에서 접속을 받아들이려면 Windows Azure의 방화벽 설정도 수정해야 합니다. 외부에서 docker container 서비스로의 접속이 안된다면 이쪽의 방화벽 설정을 꼭 확인하셔야 합니다. 마침 버튼을 클릭하여 VM 생성을 시작합니다.

 

VM의 외부 DNS 주소를 확인하고, putty로 접속을 시작합니다.

 

접속이 완료되었다면 이제부터 명령어 입력을 진행합니다. 설치 가이드의 내용은 http://docs.docker.io/en/latest/installation/ubuntulinux/#ubuntu-raring-13-04-64-bit 에서 발췌하였습니다.

우선 최신 패키지 목록을 가져오기 위하여 다음 명령을 수행합니다. 최신 릴리즈를 사용하여 VM을 만들었다면 별 다른 업데이트는 발생하지 않을 것입니다.

sudo apt-get update

그리고 Ubuntu 13.04 시스템들 중 일부 릴리즈에서 누락되어있을 수 있는 AUFS 파일 시스템에 대한 지원 (LXC 실행을 위해 꼭 필요합니다.)을 추가하기 위하여 아래 명령어를 수행합니다. 참고로 Azure Virtual Machine을 통하여 최신 릴리즈를 사용하도록 VM을 생성했다면 역시 AUFS에 대한 지원이 이미 포함되어있어서 아래 명령어로 시스템에 변화가 발생하지는 않을 것입니다.

sudo apt-get install linux-image-extra-`uname -r`

이제 docker 리포지터리에 접근하여 패키지를 설치할 차례입니다. 우선 키 체인을 다운로드하여 시스템에 등록하여 docker 패키지를 인터넷으로부터 다운로드할 수 있도록 허가합니다.

sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"

OK라는 메시지를 확인하였으면 이어서 리포지터리 목록에 docker 리포지터리를 추가하기 위하여 다음 명령어를 실행합니다.

sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"

이제 패키지 업데이트를 다시 수행하여 사용 가능한 패키지 목록을 갱신합니다.

sudo apt-get update

출력 중에서 다음과 비슷한 메시지가 들어있는지 확인합니다.

Get:3 http://get.docker.io docker/main amd64 Packages [1,395 B]

이제 패키지 목록을 업데이트하였으므로 lxc-docker 패키지가 사용 가능한 상태가 되었을 것입니다. lxc-docker 패키지를 아래 명령으로 설치합니다.

sudo apt-get install lxc-docker

종속 패키지들을 다수 설치해야 함을 알리는 메시지가 나타나면 y 키를 눌러 진행합니다.

설치가 잘 되었는지 확인해보기 위하여 docker Index 사이트로부터 ubuntu 게스트 OS 이미지를 다운로드해보겠습니다. 아래 명령어를 실행합니다.

sudo docker run -i -t ubuntu /bin/bash

아래와 같이 다운로드 메시지가 나타나고 콘솔이 바뀌는 것을 확인하기 바랍니다.

rkttu@dockertest:~$ sudo docker run -i -t ubuntu /bin/bash
Unable to find image 'ubuntu' (tag: latest) locally
Pulling repository ubuntu
8dbd9e392a96: Download complete
b750fe79269d: Download complete
27cf78414709: Download complete
root@f45ee37cf476:/#

호스트 이름이 Azure VM의 dockertest가 아니라 임의로 작명한 f45ee37cf476이라는 이름으로 바뀐 것을 볼 수 있습니다. 그리고 IP 주소 대역도 다르다는 것을 쉽게 파악할 수 있는데, ifconfig을 비롯한 네트워킹 도구가 이 버전의 이미지에는 들어있지 않기 때문에 다음 명령어를 실행하여 net-tools 패키지를 설치합니다.

apt-get install net-tools

격리 환경 상에서 자동으로 root 권한을 얻었으므로 sudo를 덧붙일 필요가 없습니다. 설치가 끝난 다음, ifconfig eth0 명령을 실행하면 다음과 같이 나타납니다.

eth0      Link encap:Ethernet  HWaddr fa:b2:4f:b9:15:84
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::f8b2:4fff:feb9:1584/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:192 errors:0 dropped:0 overruns:0 frame:0
          TX packets:81 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:267507 (267.5 KB)  TX bytes:5591 (5.5 KB)

굵게 강조 표시한대로 사설 IPv4 주소가 할당되어있습니다. 그리고 패키지 업데이트와 업그레이드를 진행하여 인터넷 연결 상태를 다시 한 번 확인해봅니다.

apt-get update
apt-get upgrade

가상 환경에서 나가기 위하여 exit 명령을 입력하면 다음과 같이 원래의 Host OS로 프롬프트가 바뀌는 것을 볼 수 있습니다.

root@f45ee37cf476:/# exit
exit
rkttu@dockertest:~$

이제 방화벽 설정을 확인합니다. 기본적으로 Ubuntu는 ufw라는 방화벽 프로그램을 이용합니다. 그리고 Windows Azure 기본 구성 이미지에서는 ufw가 비활성화되어있고, Windows Azure의 방화벽이 외부로부터의 연결을 차단하며, Windows Azure 간 네트워크에는 제한이 없습니다. 아래 명령어를 입력하여 방화벽 상태를 확인합니다.

rkttu@dockertest:~$ sudo ufw status
Status: inactive
rkttu@dockertest:~$

그리고 방금 전 테스트를 위하여 받은 이미지와 그 이미지를 기반으로 실행한 컨테이너가 잘 등록되었는지 확인하여 설치 프로세스를 마무리합니다.

rkttu@dockertest:~$ sudo docker images -a
REPOSITORY          TAG                 ID                  CREATED             SIZE
ubuntu              12.04               8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              latest              8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              precise             8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              12.10               b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
ubuntu              quantal             b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
<none>              <none>              27cf78414709        7 months ago        180.1 MB (virtual 180.1 MB)

rkttu@dockertest:~$ sudo docker ps -a
ID                  IMAGE               COMMAND             CREATED             STATUS              PORTS
f45ee37cf476        ubuntu:12.04        /bin/bash           8 minutes ago       Exit 0

예상한 대로 이미지가 다운로드되어있고, 해당 이미지를 기반으로 /bin/bash 앱에 대한 컨테이너가 생성 후 실행되었으며 종료 코드 0으로 종료되었다는 결과 표가 보입니다.

mono 개발 환경 빠르게 구축하기

mono 개발 환경을 입맛에 맞게 구축하는 방법은 여러가지가 있습니다. 방금 전처럼 base image를 받아서 수작업으로 설치하거나, 그 과정을 서술하는 Dockerfile을 만들어 한 번에 일괄 실행하여 시스템의 설치를 전개하는 방식도 있을 수 있습니다. 그렇지만 docker를 개발 환경으로 이용하기 위해서 취할 수 있는 가장 좋은 방법은 docker Index 사이트에 게시된 최신 이미지를 확인하여 해당 이미지를 직접 로컬 시스템으로 Pull 하는 것입니다.

편의를 위하여 잠시 리눅스 콘솔에서 윈도 화면으로 되돌아온 다음 https://index.docker.io/ 로 접속하여 키워드로 mono를 지정하고 검색합니다.

 

 

Ubuntu 이미지를 이용하여 Mono 3.2.3을 설치하여 배포하는 이미지가 Docker Index에 올라와있습니다. 그 외에도, 닷넷 기반의 SOA 실행 및 개발 환경 구축을 편리하게 할 수 있도록 Service Stack과 연계한 리눅스 이미지도 보입니다. 우리가 사용하려는 것은 rwentzel/ubuntu-mono 이미지이므로 이 이미지의 이름을 기록합니다.

이제 다시 리눅스 콘솔로 되돌아가서 해당 이미지를 로컬 리포지터리로 다운로드하겠습니다. 다음 명령어를 실행하여 동기화를 시작합니다. 해당 이미지는 여러 차례 수정을 거쳐 만들어진 것이기 때문에 다운로드에 다소 시간이 걸리니 조금 오래 기다리셔야 합니다.

sudo docker pull rwentzel/ubuntu-mono

이미지 다운로드가 완료되었다면, 이제 이 이미지를 이용하여 컨테이너를 만들고 정말 안에 mono 개발 환경이 들어있는지 확인해볼 차례입니다. 아래 명령어로 이미지를 우선 확인합니다.

sudo docker images -a

설치한 이미지의 갯수가 매우 많아졌습니다. 그런데 주의할 것이 하나 있습니다. docker rmi 명령으로 이미지를 제거하는 것이 가능하지만, 꼭 필요한 경우가 아니라면 가급적 리포지터리나 TAG가 none으로 설정된 이미지를 임의로 삭제하는 일은 피해야 합니다. 콘솔에서는 잘 드러나지 않지만 이미지는 차이점 보관 방식으로 생성되어있고 일종의 트리 계층을 형성하기 때문입니다. 그래서 docker로 이미지를 만들 때 정말 중요하게 관리되어야 하는 이미지는 REPOSITORY나 TAG에 정확한 속성을 지정하여 쉽게 찾을 수 있도록 해주는 것이 중요합니다.

REPOSITORY             TAG                 ID                  CREATED             SIZE
rwentzel/ubuntu-mono   latest              2e8ec476cfd1        3 weeks ago         12.29 kB (virtual 2.627 GB)
<none>                 <none>              866ee2ba174c        3 weeks ago         12.29 kB (virtual 2.627 GB)
<none>                 <none>              9d58fdd1f145        3 weeks ago         28.67 kB (virtual 2.627 GB)
<none>                 <none>              48d313d93277        3 weeks ago         194.1 kB (virtual 2.627 GB)
<none>                 <none>              a6f3f7d033e8        3 weeks ago         1.151 GB (virtual 2.627 GB)
<none>                 <none>              0063533722a2        3 weeks ago         486.2 MB (virtual 1.476 GB)
<none>                 <none>              563cc9ce1df7        3 weeks ago         43.2 MB (virtual 989.9 MB)
<none>                 <none>              d963202bdca8        3 weeks ago         62.24 MB (virtual 946.7 MB)
<none>                 <none>              fa1d9d247e8a        3 weeks ago         27.46 MB (virtual 884.5 MB)
<none>                 <none>              e01eae29dae1        3 weeks ago         32.86 kB (virtual 857 MB)
<none>                 <none>              eb5606044c61        3 weeks ago         70.23 MB (virtual 857 MB)
<none>                 <none>              519b96c9a701        3 weeks ago         29.08 MB (virtual 786.8 MB)
<none>                 <none>              552c68e56d9a        3 weeks ago         46.38 MB (virtual 757.7 MB)
<none>                 <none>              17f3c8064eb6        3 weeks ago         25.59 MB (virtual 711.3 MB)
<none>                 <none>              0cb9cc3fc7b8        3 weeks ago         144.5 MB (virtual 685.7 MB)
<none>                 <none>              898780b65670        3 weeks ago         12.29 kB (virtual 541.2 MB)
<none>                 <none>              de6790a79ec8        3 weeks ago         158.2 MB (virtual 541.2 MB)
<none>                 <none>              29002fa46318        3 weeks ago         24.58 MB (virtual 383 MB)
<none>                 <none>              270acf4d2474        3 weeks ago         96.52 MB (virtual 358.5 MB)
<none>                 <none>              1d4aaea09576        3 weeks ago         81.83 MB (virtual 261.9 MB)
ubuntu                 12.04               8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 latest              8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 precise             8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 12.10               b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
ubuntu                 quantal             b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
<none>                 <none>              27cf78414709        7 months ago        180.1 MB (virtual 180.1 MB)

위의 rwentzel/ubuntu-mono 이미지를 사용하여 내부에 들어있는 /bin/bash를 컨테이너로 실행하기 위하여 아래 명령을 실행합니다.

sudo docker run -i -t rwentzel/ubuntu-mono /bin/bash

그러면 다음과 같이 프롬프트가 변경되는 것을 볼 수 있습니다.

rkttu@dockertest:~$ sudo docker run -i -t rwentzel/ubuntu-mono /bin/bash
root@f739c613d0ae:/#

이제 간단한 C# 소스 코드를 작성하여 프로그램으로 컴파일하고 잘 작동하는지 확인해보겠습니다. vi를 사용해도 좋고, vi 사용에 익숙하지 않은 경우 아래와 같이 명령을 실행하여 pico/nano 에디터를 추가 설치할 수 도 있습니다.

apt-get install nano

Hello.cs 라는 소스 코드를 원하는 에디터로 아래와 같이 작성합니다.

using System;
using System.Linq;
using System.Collections.Generic;

public static class Program {
        [STAThread]
        public static void Main(string[] args) {
                var count = args.Count();
                List<string> options = args.Where(x => x.StartsWith("-")).ToList();
                Console.Out.WriteLine("Hello, World!");
                Console.Out.WriteLine("Total Args: {0}, Option Args: {1}", count, options.Count);
        }
}

Mono 3.2.3은 LINQ와 제네릭을 모두 잘 지원하므로 위의 코드를 아래와 같이 컴파일하였을 때 문제없이 컴파일이 완료될 것입니다.

mcs Hello.cs

그리고 JVM과 마찬가지로 Mono VM을 실행하여 컴파일한 어셈블리 파일을 실행해봅니다.

root@f739c613d0ae:/# mono Hello.exe
Hello, World!
Total Args: 0, Option Args: 0
root@f739c613d0ae:/# mono Hello.exe a b c
Hello, World!
Total Args: 3, Option Args: 0
root@f739c613d0ae:/# mono Hello.exe a b -c
Hello, World!
Total Args: 3, Option Args: 1

의도한 대로 LINQ와 제네릭을 잘 받아서 처리하고 있습니다.

mono 실행 속도 개선하기

최신 버전의 mono는 실행 속도를 개선하기 위하여 가비지 컬렉터를 새롭게 디자인하였고, 전처리 컴파일을 미리 수행하는 방법을 제공합니다. 특히, Microsoft .NET Framework와 마찬가지로 GAC에 대해 ngen을 수행하는 것과 비슷하게 AOT 컴파일을 미리 수행하도록 명령어를 한 번 실행해주면 실행 속도 개선에 큰 도움이 됩니다. 아래 명령어를 실행하여 GAC 내부의 모든 어셈블리에 대해 AOT 컴파일을 실행합니다.

for i in /usr/local/lib/mono/*/mscorlib.dll; do mono --aot $i; done
for i in /usr/local/lib/mono/gac/*/*/*.dll; do mono --aot $i; done

위의 이미지 환경 내에서의 mono 설치 경로는 /usr/local에 있으므로 경로는 mono 실행 파일의 위치를 which 명령으로 확인하여 적절하게 변경해야 합니다. 참고로 GAC에 대한 AOT 컴파일은 시간이 오래 걸릴 수 있으며, 이 버전의 가상 환경에서는 .NET Framework 1.0이나 1.1 기준으로는 개발이 불가능하므로 버전을 업그레이드 하거나 base image로부터 구 버전의 mono를 설치하도록 수동 구성해야 합니다.

그리고 개별 어셈블리에 대한 AOT 컴파일은 다음과 같이 실행할 수 있으며, 실행 결과로 .so 파일이 생성되므로 AOT 컴파일의 효과를 위하여 항상 같은 위치에 배포될 수 있도록 배포합니다.

mono --aot Hello.exe
mono Hello.exe

마무리

지금까지 Windows Azure Linux VM에서 docker를 이용한 mono 개발 환경의 구축 방법을 살펴보았습니다. 언제든 원하는 때에 즉시 Linux VM을 만들 수 있다는 것 말고도, 한 번 만든 VM을 손상시키지 않으면서 환경을 독립적으로 구성할 수 있도록 하는 환경 상의 완결성을 제공하는 docker를 이용함으로서 최상의 리눅스 개발 환경을 체험할 수 있게 된 것은 참 좋은 일입니다.

그리고 한 가지 더 덧붙이면, docker를 이용하여 컨테이너를 만들 때 -v 스위치를 사용하여 호스트 파일 시스템과 링크를 연결할 수 있으므로, 컨테이너 셸 상에서 만든 파일을 쉽게 호스트로 반출하거나 반입할 수 있습니다. -p 스위치는 가상 NAT를 통하여 포트 리디렉션을 할 수 있는 방법을 제공하며, 양쪽 스위치의 자세한 사용법은 http://blog.docker.io/2013/07/docker-0-5-0-external-volumes-advanced-networking-self-hosted-registry/ 의 내용을 확인하기 바랍니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2013. 10. 18. 22:00

C#에서 프로그램 코드를 전개하는 방법은 상대적으로 다른 언어에 비해 자유도가 높은 편입니다. 그렇지만 이런 기능들을 잘 모를 경우 코드 품질이 낮아질 수도 있고, 이해하기 어려운 코드가 되기 쉽습니다. 이러한 문제점을 극복할 수 있는 실용적 코드 작성 팁 몇 가지를 공유해보도록 하겠습니다.

양보하기 어려운 변수 작명을 만났다면?

코딩을 하다보면 그런 경우가 있습니다. 밖으로 드러내는 것이든, 안에서 사용하는 것이든 코드의 의도를 정확히 설명하기 위해서 양보하기 어려운 변수 작명을 고수해야 할 때가 있습니다. 이럴 때에는 고민하지 말고, 변수명 앞에 @ 기호를 지정해주기만 하면 됩니다. C#의 주요 키워드들 (상황에 따라 예약되는 키워드는 이 문제를 만날 가능성이 적습니다.) 상당수를 이 방법을 사용하여 약간 바꾸어 변수 작명으로 채용하는 것이 얼마든지 가능합니다.

string
    @abstract = string.Empty,    @as = string.Empty,    @base = string.Empty,    @bool = string.Empty,
    @break = string.Empty,    @byte = string.Empty,    @case = string.Empty,    @catch = string.Empty,
    @char = string.Empty,    @checked = string.Empty,    @class = string.Empty,    @const = string.Empty,
    @continue = string.Empty,    @decimal = string.Empty,    @default = string.Empty,    @delegate = string.Empty,
    @do = string.Empty,    @double = string.Empty,    @else = string.Empty,    @enum = string.Empty,
    @event = string.Empty,    @explicit = string.Empty,    @extern = string.Empty,    @false = string.Empty,
    @finally = string.Empty,    @fixed = string.Empty,    @float = string.Empty,    @for = string.Empty,
    @foreach = string.Empty,    @goto = string.Empty,    @if = string.Empty,    @implicit = string.Empty,
    @in = string.Empty,    @int = string.Empty,    @interface = string.Empty,    @internal = string.Empty,
    @is = string.Empty,    @lock = string.Empty,    @long = string.Empty,    @namespace = string.Empty,
    @new = string.Empty,    @null = string.Empty,    @object = string.Empty,    @operator = string.Empty,
    @out = string.Empty,    @override = string.Empty,    @params = string.Empty,    @private = string.Empty,
    @protected = string.Empty,    @public = string.Empty,    @readonly = string.Empty,    @ref = string.Empty,
    @return = string.Empty,    @sbyte = string.Empty,    @sealed = string.Empty,    @short = string.Empty,
    @sizeof = string.Empty,    @stackalloc = string.Empty,    @static = string.Empty,    @string = string.Empty,
    @struct = string.Empty,    @switch = string.Empty,    @this = string.Empty,    @throw = string.Empty,
    @true = string.Empty,    @try = string.Empty,    @typeof = string.Empty,    @uint = string.Empty,
    @ulong = string.Empty,    @unchecked = string.Empty,    @unsafe = string.Empty,    @ushort = string.Empty,
    @using = string.Empty,    @virtual = string.Empty,    @void = string.Empty,    @volatile = string.Empty,
    @while = string.Empty,    @__arglist = string.Empty,    @__refvalue = string.Empty,    @__makeref = string.Empty,
    @__reftype = string.Empty;

위의 코드를 컴파일하였을 때 사용하지 않는 변수라는 경고를 제외하고 컴파일에는 이상이 없음을 확인할 수 있습니다.

String.Join 메서드와 같이 시작과 끝에 구분 기호 (Delimiter)가 붙지 않는 문자열 더하기를 수행하는 방법

간혹 그런 경우가 있습니다. 기존 컬렉션으로부터 새로운 컬렉션을 만들면서 시작이나 끝에는 구분자 기호나 원소를 붙이지 않고 중간에만 원하는 내용을 삽입하고 싶을 때가 있는데, 이런 경우 인덱스를 사용하려고 하거나 굳이 배열로 변환하려는 노력을 하게 될 수 있는데, 이는 별로 바람직하지 않습니다. 대신, IEnumerator 인터페이스와 if 문 한번, while 문 한 번으로 나누어 반복문을 써주기만 하면 쉽게 문제가 해결됩니다. 참고로, C#의 foreach 문은 IEnumerator 인터페이스에 대한 포장입니다.

String.Join 메서드와 같은 기능을 하는 메서드를 만들기 위하여, 아래와 같이 코드를 작성할 수 있을 것입니다.

static string Join<T>(string delim, IEnumerable<T> cols)
{
    StringBuilder buffer = new StringBuilder();
    IEnumerator<T> @enum = cols.GetEnumerator();

    if (@enum.MoveNext())
        buffer.Append(@enum.Current);

    while (@enum.MoveNext())
    {
        buffer.Append(delim);
        buffer.Append(@enum.Current);
    }

    return buffer.ToString();
}

위의 메서드를 이용하여 문자열의 각 문자들 사이에 쉼표를 붙이는 것을 쉽게 처리할 수 있습니다.

string modified = Join<char>(", ", "Hello guys!");
Console.WriteLine(modified);

H, e, l, l, o,  , g, u, y, s, !

현재 컴퓨터를 기준으로 언제나 유일한 값을 빠르게 만들어내는 방법

완벽한 의미에서의 유일성은 상당히 많은 Factor를 반영해야만 그 성격을 보장할 수 있습니다. 그러나, 대개의 경우 지구상에서 유일한 값을 만들어내는것 보다는, 현재 실행 중인 컴퓨터나 데이터베이스를 기준으로 유일한 값을 만들어내는 것 정도만으로도 충분히 목표를 달성할 수 있습니다. 이럴 경우에도 매번 GUID를 생성하거나, 데이터베이스의 Identity Seed를 사용하는 것은 비용이 많이 들고, 특히 데이터베이스의 Identity Seed는 데이터베이스마다 커스터마이징 정도의 차이가 있지만 대개는 생성된 값을 클라이언트 측에서 확인하기 어렵기 때문에 Round Trip을 유발합니다.

지금 소개하는 방법은 이러한 문제점을 극복하면서도 매우 빠른 실행 속도를 보장하는 유일 값 생성 방법입니다. 바로, 현재 시스템의 Tick Count를 그대로 이용하는 방법입니다. Tick Count는 100 나노초 단위이므로 일정한 수준에서의 유일성을 보장하기에는 충분한 밀도가 됩니다. 그리고 생성하는 값의 데이터 형식이 64비트 정수이므로 범위 또한 충분히 넓습니다.

long uniqueVal = DateTime.UtcNow.Ticks;

위와 같이 값을 얻어올 수 있고, 위의 값을 데이터베이스에 레코드를 추가할 때 힌트용으로 사용하는 열에 지정하면 삽입 즉시 조회할 수 있는 고유한 값이 되므로 프로그램 로직 개선에 많은 도움이 됩니다.

조건문의 분기를 임의로 결정하도록 만드는 방법

Modular Operator (%)의 기능과 특징을 아신다면 당연하게 받아들일 수 있는 내용이지만, 이런 특이한 상황에 대해서 유용하게 쓰일 수 있습니다. switch나 if/else 등의 조건문의 분기 자체를 임의 결정할 수 있도록 시뮬레이션해야 하는 상황에서 난수 값이 구체적으로 어떤지를 검색하거나 값을 한정하기 위해서 제약하는 것보다 더 손쉽고 이해하기 편한 시뮬레이션 방식을 % 연산자를 이용하여 쉽게 구현할 수 있습니다.

string modified = Join<char>(", ", "Hello guys!");
Random random = new Random();
char x = '\0';

for (int i = 0; i < 100; i++)
{
    switch (Char.ToUpperInvariant(modified[random.Next() % modified.Length]))
    {
        case 'H': x = 'i'; break;
        case 'E': x = 'f'; break;
        case 'L': x = 'm'; break;
        case 'O': x = 'p'; break;
        case ' ': x = '?'; break;
        case 'G': x = 'h'; break;
        case 'U': x = 'v'; break;
        case 'Y': x = 'z'; break;
        case 'S': x = 't'; break;
        case '!': x = '@'; break;
        case ',': x = '.'; break;
        default: x = ' '; break;
    }
    Console.Write(x);
}
Console.WriteLine();

위와 같이 % 기호 다음에 오는 operand로 컬렉션의 길이나 배열의 길이를 지정해주면, 배열의 요소를 임의로 고를 수 있어서 활용폭이 더 넓어집니다.

소스 코드에 특수문자나 CJK 문자를 안전하게 기록하고 다른 사람과 공유하는 방법

드문 경우이지만, 주석 이외에 프로그램의 실행에 실제로 영향을 줄 가능성이 있는 문자열이 영어나 숫자, 혹은 ASCII 범위의 문자가 아닐 경우 다른 환경이나 언어 구성에서 소스 코드 파일을 편집한 후 되돌려받았을 때 문자열이 깨지는 일이 자주 있습니다. 지금 이야기하는 방법은 사실 실용적이지는 않지만, 정말 중요하게 지켜야 할 리소스라면 지금 소개하는 방법을 이용하여 번거롭지만 확실하게 문자열 데이터를 지키는 것도 가능하니 한 번 고려해보시는 것도 좋을 것 같습니다.

예를 들어, 중국어 문자열 "我国屈指可数的财阀。" (우리나라 굴지의 재벌)이 소스 코드에 문자열로 저장되어있고 이 문자열을 인코딩 문제로부터 보호하기 위해서, 위의 문자열을 복사하여 LINQPAD에 아래의 인라인 식에 치환하여 넣습니다. (LINQPAD는 http://www.linqpad.net 에서 다운로드합니다.)

String.Join(", ", "paste here".Select(x => "0x" + ((int)x).ToString("X4")))

그러면 다음과 같은 결과가 나타납니다.

0x6211, 0x56FD, 0x5C48, 0x6307, 0x53EF, 0x6570, 0x7684, 0x8D22, 0x9600, 0x3002

이제 위의 내용을 new String(new char[] { 0x6211, 0x56FD, 0x5C48, 0x6307, 0x53EF, 0x6570, 0x7684, 0x8D22, 0x9600, 0x3002
 }); 와 같이 바꾸어서 소스 코드에 저장하면 실행 시 원래 문자열로 복원되면서도, 소스 코드 상의 문자열이 훼손될 걱정을 하지 않아도 됩니다. 단, 이 경우 소스 코드의 내용만으로는 실제로 어떤 문자열인지 파악하기 어려워진다는 장점이자 단점이 동시에 발생합니다. 장점으로는, 일종의 난독처리가 이루어진 셈이며, 단점으로는, 관리가 어려워진 셈이기 때문입니다.

조건문을 어떻게 관리하십니까?

조건문을 어떻게 작성하고 관리하는가에 대한 문제는 개인의 취향과 논리에 따라 매우 다양한 패턴이 존재합니다. 그러나 경험 상, 코드가 간결할 수록 유리하다는 것은 보편적으로 통하는 진리입니다. 개인적인 경험으로 유추해볼 때, 코드의 간결함은, 조건문이나 분기가 얼마나 단일 메서드 내에서 잘 관리되고 있는가에 대한 이야기로 바꾸어 말할 수도 있을 것 같습니다.

이런 방침에 따라, C나 C++ 스타일의 언어들은 중첩해서 사용하는 중괄호의 여닫음 횟수가 늘어날수록 복잡도가 크게 증가합니다. C#도 예외는 아닌데, 이런 이유때문에 저는 스스로 조건문이나 코딩 스타일을 나름의 원칙을 정하여 사용하고 있습니다.

우선, 단위 메서드를 작성하기에 앞서서 조건 검사를 할 때에는 부정적인 시나리오부터 먼저 확인합니다. 다음의 예를 들어보도록 하겠습니다.

public int Divide(int a, int b, out int z)
{
    z = 0;

    if (b != 0)
    {
        z = a % b;
        return a / b;
    }
    else
    {
        throw new DivideByZeroException();
    }
}

무난한 코드입니다. 하지만, 제가 볼 때에는 중괄호를 여닫을 필요가 없어보이는 코드입니다. 아래와 같이 정리하면 어떨까요?

public int Divide(int a, int b, out int z)
{
    z = 0;

    if (b == 0)
        throw new DivideByZeroException();

    z = a % b;
    return a / b;
}

요지는 이렇습니다. 이 메서드에서 우려하는 최악의 상황은 사실 매개 변수 b가 0으로 들어오는 경우입니다. 확실히 문제가 있음을 제기해야 한다면 이 경우를 따로 다루어야 하겠지요. 이를 위해서 b가 0으로 지정되었는지를 검사하여 메서드의 시선으로부터 그런 상황을 제거합니다. 그러면 남는 일은 오로지 나눗셈에 의한 나머지와 몫을 구하는 일이 됩니다. (참고로 z = 0을 서두에 지정한 것은 out 매개 변수에 대한 제약 때문에 그렇습니다. 메서드 본문 밖을 return에 의해서이든 throw에 의해서이든 빠져나가기 전에 반드시 out 매개 변수의 값은 초기화를 해야 합니다.)

그리고 중괄호를 많이 열게 될 개연성이 있는 또 다른 유형은 바로 IDisposable 변수를 다루기 위한 using 블럭입니다. 아래의 경우를 살펴보도록 하겠습니다.

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

using (WinFormModule mod = new WinFormModule(args.FirstOrDefault()))
{
    using (StandardKernel kern = new StandardKernel(mod))
    {
        Application.Run(kern.Get<ApplicationContext>());
        mod.FormName = "Form3";
        Application.Run(kern.Get<ApplicationContext>());
        mod.FormName = "Form2";
        Application.Run(kern.Get<ApplicationContext>());
    }
}

두 번 열 필요가 없어보이는데도 두 번이나 열었습니다. 위의 코드는 아래와 같이 깔끔하게 정리할 수 있습니다.

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

using (WinFormModule mod = new WinFormModule(args.FirstOrDefault()))
using (StandardKernel kern = new StandardKernel(mod))
{
    Application.Run(kern.Get<ApplicationContext>());
    mod.FormName = "Form3";
    Application.Run(kern.Get<ApplicationContext>());
    mod.FormName = "Form2";
    Application.Run(kern.Get<ApplicationContext>());
}

IDisposable.Dispose 메서드가 항상 모든 것을 앗아가기만 하는 것은 아니다.

직전에서 다룬 using과 IDisposable에 대한 흔한 오해는, IDisposable 형식의 참조를 using 문과 함께 사용할 때에는 반드시 using 문 내부에서만 선언해야 한다는 것입니다. 그러나 이 경우 문제가 발생하는 일이 있습니다. 아래의 경우를 살펴보도록 하지요.

using (MemoryStream memStream = new MemoryStream())
using (FileStream fileStream = File.OpenRead(@"WinFormDI.exe.config"))
{
    fileStream.CopyTo(memStream, 64000);
}
// memStream에 들어있는 내용은 어디서 찾을 수 있습니까?

주석 처리한 부분에서 memStream 변수를 접근해야 하는 이유는 간단합니다. 혹시 MemoryStream의 구현 상에 있을지 모르는 버퍼링 (물론 실제로는 그럴리 없습니다만)을 모두 끝내고 실제 스트림에 쓰여진 상태를 확보하고 싶은데, 막상 MemoryStream의 존재 자체를 알 수 없는 외곽 블록에서는 실행이 다 끝나고도 데이터에 접근할 수 없는 우스운 상황이 생깁니다. 위의 코드를 아래와 같이 고치면 의도대로 잘 작동합니다.

MemoryStream memStream;
using (memStream = new MemoryStream())
using (FileStream fileStream = File.OpenRead(@"WinFormDI.exe.config"))
{
    fileStream.CopyTo(memStream, 64000);
}
byte[] buffer = memStream.ToArray();
Console.WriteLine(Convert.ToBase64String(buffer));

사실, 위와 같이 memStream 변수를 밖으로 빼내어도 이상이 없습니다.

memStream은 using 블록 밖에서는 당연히 더 이상 데이터를 기록할 수 없도록 파기된 상태입니다. 하지만, 앞에서 이야기했듯이 IDisposable.Dispose 메서드가 모든 것을 소거하지는 않습니다. 즉, MemoryStream 내부의 byte 배열 버퍼는 여전히 유효합니다. 따라서, 그것의 참조를 Dispose 메서드가 불린 이후라도 가져와서 BASE64 인코딩으로 파일 내용을 인코딩하여 문자열로 바꾸려 했던 코드를 잘 실행할 수 있습니다.

바꾸어 말하면, 아래의 코드도 유효합니다.

MemoryStream memStream = new MemoryStream();
using (memStream)
using (FileStream fileStream = File.OpenRead(@"WinFormDI.exe.config"))
{
    fileStream.CopyTo(memStream, 64000);
}
byte[] buffer = memStream.ToArray();
Console.WriteLine(Convert.ToBase64String(buffer));

객체의 생성을 using 문 밖에서 처리하고, 사용하고픈 참조를 담고 있는 변수명을 지칭하기만 해도 같은 의미가 됩니다. using 문 밖으로 나가면 당연히 memStream은 Dispose 메서드가 호출된 상태가 됩니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Azure Storage/Database2013. 10. 13. 21:00

Windows Azure Storage는 익히 잘 알려져 있는대로 Object Storage의 역할을 충실히 잘 수행할 수 있는 매우 높은 기능 수준을 가진 Cloud 기반 BLOB Storage입니다. 단순히 웹 기반의 파일 업로드와 다운로드만을 하는 컨테이너들의 집합으로 생각하기 쉽지만, 잘 활용하면 어렵지 않게 전송 보안과 간단한 DRM까지 한번에 달성할 수 있습니다. 그리고 이런 고급 기능 외에도 저수준의 입출력도 지원합니다.

덕분에 매우 유용한 조합을 이용할 수 있는데, 바로 SharpZipLib의 ZipFile 클래스와 Windows Azure Storage Library의 CloudBlobStream 클래스를 결합하는 것입니다. 방금 이야기한 저수준의 입출력을 위해서는 HTTP의 Byte Range Request와 비슷한 입출력 방법이 가능해야 하고 이를 지원하는 Stream 클래스가 있어야 하는데 이를 구현하는 것이 바로 http://msdn.microsoft.com/ko-kr/library/windowsazure/dd179440.aspx 에서 소개하는 x-ms-range 요청 헤더 (http://msdn.microsoft.com/ko-kr/library/windowsazure/ee691967.aspx)와 이를 사용하는 CloudBlobStream 클래스입니다.

우선 Windows Azure SDK Storage Library의 버전이 최신 버전으로 업그레이드가 되어야 합니다. NuGet 패키지 관리자를 최신 버전으로 업그레이드한 다음, 아래 그림과 같이 시험해보려는 프로젝트에 대해서 Windows Azure Storage 라이브러리 패키지를 설치합니다. 단, 이 작업을 하기에 앞서서 해당 프로젝트의 기준 .NET Framework 버전을 적어도 .NET Framework 4 Client Profile 이상으로 설정해야 합니다.

 

패키지를 설치한 다음, 아래와 같이 간단한 코드를 작성해보도록 하겠습니다. 강조표시한 부분은 환경에 맞게 변경해주셔야 하는 부분들입니다.

CloudStorageAccount account = new CloudStorageAccount(
    new StorageCredentials("accountName", "accessKey"),
    false);

var client = account.CreateCloudBlobClient();
var containerRef = client.GetContainerReference("example");

var blobRef = containerRef.GetBlockBlobReference("SampleDoc.docx");
var blobStreamAsync = blobRef.OpenReadAsync();
blobStreamAsync.Wait();

using (Stream srcStream = blobStreamAsync.Result)
using (ZipFile file = new ZipFile(srcStream))
{
    foreach (ZipEntry entry in file)
    {
        if (!entry.Name.StartsWith("word/media", StringComparison.OrdinalIgnoreCase))
            continue;

        Console.WriteLine("Extracting {0}...", entry.Name);

        using (Stream innerFile = file.GetInputStream(entry))
        using (Stream localFile = File.OpenWrite(Path.GetFileName(entry.Name)))
        {
            innerFile.CopyTo(localFile);
        }
    }
}

위의 코드는 Windows Azure Storage에 올라가있는 Microsoft Word 문서 파일 안에 들어있는 미디어 파일들의 목록을 조회하여 전체 Word 문서를 다운로드하지 않고 즉시 미디어 파일들을 추출하는 예제 프로그램 코드입니다. 여기서 중요한 것은 최소한의 I/O 요청만을 이용하여 ZIP 파일을 마치 로컬 하드 디스크에서 다루던 것과 동일하게 그 안에 압축되어있던 파일을 자유롭게 액세스할 수 있다는 점입니다.

아래는 실행 결과입니다.

 

 

그렇다면 궁금한 것이 있습니다. 실제 네트워크 트래픽은 어떻게 주고 받는 것일까요? Fiddler를 통하여 실제 네트워크 트래픽 상태를 확인해보도록 하겠습니다.

 

예상대로 Windows Azure BLOB Storage 서버에서 우리가 다루려는 BLOB 객체에 대한 요청이 이루어지고 있으며, 최초에는 파일에 대한 정보 확인을 위하여 보낸 요청에 대한 응답으로 200 코드를 반환하였습니다. 

그리고 후속 요청으로 특정 Byte Range에 대한 데이터만을 보내는 요청이 SharpZipLib의 ZipFile 클래스가 내부 스트림에게 Seek/Read 요청을 보내게 되어 발생하게 되고, 이것은 다시 Windows Azure Storage REST API에 맞게 번역되어 실제 HTTP 요청으로 바뀌어 전달됩니다. 그 결과 여기에 대한 응답으로 HTTP 206 응답 코드와 함께 특정 구간의 데이터에 대한 정보와 함께 바이너리 데이터가 다운로드됩니다.

뒤이어 나오는 요청들은 전부 이와 같은 형태로 Partial Request와 Partial Response로 구성되어있으며, BLOB 데이터가 변경되지 않는 한 계속 유효하므로 이렇게 수신한 데이터는 내부적으로 계속 재사용됩니다. 또한, 일정 단위의 Chunk를 내려받는 것이므로 너무 빈번하게 요청이 발생하지 않도록 트래픽에 대한 조절도 잘 되고 있다는 것을 알 수 있습니다.

결론

이 기법은 HTTP 표준을 기반으로 동작하는 것이므로, 제대로 구현이 되어있기만 하다면, Dependency Injection이나 Inversion of Control 등의 기법을 통하여 여러 Cloud Object Storage 상의 파일들을 마치 로컬에 있는 자원을 가져다 사용하는 것과 같이 투명하게 관리할 수 있습니다. 저수준의 I/O가 가능하므로 여기서 예로 든 ZIP 파일에 대한 부분 요청 이외에도 동영상이나 미디어 스트리밍 등에서도 활용할 수 있는 여지가 많고, 무엇보다도 중요한 것은 이 모든 요청이 REST API 이기 때문에 여전히 Windows Azure Storage의 컨테이너나 BLOB에 대한 보안 정책이나 Shared Access Signature 등의 요소와 유연하게 연동되므로, 리소스에 대한 시간 제한을 통하여 인프라의 능력을 이용한 DRM을 자연스럽게 구현할 수 있고, 중간 전송 매개체로 HTTPS를 사용하도록 결정하면 송수신 보안도 자동으로 해결되므로 유용한 면이 있습니다.

여기서 예로 든 Microsoft Word 문서의 활용은 기본이고, 만약 여러분이 필요로 하시는 ZIP 컨테이너 기반의 파일 I/O 작업을 지금 여기에서처럼 클라우드 상의 object storage 상으로 옮겨가서 작업하는 것도 가능하니 많이 활용하셔서 도움을 얻으시기를 바랍니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2013. 10. 12. 21:00

요즈음은 Infrastructure as a Service를 이용하여 VM의 Mobility를 통한 클라우드 서비스 사업자 간 이동 및 이전이 요즈음 주목 받고 있습니다. 익히 잘 알려진대로, Windows Azure의 경우 사설 혹은 기업 데이터센터 구축을 위하여 사용하는 Hyper-V 환경의 가상 PC를 Windows Azure로 진출시키거나 역으로 가져오는 등의 기능은 상당히 잘 알려져 있어서 많이들 이용하고 계실 것 같습니다.

그런데 Platform as a Service는 이런 부분에서 확실한 이동성을 보장하기 많이 어렵다는 것이 문제가 됩니다. 특히 비즈니스 로직을 내부에 많이 포함하고 있는 클라우드 응용프로그램일 수록 해당 클라우드 서비스에 종속적이기 때문에 클라우드 사업자간 이동이 훨씬 더 어렵습니다. 물론, 이를 극복하기 위하여 Dependency Injection이나 Inversion of Control을 이용하여 클라우드 사업자들이 제공하는 SDK의 공통 분모를 추출하여 그 대상으로 삼는 것도 가능한 전략이기는 하겠습니다만 사실 쉽지 않습니다.

좀 더 현실적이고 직관적인 방법이 없을까 많이들 고민하시는 데에 어느정도 도움이 될까 하여 한 가지 방안을 제시해 보겠습니다. 바로 ASP.NET과 Web Deploy를 이용한 개발입니다.

왜 ASP.NET과 Web Deploy를 사용하는가 - ASP.NET을 사용할 수 있는 환경의 다양성

PHP, Node.js, Ruby on Rails 등 웹 세계에서 유명한 프레임워크나 개발 환경이 많이 있습니다만 상대적으로 ASP.NET은 늘 저평가되어있습니다. 특히 ASP.NET을 버전 1.0~2.0 시절의 Web Form과 연결하여 선입견을 가지는 경우가 무척 많습니다. 그렇지만 이전부터 필자가 계속 강조해왔던 NuGet, Web Developer Express 등 다양한 OSS에 친화적인 기술들과의 결합과 ASP.NET MVC의 등장은 이 선입견에 묻혀서 거의 알려지지 않은 경우가 많습니다. (특히 국내에서는 더욱 그렇지요.)

그러나 이런 와중에 생각보다 ASP.NET의 입지는 넓습니다. Microsoft 기술과 가장 친화적이지 않을 것 같은 Amazon의 BeansTalk가 ASP.NET Web Deploy 패키지를 사용한 PaaS 배포 기법을 전면에서 지원하고 있으며, 사용 중인 환경이 Linux인 경우 다양한 방법으로 Mono ASP.NET 런타임과 결합하여 ASP.NET 웹 사이트를 호스팅할 수 있습니다. 그리고 Windows Azure는 Web Site와 Cloud Service를 이용하여 각각 웹 사이트를 호스팅할 수 있는 방법을 제공합니다. 멀리 가지 않아도 시중에 나와있는 웹 호스팅 서비스는 모두 FTP를 통한 ASP.NET 웹 사이트 배포까지 지원합니다. 수단은 얼마든지 많고, 남는 것은 전략의 수립에 관한 부분만이 남는 셈입니다.

왜 ASP.NET과 Web Deploy를 사용하는가 - 단일 코드 베이스 유지

ASP.NET을 이용하여 독립적으로 실행할 수 있는 Web Application Project를 만들어두면, Public Cloud만을 이용해서 서비스를 구축하였을 때 간간히 문제가 발생하는 특정 데이터센터, 혹은 특정 클라우드 서비스의 장애로 인해서 전체 서비스가 중단되는 사고로부터 좀 더 자유로워질 수 있습니다.

여러 위치에 분산된 응용프로그램을 배포하는 것은 기본적으로 매우 어려운 작업입니다. 그리고 이 어려운 작업의 난해함을 지수승으로 더 복잡하게 만드는 것은 바로 클라우드 서비스 사업자 간의 고유한 기능 집합 때문입니다. 이 문제를 해결하기 위해서는 사업자 간 이해 관계가 비교적 덜 연결되어있으면서도 구성의 차이가 거의 없는 기술을 택하는 것이 바람직한데, 여기에 들 수 있는 후보로는 ASP.NET을 포함하여 다수의 기술들이 있습니다. 그 중에서도 오늘 소개하려는 ASP.NET은 수준 높은 IDE 지원과 다양한 OSS 프로젝트와의 연계가 가능하기 때문에 유용한 점이 많습니다.

왜 ASP.NET과 Web Deploy를 사용하는가 - 배포 시의 문제점을 최소화

기능 상의 유용함 이외에도, 스스로 스케줄링을 해야 하는 Daemon Type의 서비스 프로세스가 아닌 RPC나 REST 형태의 시스템일 경우 ASP.NET을 사용하여 프로그래밍하고 Visual Studio의 지원을 받을 때의 큰 이점이 하나 더 있습니다. 바로 배포 시의 문제점을 최소화할 수 있다는 것입니다.

ASP.NET Web Deploy 및 관련 프로세스들은 패키지로 만들 때, 종속성에 관련된 모든 문제를 예방할 수 있도록, 웹 프로젝트와 연관성이 있는 모든 종속성 관계 상의 파일을 자동으로 복사하여 패키지에 포함시키는 정책을 사용합니다. 이러한 특징 때문에, ASP.NET MVC나 Razor의 최신 버전이 처음 .NET Framework가 배포된 이후에 바뀌더라도 최신 기능을 사용하면서도 배포 상의 DLL 버전 차로 인한 문제가 발생할 가능성이 높지 않습니다.

클라우드 서비스 별 배포 방법 - Amazon BeansTalk

우선 Amazon BeansTalk의 경우를 예로 들어보도록 하겠습니다. Amazon BeansTalk는 Visual Studio에 추가 기능으로 설치할 수 있는 Toolkit을 통한 배포/모니터링과 Management Console (Web)를 통한 패키지 파일 업로드 방식의 배포를 모두 지원합니다.

웹 프로젝트를 만들고, 아래 화면과 같이 배포 프로필을 Web Deploy 패키지로 만드는 것으로 설정한 다음, 지정된 위치에 패키지 파일을 만들도록 합니다. 패키지 파일은 ZIP 파일입니다.

만들어진 파일을 AWS Management Console로 이동하여 다음과 같이 업로드를 시작합니다.

업로드 완료 후 배포 프로세스가 시작되면 다음과 같이 진행 상황이 표시됩니다.

잠시 기다리면 다음과 같이 정상 배포가 완료되었음을 알리는 화면이 표시됩니다.

그리고 로드 밸런서 앞으로 부여된 FQDN 앞으로 접속하면 다음과 같이 로컬 개발 환경에서 개발할 때와 비슷한 응용프로그램이 실제 클라우드 서비스에서 실행 중인 것을 볼 수 있습니다.

BeansTalk의 멤버 노드로 참여하는 VM 인스턴스들이 있다면 여느 PaaS 플랫폼들과 마찬가지로 모든 배포를 자동화하여 처리하므로 배포 프로세스의 상당 부분을 쉽게 완료할 수 있습니다.

클라우드 서비스 별 배포 방법 - Azure Web Site

이번에는 Azure Web Site를 위한 배포 방법을 살펴보도록 하겠습니다. Visual Studio에서 앞의 경우와 마찬가지로 웹 게시 대화 상자를 띄운 다음, 가져오기 버튼을 클릭합니다.

 

처음 이 기능을 실행하면 배포 프로필을 한 번도 사용한 적이 없으므로 아래와 같이 빈 목록이 나타나게 됩니다. Windows Azure 구독 추가 링크 (드롭 다운 상자 아래)를 클릭합니다.

 

Windows Azure 구독 가져오기 대화 상자가 나타나면 구독 파일 다운로드 링크를 클릭합니다.

 

로그인 페이지에서 로그인을 완료하면 로그인한 Microsoft ID와 연결된 모든 Windows Azure Web Site 프로필 정보를 취합한 통합 프로필 파일의 다운로드를 시작하게 됩니다.

 

앞의 대화 상자로 되돌아와서, publishsettings 파일을 다시 지정하고, 가져오기 버튼을 클릭합니다. 잠시 기다리면 지정한 Live ID로 만든 여러 가입 상의 여러 Azure Web Site에 대한 정보가 한번에 나타납니다. 관리자 권한만을 가지고 있었다고 할지라도 여기에 한번에 나타나므로 손쉽게 배포를 진행할 수 있고, 이 과정 이후에 따로 웹 사이트를 새로 만들더라도 다시 이 대화 상자만 띄우면 목록이 새로 업데이트됩니다.

 

이와 같이 설정을 마무리하고 연결에 이상이 없는지 유효성 검사 버튼을 클릭하여 녹색 체크 마크가 나타나면 배포를 위한 연결이 성립된 것으로, 계속 진행해도 좋습니다.

 

클라우드 서비스 별 배포 방법 - Azure Cloud Service / Web Role

기존에 만들어진 ASP.NET 웹 사이트 프로젝트를 Web Role로 변환하여 내보내는 작업은 생각보다 어렵지 않습니다. 기술적인 고려 사항을 제외하면, Web Role용 프로젝트는 Beans Talk의 경우와 마찬가지로 Azure Cloud Service만을 위해서 무언가 꼭 추가해야 하거나 심각하게 변경해야 할 것이 전혀 없습니다.

Windows Azure Tools 최신 버전을 설치한 다음, 클라우드 서비스 프로젝트를 생성할 때 아래와 같이 아무것도 추가하지 않은 상태로 확인 버튼을 클릭합니다. 기존 프로젝트를 가져오기 위해서입니다.

 

그러면 멤버 역할이 없는 상태의 빈 클라우드 서비스 프로젝트가 만들어지게 됩니다. 이제 솔루션 탐색기의 역할 폴더를 오른쪽 버튼으로 클릭하고 아래 그림과 같은 순서로 팝업 메뉴를 접근합니다.

 

그러면 아직 지금 클라우드 서비스와 연결된 적이 없는 프로젝트의 목록이 나타나게 됩니다. 항목을 선택하여 클라우드 서비스 프로젝트로 가져옵니다.

 

정상적으로 가져오게 되면 다음과 같이 솔루션 탐색기의 클라우드 서비스 프로젝트 아래의 역할 폴더에 프로젝트 이름이 나타나면서 클라우드 서비스의 웹 역할로 등록됩니다.

 

이제 Azure Cloud Service로 배포하기 위하여 패키지를 만들 차례입니다. 패키지를 만들어서 Azure Management Portal을 이용하여 업로드를 해도 좋고, 사전에 인증서 및 계정 연동을 미리 구성하여 Visual Studio에서 진행해도 상관 없습니다. 여기서는 패키지 만들기 기능으로 배포를 해보겠습니다. 클라우드 서비스 프로젝트를 오른쪽 버튼으로 클릭한 다음 패키지 메뉴를 클릭합니다.

 

그러면 배포 대화 상자가 나타나게 됩니다. 각각의 드롭 다운 상자의 의미는 어렵지 않습니다. 서비스 구성이란 클라우드 서비스의 구성을 의미하는 것이고, 빌드 구성은 각 역할과 연결된 C#이나 VB.NET 프로젝트들의 빌드 구성을 의미하는 것으로 양쪽은 서로 독립적인 관계이므로 적절한 옵션을 선택합니다. 설정을 확인한 다음 패키지 버튼을 클릭합니다.

 

빌드 상의 오류가 없으면 탐색기 창이 열리면서 .CSCFG 파일과 .CSPKG 파일이 표시됩니다. 이 파일을 다루기 쉬운 위치로 이동시켜 Windows Azure Management Portal에서 클라우드 서비스를 만든 다음 게시합니다.

 

 

고려할 사항들

전통적인 웹 응용프로그램들과는 달리 클라우드 서비스들은 단일 인스턴스에서 실행하는 것 보다는 로드 밸런싱을 전제로 여러 인스턴스에서 실행하는 것을 기준으로 생각합니다. 그렇기 때문에, 전통적인 웹 기반 응용프로그램을 그대로 전환하는 것은 생각보다 쉽지 않습니다. 특히, 컴퓨터 자체적으로 업로드한 파일을 보관하거나, 서버 컴퓨터의 상태에 의존적인 웹 응용프로그램은 클라우드 기반의 서비스로 이동하기 전에 적절한 보완이 반드시 필요합니다.

그리고 여러 위치에 클라우드 서비스를 배포하고 관리하기 위해서는 자동화된 빌드, 배포, 모니터링 도구가 필요합니다. 각각의 개별 클라우드 서비스들이 제공하는 REST API를 이용하는 도구를 직접 만들 수도 있지만, 재미있는 것은 대다수의 클라우드 서비스 공급자들이 Windows에서는 PowerShell에 대응하는 모듈을 개발하여 배포하고 있다는 것이고, Unix나 Linux에서는 node.js의 클라이언트 기반 런타임을 개발하여 배포하는 일이 많다는 점입니다.

이러한 특징들을 이용하여, 여러 클라우드 서비스를 손쉽게 관리할 수 있는 노하우를 개발하여 여러분의 개발 및 운영 프로세스에 자연스럽게 통합시킬 수 있을 것으로 기대합니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

PaaS2013. 10. 5. 15:02

이 글을 읽기 전에 주의 사항 - 이 강좌는 기술적인 내용을 다루기 위하여 작성된 것으로, 여기서 소개하는 내용을 이용하여 스팸 메일을 대량 발송하는데 악용해서는 안됩니다. 또한 필자는 불법적인 소프트웨어 개발 의뢰로 오는 연락에는 회신하지 않습니다. 이 점 주의하여 주십시오.

소셜 네트워크 서비스나 다른 여러가지 의사소통 채널들이 많이 늘어났지만, 오랜 시간동안 변함없이, 그리고 지금까지도 널리 사용되고 꾸준히 활용되는 광범위한 메시징 서비스로 여전히 E-MAIL은 그 값어치를 인정받고 있고 중요성이 유지되고 있습니다.

그런데 요즈음은 원치 않는 메일 수신이 늘어나면서 메일이 정상적으로 도착하게 될 것인지 아닌지 불분명한 메시지 송신을 담보로 해야 하는 경우가 많습니다. 이는 스팸 메일 필터링 규칙이 까다로워진 것에 큰 원인이 있을 것입니다.

대규모로 메일을 보내면서도 안정적인 송신이 가능한 방법으로 요즈음은 대량 E-MAIL 발송 서비스를 많이 이용하곤 합니다. 여러가지 서비스들이 있을 수 있지만, 만약 Windows Azure를 사용 중인 경우, Windows Azure Add-on과 연동되는 SendGrid E-MAIL 서비스를 사용하면 간편하게 SMTP 발송 서비스를 사용할 수 있습니다. 특히 초기 개발을 위하여 사용할 경우 무료로 약 5만여통의 E-MAIL 발송을 테스트해볼 수 있습니다.

SendGrid 신청 방법은 쉽습니다. Windows Azure Management Portal에 접속하여, 신규 Addon 서비스 신청 (2013년 10월 현재 한국어 메뉴에서는 저장소라고 나타나는 부분) 메뉴를 클릭하여 다음과 같이 SendGrid 서비스 신청을 진행하면 됩니다. 무료 서비스를 택하여 계정을 생성할 수 있습니다.

서비스 신청이 완료된 후, SendGrid 서비스 항목에 대한 상세 페이지로 들어가면 다음과 같이 대시보드 화면이 나타납니다. 기본적인 서비스 연결 정보 확인 및 계약 관리를 이곳에서 할 수 있으며, 실제 서비스에 대한 속성은 SendGrid 측의 관리자 페이지로 이동하여 진행할 수 있고, 이 관리자 페이지는 현재 Windows Azure 계정과 1:1 관계로 대응되는 SendGrid 측에서 자동으로 생성한 사용자 계정과 연결되어 Single Sign On으로 연동됩니다.

하단의 도구 모음에서 관리 버튼을 클릭하면 다음과 같이 SendGrid 측의 관리자 페이지로 이동합니다. 

이 관리자 페이지에 등록된 개인 정보는 수동으로 다시 입력해주어야 합니다. 프로필 변경을 통해서 수동으로 변경 가능하며 정확한 정보를 입력해야 나중에 유료 계정으로 업그레이드할 때 문제가 발생하지 않습니다. 그 외에는 메일의 발신 상태, 발신 시 표시할 문구 및 부가 기능 설정 등을 관리할 수 있으므로 서비스 자체에 대한 속성은 여기서 설정하시면 됩니다.

연결 정보 버튼을 클릭하면 위와 같이 SMTP 서버 접속 정보가 나타납니다. 위 정보를 메모하시고 다른 곳에 유출되지 않도록 관리해야 합니다. 이제 C# 프로그램 코드를 이용하여 메일을 보내는 예제 코드를 한 번 만들어보도록 하겠습니다.

string connString = CloudConfigurationManager.GetSetting("DefaultStorage");
var account = CloudStorageAccount.Parse(connString);
var blobClient = account.CreateCloudBlobClient();

var getBlobReferenceFromServer = Task<ICloudBlob>.Factory.FromAsync<Uri>(
    blobClient.BeginGetBlobReferenceFromServer,
    blobClient.EndGetBlobReferenceFromServer,
    new Uri(o as string), null);
var blob = await getBlobReferenceFromServer;

try
{
    using (SmtpClient client = new SmtpClient("smtp.sendgrid.net", 587))
    using (MailMessage message = new MailMessage(
        new MailAddress("rkttu@somewhere.com", "보내는 사람 이름"),
        new MailAddress("someone@abc.com")))
    {
        string[] nameParts = blob.Name.Split(new char[] { '/' }, StringSplitOptions.None);
        string siteIdPart = nameParts[0];
        string userIdPart = nameParts[1];

        message.Subject = String.Format("[Xinics Commons - {0}] {1} has uploaded the content.", siteIdPart, userIdPart);

        message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(
            String.Format(@"
New content has arrived.

* Site ID: {0}
* User ID: {1}
* Media URL: {2}

", siteIdPart, userIdPart, o), null, MediaTypeNames.Text.Plain));
        message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(

            String.Format(@"
<p>New file has arrived.</p>
<ul>
<li>Site ID: {0}</li>
<li>User ID: {1}</li>
<li>Media URL: {2}</li>
</ul>

", siteIdPart, userIdPart, o), null, MediaTypeNames.Text.Html));

        client.Credentials = new NetworkCredential(
            "<SENDGRID 사용자 ID>",
            "<비밀 번호>");

        await client.SendMailAsync(message);
    }
}
catch (Exception ex)
{
    Trace.TraceWarning(ex.ToString(), "Exception");
}

클라우드 서비스를 통하여 수신한 BLOB 파일에 대한 정보를 특정 사용자에게 메일로 보내기 위하여 위와 같이 코드를 작성하였습니다. 여기서 굵게 강조표시한 항목들이 바로 연결 정보에서 나타난 부분으로 교체해야 하는 부분입니다.

SendGrid를 통하여 E-MAIL을 보내면 수신 거부 링크를 자동으로 첨부하여 보내줍니다. 수신 거부한 사용자들은 제외하고 메일 발송을 계속 할 수 있으며, 수신 거부 내역은 관리자 페이지를 통하여 쉽게 확인할 수 있습니다. 관리 목적으로 보내는 E-MAIL 말고도 마케팅 관련 메일을 보낼 때도 SendGrid를 사용하면 법률을 준수할 수 있습니다.

주의 사항 - 이 강좌는 기술적인 내용을 다루기 위하여 작성된 것으로, 여기서 소개하는 내용을 이용하여 스팸 메일을 대량 발송하는데 악용해서는 안됩니다. 또한 필자는 불법적인 소프트웨어 개발 의뢰로 오는 연락에는 회신하지 않습니다. 이 점 주의하여 주십시오.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

이벤트2013. 9. 14. 14:27

클라우드 서비스 (PaaS)를 이용한 확장성있는 서비스 개발

http://channel9.msdn.com/Events/TechDays/TechDays-2013-Korea/Paas-1-2-

http://channel9.msdn.com/Events/TechDays/TechDays-2013-Korea/pass-2-2-


Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

이벤트2013. 9. 13. 01:00
top.jpg
Windows Azure 클라우드 서비스를 무료로 체험하실 수 있는 절호의 기회!
온라인 캠프 참여 후, 후기만 작성하면 Microsoft 무선 마우스!! 최우수 후기로 선정되면 XBOX360 Kinect!!!
온라인으로 언제 어디서나 Azure를 만나는 Windows Azure 커뮤니티 온라인 캠프, 지금 바로 참여하세요!
but01.jpg
Windows Azure 커뮤니티 온라인 캠프
참가대상 Windows Azure 체험을 원하시는 커뮤니티 회원
(선착순 40명 마감, 이전 캠프 참여자의 중복 참여 가능)
신청방법 Onoffmix에서 참석 신청 이용
참여자발표 온오프믹스 웹 페이지 공지 후 개별 참석 메일 전달
캠프기간 2013년 9월 25일(수) 오후 6시 ~ 2013년 9월 27일(금) 오전 9시
캠프미션 [3기 9차] Windows Azure 가상머신과 Active Directory 서비스
Azure 포털에 만들어진 가상머신과 Active Directory 서비스를 이용해 체험(참석자 대상 메일 전달)
후기응모기간 2013년 9월 29일(일) 24:00까지 후기를 적어 주셔야 합니다.
후기응모방법 Azure 커뮤니티에서 지정한 게시판에 후기를 작성해 주시면 됩니다(블로그에도 올리시면 가산점!)
- 9차 캠프 후기 작성 위치 - SQLER.com Windows Azure 게시판
온라인 캠프 진행 형식
메일을 통해
전달된 Microsoft
계정으로
Windows Azure
관리 포털에
로그인
bullet01.jpg
Azure 포털에
기 생성된
다양한
클라우드
서비스
체험
bullet01.jpg
캠프
기간 내에
Windows Azure
서비스를
체험하고
후기 작성!
bullet01.jpg
Microsoft
무선 마우스
받고,
XBOX360 Kinect
최우수 후기
선정 기다리기
gift.jpg
title01.jpg
Windows Azure 동영상 강좌 및 관련 Q&A 게시판을 참고하시면 더 다양하고 즐거운 캠프를 즐기실 수 있습니다.
Windows Azure
동영상 강좌
arrow01.png
 
Taeyo.NET
커뮤니티
arrow01.png
 
네이버 Windows Azure Cafe
arrow01.png
 
SQLER.com
Azure 커뮤니티
arrow01.png
 
Windows Azure
공식웹사이트
arrow01.png
 
 

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2013. 8. 6. 12:43

C#이나 .NET Framework를 이용하여 프로그램 코드를 작성할 때, 실제로 실행 시간이 오래 걸리는 코드 뿐 아니라 입출력 작업이나 여러가지 외부적인 요인 (데이터베이스, 네트워크를 통한 원격 시스탬 액세스 등)에 의하여 요즈음은 언제 끝나는지 정확한 실행 시간을 측정할 수 없는, 실행 시간에 변수가 생기는 코드를 작성하는 일이 많습니다. 이러한 프로그래밍 코드는 늘 그렇듯이 사용자 인터페이스와는 친화적이지 않은 경우가 많습니다. (잘 아시다시피 큐를 기반으로 하는 사용자 인터페이스 메시징 처리에 방해가 되기 때문입니다.)

이러한 문제를 해결하기 위해서, Windows Forms에서는 BackgroundWorkerThread를 사용할 수 있고, Timer나 직접 Thread를 이용하는 경우도 있습니다. 하지만 잘못된 관점을 가지고 프로그램을 작성할 경우 관리하기 어려운 코드가 되는 문제가 있습니다. 좀 더 간결하고 알기 쉬운 비동기 코드를 만드려는 노력이 필요한 이유가 여기에 있습니다.

이번 글에서는 어렵게 스레드를 만들고 생명 주기를 관리하려는 노력 대신, 실제 로직에 집중해서 메서드의 실행을 비동기화하고, 원격에서 통제할 수 있는 명쾌한 방법을 하나 소개해볼까 합니다.

다음과 같은 임의의 메서드가 하나 있다고 가정하겠습니다. 컨셉을 이해하기 위함이므로 메서드 본문 안의 내용은 중요하지 않음을 미리 언급해두겠습니다.

public int CalcXYZ(int x, int y, int z) {
    return x * y * z;
}

.NET Framework 3.5부터 새로 추가된 LINQ는 Lambda Expression과 함께 좀 더 적극적인 Type Inference를 가능하게 하기 위하여, Action과 Func 대리자들의 가짓수가 매우 다양해졌습니다. 이전 버전의 .NET Framework에서는 위의 메서드를 포장하기 위하여 아래와 같이 대리자 형식을 따로 정의해야 했습니다.

public delegate int CalcXYZDelegate(int x, int y, int z);
...
private CalcXYZDelegate f;
...
f = this.CalcXYZ;

물론 위와 같이 대리자를 따로 정의해서 담아두어도 좋습니다. 하지만, 타이핑하고 관리해야 할 코드의 분량을 조금이라도 줄일 방법을 찾는게 좋을 것입니다. 이를 위해서, 반환 형식이 있는 메서드이므로 Func<T1, T2, T3, TResult> 대리자를 사용할 수 있을 것입니다.

private Func<int, int, int, int> f;
...
f  = this.CalcXYZ;

여기서 한 가지 기억해야 할 것은, 비동기 메서드를 만들기 위해서 비동기 작업을 발행하고 관리하는 주체로 대리자를 사용한다는 점입니다. 따라서, 대리자 인스턴스를 하나로 고정하기 위하여, private 멤버 변수로 선언하는 것이 중요합니다. 즉, 위의 코드를 전개하면 다음과 같은 클래스 선언이 나타난다고 할 수 있습니다.

public class MyClass {
    public MyClass() : base() { f = this.CalcXYZ; }
    private Func<int, int, int, int> f;
    public int CalcXYZ(int x, int y, int z) { return x * y * z; }
}

이제 위의 MyClass 정의에 Begin/End 짝 메서드를 정의해보겠습니다. Begin/End 메서드에 대해서 설명하면, BeginXXXX 메서드는 메서드의 호출을 시도하고 비동기 작업을 예약하는 역할을 하며, EndXXXX 메서드는 메서드 호출이 완료되었을 때 결과를 회수하는 역할을 합니다. 이 때, BeginXXXX 메서드에 전달하는 콜백 함수의 호출을 활용하거나, BeginXXXX 메서드가 반환하는 IAsyncResult 및 그 안에 들어있는 스레드 이벤트 동기화 객체가 실행 흐름을 관리하는데 매우 중요한 역할을 담당하게 됩니다.

위의 정의에 내용을 좀 더 추가하면 다음과 같습니다. 굵게 강조 표시한 부분을 확인해 주세요.

public class MyClass {
    public MyClass() : base() { f = this.CalcXYZ; }
    private Func<int, int, int, int> f;
    public int CalcXYZ(int x, int y, int z) { return x * y * z; }
    public IAsyncResult BeginCalcXYZ(int x, int y, int z, AsyncCallback callback, object result) {
        return f.BeginInvoke(x, y, z, callback, result);
    }
    public int EndCalcXYZ(IAsyncResult asyncResult) { return f.EndInvoke(asyncResult); }
}

규칙이 있음을 알 수 있습니다. 다시 살펴보면,

public <반환 형식> <메서드 이름>(<인수들>);

위의 메서드가 원형이라고 하면,

public IAsyncResult Begin<메서드 이름>(<인수들>, AsyncCallback callback, object result) { ... }
public <반환 형식> End<메서드 이름>(IAsyncResult asyncResult) { ... }

이와 같이 함수 호출의 시작과 끝을 관리하는 메서드로 분할하여 정의하고 있으며, 여기에 대한 실질적인 구현은 .NET Framework가 제공하는 대리자를 이용하여 처리하므로 어렵지 않게 비동기 메커니즘을 구현할 수 있게 되는 것입니다. 여기서 조금 더 응용하면, 만약 반환 형식이 void라면 그대로 적어도 무방하며 이 때에는 Func 대리자 대신 Action 대리자로 바꿔주면 됩니다. 인수들이 없다면? 당연히 생략하고 비동기 호출에서 필수적인 인자들만 맞추어 서술하면 끝납니다.

여기서 한 가지 더 이야기할 것은, 최근에 Windows 8 출시와 함께 WinRT가 등장하면서 급부상한 C# 5.0의 async 키워드와 TPL (Task Parallel Library)와의 상관 관계입니다. async 키워드를 직접 사용하지는 않는다 할지라도, WinRT를 자연스럽게 지원할 수 있는 방법이 바로 TPL에 대한 지원을 추가하는 것입니다. 바꾸어 말하면, async 키워드는 컴파일러의 서비스이므로, TPL에 대한 지원만 정확히 하고 있다면, C# 5.0 컴파일러가 TPL 기반의 비동기 코드를 손쉽게 작성할 수 있도록 도움을 준다는 의미도 됩니다.

위의 내용까지 구현했다면, TPL로 가는 길은 바로 코앞에 있는 셈입니다.

using System.Threading.Tasks;
...
public Task<int> CalcXYZAsync(int x, int y, int z) {
    return Task<int>.Factory.FromAsync(this.BeginCalcXYZ, this.EndCalcXYZ, x, y, z, this);
}

위와 같은 코드만 작성하면 Begin/End 패턴을 즉시 TPL 기반의 비동기 패턴으로 변환할 수 있습니다. 여기에서도 규칙을 찾을 수 있는데, 다음과 같습니다.

return Task<<반환 형식>>.Factory.FromAsync(Begin<메서드 이름>, End<메서드 이름>, <인수들>, <this 또는 null>);

여기서 Task 클래스에 제네릭 인자를 지정하는 이유는, 메서드의 반환 형식에 대한 형식 안정성을 지키기 위함입니다. 만약 반환 형식이 void라면 다음과 같이 제네릭 인자 자체를 생략하면 됩니다.

public Task SomeMethod() {
    return Task.Factory.FromAsync(Begin<메서드 이름>, End<메서드 이름>, <this 또는 null>);
}

위의 내용까지 포함한 수정된 MyClass 코드는 다음과 같습니다.

public class MyClass {
    public MyClass() : base() { f = this.CalcXYZ; }
    private Func<int, int, int, int> f;
    public int CalcXYZ(int x, int y, int z) { return x * y * z; }
    public IAsyncResult BeginCalcXYZ(int x, int y, int z, AsyncCallback callback, object result) {
        return f.BeginInvoke(x, y, z, callback, result);
    }
    public int EndCalcXYZ(IAsyncResult asyncResult) { return f.EndInvoke(asyncResult); }
    public Task<int> CalcXYZAsync(int x, int y, int z) {
        return Task<int>.Factory.FromAsync(this.BeginCalcXYZ, this.EndCalcXYZ, x, y, z, this);
    }
}

여기서 한 가지 염두에 두어야 할 것은, 기본 메서드의 매개 변수 갯수가 3개보다 많을 경우, FromAsync 메서드에서는 3개까지만 매개 변수를 전달할 수 있도록 선언이 되어 있기 때문에, IAsyncResult 형식의 객체를 받아서 Wrapping하거나, 템플릿 인자를 확장하거나, 중요도가 낮은 매개 변수들을 Dictionary 또는 별도의 POCO (Plain-old-CLR-object) 형식으로 정의하여 매개 변수로 사용하도록 하는 부수적인 절차가 필요합니다.

또한, 메서드 오버로딩을 비동기 메서드에도 적용하고자 하는 경우, 오버로딩에서 가장 핵심이 되는 메서드에 대해서 위의 패턴을 적용하고, 나머지는 핵심 메서드를 호출하는 과정을 동일하게 맞추어야 합니다. 예를 들어, 위의 CalcXYZ 메서드의 오버로딩으로 short 형식을 사용한다고 가정하면 다음과 같이 사용해야 함을 뜻합니다.

public class MyClass {
    // ...
    public int CalcXYZ(short x, short y, short z) { return CalcXYZ((int)x, (int)y, (int)z); }
    public IAsyncResult BeginCalcXYZ(short x, short y, short z, AsyncCallback callback, object result) {
        return
BeginCalcXYZ((int)x, (int)y, (int)z, callback, result);
    }
    // EndCalcXYZ는 따로 정의하지 않습니다.
    public Task<int> CalcXYZ(short x, short y, short z) { return CalcXYZAsync((int)x, (int)y, (int)z); }
    // ...
}

이제 위와 같이 정의했으니, 호출하고 사용하는 방법을 알아보도록 하겠습니다.

비동기 메서드는 기본적으로 실행을 예약하고, 콜백 메서드를 통하여 실행 완료 통지를 받았을 때 따로 실행하도록 하는 것이 기본입니다. 하지만, 비동기로 실행하는 것과는 별개로 실행 흐름을 동기화해야 하는 경우도 있을 수 있는데, 일반적인 메서드 호출과는 다른 특별한 기능을 하나 더 이용할 수 있습니다. 바로 Time out 개념을 사용할 수 있다는 것인데, 이것은 Thread를 직접 제어할 때와는 또 다른 이점입니다.

MyClass inst = new MyClass();
IAsyncResult res = inst.BeginCalcXYZ(1, 2, 3, null, null);
res.WaitHandle.WaitOne();
if (res.IsCompleted) {
    int val = inst.EndCalcXYZ(res);
    Console.WriteLine(val);
} else {
    throw new TimeoutException();
}

Begin/End 메서드는 위와 같이 사용합니다. 여기서 재미있는 부분은 콜백과 상태 관리 객체를 지정하지 않고 실행이 끝날 때 까지 기다리게 했다는 부분입니다. 그런데 한 가지 궁금증이 생깁니다. 이렇게 하면 비동기의 이점이 없는 것이 아닌가 하는 부분입니다.

그런데 WaitOne 메서드에 매개 변수를 하나 지정할 수 있습니다. 바로 밀리초 단위의 time out 대기 시간입니다. 이 값을 생략할 경우 자동으로 System.Threading.TimeOut.Infinite가 지정된 것과 같이 실행되며, 이 상수 필드의 값은 (-1)입니다. 즉, 실행이 끝날 때까지 이 코드를 실행하는 스레드의 실행을 동결한다는 것입니다. 이 값 대신 1000을 지정하면 1초 이내에 실행이 끝나지 않을 때 그 다음 코드로 바로 실행이 이어지는 것입니다. 여기서 IsCompleted 속성을 사용하여 실행이 정말 완료가 되었다면 결과를 회수하고, 그렇지 않으면 Timeout으로 처리할 수 있게 됩니다.

이러한 시나리오가 유용하게 쓰일 수 있는 곳은 매우 많습니다. 단순한 테스트 유닛 실행에서부터 네트워크 및 입출력 관련 처리에 이르기까지 매우 다양한데, 프로그램을 그저 응답 없음 상태로 내버려두는 것이 아니라 좀 더 주도적으로 실행 흐름을 관리할 수 있게 되는 것입니다.

Begin/End 대신 Async 메서드를 사용하는 경우, C# 5.0 이전의 언어를 사용할 경우 다음과 같이 코드를 작성할 수 있을 것입니다.

MyClass inst = new MyClass();
var res = inst.CalcXYZAsync(1,2,3);
res.Wait();
if (res.IsCompleted) {
    int val = res.Result;
    Console.WriteLine(val);
} else {
    throw new TimeoutException();
}

그리고 C# 5.0부터는 좀 더 간결하게 아래와 같이 코드를 작성할 수 있을 것입니다.

public async void Method1() {
    MyClass inst = new MyClass();
    int val = await inst.CalcXYZAsync(1,2,3);
    Console.WriteLine(val);
}

그런데 한 가지 남는 의문은, 전통적인 Event Driven 방식의 Windows 응용프로그램 개발 환경에서 단순히 이벤트 처리기를 사용하여 위의 코드를 직접 실행하면 프로그램이 굉장히 부자연스럽게 동작한다는 점입니다. 왜 그럴까요?

그 원인은 틀림없이 메시지를 처리하는 스레드에서 위의 코드를 실행하기 때문일 것입니다. 즉, 엄밀한 의미에서 모든 작업들은 비동기화 되어야하고, 비동기화된 스레드들의 상태를 모니터링하면서 사용자 인터페이스에 적절한 신호를 주어야 하는 셈입니다. 이렇게 본다면 GUI 프로그래밍은 매우 어려운 작업 중 하나가 될 수 있습니다. 그래서 기준이 하나 있어야 하는데, 바로 I/O Bound Operation에 한하여 위와 같이 작업을 비동기로 분할하여 상태 보고를 할 수 있도록 구성하는 방법을 적절히 사용해야 하는 것입니다. 그러한 방면으로 잘 포장된 것이 바로 BackgroundWorker 컴포넌트입니다.

어떤 방법을 사용하는가에 관계없이, 프로그래밍을 할 때에는 항상 실행 시간과 흐름 관리에 최선을 다해야 합니다. 디자인 패턴 이외에도, 시간이 오래 걸릴 수 있는 불확정성에 기대는 작업을 다룰 때 이러한 세밀한 노력이 얼마나 들어갔는지에 따라서 얼마나 완성도 높은 코드를 생산할 수 있는가가 결정될 것입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2013. 7. 16. 00:00

NuGet Visual Studio에 추가하여 사용할 수 있기도 하고, 독립적으로도 사용할 수 있는 패키지 관리 시스템으로 기존의 Windows Forms 응용프로그램에서부터 ASP.NET, 그리고 Windows 8Windows Phone 8에 이르기까지 다양한 종류의 프로젝트를 지원하는 전천후 패키지 관리 시스템이자 또한 NuGet 웹 사이트와 연동하여 최신의 패키지를 자유롭게 활용할 수 있는 멋진 기능입니다.

.NET Framework 관련 소프트웨어 개발을 시작할 때 새로운 프로젝트를 만들고 TDD 초기 환경 구축을 하는 과정은 다양합니다. Visual Studio가 제공하는 Test Project를 만드는 방법이 있을 수도 있고, 나름대로 Test Mockup을 만드는 방법도 있을 수 있지만, 무료로 사용할 수 있으면서도 분명한 효과를 제공하는 도구로는 단연 NUnit이 거론됩니다. 그런데 Visual Studio의 기본 구성 요소도 아니고, 프로젝트에 추가해서 사용하기 번거로운 면도 일부 있습니다. 그리고 테스트 코드를 만들고 프로젝트에 포함시키는데 있어서도 프로젝트의 코드 관리를 어렵게 만드는 면이 있습니다.

이러한 문제를 해결하기 위한 방법으로 두 가지 방안을 소개하려고 합니다.

첫 번째는, NuGet 패키지 관리자를 이용하여 NUnit Framework는 물론 NUnit RunnerMSI 패키지 설치 방식이 아닌 솔루션 단위의 패키지로 설치하여 버전 관리 시스템에 같이 포함하여 배포할 수 있는 방법에 관한 것입니다. 두 번째는, Friend 어셈블리를 이용하여 테스트 어셈블리에 대해서만 독점적인 접근 권한을 부여하여 테스트 논리를 만드는 절차를 간소화하는 방법에 관한 것입니다.

NuGet 패키지 관리자 버전 확인 후 업데이트하기

NuGet 패키지 관리자는 Visual Studio 2010 이후부터 서비스 팩을 설치하면 극 초기의 버전이 자동으로 추가되는 경우가 있습니다. 하지만 제품과 함께 제공되거나 서비스 팩을 이용하여 설치한 패키지 관리자는 버전이 너무 낮고 기능에도 일부 오류가 있어 쓰기 불편합니다. 당연히 여기에 대한 업데이트가 배포 중이며, 다음과 같은 방법으로 업데이트할 수 있습니다. Visual Studio 2010 이후의 버전은 모두 다음과 같은 방법으로 진행하면 됩니다.

Visual Studio를 시작합니다.

도구 메뉴를 선택한 다음 확장 관리자 메뉴를 아래와 같이 선택합니다.

 

나타나는 대화 상자의 왼쪽 편의 항목들 중 온라인 갤러리선택 후 모두를 선택합니다. 그러면 아래와 같이 NuGet Package Manager가 상위권 항목에 나타납니다. 많이들 사용하는 기능이기 때문에 검색할 필요도 없이 금세 발견할 수 있을 것입니다.

 

업그레이드를 할 필요가 없거나 이미 최신 버전이 설치된 경우 위와 같은 화면이 나타나지만, 대개는 업그레이드가 필요함을 알려줄 것입니다. 리스트에서 다운로드 버튼이 보이면 클릭하여 설치나 업데이트를 진행하시면 됩니다.

설치를 완료한 다음에는 Visual Studio를 다시 시작하라는 메시지가 나타나며, 이 메시지에 따라 다시 시작 버튼을 클릭하면 자동으로 다시 실행됩니다.

기존 프로젝트 또는 새 프로젝트에 NUnit 프레임워크와 NUnit Runner 추가하기

이제 NuGet 패키지 관리자를 새로 업그레이드하였으니 이 패키지 관리자를 사용하여 기존 프로젝트 또는 새 프로젝트에 NUnit 프레임워크와 NUnit Runner를 추가할 차례입니다. 단위 테스트 기능을 추가하려는 프로젝트를 열거나 새로운 프로젝트를 만들고, 아래와 같이 솔루션 탐색기에서 해당 프로젝트를 마우스 오른쪽 버튼으로 클릭한 다음, NuGet 패키지 관리 메뉴를 선택합니다. 만약 테스트 코드와 실제 제품 코드를 분리하고자 할 경우에는 별도의 새로운 프로젝트를 만든 다음 그 프로젝트에 아래 그림과 같이 패키지 관리자를 실행하도록 하면 됩니다.

 

 

그러면 NuGet 패키지 관리자가 다음과 같이 나타납니다. 아무것도 설치한 것이 없으므로 처음에는 덩그러니 빈 화면만 나타나는데, 이번에도 좌측편의 항목들 중 온라인을 선택합니다.

 

그 다음, 우측 상단의 검색 창에 NUnit을 입력하고 검색 버튼을 클릭하면 다음과 같이 NuGet 관련 패키지들이 나타나게 됩니다.

 

이 중에서 우리가 필요로 하는 것은 NUnitNUnit.Runners 패키지입니다. NUnit 패키지에서는 NUnit 프레임워크 어셈블리를 포함하고 있으며, NUnit.Runners 패키지는 NUnit 테스트 실행을 위한 프로그램의 GUI, CLI 및 플랫폼 중립, x86 버전의 파일도 같이 들어있습니다. 그러나 Runners 패키지는 실제 프로젝트에 참조로 추가되는 것은 아니며 Windows 탐색기를 사용하여 파일을 별도로 실행하거나 빌드 자동화 시점에서 활용할 수 있는 유틸리티 정도로 생각하면 편합니다.

이제 새로운 Test Fixture 클래스와 Test Case 메서드들을 몇 가지 추가해봅니다. 테스트해 보고픈 임의의 코드를 추가하고 컴파일이 잘 되는지 확인합니다. 여기서는 다음과 같이 코드를 작성했다고 가정해 보겠습니다.

이제 위의 테스트 어셈블리를 포함한 솔루션을 NuGet이 설치한 NUnit Runner를 통하여 열어보도록 하겠습니다. 솔루션 폴더를 찾아서 폴더 창을 열려고 하면 번거롭습니다. 이를 단순하게 하기 위하여, 현재 열려있는 코드 편집기 창의 탭 부분을 오른쪽 버튼으로 클릭하면 상위 폴더 열기 메뉴가 아래 그림과 같이 나타납니다. 이 메뉴를 클릭합니다.

 

그러면 다음과 같이 폴더 창이 정확한 위치를 가리키며 나타나게 됩니다. 이제 이 위치에서 SLN 파일이 있는 위치로 상위 폴더로 몇 번 이동합니다. 그 다음, 해당 폴더 위치를 기준으로 packages 폴더 > NUnit.Runners.x.x.x 폴더 > tools 폴더 순으로 접근합니다. 그리고 아래 그림과 같이 nunit.exe 파일을 찾아 실행합니다.

 

익숙한 화면이 나타납니다. 시스템에 관리자 권한을 이용하여 설치하지 않았어도 NUnit Runner가 즉시 실행되고 사용 가능한 상태로 준비된 것이 보입니다. 이제 여기서 SLN 파일을 열어보겠습니다. File 메뉴의 Open 메뉴를 선택하여 SLN 파일을 찾아 엽니다.

 

만약 솔루션 파일을 열려고 시도하였을 때, 솔루션이 이상 없이 컴파일이 잘 됨에도 불구하고 다음과 같이 오류 메시지가 나타나면 대상 플랫폼 설정이 NUnit의 대상 플랫폼과 일치하지 않기 때문에 오류가 발생하는 것입니다.

이 경우 문제 해결을 위하여 아래 그림과 같이 대상 플랫폼을 Mixed Platform 대신 x86으로 변경하고 nunit-x86.exe Runner를 대신 사용하거나, Any CPU로 맞추어 다시 솔루션을 빌드합니다.

SLN 파일을 열고 난 다음에는 테스트를 진행할 수 있게 화면이 나타납니다. 현재 활성화된 환경 설정을 기준으로 자동으로 포커스가 변경됩니다.

테스트가 잘 실행되는지 살펴봅니다. 예상대로 Case 1decimal이 정확한 덧셈을 처리하고 있음을 증명하며, Case 2Windows 환경에서 언제나 성공합니다. 그러나 Case 3Windows 환경에서 언제나 실패하며, Case 41글자이지만 StringChar가 분명히 다른 형식임을 확인해주고 있습니다.

실제 코드 어셈블리와 테스트 어셈블리를 분할하는 방법

NuGet 패키지 관리자를 사용하여 NUnit을 전보다 더 가깝고 편리하게 사용할 수 있게 된 것은 좋은 일입니다. 그렇지만 한 가지 고민이 남는데, 인프라의 개선과는 별도로 설계와 유지에 있어서 테스트 코드와 실제 제품 코드가 한 배를 타는 것은 별로 좋은 것 같지 않습니다. 테스트 코드가 제품 코드에 자유롭게 접근할 수 있으면서도, 제품 코드가 테스트 코드를 배려하는 별도의 부수적인 옵션 구성 요소들을 추가하는 일 없이, 테스트 코드가 자유롭게 제품의 기능을 접근하여 확인할 수 있는 수단이 필요할 것입니다.

여기에 대한 답을 .NET FrameworkFriend Assembly라는 이름의 개념으로 정의하고 있는데, 기본적으로 Assembly는 그 안에 속한 Module들 간에는 internal로 선언한 멤버들을 자유롭게 제어하고 다룰 수 있게 되어있습니다. 그런데 이 Assembly 간의 관계를 설정해두면 특정 어셈블리 상의 코드에 대해서만 internal로 선언한 멤버들을 자유롭게 제어하고 호출하거나 다룰 수 있게 해주는 특권의 부여가 가능합니다.

이 기능을 사용하면, 제품에 대한 실제 코드를 담고 있는 클래스 라이브러리나 실행 파일 모듈을 가지고 있는 .NET 어셈블리와 각 유형별 테스트 케이스를 따로 모아놓은 테스트 어셈블리들을 분리하여 테스트 어셈블리는 배포하지 않고, 실제 코드 어셈블리만 배포하는 것이 가능합니다. 그러면서도, 실제 코드 어셈블리의 모든 internal 멤버들을 테스트 어셈블리들이 자유롭게 활용할 수 있습니다. 이렇게 하여 둘 사이에 발생할 수 있는 상호 종속적인 관계를 분리할 수 있으니 훨씬 자유로운 테스트 코드 작성이 가능합니다.

위의 예제에서 보인 것처럼 실제 코드와 테스트 코드를 분리한 상태에서, 실제 코드를 가지고 있는 어셈블리에서는 우선 보호하고 싶은 클래스나 멤버에 대해 internal 키워드를 사용하여 선언합니다. 여기까지는 우리가 알고 있는 그대로이며, 다른 어셈블리에서는 internal 키워드를 사용하여 선언한 멤버들을 접근하거나 활용할 수 없습니다. 그러나, 프로젝트 내에 추가할 테스트 어셈블리의 이름을 아래 그림과 같이 확인해둡니다.

접근을 허용하려는 테스트 어셈블리의 이름을 찾아 복사합니다. 그리고 실제 코드를 포함하는 어셈블리의 적당한 위치에 다음과 같이 코드를 작성합니다. 아래와 같이 어셈블리에 대한 특성을 부여하는 코드는 보통 Visual Studio 프로젝트와 함께 자동으로 생성되는 AssemblyInfo.cs 파일에 기술하면 편리합니다.

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("<Assembly Name>")]

 

위와 같이 코드를 작성하고 컴파일 한 다음 경고 메시지가 나타나지 않으면 됩니다. 그 다음, 접근을 허용한 어셈블리에서 테스트하려는 코드를 포함한 어셈블리를 참조에 추가한 다음, internal로 선언한 모든 멤버들을 정상적으로 사용할 수 있는지 확인하여, 테스트 코드를 작성할 수 있는 상태이면 테스트 케이스를 만들어나가기 시작하면 됩니다.

결론

테스트 주도 개발은 이번 아티클에서 살펴본 것과 같이 초기의 개발 환경 구축을 단순화할 수 있다면 얼마든지 쉽게 시작할 수 있는 효율적이고 인상적인 개발 방법론입니다. 그러나 여기에서 염두에 두어야 할 것은 이렇게 구축한 개발 환경을 어떤 관점을 유지하면서 활용해 나아갈 것인가에 대한 전략의 설정과 실천에 있을 것입니다.

테스트 주도 개발에 관한 좀 더 근본적인 내용을 검토하기 위해서는 다양한 자료들을 참조할 수 있지만, 가장 추천해 드릴 만한 자료로는 단연 Kent BeckTest Driven Development (ISBN 978-89-91268-04-3)이라는 도서입니다. 테스트 주도 개발의 원칙과 방향성에 대한 이야기를 자세히 들어볼 수 있으므로 꼭 살펴보실 것을 권합니다.


Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

이벤트2013. 7. 11. 18:26
top.jpg
Windows Azure 클라우드 서비스를 무료로 체험하실 수 있는 절호의 기회!
온라인 캠프 참여 후, 후기만 작성하면 Microsoft 무선 마우스!! 최우수 후기로 선정되면 XBOX360 Kinect!!!
온라인으로 언제 어디서나 Azure를 만나는 Windows Azure 커뮤니티 온라인 캠프, 지금 바로 참여하세요!
but01.jpg
Windows Azure 커뮤니티 온라인 캠프
참가대상 Windows Azure 체험을 원하시는 커뮤니티 회원
(선착순 40명 마감, 이전 캠프 참여자의 중복 참여 가능)
신청방법 Onoffmix에서 참석 신청 이용
참여자발표 온오프믹스 웹 페이지 공지 후 개별 참석 메일 전달
캠프기간 2013년 7월 17일(수) 오후 6시 ~ 2013년 7월 19일(금) 오전 9시
캠프미션 [3기 7차] Windows Azure 가상머신과 가상네트워크
Azure 포털에 만들어진 가상머신과 가상 네트워크를 이용해 체험(참석자 대상 메일 전달)
후기응모기간 2013년 7월 21일(일) 24:00까지 후기를 적어 주셔야 합니다.
후기응모방법 Azure 커뮤니티에서 지정한 게시판에 후기를 작성해 주시면 됩니다(블로그에도 올리시면 가산점!)
- 7차 캠프 후기 작성 위치 - 네이버 Windows Azure Cafe Azure 사용후기 게시판
온라인 캠프 진행 형식
메일을 통해
전달된 Microsoft
계정으로
Windows Azure
관리 포털에
로그인
bullet01.jpg
Azure 포털에
기 생성된
다양한
클라우드
서비스
체험
bullet01.jpg
캠프
기간 내에
Windows Azure
서비스를
체험하고
후기 작성!
bullet01.jpg
Microsoft
무선 마우스
받고,
XBOX360 Kinect
최우수 후기
선정 기다리기
gift.jpg
title01.jpg
Windows Azure 동영상 강좌 및 관련 Q&A 게시판을 참고하시면 더 다양하고 즐거운 캠프를 즐기실 수 있습니다.
Windows Azure
동영상 강좌
arrow01.png
 
Taeyo.NET
커뮤니티
arrow01.png
 
네이버 Windows Azure Cafe
arrow01.png
 
SQLER.com
Azure 커뮤니티
arrow01.png
 
Windows Azure
공식웹사이트
arrow01.png
 
 

 

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Web Programming2013. 7. 4. 01:22

안녕하세요. Windows Azure MVP 남정현입니다.

요즈음 Windows 8.1과 Windows Server 2012 R2 출시로 많은 이야기들이 오고 가고 있는데, 그 중에서도 Internet Explorer 11에 대한 이야기도 많이 눈에 띕니다.

Internet Explorer 11에 등장하는 새로운 기능들도 많지만, 기존 버전과 확연히 달라지고 제거되는 기능들 중에 호환성에서 문제를 일으킬만한 요소들이 많이 눈에 띕니다. 이러한 부분들을 잘 정리한 아티클이 있어 소개합니다. (http://www.nczonline.net/blog/2013/07/02/internet-explorer-11-dont-call-me-ie/)

이 아티클에서 이야기하는 내용을 요약하면 다음과 같습니다.

  • 11부터 User Agent 문자열에서 MSIE 문자열이 사라집니다. Internet Explorer인지 확인하기 위하여 User Agent 문자열을 검사하는 시나리오에 의존하는 모든 종류의 프로그램 및 로직은 MSIE 문자열 대신 Trident 문자열을 검사하도록 패턴을 수정하는 것이 구 버전부터 11.0까지 모두 지원할 수 있는 정확한 방법일 것입니다.
  • navigator 객체의 appName 속성은 'Netscape', product 속성은 'Gecko' 문자열을 반환합니다. 이러한 속성에 의존하여 IE 인지 아닌지를 판단하는 코드에서는 이런 이유로 IE 11을 IE가 아닌 Gecko 렌더링 엔진을 사용하는 브라우저로 오판할 수 있습니다.
  • typeof document.all 식을 사용하여 결과값을 평가하면 'undefined'로 반환되지만, 실제 document.all 속성에 대한 호출은 정상적으로 작동합니다. 달리 말하면, Modernizer와 같은 Presence Test Framework에서는 document.all 속성을 IE11이 지원하지 않는 것으로 해석하지만, 호환성을 이유로 document.all을 평가 과정 없이 직접 호출하는 Legacy Web Page들은 문제없이 잘 실행됩니다. 그러나 Legacy Web Page이더라도 typeof document.all 과 같은 표현식의 값을 의존하는 경우 역시 호환성 문제가 발생할 수 있습니다.
  • 같은 이유로 element.attachEvent 메서드의 Presence를 확인하기 위해서 typeof 연산자를 사용하면 'undefined'로 평가되지만 실제로는 호출이 가능합니다. 이러한 평가 사실에 의존하는 코드는 역시 호환성 문제가 발생할 수 있습니다.
  • window.execScript 메서드, window.doScroll 메서드, script.onreadystatechange 이벤트, script.readyState 속성, document.selection 속성, document.createStyleSheet 메서드, style.styleSheet 속성은 완전히 제거되었으므로 typeof 식은 물론 실제로도 작동하지 않습니다.

Windows 8.1에 대한 호환성을 점검하고 계시다면, 위의 사항들을 잘 참고하시어 대비하는 것이 필요해보입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2013. 4. 20. 00:54

안녕하세요. Windows Azure MVP 남정현입니다.

IronPython은 이제 다른 Python Implementation과 어깨를 나란히 할 수 있는 수준까지 완성도가 개선되었습니다. Python을 이용해서 흔히 기대하는 NumPy, SciPy 같은 수학 및 공학용 라이브러리는 당연히 쉽게 처리할 수 있고, 이를 기반으로 하는 NLTK (Natual Language Tool Kit) 라이브러리도 약간의 불편함이 따르기는 하나 종속성을 충족하면 자연어 처리도 원활하게 수행합니다.

그리고 당연하다면 당연한 이야기이지만 Windows NT 서비스를 IronPython으로 구현하는 것도 생각해볼 수 있습니다. Windows NT 서비스를 관리 언어를 이용하여 개발하는 것 자체는 그렇게 새로울 것이 없습니다만, 한 가지 많이 오해를 하는 부분이 있는데 Visual Studio가 제공하는 템플릿이 아니면 만들 방법이 없는 것 처럼 여겨지는 것 같습니다. 그러나 실제로는 전혀 그렇지 않으며, .NET Framework의 실행 모델을 처리할 수 있는 프로그래밍 언어는 모두 간편하게 System.ServiceProcess.dll 어셈블리가 제공하는 SCM 상호 작용 컴포넌트를 쉽게 이용할 수 있습니다.

그렇다면 IronPython을 이용해서 Windows NT 서비스를 개발하려면 어떻게 해야 할까요? 생각보다 단순합니다. 준비물은 이 글을 쓰는 현 시점에서 최신 버전인 IronPython 2.7 패키지와 관리자 권한만 있으면 됩니다. 만약 sc.exe 유틸리티가 없다면 이 유틸리티가 이번 강좌에서는 꼭 필요하므로 Windows SDK를 찾아보시기 바랍니다.

초간단 IronPython 기반 Windows NT 서비스 코드 살펴보기

글을 쓰는 것이 무안할 정도로 정말 초간단합니다.

import clr
clr.AddReference('System.ServiceProcess')
from System.ServiceProcess import ServiceBase

class MySvc(ServiceBase):
  def OnStart(self, args):
    ServiceBase.OnStart(self, args)
    print args
  def OnStop(self):
    ServiceBase.OnStop(self)

svc1 = MySvc()
ServiceBase.Run(svc1)

코드의 각각의 줄을 하나씩 살펴보도록 하겠습니다.

우선 처음의 세 줄은 CLR 모듈을 로드해서 System.ServiceProcess 어셈블리를 현재 IronPython의 스코프에서 사용할 수 있도록 준비하는 과정입니다. 관리되는 언어로 Windows NT 서비스를 등록하고 SCM과 상호 작용하기 위해서 필요한 모든 코드가 이 어셈블리에 들어있다고 보시면 되겠습니다.

그리고 Windows NT 서비스의 API를 객체 지향 방식으로 모델링한 ServiceBase 클래스를 상속받는 새로운 클래스를 하나 만들어야 합니다.

Python 문법에 익숙하지 않은 분들을 위하여 부연 설명을 더하면, 현재 스코프에 ServiceBase라는 클래스가 있고, 이 클래스를 상속받는 MySvc 클래스를 정의하고 있습니다. 그리고 따로 지시자는 없지만 이름이 같게 설정된 메서드는 Python 세계에서는 자동으로 메서드를 재정의한 것이 됩니다. 이에 따라, OnStart과 OnStop 메서드가 SCM에 의해서 자동으로 호출되는 메서드가 되는데, 서비스 시작 시 서비스가 잘 작동하고 있음을 알리기 위해 부모 클래스인 ServiceBase 클래스의 OnStart 메서드를, 그리고 프로세스 종료를 해도 괜찮음을 알리기 위해 OnStop 메서드를 호출합니다. 그리고 self라는 인자는 인스턴스 메서드임을 설명하는 것이며 동시에 여기에 인스턴스 참조가 전달됩니다.

그리고 따로 시작점이 있는 것이 아니라 Python 코드는 그 자체가 즉시 실행 가능한 Main 메서드 역할을 합니다. 그래서 곧바로 MySvc 클래스를 인스턴스로 만들어 ServiceBase.Run 메서드를 호출합니다.

SCM을 이용하지 않고 실행할 경우

ServiceBase.Run 메서드는 기본적으로 SCM과 상호 작용을 시작하기 위해서 필요한 기능들을 미리 제공합니다. 그런데 위의 코드를 일상적으로 사용하는 IronPython 콘솔에서 실행하면 어떻게 결과가 나타날까요? 아래와 같은 메시지가 나타납니다.

"명령줄 또는 디버거에서 서비스를 시작할 수 없습니다. 먼저 installutil.exe를 사용하여 Windows 서비스를 설치한 다음 서버 탐색기, Windows 서비스 관리 도구 또는 NET START 명령을 사용하여 시작해야 합니다."

이런 오류 메시지가 나타납니다. 그런데 제가 하고 싶은 이야기는 정말 installutil.exe를 사용해야만 관리 언어로 만든 NT 서비스를 등록할 수 있는가에 대한 부분입니다. 결론부터 말하면 "아니오"입니다. installutil.exe를 사용하여 NT 서비스를 설치하기 위해서는 위의 코드 말고도 사실 설치 관리자 컴포넌트를 따로 구현해야 하는데, 이것을 Visual Studio Professional 이상의 버전에서는 템플릿으로 제공하고 있고 설치 프로젝트와 연계해서 만들 수 있도록 해주는 것입니다. 그러나 개인적으로는 이러한 설정을 마음에 들지 않습니다. 좀 더 간단하게 갈 수 있는 방법도 많으니까요.

그러면 installutil.exe를 대신할 도구가 있을까요? 바로 sc.exe입니다. sc.exe 자체는 개발된지 오래된 유틸리티이지만, 관리 언어로 만든 Windows NT 서비스까지도 매우 유연하게 지원합니다. 바로 EXE 파일로 만들어지는 NT 서비스에 한해서 그 진가가 십분 발휘됩니다.

SC.EXE 유틸리티를 사용하여 NT 서비스 등록하기

이제 SC.EXE 유틸리티를 사용하여 NT 서비스를 등록해보겠습니다. 그러나 SC.EXE 유틸리티로 서비스를 등록하려면 시스템 관리자 권한이 필요하므로 명령 프롬프트를 관리자 권한 또는 권한 상승 모드에서 시작해주셔야 합니다. 만약 설치 프로그램에서 구동한다면 설치 프로그램 초입에 권한 상승이 동반되므로 따로 신경쓸 것은 없습니다.

SC.EXE 유틸리티의 문법은 사소한 부분에서 실수하기 쉬우므로 문자그대로 아래와 같이 정확하게 입력해야 함을 유의하기 바랍니다.

sc create <서비스 이름> binPath= "<ipyw64.exe의 절대 경로> <IronPython 스크립트 파일의 절대 경로>"

sc create mysvc binPath= "C:\Program Files\IronPython 2.7\ipyw64.exe c:\users\rkttu_000\desktop\service.py"

다른 것보다도 binPath= 부분에 유의합니다. binPath = "~" 도 아니고 binPath ="~"도 아니며 오로지 binPath= "~" 로 해야만 올바른 문법으로 인지됩니다. binPath 옵션에 IronPython Non-Console 인터프리터의 경로와 함께 실제 구현 코드를 포함하는 IronPython 스크립트 파일을 지정했습니다.

주의할 것은 여기에 서술하는 모든 경로는 절대 경로로 서술해야 합니다.

서비스 테스트하기

정상적으로 서비스가 설치되었다면 "[SC] CreateService 성공" 이라는 메시지를 볼 수 있습니다. 그리고 서비스의 시작을 위해서 아래와 같이 명령어를 실행하거나 서비스 관리자에서 여러분이 등록한 서비스를 찾아 시작시킵니다.

C:\Windows\system32>net start mysvc
mysvc 서비스를 시작합니다..
mysvc 서비스가 잘 시작되었습니다.

그리고 서비스 중지도 잘 되는지 확인합니다.

C:\Windows\system32>net stop mysvc
mysvc 서비스를 멈춥니다..
mysvc 서비스를 잘 멈추었습니다.

팁 한가지

IronPython을 이용하여 Windows NT 서비스를 만들 수 있으므로, 복잡한 템플릿에 의존하거나 유틸리티를 사용하는 일 없이 기존 서버 로직을 얼마든지 NT 서비스로 옮길 수 있는 것은 참 바람직합니다. 하지만 NT 서비스는 디버깅하기 매우 불리한 구조를 가지고 있는데, 이를 극복할 방법이 없을지 고민을 해보게 됩니다.

Microsoft의 기술 자료 (http://msdn.microsoft.com/ko-kr/library/7a50syb3(v=vs.90).aspx) 의 내용을 참고하여 직접 서비스를 디버깅하는 것이 널리 알려진 방법입니다. 이 방법은 정말 살아있는 실제 서비스를 대상으로 디버깅을 하는 것이므로 보안 권한을 포함한 모든 사항이 가장 실제 운영 환경에 근접한 것입니다.

그러나 좀 더 단순하게 서비스 로직 자체에 대한 디버깅이나 검증이 필요하다면 다른 접근 방법이 더 좋을 수 있습니다. NT 서비스로 만들기에 앞서 보통의 콘솔 응용프로그램으로 띄울 수 있도록 핵심 로직만 발췌해서 가지고 오도록 하면, NT 서비스로 배포하기에 앞서서 핵심 로직 자체만 따로 평가할 수 있으므로 더 유용할 것입니다.

그리고 프로그램에 전달된 인자를 활용하여 -service 같은 문자열이 전달된 것을 확인할 때에만 ServiceBase.Run 메서드를 호출한다면 NT 서비스가 아닌 상태와 NT 서비스인 상태를 동시에 지원할 수 있어 더욱 유용할 것입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

이벤트2013. 4. 16. 21:17



전세계 90개국 이상에서 동시에 열리는 Global Windows Azure Bootcamp의 한국 행사인 Windows Azure Lounge가 2013년 4월 27일 토요일 오전 9시부터 오후 6시까지 서울시 강남구 역삼동 (주)세완/세완교육센터에서 열립니다. 행사에 등록하시려면 아래 웹 페이지에 방문하여 주십시오.

행사 등록 신청 바로가기 - http://onoffmix.com/event/14374

행사 등록 신청 바로가기 - http://onoffmix.com/event/14374

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

IaaS2013. 4. 1. 10:01

안녕하세요. Windows Azure MVP 남정현입니다.

오늘 살펴보려는 내용은 Windows Azure Virtual Machine 환경에서 FileZilla Server를 구축하려는 경우 어떻게 해야 파일 전송 채널들을 열 수 있는지 그 방법을 살펴보려고 합니다. FileZilla Server는 기본적으로 FTP (File Transfer Protocol)를 구현하는 서버이며 무료로 사용 가능한 서버이고 다양하고 풍부한 기능을 제공하면서도 안정적으로 작동해서 많은 곳에서 널리 사용되고 있습니다.

우선 FTP 서버가 어떻게 동작하는지 잠시 상기해볼 필요가 있습니다. FTP 서버는 기본적으로 명령어를 주고 받기 위한 채널 포트인 21번 포트가 가장 핵심이 됩니다. 최초의 연결도 이 21번 포트를 시작으로 이루어지고 이후에 모든 데이터 전송은 별도의 포트를 통해서 이루어지게 됩니다. FTP 서버를 방화벽 뒤에 배치하는 것이 왜 까다로운가 하면 바로 이러한 FTP의 설계 때문입니다. 데이터 전송을 위한 포트를 FTP 서버의 사용량에 맞추어 개방을 해야 하기 때문입니다. 그래서 서버가 사전에 TCP 포트 대역을 정해놓고 지정된 범위에서만 데이터 전송을 위한 채널을 제공하는 Passive Mode가 많이 사용됩니다.

1단계: FileZilla Server Passive Mode 설정

기본적으로 Passive Mode를 사용하는 것은 특이 사항이 없습니다만, Windows Azure Virtual Machine의 경우 Windows Azure Virtual Machine이 제공하는 기본 방화벽과 Windows Server의 기본 방화벽, 그리고 FileZilla Server만을 위한 특별한 설정이 더해져야 하기 때문에 따로 아티클로 정리할 필요가 있어 이번 글을 작성하게 되었습니다.

FileZilla 서버에 사용자 계정을 등록하고 홈 디렉터리를 설정하는 방법은 생략하겠습니다. 이 과정이 되어있어야 실제 접속과 테스트가 가능하니 설정되어있지 않은 경우 반드시 사용자 계정과 홈 디렉터리 설정을 먼저 해주셔야 합니다. 이 설정이 끝난 상태에서, FileZilla Server Options 페이지로 들어가서 아래 그림과 같이 Passive Mode Settings 항목을 클릭하여 주십시오.

위의 그림에 나타난 것 처럼, 다음의 설정 사항들을 확인하여 적절히 수정해야 합니다.

  • Use Custom Port Range: 체크하시고 적절한 포트 대역을 지정합니다. Windows Server 및 Windows Azure Virtual Machine의 방화벽 설정에 모두 등록되지 않은 대역을 지정합니다. Windows Azure Virtual Machine의 방화벽 설정은 범위 설정이 아니라 1개 포트씩 따로 등록하도록 되어있고 등록과 할당에 시간이 걸리니 이를 감안하시어 2개~3개 정도의 포트만 개방하는 것이 작업 시간 단축에 도움이 됩니다.
  • IPv4 specific 아래의 Retrieve external IP address from: 기본값은 이 항목으로 설정되어있습니다. 그러나 간혹 이 설정으로 IP 주소를 얻어올 수 없는 경우, Use the following IP 항목의 라디오 버튼을 체크하고 이 서비스에 접속할 수 있는 외부 IP 주소 (Virtual IP)를 직접 설정하거나 FQDN을 지정하여 문제를 해결할 수 있습니다.
  • Don't use external IP for local connections: 이 항목도 체크합니다.
2단계: Windows Server 방화벽 구성

위와 같이 설정이 끝나면, Windows 방화벽 설정으로 이동하여 방화벽에 FileZilla Server 프로그램 자체를 허용 대상에 추가하여 주십시오. Windows Server 64비트 버전을 사용하게 되실 것이므로 아래 경로를 지정하시면 됩니다.

%PROGRAMFILES(X86)%\FileZilla Server\FileZilla server.exe

정상적으로 등록이 되면 아래 그림과 같이 속성에 나타날 것입니다.

3단계: Windows Azure Virtual Machine 방화벽 설정

1단계에서 설정한 포트 대역의 각 포트 번호들과 TCP 21번 포트를 빠짐없이 Windows Azure Virtual Machine 방화벽 설정으로 가서 개방해야 밖에서 데이터 전송이 가능합니다.

위 그림에서 보시는 것과 같이 Passive 포트 대역을 모두 TCP 포트 번호로 지정하여 공용 포트와 개인 포트 번호를 일치시켜주고, 최종적으로 21번 포트를 지정하면 됩니다. 여기서는 Secured FTP를 위하여 별도 포트 번호를 지정하였습니다만 상황에 맞게 설정해주시면 문제 없습니다.

4단계: 접속 및 파일 송수신 테스트

정상적으로 접속과 파일 송수신이 이루어지는지 다시 한 번 확인합니다. 클라이언트의 경우 아래 그림과 같은 모습으로 진행이 이루어져야 합니다.

서버의 경우 아래 화면처럼 로그가 나타나게 될 것입니다.


다른 FTP 서버에 대한 설정도 지금 설명한 사항들을 점검하여 설정을 업데이트하면 Virtual Machine에서 정상적인 FTP 서비스 구축이 가능할 것입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요