<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>OddPoet&#039;s βetalog</title>
	<atom:link href="http://oddpoet.net/feed" rel="self" type="application/rss+xml" />
	<link>http://oddpoet.net</link>
	<description>언제나 미완성... 목표는 인생의 달인!</description>
	<lastBuildDate>Thu, 17 May 2012 10:17:44 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>SPDY : 더 빠른 웹을 위한 실험적인 프로토콜</title>
		<link>http://oddpoet.net/archives/409</link>
		<comments>http://oddpoet.net/archives/409#comments</comments>
		<pubDate>Thu, 17 May 2012 10:02:12 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[웹]]></category>
		<category><![CDATA[Google Chrome]]></category>
		<category><![CDATA[SPDY]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=409</guid>
		<description><![CDATA[이 글은 SPDY의 백서 &#8220;SPDY : An experimental protocol for a faster web&#8220;를 번역한 글입니다.  개요 &#8220;웹을 더 빠르게 하자(Let&#8217;s make the web faster)&#8221; 계획의 부분으로써 우리는 웹페이지들의 반응속도를 높일 수 있는 대안 프로토콜을 실험 중에 있다. 이러한 실험 중에 하나가 SPDY(SPeeDY-스피디라고 읽는다)인데, SPDY는 웹에서 컨텐츠 전송을 위한 애플리케이션 레이어 프로토콜이다. 이 프로토콜 명세와 더불어 [...]]]></description>
			<content:encoded><![CDATA[<p>이 글은 SPDY의 백서 &#8220;<em><a href="http://dev.chromium.org/spdy/spdy-whitepaper" onclick="pageTracker._trackPageview('/outgoing/dev.chromium.org/spdy/spdy-whitepaper?referer=');">SPDY : An experimental protocol for a faster web</a></em>&#8220;를 번역한 글입니다.</p>
<h2> 개요</h2>
<p>&#8220;웹을 더 빠르게 하자(<a href="https://developers.google.com/speed/?hl=ko-KR" onclick="pageTracker._trackPageview('/outgoing/developers.google.com/speed/?hl=ko-KR&amp;referer=');">Let&#8217;s make the web faster</a>)&#8221; 계획의 부분으로써 우리는 웹페이지들의 반응속도를 높일 수 있는 대안 프로토콜을 실험 중에 있다. 이러한 실험 중에 하나가 SPDY(SPeeDY-스피디라고 읽는다)인데, SPDY는 웹에서 컨텐츠 전송을 위한 애플리케이션 레이어 프로토콜이다. 이 프로토콜 명세와 더불어 우리는 SPDY를 지원하는 구글 크롬 브라우저와 오픈소스 웹서버를 만들었다. 내부 테스트를 통해 우리는 HTTP와 SPDY 위의 어플리케이션의 성능을 비교한 결과 SPDY를 사용할 때 페이지 로드 타임을 64%정도 줄일 수 있었다. 우리는 오픈 소스 커뮤니티에 아이디어, 피드백, 코드, 테스트 결과들을 기여함으로써 보다 SPDY가 빠른 웹을 위한 차세대 애플리케이션 프로토콜이 되길 바란다.<span id="more-409"></span></p>
<h2>배경 : 웹 프로토콜과 웹 반응속도</h2>
<p>현재 웹에서 사용하는 프로토콜은 HTTP와 TCP이다. TCP는 일반적이고 신뢰성있는 전송프로토콜(transport protocol)로써 전송 보증(guaranteed delivery), 중복 제거(duplicate suppression), 순서유지(in-order delivery), 흐름제어(flow control), 혼잡 회피(congestion avoidance) 등을 제공한다. HTTP는 어플리케이션 레벨 프로토콜로써 기본적인 요청/응답의 시맨틱스를 제공한다. 우리는 전송계층(transport layer)에서 반응속도를 개선할 가능성이 더 있다고 생각하지만, 최초에는 어플리케이션 레이어(HTTP) 쪽을 중점적으로 조사를 했다. 불행히도 HTTP는 반응속도에 대해 특별히 고려된 프로토콜이 아니었다. 게다가 현재 네트웍 상에서 전송되는 웹페이지들은 10년 전의 웹페이지들과는 많이 달라졌고, 이에 따라 HTTP가 개발되던 시절에 예상할 수 없었던 개선이 필요하게 됐다. 다음은 HTTP가 최적의 성능을 내는데 저해가 되는 기능들이다.</p>
<ul>
<li><strong>커넥션 당 하나의 요청</strong>. HTTP가 한번에 하나의 리소스만 가져올 수 있기 때문에(HTTP piplelining이 도움이 되지만 여전히 FIFO queue라는 제약이 있다), 500ms의 서버 지연시간이 다른 요청에 대해서 TCP channel의 재사용을 어렵게 한다. 그래서 브라우저들은 다수의 컨넥션을 사용하여 이 문제를 우회하고 있다. 2008년 이후 대부분의 브라우저들은 결국 도메인 당 커넥션 수를 2개에서 6개로 늘렸다.</li>
<li><strong>클라이언트만 가능한 요청.</strong> HTTP하에서 오직 클라이언트만 요청을 보낼 수 있다. 서버 측에서 클라이언트가 어떤 리소스가 필요하리라는 걸 알더라도 클라이언트에 알릴 메커니즘이 존재하지 않는다. 서버는 클라이언트가 요청할 때까지 기다려야만 한다.</li>
<li><strong>압축되지 않는 요청/응답 헤더</strong>. 요즘 요청 헤더의 크기는 200byte에서 2KB까지 다양해졌다. 어플리케이션에서 쿠키를 더 많이 사용하고, user agent의 기능이 확장됨에 따라 일벅으로 헤더 크기가 700~800 바이트가 되었다. 업링크 대역폭이 아주 작은 모뎀이나 ADSL 연결에서 헤더의 데이터를 줄이는 것은 요청을 보낼 때의 응답시간을 직접적으로 향상 시킬 수 있다.</li>
<li><strong>중복 헤더들</strong>. 같은 채널에서의 대부분의 헤더들은 모든 요청에 반복적으로 사용된다. 그러나 User-Agent, Host, Accept* 등의 헤더 값들은 고정된 값이며 일반적으로 재전송이 필요하지 않다.</li>
<li><strong>선택적인 데이터 압축.</strong> HTTP는 선택적으로 데이터에 대한 압축을 사용할 수 있다. 하지만 컨텐츠는 항상 압축되어 전송되는 것이 좋다.</li>
</ul>
<h3>기존의 접근방법들</h3>
<p>SPDY는 HTTP를 빠르게 하려는 유일한 연구는 아니다. 웹의 반응속도 개선에 대한 제안들 있었고, 그 대부분이 전송계층이나 세션계층에서의 연구였다.</p>
<ul>
<li>Stream Control Transmission Protocol (SCTP) &#8212; TCP를 대체하는 전송계층 프로토콜로써 멀티플렉스 스트림과 스트림 기반 혼잡제어를 제공한다.</li>
<li>HTTP over SCTP &#8212; SCTP 위에 HTTP를 작동하자는 제안. Comparision of HTTP Over SCTP and TCP in High Delay Networks 는 두 전송프로토콜 위에서의 성능 비교 연구 결과를 보여준다.</li>
<li>Structured Stream Transport (SST) &#8212; &#8220;structured streams&#8221;는 일반적인 전송층 위에서 사용될 수 있는 가볍고, 독립적인 스트림을 만드는 프로토콜. 이것은 TCP를 대체하거나 UDP의 위에서 작동할 수 있다.</li>
<li>MUX와 SMUX &#8212; 전송계층과 어플리케이션 레이어 사이에 작동하는 중간 레이어 프로토콜. 스트림의 멀티플렉싱을 지원한다. HTTP/1.1과 비슷한 시기에 제안됐다.</li>
</ul>
<p>상기 제안들은 웹의 반응속도 문제에 대한 부분적인 해결책을 제공한다. HTTP의 압축, 우선순위 등의 고유의 문제는 여전히 남아있고, 그 아래의 전송계층과 별개의 문제다. 현실상황의 어떤 경우라도 전송계층을 변경하는 일은 매우 어렵다. 대신 우리는 어플리케이션 레이어에서의 문제들을 정리하면서 더 쉬운 해결방안 있다고 생각했다. 이 방법은 기존의 인프라스트럭쳐에 최소한의 변경만 필요로 하고, (우리 생각에는) 상당한 성능 향상을 얻어낼 수 있을 것이다.</p>
<h2>SPDY의 목표</h2>
<p>SPDY 프로젝트는 웹의 반응속도를 개선할 수 있는 어플리케이션 프로토콜을 정의하고 구현한다. SPDY의 상위 수준 목표는 다음과 같다.</p>
<ul>
<li>페이지 로드 타임 50% 감소. 우리의 사전작업 결과는 이 목표에 이미 근접하고 있다. (아래)</li>
<li>도입 복잡도를 최소화. SPDY는 전송계층으로 TCP를 사용하여 기존 네트웍 인프라스트럭쳐에 어떤 변화도 필요로 하지 않는다.</li>
<li>웹사이트의 컨테츠에 변경이 필요하지 않도록 함. SPDY는 오직 클라이언트 user agent와 웹서버만 변경하면 되도록 한다.</li>
<li>반응속도의 문제를 해결하기 위한 방법으로 프로토콜을 탐구하는 사람들이 함께 할 수 있도록 한다. 우리는 오픈 소스 커뮤니티와 산업계 전문가들의 협력하에서 이 새로운 프로토콜을 개발하길 원한다.</li>
</ul>
<p>기타 기술적인 목표들은 다음과 같다.</p>
<ul>
<li>하나의 TCP 세션을 통해 다수의 HTTP요청을 동시에 수행할 수 있게 한다.</li>
<li>헤더에 대한 압축과 불필요한 헤더를 제거함으로써 HTTP가 현재 사용중인 대역폭을 줄인다.</li>
<li>구현하기 쉬우면서 서버측에 효율적인 프로토콜을 정의한다. 우리는 예외적인 상황들을 줄이고 파싱하기 쉬운 메시지 포맷을 정의함으로써 HTTP의 복잡도를 낮추려고 한다.</li>
<li>전송계층 위의 SSL을 보다 보안성있고 기존 네트웍 인프라스트럭쳐에 보다 호환성이 좋게 만든다. SSL은 반응속도 측면에서는 불리하지만, 장기적인 관점에서 웹은 결국 보안 네트웍 연결에 의존하게 될 것이라 믿는다. 추가적으로 SSL의 사용이 기존의 프락시들을 통한 통신이 깨지지 않도록 보장해야 한다.</li>
<li>서버가 클라이언트와의 통신을 시작할 수 있게 하여, 가능한 상황에서는 클라이언트에 데이터를 push해줄 수 있도록 한다.</li>
</ul>
<h2>SPDY 설계와 기능</h2>
<p>SPDY는 SSL 위에 세션계층을 추가했다. 이것은 하나의 TCP 연결 위에서 다수의, 동시수행되는 스트림을 교차배치할 수 있게 한다.</p>
<p>일반적인 HTTP GET과 POST 메시지 포맷은 동일하다. 그러나 SPDY는 연결위에서 데이터를 인코딩하고 전송하는 새로운 프레이밍 포맷을 정의한다.</p>
<p><img class="alignnone size-full wp-image-416" title="soarjOjSeS5hoFYvjtAnxCg" src="http://oddpoet.net/wp-content/uploads/2012/05/soarjOjSeS5hoFYvjtAnxCg1.png" alt="" width="267" height="132" /></p>
<p>스트림들은 양방향이다. 즉, 스트림은 클라이언트와 서버에 의해서 생성될 수 있다.</p>
<p>기본 기능(항상 활성화됨)과 고급 기능(선택적으로 활성화)을 통해서 SPDY는 빠른 반응속도를 얻을 수 있다.</p>
<h3> 기본 기능</h3>
<ul>
<li><strong>멀티플렉스 스트림<br />
</strong>SPDY는 하나의 TCP 연결 위에 무제한 동시 스트림을 지원한다. 하나의 채널에서 요청들이 교차배치되기 때문에 TCP의 효율은 높아진다. 적은 네트웍 연결만 필요하고, 더 적은 수의 패킷만 사용된다.</li>
</ul>
<ul>
<li><strong>요청 우선순위<br />
</strong>무제한으로 스트림을 병렬화 할 수 있기 때문에 직렬화 문제를 해결할 수 있지만, 이것은 다른 문제를 만든다. 채널의 대역폭이 제한되어 있다면 채널이 막힐 수 있기 때문에 클라이언트는 요청을 블록할 수 있다. 이 문제를 극복하기 위해서 SPDY는 요청 우선순위를 구현한다 : 클라이언트는 원하는 수 만큼 요청할 수 있고, 각 요청에 우선순위를 부여한다. 이것은 네트웍 채널이 중요하지 않은 리소스들 때문에 혼잡해져서 높은 우선순위의 요청이 지연되는 것을 막는다.</li>
</ul>
<ul>
<li><strong>헤더 압축<br />
</strong>SPDY는 요청과 응답의 HTTP 헤더를 압축한다. 결과적으로 더 적은 수의 패킷과 더 적은 수의 데이터가 전송된다.</li>
</ul>
<h3> 고급 기능 (advanced features)</h3>
<p>SPDY는 서버에서 스트림을 생성할 수 있는 기능을 제공한다. 서버에서 생성된 스트림은 클라이언트의 요청이 없어서도 컨텐츠를 클라이언트에 전송할 수 있다. 웹개발자는 이 옵션을 2가지 방식으로 설정할 수 있다.</p>
<ul>
<li><strong>Server push<br />
</strong>SPDY는 서버가 클라이언트에 데이터를 푸쉬(push)하는 것을 X-Associated-Content 헤더를 통해서 실험했다. 이 헤더는 클라이언트가 요청하기 전에 서버가 리소스를 푸시하고 있다는 것을 클라이언트에 알린다. 사용자가 사이트에 처음으로 방문하여 첫 페이지를 다운로드할 때, 이 기능은 사용자 경험을 크게 향상시킬 수 있다.</li>
</ul>
<ul>
<li><strong>Server hint.<br />
</strong>자동으로 클라이언트에 리소스를 push하는 대신, X-Subresources 헤더를 사용하여 클라이언트가 특정 리소스를 요청하도록 제안하지만, 컨텐츠를 보내기 전에 클라이언트의 요청을 기다리게 된다. 네트웍 연결이 느린 경우, 클라이언트가 필요한 리소스들을 파악하는 걸리는 수백 밀리세컨드의 시간을 줄여줄 수 있다. 이 옵션은 초기 페이지가 아닌 페이지에서 더 나은 선택이 될 것이다.</li>
</ul>
<p>기술적인 세부사항은 <a href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft1" onclick="pageTracker._trackPageview('/outgoing/dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft1?referer=');">SPDY draft protocol specification</a>을 참조하라.</p>
<h2> SPDY 구현 : 우리가 만들어온 것들</h2>
<ul>
<li>HTTP와 SPDY로 TCP와 SSL 위에서 효율적으로 응답할 수 있는 high-speed의 in-memory 서버. 우리는 조만간 오픈소스로 이 코드를 릴리즈할 예정이다.</li>
<li>TCL와 SSL위에서 HTTP나 SPDY를 사용할 수 있는 수정된 구글 크롬. 소스 코드는 <a href="http://src.chromium.org/viewvc/chrome/trunk/src/net/spdy/" onclick="pageTracker._trackPageview('/outgoing/src.chromium.org/viewvc/chrome/trunk/src/net/spdy/?referer=');">http://src.chromium.org/viewvc/chrome/trunk/src/net/spdy/</a> 에 있다. (주: 현재 내부적인 코드네임은 &#8220;flip&#8221;이지만, 조만간 바뀌게 될 것이다.)</li>
<li>페이지들이 정확하게 복사되었는지 검증할 수 있는 테스팅과 벤치마킹 인프라스트럭처. SPDY는 원래의 서버 헤더들과 컨텐츠 인코딩, URLs 등을 그대로 유지한다. 우리는 곧 테스팅 툴과 우리의 결과를 재활용하기 위한 가이드를 릴리즈할 것이다.</li>
</ul>
<h3>사전 실험 결과</h3>
<p>우리가 개발한 구글 크롬 클라이언트와 웹서버를 가지고, SPDY의 HTTP 대비 성능을 벤치마크하기 위해서 많은 실험실 테스트를 수행했다.</p>
<p>우리는 1%의 패킷손실을 가지는 홈 네트워크 시뮬레이션 하에서 &#8220;Top 100&#8243; 웹사이트 중 25개를 다운로드 해봤다. 각 사이트를 10번씩 다운로드했고, 평균 페이지 로딩 시간을 계산했다. 그 결과 HTTP 대비 TCP 하에서는 27%~60%정도, SSL하에서는 39%~55% 정도의 성능향상을 확인할 수 있었다.</p>
<p><em>표1 : Top 25 웹사이트들에 대한 평균 페이지 로딩 타임</em></p>
<table border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<td width="20%"></td>
<td colspan="2">DSL 2 Mbps downlink,<br />
375 kbps uplink</td>
<td colspan="2">Cable 4 Mbps downlink,<br />
1 Mbps uplink</td>
</tr>
<tr>
<td width="20%"></td>
<td width="20%"><strong>A</strong><strong>verage ms</strong></td>
<td width="20%"><strong>Speedup</strong></td>
<td width="20%"><strong>Average ms</strong></td>
<td width="20%"><strong>Speedup</strong></td>
</tr>
<tr>
<td width="20%">HTTP</td>
<td width="20%">3111.916</td>
<td width="20%"></td>
<td width="20%">2348.188</td>
<td width="20%"></td>
</tr>
<tr>
<td width="20%">SPDY basic multi-domain* connection / TCP</td>
<td width="20%">2242.756</td>
<td width="20%">27.93%</td>
<td width="20%">1325.46</td>
<td width="20%">43.55%</td>
</tr>
<tr>
<td width="20%">SPDY basic single-domain* connection / TCP</td>
<td width="20%">1695.72</td>
<td width="20%">45.51%</td>
<td width="20%">933.836</td>
<td width="20%">60.23%</td>
</tr>
<tr>
<td width="20%">SPDY single-domain + server push / TCP</td>
<td width="20%">1671.28</td>
<td width="20%">46.29%</td>
<td width="20%">950.764</td>
<td width="20%">59.51%</td>
</tr>
<tr>
<td width="20%">SPDY single-domain + server hint / TCP</td>
<td width="20%">1608.928</td>
<td width="20%">48.30%</td>
<td width="20%">856.356</td>
<td width="20%">63.53%</td>
</tr>
<tr>
<td width="20%">SPDY basic single-domain / SSL</td>
<td width="20%">1899.744</td>
<td width="20%">38.95%</td>
<td width="20%">1099.444</td>
<td width="20%">53.18</td>
</tr>
<tr>
<td width="20%">SPDY single-domain + client prefetch / SSL</td>
<td width="20%">1781.864</td>
<td width="20%">42.74%</td>
<td width="20%">1047.308</td>
<td width="20%">55.40%</td>
</tr>
</tbody>
</table>
<p><em>* 많은 경우 요청 리소스가 속한 도메인의 수와 무관하게 SPDY는 하나의 연결로 모든 요청을 처리할 수 있다. 이 경우에는 모든 다운로드는 완전히 병렬로 진행될 수 있다. 그러나 모든 도메인을 하나의 도메인으로 줄일 수 없는 경우가 있는데, 이 경우에는 SPDY는 각 도메인별로 연결을 만들어야만한다. 이때 연결 생성마다 초기 RTT(round trip time)만큼의 시간이 필요하다.</em></p>
<p><em>그래서 우리는 2가지 모드에 대해서 테스트를 수행했다. 하나는 모든 도메인을 하나로 줄여놓은 경우(즉, 하나의 TCP 연결)와 리소스를 원래 속한 다수의 도메인으로 나누어놓은 경우(즉, 도메인 당 하나의 TCP 연결). 우리는 테스트 결과에 이를 각각 &#8220;single-domain&#8221;과 &#8220;multi-domain&#8221;이라고 표기해놓았다. 실제 환경에서의 결과는 이 결과의 사이값이 될 것이다.</em></p>
<h4>헤더 압축의 효과</h4>
<p>헤더 압축은 요청헤더의 크기를 88%, 응답헤더의 크기를 85%정도 줄인다. 낮은 대역폭을 갖는 DSL 환경에서(업로드가 375Kbps) 요청헤더 압축은 특정사이트에서 페이지 로딩 시간에 있어서 상당한 성능향상을 보여주었다(리소스 요청이 수가 많은 경우). 헤더압축만 해도 페이지 로딩 시간을 45~1142ms만큼 줄일 수 있었다.</p>
<h4>패킷 손실과 RTT(round-trip time)의 효과</h4>
<p>우리는 두번째 테스트로 패킷 손실율과 RTT가 결과에 어떤 영향을 주는지 확인하기 위한 테스트를 수행했다. 이 테스트를 위해서 패킷 손실율과 RTT를 실험변수가 아니라 케이블 연결에서 측정을 했다.</p>
<p>우리는 SPDY의 응답시간 향상효과가 패킷손실율이 높아질 수록 증가한다는 것을 알아냈다. 패킷손실율이 2%일때 최대 48%의 성능향상이 있었다. (패킷손실이 2%는 넘어서면 성능향상폭이 줄어들기 시작해서 2.5%가 되면 성능향상율이 떨어진다. 실제 미국내에서 패킷손실율은 일반적으로 1~2%이고, RTT는 평균 50-100ms이다.)</p>
<p>패킷손실율이 증가할 수록 SPDY가 더 나은 이유는</p>
<ul>
<li>SPDY는 HTTP보다 40% 정도 적은 패킷을 전송한다. 즉 패킷손실율의 영향을 덜 받는다.</li>
<li>SPDY는 보다 적은 TCP 컨넥션을 사용한다. 이것은 SYN packet이 손실될 가능성을 줄인다. 많은 TCP 구현에서 이것에 대한 지연은 의외로 크다. (3초)</li>
<li>SPDY는 재전송 타이머보다는 TCP의 빠른 재전송을 이용하여 TCP를 보다 효율적으로 이용한다.</li>
</ul>
<p>SPDY는 RTT가 증가하면 더 나은 성능을 보였다. RTT가 200ms일때 최대 27%의 성능향상이 있었다. RTT가 증가할 때 SPDY가 HTTP 대비 더 나은 성능보이는 이유는 SPDY가 모든 요청을 병렬로 처리하기 때문이다. HTTP 클라이언트가 도메인당 4개의 연결을 사용하고, 20개의 리소스를 받아야 한다면, 대략 5RT(round trip)가 걸릴것이다. SPDY는 20개의 리소스를 한번의 RT만에 가져올 수 있다.</p>
<p><em>표2 : Top 25 웹사이트에 대한 평균 페이지로딩시간 (패킷손실율 별)</em></p>
<div>
<div>
<table width="581" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<td width="25%"></td>
<td colspan="2">Average ms</td>
<td width="25%">Speedup</td>
</tr>
<tr>
<td width="25%">Packet loss rate</td>
<td width="25%">HTTP</td>
<td width="25%">SPDY basic (TCP)</td>
<td width="25%"></td>
</tr>
<tr>
<td width="25%">0%</td>
<td width="25%">1152</td>
<td width="25%">1016</td>
<td width="25%">11.81%</td>
</tr>
<tr>
<td width="25%">0.5%</td>
<td width="25%">1638</td>
<td width="25%">1105</td>
<td width="25%">32.54%</td>
</tr>
<tr>
<td width="25%">1%</td>
<td width="25%">2060</td>
<td width="25%">1200</td>
<td width="25%">41.75%</td>
</tr>
<tr>
<td width="25%">1.5%</td>
<td width="25%">2372</td>
<td width="25%">1394</td>
<td width="25%">41.23%</td>
</tr>
<tr>
<td width="25%">2%</td>
<td width="25%">2904</td>
<td width="25%">1537</td>
<td width="25%">47.7%</td>
</tr>
<tr>
<td width="25%">2.5%</td>
<td width="25%">3028</td>
<td width="25%">1707</td>
<td width="25%">43.63%</td>
</tr>
</tbody>
</table>
</div>
</div>
<div><em><br />
</em></div>
<div>
<div>
<div>
<div>
<div>
<div>
<div><em>표3 : Top 25 웹사이트에 대한 평균 페이지로딩시간 (RTT 별)</em></div>
<div></div>
<div>
<div>
<table width="582" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<td width="25%"><strong><br />
</strong></td>
<td colspan="2"><strong>Average ms<br />
</strong></td>
<td width="25%"><strong>Speedup</strong></td>
</tr>
<tr>
<td width="25%"><strong>RTT in ms<br />
</strong></td>
<td width="25%"><strong>HTTP<br />
</strong></td>
<td width="25%"><strong>SPDY basic (TCP)<br />
</strong></td>
<td width="25%"></td>
</tr>
<tr>
<td width="25%">20</td>
<td width="25%">1240</td>
<td width="25%">1087</td>
<td width="25%">12.34%</td>
</tr>
<tr>
<td width="25%">40</td>
<td width="25%">1571</td>
<td width="25%">1279</td>
<td width="25%">18.59%</td>
</tr>
<tr>
<td width="25%">60</td>
<td width="25%">1909</td>
<td width="25%">1526</td>
<td width="25%">20.06%</td>
</tr>
<tr>
<td width="25%">80</td>
<td width="25%">2268</td>
<td width="25%">1727</td>
<td width="25%">23.85%</td>
</tr>
<tr>
<td width="25%">120</td>
<td width="25%">2927</td>
<td width="25%">2240</td>
<td width="25%">23.47%</td>
</tr>
<tr>
<td width="25%">160</td>
<td width="25%">3650</td>
<td width="25%">2772</td>
<td width="25%">24.05%</td>
</tr>
<tr>
<td width="25%">200</td>
<td width="25%">4498</td>
<td width="25%">3293</td>
<td width="25%">26.79%</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h2>SPDY 다음 단계 : 어떻게 우리를 도울 수 있는 방법</h2>
<p>우리의 초기 결과는 좋지만, 우리는 그것들을 어떻게 현실 세계에 적용해야 할지 잘 모르겠다. 게다가 SPDY는 더 개선할 여지들이 있다. 예를 들면</p>
<ul>
<li>대역폭 효율성은 여전히 낮다. 전화망의 대역폭 효율이 90%에 근접한다고 하지만, 고속망의 효율은 약 32% 밖에 안된다.</li>
<li>SSL은 추가적인 지연시간과 도입에 대한 난관들을 가지고 있다. SSL 핸드셰이킹를 위한 추가적인 RTT, 암호화, 프락시에서의 캐싱 문제 등이다. 우리는 SSL에 더 적합하게할 일들이 있다.</li>
<li>우리의 패킷 손실율에 대한 실험결과로는 아직 결론을 내릴 수 었다. 패킷 손실율에 대한 연구들이 있었지만, 우리는 웹을 위한 현실적인 모델을 만들기 위한 충분한 데이터를 가지고 있지 않다. 패킷 손실율 시뮬레이션을 더 정교하게 할 수 있는 데이터들을 더 수집할 필요가 있다.</li>
<li>SPDY의 단일 커넥션은 연결이 끊겨서 복구할 경우 다수 컨넥션보다 때로는 성능이 낮을 수 있다. 특히 RTT가 매우 높을 때는 단일 커넥션보다 멀티플 커넥션이 빠를 수 있다. 언제 SPDY 클라이언트가 새로운 커넥션을 만들거나 기존 커넥션을 끊을지, 이것이 서버에 어떤 영향을 줄지를 더 고민할 필요가 있다.</li>
<li>서버는 우리가 만든 것보다 더 지능적으로 구현할 수 있을 것이다. 서버가 스트림을 생성하는 경우, 프리패칭 제안을 위해서 클라이언트 네트웍 정보를 수집하는 등의 케이스에 대해서 더 많은 연구가 필요하다.</li>
</ul>
<p>이런 문제에 도전하기 위해 당신이 참여하길 원한다면…</p>
<ul>
<li><a href="http://groups.google.com/group/chromium-discuss?pli=1" onclick="pageTracker._trackPageview('/outgoing/groups.google.com/group/chromium-discuss?pli=1&amp;referer=');">chrome-discuss discussion group</a> 으로 피드백, 코멘트, 제안, 아이디어를 보낸다.</li>
<li><a href="http://src.chromium.org/viewvc/chrome/trunk/src/net/spdy/" onclick="pageTracker._trackPageview('/outgoing/src.chromium.org/viewvc/chrome/trunk/src/net/spdy/?referer=');">Google Chrome client code</a> 에서 소스를 다운로드 받아서 빌드, 실행, 테스트 해보고</li>
<li>소스코드에 직접 개선을 한다.</li>
</ul>
<h2>SPDY에 대한 FAQ</h2>
<p><strong>Q: HTTP piplelining이 이미 응답시간에 대한 문제를 해결하지 않았나요?</strong></p>
<p>A: No. 파이프라이닝이 다수의 요청을 하나의 TCP 연결하에서 병렬로 보내는 것을 지원하고 있지만, 여전히 단일 스트림입니다. 스트립 내의 과정에서 지연이 발생하면(첫 요청이 long request이거나 패킷 손실이 발생하거나해서) 전체 스트림이 지연됩니다. 파이프라이닝은 적용이 어렵기도 해서 대부분의 주요 브라우저에서 기본적으로 비활성화된 채로 남아있지요.</p>
<p><strong>Q: SPDY는 HTTP를 대체하는 겁니까?</strong></p>
<p>A: 아니오. SPDY는 HTTP의 일부분을 대체할 뿐이고, HTTP를 확장합니다. 어플리케이션 계층의 최상위 레벨에서 요청-응답 프로토콜은 여전히 동일합니다. SPDY는 여전히 HTTP 메소드, 헤더 등을 사용합니다. 그러나 SPDY는 커넥션 관리와 데이터 전송 포맷 등의 프로토콜의 다른 부분을 오버라이드합니다.</p>
<p><strong>Q: 왜 이름을 이렇게 지었습니까?</strong></p>
<p>A: 우리는 이름이 &#8216;속도&#8217;의 의미를 내포하길 원했습니다. SPDY(SPeeDY라고 읽음)는 그에 부합할 뿐만 아니라 압축이 어떻게 속도 향상에 도움되는지를 보여주기도 하지요.</p>
<p><strong>Q: SPDY는 전송계층을 변경해야합니까?</strong></p>
<p>A: 전송계층의 보완이 지연을 줄일 수 있는지는 더 연구해봐야 합니다. 그러나 전송계층의 교체는 복잡한 일이고, 어플리케이션 레이어에서 TCP와 HTTP의 비효율적인 부분들을 극복할 수 있다면 현실 적용이 더 쉬울거라고 봅니다.</p>
<p><strong>Q: TCP는 네트웍의 혼잡이나 붕괴를 유발하지 않는지 오랜 시간 사용되면서 검증되었습니다. SPDY가 인터넷을 깨뜨릴 수도 있습니까?</strong></p>
<p>A: 아니오. SPDY는 TCP 위에서 작동하므로 TCP의 혼잡제어 알고리즘들의 혜택을 받습니다. 게다가 혼잡 제어 방법을 수정한 HTTP가 인터넷에서 동작하고 있습니다. 예를 들면, 오늘날의 HTTP 클라이언트는 하나의 서버에 6개의 연결을 동시에 엽니다. 그리고 어떤 HTTP 서버는 초기 congestion window를 4 패킷으로 증가시켰습니다. TCP가 각각의 연결들을 독립적으로 속도를 올릴 수 있기 때문에 서버는 초기부터 효과적으로 24패킷을 보낼 수 있습니다. 다수 연결에서 TCP의 slow-start 문제를 회피한 것이죠. 반면 SPDY는 하나의 연결 위에 다수의 스트림을 구현한 겁니다.</p>
<p><strong>Q: SCTP는 뭡니까?</strong></p>
<p>A: SCTP는 재미있는 대한 전송 프로토콜이에요. 이것은 하나의 커넥션 위에 다수의 스트림을 제공합니다. 그러나 이건 전송계층의 변경을 필요로 해서 가정용 라우터에까지 적용하려면 매우 어렵운 일이 되죠. 또한 SCTP는 은총알(silver bullet)이 아니에요. 서버와 클라이언트 사이의 채널을 효율적으로 사용하려면 어플리케이션 계층의 변경 역시 필요하죠.</p>
<p><strong>Q: BEEP은 뭡니까?</strong></p>
<p>A: BEEP은 비슷한 여러가지 기능을 제공하는 재미있는 프로토콜이지만, 이것은 페이지 로드 타임을 줄이는 것을 목표로 하지 않습니다. 그걸 가능하게 할 몇몇 기능이 없지요. BEEP은 바이너리 프레이밍 대신에 텍스트 기반의 프레이밍을 사용합니다. 이것은 확장성에 있어서 경쟁력있는 좋은 프로토콜이지만, 몇몇 보안 문제들도 있고 정확히 파싱하기가 어렵다는 문제도 있습니다.</p>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/409/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu에 AirVideo server 설치하기</title>
		<link>http://oddpoet.net/archives/386</link>
		<comments>http://oddpoet.net/archives/386#comments</comments>
		<pubDate>Thu, 16 Jun 2011 16:17:42 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[기타]]></category>
		<category><![CDATA[airvideo server]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=386</guid>
		<description><![CDATA[sun jdk 및 기타 의존 패키지 설치를 위해서 /etc/apt/source.list 파일에  parter, multiverse 관련 repository가 있는지 확인한다. 주석처리되어 있으면 주석해제한다. # /etc/apt/source.list &#8230; deb http://archive.canonical.com/ubuntu lucid partner deb-src http://archive.canonical.com/ubuntu lucid partner deb http://ftp.daum.net/ubuntu/ lucid multiverse deb-src http://ftp.daum.net/ubuntu/ lucid multiverse deb http://ftp.daum.net/ubuntu/ lucid-updates multiverse deb-src http://ftp.daum.net/ubuntu/ lucid-updates multiverse sun java6 jdk 를 설치한다. sudo install sun-java6-jdk AirVideo [...]]]></description>
			<content:encoded><![CDATA[<div>sun jdk 및 기타 의존 패키지 설치를 위해서 /etc/apt/source.list 파일에  parter, multiverse 관련 repository가 있는지 확인한다. 주석처리되어 있으면 주석해제한다.</div>
<blockquote>
<div>
<div><em># /etc/apt/source.list</em></div>
<div><em>&#8230;</em></div>
<div><em>deb http://archive.canonical.com/ubuntu lucid partner</em></div>
<div><em>deb-src http://archive.canonical.com/ubuntu lucid partner</em></div>
<div><em><br />
</em></div>
<div><em>deb http://ftp.daum.net/ubuntu/ lucid multiverse</em></div>
<div><em>deb-src http://ftp.daum.net/ubuntu/ lucid multiverse</em></div>
<div><em>deb http://ftp.daum.net/ubuntu/ lucid-updates multiverse</em></div>
<div><em>deb-src http://ftp.daum.net/ubuntu/ lucid-updates multiverse</em></div>
</div>
</blockquote>
<div>sun java6 jdk 를 설치한다.</div>
<blockquote>
<div><em>sudo install sun-java6-jdk</em></div>
</blockquote>
<div>AirVideo PPA repository를 등록하고, 설치한다.</div>
<blockquote>
<div><em>sudo add-apt-repository ppa:rubiojr/airvideo</em></div>
<div><em>sudo apt-get update</em></div>
<div><em>sudo apt-get install airvideo-server</em></div>
</blockquote>
<p><em><strong>/opt/arivideo-server/AirVideoServerLinux.properties</strong></em>를 적절하게 수정한다.</p>
<blockquote>
<div>
<div id="_mcePaste"><em>path.ffmpeg = /opt/airvideo-server/bin/ffmpeg</em></div>
<div id="_mcePaste"><em>path.mp4creator = /usr/bin/mp4creator</em></div>
<div id="_mcePaste"><em>path.faac = /usr/bin/faac</em></div>
<div id="_mcePaste"><em>password = p@ssW0rd</em></div>
<div id="_mcePaste"><em>subtitles.encoding = euc-kr</em></div>
<div id="_mcePaste"><em>subtitles.font = 나눔고딕</em></div>
<div id="_mcePaste"><em>folders = Movies:/path/to/movies</em></div>
</div>
</blockquote>
<div>shell에서 airvideo-server를 실행해서 잘 되는지 확인한다.</div>
<blockquote>
<div><em>airvideo-server</em></div>
</blockquote>
<div>서버 부팅시 자동으로 실행되도록 <em><strong>/etc/init/airvideo.conf </strong></em>파일을 아래와 같이 만든다.</div>
<blockquote>
<div>
<div><em>start on runlevel [2345]</em></div>
<div><em>stop on shutdown</em></div>
<div><em>respawn</em></div>
<div><em><br />
</em></div>
<div><em>exec sudo -H -n -u <span style="color: #666699;">username</span> LANG=ko_KR.UTF-8 /usr/bin/airvideo-server</em></div>
</div>
</blockquote>
<div></div>
<div>* 참조 사이트 : <a href="http://blog.frameos.org/2011/02/08/installing-airvideo-linux-server-in-ubuntu/" onclick="pageTracker._trackPageview('/outgoing/blog.frameos.org/2011/02/08/installing-airvideo-linux-server-in-ubuntu/?referer=');">http://blog.frameos.org/2011/02/08/installing-airvideo-linux-server-in-ubuntu/</a></div>
<div><em><br />
</em></div>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/386/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>RedMine+Gitolite 연동하기</title>
		<link>http://oddpoet.net/archives/355</link>
		<comments>http://oddpoet.net/archives/355#comments</comments>
		<pubDate>Fri, 28 Jan 2011 04:36:56 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[개발]]></category>
		<category><![CDATA[gitosis]]></category>
		<category><![CDATA[redmine plugin]]></category>
		<category><![CDATA[redmine-gitosis]]></category>
		<category><![CDATA[Remine]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=355</guid>
		<description><![CDATA[Redmine-Gitolite으로 Redmine에 Gitolite를 연동하면 아래와 같은 이점이 있습니다. Redmine에서 직접 Git Repository를 생성할 수 있다. GitHub와 유사하게 Redmine에서 Git Repository 접근을 위한 publickey를 등록할 수 있다. Redmine 계정 기반으로 사용자들의 Git Repository 접근 권한을 편하게 관리할 수 있다. 아래 링크를 참고하시면 Redmine 설치는 10분 이내에 끝나니 여기서는 Gitolite와 Redmine+gitolite plugin설치 및 연동만 다룹니다. Redmine 공식 [...]]]></description>
			<content:encoded><![CDATA[<p>Redmine-Gitolite으로 Redmine에 Gitolite를 연동하면 아래와 같은 이점이 있습니다.</p>
<ol>
<li>Redmine에서 직접 Git Repository를 생성할 수 있다.</li>
<li>GitHub와 유사하게 Redmine에서 Git Repository 접근을 위한 publickey를 등록할 수 있다.</li>
<li>Redmine 계정 기반으로 사용자들의 Git Repository 접근 권한을 편하게 관리할 수 있다.</li>
</ol>
<p>아래 링크를 참고하시면 Redmine 설치는 10분 이내에 끝나니 여기서는 Gitolite와 Redmine+gitolite plugin설치 및 연동만 다룹니다.</p>
<ul>
<li>Redmine 공식 설치 가이드 :  <a href="http://www.redmine.org/projects/redmine/wiki/RedmineInstall" onclick="pageTracker._trackPageview('/outgoing/www.redmine.org/projects/redmine/wiki/RedmineInstall?referer=');">http://www.redmine.org/projects/redmine/wiki/RedmineInstall</a></li>
</ul>
<p><span id="more-355"></span></p>
<h3>Gitolite 설치</h3>
<p>Gitolite는 Gitosis와 유사한 ssh 기반의 Git server 관리툴입니다. Gitosis는 python으로, Gitolite는 perl로 구현되었다는게 조금 다를 뿐이죠. (Gitolite home: <a href="https://github.com/sitaramc/gitolite" onclick="pageTracker._trackPageview('/outgoing/github.com/sitaramc/gitolite?referer=');">https://github.com/sitaramc/gitolite</a>)</p>
<p>자세한 내용은 홈페이지를 참고하시고, 여기서는 빠른 설치를 위해서 속성으로 가이드하겠습니다. ^^</p>
<pre class="brush:shell">## install gitolite
git clone git://github.com/sitaramc/gitolite gitolite-source
cd gitolite-source
sudo mkdir -p /usr/local/share/gitolite/conf
/usr/local/share/gitolite/hooks
sudo src/gl-system-install /usr/local/bin
/usr/local/share/gitolite/conf /usr/local/share/gitolite/hooks

## add user 'git'
sudo adduser --system --group --disabled-password --shell /bin/bash --home /opt/gitolite git

## generate key for admin
ssh-keygen -t rsa
cp ~/.ssh/id_rsa.pub /tmp/USERNAME.pub

## init gitolite
sudo -H -u git  gl-setup /tmp/USERNAME.pub</pre>
<p>gitolite 초기화할 때 설정파일이 열리게 되는데 이때 아래처럼 repository의 umask 값을 바꿔줍니다.</p>
<pre># ------------------------------------------------------------------------------
# most often used/changed variables
# ------------------------------------------------------------------------------
$GL_WILDREPOS = 0;
$PROJECTS_LIST = $ENV{HOME} . "/projects.list";
#$REPO_UMASK = 0077;
#0077에서 아래처럼 0027로 바꿔줍니다.
$REPO_UMASK = 0027;</pre>
<p>이렇게 umask값을 바꾸는건 redmine에서 gitolite repository를 읽을 수 있어야 하기 때문입니다. redmine이 실행되는 사용자 계정이 redmine이라면 아래처럼 git group에 추가시켜줍니다.</p>
<pre class="brush:shell">&gt; sudo addgroup redmine git</pre>
<p>그리고 &#8216;redmine&#8217; user가 gitlite-admin repository에 접근할 수 있도록 아래와 같이 publickey를 추가합니다.</p>
<pre class="brush:shell">sudo -H -u redmine ssh-keygen
cp ~redmine/.ssh/id_rsa.pub /tmp/redmine.pub
git clone git@localhost:gitolite-admin.git
cd gitolite-admin
cp /tmp/redmine.pub keydir/</pre>
<p>이제 gitolite-admin/conf/gitolite.conf 파일에도 redmine user로 gitolite-admin에 접근할 수 있도록  아래처럼 내용을 수정합니다.</p>
<pre>repo    gitolite-admin
RW+     =       oddpoet <strong><span style="color: #333399;">redmine</span></strong>
repo    testing
RW+     =       @all</pre>
<p>이제 gitolite-admin을 commit하고 push합니다.</p>
<pre class="brush:shell">git add .
git commit -m "add redmine user'
git push origin master

## 아래 명령이 잘 수행되면 성공!
sudo -H -u redmine git@localhost:gitolite-admin.git</pre>
<h3><strong>Redmine-gitolite 설치 </strong></h3>
<p>redmine-gitolite plugin은 redmine-gitosis(<a href="https://github.com/rocket-rentals/redmine-gitosis" onclick="pageTracker._trackPageview('/outgoing/github.com/rocket-rentals/redmine-gitosis?referer=');">https://github.com/rocket-rentals/redmine-gitosis</a>)에서 fork되었습니다만, original plugin도 그렇고 다수 fork들도 제 환경에서는 문제들이 많더군요. 그래서 제가 patch한 버전(<a href="https://github.com/odd-poet/redmine-gitolite" onclick="pageTracker._trackPageview('/outgoing/github.com/odd-poet/redmine-gitolite?referer=');">https://github.com/odd-poet/redmine-gitolite</a>)을 씁니다.</p>
<p>편의를 위해 redmine의 설치 경로가 ~/redmine/apps/redmine 이라고 가정하겠습니다.</p>
<pre class="brush:shell"># 필요한 gem 설치.
gem install lockfile 

# plugin 설치
sudo -H -i -u redmine
cd apps/redmine
script/plugin install git@github.com:odd-poet/redmine-gitolite.git
rake db:migrate_plugins</pre>
<p>이제 rails만 재시작해주면 <em>redmine&gt;관리&gt;플러그인</em> 페이지에 redmine-gitolite 가 보이고 설정으로 들어가면 아래와 같은 화면을 볼 수 있습니다. 여기에 Repository 정보를 잘 입력해주시면 됩니다.</p>
<div id="attachment_372" class="wp-caption aligncenter" style="width: 627px"><img class="size-full wp-image-372 " title="redmine_gitolite 설정" src="http://oddpoet.net/wp-content/uploads/2011/01/redmine_gitolite.png" alt="redmine_gitolite 설정" width="617" height="320" /><p class="wp-caption-text">redmine_gitolite 설정</p></div>
<p>그리고 publickey는 내계정 페이지서 등록 및 관리할 수 있습니다.</p>
<div id="attachment_375" class="wp-caption aligncenter" style="width: 736px"><img class="size-full wp-image-375" title="publick_key 등록" src="http://oddpoet.net/wp-content/uploads/2011/01/redmine_gitolite_pubkey.png" alt="publick_key 등록" width="726" height="199" /><p class="wp-caption-text">publick_key 등록</p></div>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/355/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>install ruby-1.9.2 on ubuntu/debian</title>
		<link>http://oddpoet.net/archives/346</link>
		<comments>http://oddpoet.net/archives/346#comments</comments>
		<pubDate>Tue, 25 Jan 2011 08:37:30 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby 1.9]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=346</guid>
		<description><![CDATA[linux에서 일반 유저 계정이면 rvm(http://rvm.beginrescueend.com/)을 사용하여 ruby를 설치하는게 유용하지만 시스템 레벨 설치에서는  rvm이 번거로운 점이 있습니다. debian/ubuntu package가 항상 조금씩 늦게 나오기 때문에 ruby 최신버전(현재 1.9.2-p136)을 사용해야 할 경우 source compile이 최선책이죠. (참고:  debian package의 ruby-1.9.1은 실제로 ruby-1.9.2-p0입니다.) 그런데  source compile 해서 사용할때 의존성이 있는  library들 때문에 rails 등에서  문제가 발생하지요. 대략적으로 rails 환경을 위한 [...]]]></description>
			<content:encoded><![CDATA[<p>linux에서 일반 유저 계정이면 rvm(<a href="http://rvm.beginrescueend.com/" onclick="pageTracker._trackPageview('/outgoing/rvm.beginrescueend.com/?referer=');">http://rvm.beginrescueend.com/</a>)을 사용하여 ruby를 설치하는게 유용하지만 시스템 레벨 설치에서는  rvm이 번거로운 점이 있습니다.</p>
<p>debian/ubuntu package가 항상 조금씩 늦게 나오기 때문에 ruby 최신버전(현재 1.9.2-p136)을 사용해야 할 경우 source compile이 최선책이죠.<br />
(참고:  debian package의 ruby-1.9.1은 실제로 ruby-1.9.2-p0입니다.)<span id="more-346"></span></p>
<p>그런데  source compile 해서 사용할때 의존성이 있는  library들 때문에 rails 등에서  문제가 발생하지요. 대략적으로 rails 환경을 위한 ruby source compile에 필요한 library는 다음과 같습니다.</p>
<ul>
<li>zlib</li>
<li>openssl</li>
<li>readline</li>
<li>sqlite3</li>
</ul>
<p>그래서 ruby source compile을 할 경우 아래와 갈이 관련 lib들을 먼저 설치해줘야 합니다.</p>
<pre class="brush:shell">$ sudo apt-get install build-essential libssl-dev libsqlite3-dev libreadline5-dev zlib1g-dev</pre>
<p>그런 후에  아래처럼 ruby source를 compile하고, rails를 설치하면 rails를 위한 기본  환경설정이 끝납니다.</p>
<pre class="brush:shell">$ cd /tmp
$ mkdir src
$ cd src
$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p136.tar.gz
$ tar xzvf ruby-1.9.2-p136.tar.gz
$ cd ruby-1.9.2-p136
$ ./configure
$ sudo make &amp;&amp; sudo make install

$ gem install rails</pre>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/346/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>gem을 rubygems에 등록하고 관리하기</title>
		<link>http://oddpoet.net/archives/335</link>
		<comments>http://oddpoet.net/archives/335#comments</comments>
		<pubDate>Wed, 17 Nov 2010 14:50:13 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[개발]]></category>
		<category><![CDATA[gem]]></category>
		<category><![CDATA[gem prerelease]]></category>
		<category><![CDATA[gem yank]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=335</guid>
		<description><![CDATA[간만의 포스팅입니다. 자신이 만든 ruby library를 gem으로 만들고 공개배포할 계획이 있다면 rubygems를 이용할 수 있습니다. 그러면 누구나 gem 명령어를 이용하여 내가 만든 gem을 설치할 수 있지요. 그럼 gem을 등록하고, 삭제하는 방법과 prerelease 버전으로 등록하는 방법을 알아봅니다. gem 생성하기 NetBeans에서 ruby project를 만들면 기본 Rakefile 템플릿을 만들어줍니다만, 없으면 만들면 되지요. ^^ 대충 아래처럼 프로젝트 루트에 Rakefile을 [...]]]></description>
			<content:encoded><![CDATA[<p>간만의 포스팅입니다.</p>
<p>자신이 만든 ruby library를 gem으로 만들고 공개배포할 계획이 있다면 rubygems를 이용할 수 있습니다.<br />
그러면 누구나 gem 명령어를 이용하여 내가 만든 gem을 설치할 수 있지요. 그럼 gem을 등록하고, 삭제하는 방법과 prerelease 버전으로 등록하는 방법을 알아봅니다. <span id="more-335"></span></p>
<h3>gem 생성하기</h3>
<p>NetBeans에서 ruby project를 만들면 기본 Rakefile 템플릿을 만들어줍니다만, 없으면 만들면 되지요. ^^<br />
대충 아래처럼 프로젝트 루트에 Rakefile을 생성합니다. 설정 세부 사항은 <a href="http://docs.rubygems.org/read/chapter/20" target="_blank" onclick="pageTracker._trackPageview('/outgoing/docs.rubygems.org/read/chapter/20?referer=');">Gem::Specification Reference</a> 를 참조하시면 됩니다.</p>
<pre class="brush:ruby">require 'rubygems'
require 'rake'
require 'rake/clean'
require 'rake/gempackagetask'
require 'rake/rdoctask'

spec = Gem::Specification.new do |s|
  s.name = 'sample' # gem name
  s.version = '1.0.0' # version
  s.extra_rdoc_files = ['README', 'LICENSE']
  s.summary = 'simple description'
  s.description = "description'
  s.author = 'my name'
  s.email = 'abc@gmail.com'
  s.files = %w(LICENSE README) + Dir.glob("{lib}/**/*")
  s.require_path = "lib"
end

Rake::GemPackageTask.new(spec) do |p|
  p.gem_spec = spec
  p.need_tar = true
  p.need_zip = true
end</pre>
<p>이제 shell에서 &#8216;rake gem&#8217; 이라고 타이핑하시면 gem파일이 &#8216;sample-1.0.0.gem&#8217;이라는 이름으로 생성됩니다.</p>
<h3>rubygems.org 에 등록하기</h3>
<p><a href="http://rubygems.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/rubygems.org?referer=');">rubygems.org</a>에 등록하기 위해서는 rubygems.org에 계정이 있어야합니다. 계정을 만드셨다면 이제 shell에서 gem push 명령으로 간단하게 등록하실 수 있습니다.</p>
<pre class="brush:shell">&gt; gem push sample-1.0.0.gem
email address:
password:</pre>
<p>이메일 주소와 비밀번호를 물어보는데 rubygems에 가입할 때 사용했던 이메일과 비밀번호를 입력하면 됩니다. 수 분뒤에는 rubygems 에서도 등록된 나의 gem을 확인할 수 있고 shell에서  &#8217;gem list -r&#8217; 명령으로도 확인해볼 수 있답니다.</p>
<h3>rubygems.org에서 gem 삭제하기</h3>
<p>rubygems.org의 dashboard를 아무리 뒤져봐도, gem help를 아무리 뒤져봐도 등록했던 gem을 삭제하는 방법은 보이지 않습니다. 구글링을 해보니 yank라는게 있더군요. gem 1.3.5 버전 이후부터 가능하니 gem update를 하시고 gemcutter라는 gem을 설치하면 gem을 삭제할 수 있습니다.</p>
<pre class="brush:shell">&gt; gem update --system # gem 자체 업데이트
&gt; gem install gemcutter # gemcutter 설치
&gt; gem yank -h # gem yank help
&gt; gem yank sample -v 1.0.0</pre>
<p>잘못 등록한 gem은 이런 방법으로 삭제하시면 됩니다. (버전 단위로만 삭제할 수 있습니다.)</p>
<h3>prerelease 버전으로 gem 올리기</h3>
<p>언제부터인가 gem install 시에 <em>&#8211;pre</em> 옵션이 생겼지요. 개발 중인 버전을 미리 배포할 경우 일반적인 gem list, install 명령으로는 prerelease 버전의 정보를 보거나 설치할 수 없습니다. &#8216;<em>&#8211;pre</em>&#8216; 옵션을 줄 때만 설치하거나 정보를 확인할 수 있지요.</p>
<p>prerelease 버전으로 gem을 등록하는 방법을 한참 찾아헤맸으나 관련 문서를 찾지 못했답니다. 그러나 어느 뉴스그룹에 올라온 게 있더군요. 그냥 버전을 붙일때 숫자가 아닌 문자를 쓰면 된답니다. 즉, &#8217;1.0.0&#8242; 대신 &#8217;1.0.beta1&#8242; 이런 식으로 패치버전 부분에 숫자대신 문자를 사용해서 gem을 만들고 등록하면 됩니다.</p>
<pre class="brush:ruby">spec = Gem::Specification.new do |s|
  s.name = 'sample'
  s.version = '1.1.beta1' # 이렇게 써서 gem을 만들면 prerelease 버전이 됨.
  ...
end</pre>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/335/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Netbeans에서 Ruby 1.9 사용하기 (Windows환경)</title>
		<link>http://oddpoet.net/archives/285</link>
		<comments>http://oddpoet.net/archives/285#comments</comments>
		<pubDate>Tue, 17 Aug 2010 10:07:46 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[개발]]></category>
		<category><![CDATA[Netbeans]]></category>
		<category><![CDATA[Ruby 1.9]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=285</guid>
		<description><![CDATA[저는 Ruby 개발환경으로 요즘 NetBeans 를 사용하고 있습니다. RadRails는 이클립스 기반 IDE답게 느리고 무겁고, TextMate는 수년간 업데이트도 없는데다가 cucumber/rspec 을 이용하면서 한글 사용할 일이 많아졌는데 한글입출력이 많이 불편하지요. NetBeans의 경우 많이 무겁지도 않고, 기능상으로도 꽤나 괜찮은 편이라서 Ruby IDE로 개인적으로 추천할 만 하네요. (IntelliJ기반의 RubyMine은 30일 평가판을 쓸 수 있지만, 기본적으로 유료라서 써보질 않았네요.) Netbeans [...]]]></description>
			<content:encoded><![CDATA[<p>저는 Ruby 개발환경으로 요즘 NetBeans 를 사용하고 있습니다.</p>
<p>RadRails는 이클립스 기반 IDE답게 느리고 무겁고, TextMate는 수년간 업데이트도 없는데다가 cucumber/rspec 을 이용하면서 한글 사용할 일이 많아졌는데 한글입출력이 많이 불편하지요. NetBeans의 경우 많이 무겁지도 않고, 기능상으로도 꽤나 괜찮은 편이라서 Ruby IDE로 개인적으로 추천할 만 하네요. (IntelliJ기반의 RubyMine은 30일 평가판을 쓸 수 있지만, 기본적으로 유료라서 써보질 않았네요.)</p>
<p>Netbeans 6.9.1 기준으로 Windows / Ruby 1.9 환경에서 필요한 설정들을 정리해봅니다.<br />
<span id="more-285"></span></p>
<h3>Ruby 1.9 설치</h3>
<p>Netbeans에 내장된 Ruby는 JRuby이므로 아직 완전하게 Ruby1.9를 지원하지 않습니다. 따라서, 별도로 Ruby1.9를 설치하는 것이 좋습니다.</p>
<p>Windows환경을 위한 인스톨러는  RubyInstaller(<a href="http://rubyinstaller.org/" onclick="pageTracker._trackPageview('/outgoing/rubyinstaller.org/?referer=');">http://rubyinstaller.org/</a>)에서 구하실 수 있습니다. 단, 2010/08/19  기준으로 ruby 1.9.1p378 버전을 사용하시는 것이 좋습니다. 아직은 p400번대 버전들이 문제가 많습니다.</p>
<h3>인코딩 설정</h3>
<p>요즘은 Linux이던 Mac이던 콘솔환경 자체도 UTF-8을 쓰는게 대세입니다만, 한글 Windows의 경우 cmd창은 기본적으로 &#8216;EUC-KR&#8217;을 사용합니다. 따라서 Netbeans에서의 Output에 한글이 있을 경우 처참히 깨집니다.</p>
<p>NetBeans의 설치폴더(default: <code>C:\Program Files (x86)\NetBeans 6.9.1\</code>) 의 etc/netbeans.conf 파일을 열어보면 <code>netbeans_default_options="...." </code> 라고 되어 있는 라인에 <strong><code>-J-Dfile.encoding=UTF-8</code></strong>를 추가해줍니다.</p>
<pre class="brush:bash"># encoding 설정을 추가함.
netbeans_default_options="... -J-Dfile.encoding=UTF-8"</pre>
<p>Ruby 1.9에서 파일을 UTF-8 인코딩으로 작성할 경우, 파일 첫 라인에  <strong><code># coding: utf-8 </code></strong>이라는 매직 코맨트를 써주어야 합니다.</p>
<pre class="brush:ruby;highlight:[1]"># coding: utf-8
# 반드시 첫라인에 인코딩 설정이 있어야 합니다.

class KoreanSample
  def 한글_메소드
    puts "헬로우 월드"
  end
end

KoreanSample.new.한글_메소드</pre>
<p>(참고) Ruby  1.8의 경우에는 ruby 실행 옵션으로 &#8216;-Ku&#8217;를 주셔야 합니다. 따라서 &#8216;Project Propetries &gt; Run&#8217; 의 Ruby options: 에 &#8216;-Ku&#8217;를 넣어주시면 됩니다.</p>
<h3>Debuging 설정</h3>
<p>Netbeans에서 Ruby debuggin을 하기 위해서는  <strong><code>ruby-debug, ruby-debug-ide</code></strong> gem이 필요합니다. Ruby 1.9의 경우에는 <strong><code>ruby-debug19, ruby-debug-ide19</code></strong> gem이 필요하지요.</p>
<p>그러나 Windows 환경에서는 <strong><code>ruby-debug19, ruby-debug-ide19</code></strong> gem을 설치할 때 native compile 관련 문제로 정상적으로 설치가 되지 않습니다. 따라서 이 gem들을 설치하기 위해서는 RubyInstaller for windows(<a href="http://rubyinstaller.org/" onclick="pageTracker._trackPageview('/outgoing/rubyinstaller.org/?referer=');">http://rubyinstaller.org/</a>) 에서 devkit을 추가로 설치하셔야 합니다.</p>
<h4>devkit 설치</h4>
<ol>
<li>RubyInstaller 다운로드  페이지(<a href="http://rubyinstaller.org/downloads/" onclick="pageTracker._trackPageview('/outgoing/rubyinstaller.org/downloads/?referer=');">http://rubyinstaller.org/downloads/</a>)에서 devkit을 다운로드 받아서, Ruby설치폴더(e.g. <code>C:\Ruby191</code>)에 압축을 풉니다.</li>
<li>그리고 <code>devkit\msys\1.0.11\tec\fstab</code> (e.g. <code>C:\Ruby191\devkit\msys\1.0.11\etc\fstab</code>) 파일을 열어서, 경로 설정을 자신의 환경에 맞게 수정합니다.</li>
<li>cmd 창에서 아래와 같이 입력하여, 정상적으로 결과가 표시되는지 확인합니다. 아래처럼 autoconf의 경로가 정상적으로 나오지 않으면 뭔가 잘못된 겁니다.
<pre class="brush:shell;">C:\Users\oddpoet&gt; sh which autoconf
/usr/local/bin/autoconf</pre>
</li>
</ol>
<h4>Ruby debug gem 설치</h4>
<p>이제 ruby debug gem들을 설치할 수 있습니다.</p>
<pre class="brush:shell">
C:\Users\oddpoet&gt; gem install ruby-debug19 ruby-debug-ide19
...[이제 정상설치된다]...
</pre>
<p>Netbeans를 다시 실행하면 Ruby1.9에서도 Fast debug engine이 정상적으로 동작하게 됩니다.</p>
<p>이제 Netbeans에서 Ruby1.9를 문제없이 사용하실 수 있습니다. 저는 요즘 RSpec 2.0 beta 버전을 사용해서 테스트를 작성하고 있는데, 아직 RSpec2는 Netbeans에서 정상 이용이 안되네요. RSpec2가 정식 릴리즈되는 9월 이후를 기다려봐야 겠습니다.</p>
<p>(추가)<br />
2010.08.22에 릴리즈된 ruby-debug-base19 (ver0.11.24)가 정상적으로 설치되지 않습니다.  따라서 아래와 같이 ruby-debug-base19의 v0.11.23버전을 설치하신 후에 ruby-debug-ide19를 설치하시기 바랍니다. </p>
<pre class="brush:shell">
C:\Users\oddpoet> gem install ruby-debug-base19 -v=0.11.23
C:\Users\oddpoet> gem install ruby-debug19 ruby-debug-ide19
</pre>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/285/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TDD에 대한 조금 다른 생각</title>
		<link>http://oddpoet.net/archives/242</link>
		<comments>http://oddpoet.net/archives/242#comments</comments>
		<pubDate>Mon, 02 Aug 2010 02:46:00 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[개발]]></category>
		<category><![CDATA[BDD]]></category>
		<category><![CDATA[Behaviour Driven Development]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[TDD]]></category>
		<category><![CDATA[Test Driven Development]]></category>
		<category><![CDATA[UnitTest]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=242</guid>
		<description><![CDATA[Dave Astels의 &#8220;A New Look at Test Driven Development&#8220;라는 Article을 번역한 글입니다. BDD(Behavior Driven Development)의 시작점이라 할 만한 글이지요. 2005년도에 씌여진 아티클이지만, 개발조직에서 TDD의 수행지표로 code coverage를 사용하고 있는 작금의 현실에, TDD의 의미를 다시금 새겨보는데 도움이 될 듯 합니다. 참고로 BDD(Behaviour Driven Development)를 일반적으로 &#8216;행위주도개발&#8217;이라고 번역하는 듯하지만, 문맥상 Behavior를 &#8216;행위&#8217;로 번역할 경우 의미전달이 힘든 부분이 [...]]]></description>
			<content:encoded><![CDATA[<p>Dave Astels의 &#8220;<a href="http://techblog.daveastels.com/2005/07/05/a-new-look-at-test-driven-development/" onclick="pageTracker._trackPageview('/outgoing/techblog.daveastels.com/2005/07/05/a-new-look-at-test-driven-development/?referer=');">A New Look at Test Driven Development</a>&#8220;라는 Article을 번역한 글입니다. BDD(Behavior Driven Development)의 시작점이라 할 만한 글이지요. 2005년도에 씌여진 아티클이지만, 개발조직에서 TDD의 수행지표로 code coverage를 사용하고 있는 작금의 현실에, TDD의 의미를 다시금 새겨보는데 도움이 될 듯 합니다. </p>
<p>참고로 BDD(Behaviour Driven Development)를 일반적으로 &#8216;행위주도개발&#8217;이라고 번역하는 듯하지만, 문맥상 Behavior를 &#8216;행위&#8217;로 번역할 경우 의미전달이 힘든 부분이 있어서 &#8216;기대행동(Behavior)&#8217;라고 번역/표기했습니다.<br />
사실 길지 않은 글이니 원문을 읽어보시는 걸 권장합니다. (발번역이라 죄송~)<br />
<span id="more-242"></span></p>
<hr/>
<h2>A New Look at Test Driven Development</h2>
<p>Test Driven Development (TDD)는 요즘 전성기를 맞고 있다. 큰 회사들은 개발자들이 TDD를 교육하는데 많은 돈을 쓰고 있으며, 컨퍼런스에도 TDD는 단골주제다. 내 책(<a href="http://www.amazon.com/Test-Driven-Development-Practical-David-Astels/dp/0131016490" onclick="pageTracker._trackPageview('/outgoing/www.amazon.com/Test-Driven-Development-Practical-David-Astels/dp/0131016490?referer=');">Test-Driven Development: A Practical Guide</a>)은 Jolt award를 수상했다. 모든게 잘 되어 가는 것 같다. TDD를 적용한 모두가 TDD를 완전히 이해하고 있고, 그로 인한 혜택을 누리고 있다. 정말 그런가? </p>
<h3>우울한 현실 (Fat Chance!)</h3>
<p>내가 얘기해본 10% 정도의 사람들만 TDD가 무엇인지 이해하고 있다. 사실 5% 정도밖에 안될지도&#8230; 어디서 잘못된걸까? 사람들은 TDD를 &#8220;test&#8221;에 대한 것이라고 생각한다. &#8220;case&#8221;라고 생각하지 않는다는 거다. </p>
<p>조금 비슷하기는 하지만, 일반적으로 사람들은 TDD를 low level의 회귀테스트 정도로 본다. 그런데 그건 TDD의 부수적인 이익 정도에 불과하다. 왜 이런 일들이 비일비재하게 일어나는 걸까? 잠깐 과거로 돌아가보자. 만약 &#8220;<a href="http://bdn.borland.com/article/0,1410,29689,00.html" onclick="pageTracker._trackPageview('/outgoing/bdn.borland.com/article/0_1410_29689_00.html?referer=');">my old Coad Letter</a>&#8221; 의 예전 이슈들을 봤다면, 그곳에서 TDD의 배경이 되는 것들을 찾을 수 있다. </p>
<blockquote><p>원래 XP는 깨질 수 있는 모든 것들을 테스트한다는 원칙을 가지고 있었다. 그리고 XP의 테스팅 관습은 Test Driven Development로 진화했다.<br />
XP originally had the rule to test everything that could possibly break. Now, however, the practice of testing in XP has evolved into Test-Driven Development.
</p></blockquote>
<p>그들이 테스트를 작성하는 것에 대해 얘기하던 그 시절을 생각해보면, 왜 TDD가 테스트 중심의 어휘들을 가지게 되었는지를 상상할 수 있다. 그리고 이게 사람들이 TDD가 테스트가 목적이라고 생각하게된 이유이다. 그들이 TestCases, TestSuites, Tests 등에 대해 얘기했고, 메소드 이름을 &#8220;test&#8221;로 시작하게 했기 때문이다. jUnit의 경우 그렇다. nUnit은 test로 메소드 이름이 시작해야 한다는 제약은 없다. (역주: JUnit도 annotation을 활용하게 되면서 이제 method 이름에 대한 명명제약은 없어졌지요.)</p>
<p>결국 TDD로 진화하면서 우리는 전혀 다른 종의 동물을 만들어 냈다. 원래 XP 관습은 깨질 수 있는 모든 것들에 대한 테스트를 작성하는 것이었다. 그래서 테스트를 먼저 작성하는 것에서 시작했고, 결국 TDD가 되었다. 그러나 TDD는 사람들이 생각하는 것처럼 최종점이 아니다. TDD는 단지 다음 단계를 위한 발판일 뿐이다. </p>
<p>사실 &#8220;unit&#8221;이라는 단어가 가장 큰 문제다. 그 이유는 첫번째로 &#8220;unit&#8221;은 막연한 용어라는 점이고, 두번째로는 그 단어가 코드의 구조적 분할을 의미한다는 점이다. 그래서 사람들은 method나 class를 테스트해야 한다고 생각한다. 우리는 <em>unit</em>에 대해서 생각해야 하는게 아니고, <strong>기대행동(behavior)</strong>의 관점에서 생각해야 한다. </p>
<p>unit test에 대한 이런 생각들은 우리들이 코드의 구조에 따라 테스트를 나누도록 한다. 예를 들면, product class들과 test class들이 1:1 관계를 만드는 것이다. </p>
<p>그건 우리가 원하는 게 아니다. 실제로는 기대행동(behaviour) 단위로 나누는 것이 필요하다. 또한 일반적인 unit test보다 더 작은 단위의 레벨에서 작업하는 것이 필요하다. 우리는 작은 단위의 기대행동들에 초점을 맞추어, 매우 작은 단위로 작업해야 한다. 예를 들면, &#8220;리스트가 비어 <em>있을 때[Given]</em>, 객체에 대해 add() 메소드가 <em>호출되면[When]</em>, 리스트에는 하나만 들어있어야 한다<em>[Then]</em>.&#8221;에서 메소드는 매우 특정 상황(context)에서 특정 매개변수와 함께 호출되고, 매우 특정한 결과가 만들어지는&#8230; 이렇게 하나의 메소드에 대해 더 작은 단위로 작업해야 한다.</p>
<p>이것이 바로 &#8220;테스트&#8221;라는 관점에 의해서 가려져버린 고대의 아이디어이다. 왜 이렇게 되었을까? 사람들이 &#8220;Test&#8221;에 대해 어떻게 생각하는지 보자. </p>
<p>개발자들은 보통 이렇게 생각한다: &#8220;난 모든 테스트를 작성할 생각은 없어&#8221;, &#8220;이건 매우 단순한 코드라서 테스트할 필요가 없거든&#8221;, &#8220;테스트는 시간 낭비야&#8221;, &#8220;난 이미 많이 해봤어&#8221;. </p>
<p>프로젝트 매니져들은 이렇게 생각한다: &#8220;테스트는 코드가 다 만들어 진 후 하는거야&#8221;, &#8220;우리는 테스팅할 사람들이 따로 있어&#8221;, &#8220;당장 거기에 쏟을 시간은 없어&#8221;&#8230;.</p>
<p>사람들은 테스팅에 대한 생각은 이렇게도 부정적이고, 하지 않을 이유를 쉽게도 찾아낸다. 특히나 시간이 없고, 압박이 심한 경우는 더욱&#8230;.</p>
<h3>TDD가 테스트가 아니라면, 뭔데? (So if it&#8217;s not about testing, what&#8217;s it about?)</h3>
<p>TDD는 실제 코딩을 하기 전에 무엇을 만들 것인지 생각해보는 작업이다. 즉, 간략하고, 모호하지 않으며, 실행가능한 형태으로 기대행동(behavior)들을 보다 작은 단위로 명세화(specification)하는 과정이다. 이 과정은 테스트를 작성하는 것일까? 아니다. 이것은 코드가 무엇을 해야하는지에 대한, 명세(specification)을 작성하는 것이다. 그래서 이것은 코드를 작성하기 전에 작성하는 것이 좋다. 더 많은 명세들이 해야할 일을 더 명확히 해주기 때문이다. TDD의 기본 방식대로 조금씩 점진적으로 작업한다. 기대행동(behavior)들을 작은 단위로 명세화하고, 그리고 그것을 구현한다. </p>
<p>이제 TDD가 테스트를 작성하는 것이 아니라, 기대행동(behavior)에 대한 명세에 대한 것이라는 걸 알았다면 관점이 바뀔 것이다. 각 product class마다 test class를 만든다거나, 어떤 method에 대한 test method를 만들어서 테스트한다던가 하는 일들이 얼마나 어이없는 일인지 느껴질 것이다. </p>
<h3>그럼 무엇을 할까?(So What to do?)</h3>
<p>test라는 관점에서 생각하지 않으려고 해도 JUnit은 그걸 어렵게 만든다. 그럼 이런 일에 걸맞는 새로운 프레임웍이 있을까? ThoughtWorks의 Dan North는 jBehave 라는 프로젝트를 시작했다. </p>
<p>기대행동(behavior) 중심의 어휘와 개념들을 사용하는 행동 중심의 프레임웍을 사용하면, 기대행동(behavior)에 대한 명세로써 생각하는데 도움이 된다. </p>
<h3>기대행동 명세 프레임웍(A Behaviour Specification Framework)</h3>
<p>기대행동(behavior)에 대한 명세는 어떤 모습이어야 할까? 다음과 같은 부분에서는 jUnit과 같은 것들과 비슷해야 할 것이다.</p>
<ol>
<li> 충분히 잘 동작한다.</li>
<li> 모든 사람들에게 익숙한 형태이다.</li>
</ol>
<p>가장 큰 차이는 어휘(vocabulary)이다. <em>TestCase</em>를 상속받는 것이 아니라, <em>Context</em>를 상속받는다. 그리고 method를 쓸때 <em>test</em>가 아니라, <em>should</em>로 시작하는 이름을 사용한다. 검증에는 <em>assertions(e.g. assertEquals(exp, act))</em> 대신, <em>shouldBeEqual(act, exp)</em> 같은 어휘들을 사용한다. </p>
<p>Smalltalk와 Ruby의 경우, class library 안으로 이런 framework을 내장시키는데 좀더 용이하다. 예를 들면, &#8216;<em>actual shouldEqual: expected</em>&#8216; 나, <em>&#8216;result shouldBeNull</em>&#8216;, &#8216;<em>[2/0] shouldThrow: DivideByZeroException</em>&#8216; 과 같은 형태로 쓰는 것이 가능하다. </p>
<h3>현재 진행 상황은&#8230;(What Now?)</h3>
<p>앞에서 얘기한 대로 Dan North는 기대행동(behavior) 명세를 위한 junit 대체물로 jBehave 프로젝트를 시작했다. 나도 조금 다른 방법으로 그 프로젝트에 참여할 것이다. 나는 Smalltalk와 Ruby에서 이러한 아이디어들을 적용해볼 계획을 가지고 있고, 행위기반의 jUnit 버전을 위한 작업을 진행중이다. </p>
<h3> 요약</h3>
<p>TDD에 대해 알고 있는 모든 것이 무용지물이 된다는 얘기인가? 아니다. TDD의 모든 기술을은 BDD에도 적용가능하다. 행위를 기술한다는 접근에서 이것은 TDD와 같다. 여전히 mock을 사용할 수도 있다. 바뀐 것은 어휘와 관점이다. </p>
<p>관점은 매우 중요하다. 프로세스의 가치가 어떻게 바뀌었는가? 다수의 &#8216;sould&#8217;를 사용하며, should method의 이름들은 보다 명세를 명확하게 해줄 것이다. 예를 들면&#8230;</p>
<ul>
<li>shouldAllowValidUser</li>
<li>shouldCheckIsValidBeforeUpdate</li>
<li>shouldUpdateRecordSet</li>
<li>shouldWriteToUpdateLog</li>
</ul>
<p>요약하면&#8230;</p>
<ol>
<li>TDD에 대한 문제는 우리들이 서로 다른 방향을 바라보고 있었다는 것이다. 틀린 방향을&#8230; </li>
<li>우리는 검증 테스트(verification test)가 아니라, 기대행동 명세(behavior specifications)를 생각하는 것에서 시작할 필요가 있다. </li>
<li>클래스나 메소드에 대한 테스트라는 관점보다, 각각의 기대행동(behavior)의 관점이 더 명확해지므로써, 실행가능한 문서를 가질 수 있게 된다는 잇점이 있다. </li>
<li>TDD가 생겨난 이후 어느 누구도 그 이름의 의미를 바꾸거나, 우리가 기대하는 바를 바꾸지 않았다. 그래서 우리는 이 새로운 작업 방식에 <strong>BDD:&#8221;Behaviour Driven Development&#8221;</strong> 라는 새로운 이름을 사용하려고 한다. </li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/242/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Ruby Symbol 이해하기</title>
		<link>http://oddpoet.net/archives/231</link>
		<comments>http://oddpoet.net/archives/231#comments</comments>
		<pubDate>Fri, 30 Jul 2010 06:19:43 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[개발]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[symbol]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=231</guid>
		<description><![CDATA[[예전 TextCube로 블로그하던 시절에 썼던 포스트인데, 다시 Ruby 만지작거리다가 생각이나서 DB에서 내용 복원했습니다.] 최근 Ruby와 Rails를 공부하고 있는데, Ruby의 Symbol이라는 게 잘 이해가 되지 않았다. 기존에 내가 배웠던 언어들 중에는 그와 비슷한게 없었던 터라 도무지 감이 안 잡혔다. 구글링 결과 대략 감을 잡았으니, Ruby Symbol의 정체에 대해 간단히 정리해본다. String vs. Symbol puts :hello  #symbol [...]]]></description>
			<content:encoded><![CDATA[<p>[예전 TextCube로 블로그하던 시절에 썼던 포스트인데, 다시 Ruby 만지작거리다가 생각이나서 DB에서 내용 복원했습니다.]</p>
<p>최근 Ruby와 Rails를 공부하고 있는데, Ruby의 Symbol이라는 게 잘 이해가 되지 않았다.<br />
기존에 내가 배웠던 언어들 중에는 그와 비슷한게 없었던 터라 도무지 감이 안 잡혔다.<br />
구글링 결과 대략 감을 잡았으니, Ruby Symbol의 정체에 대해 간단히 정리해본다.<br />
<span id="more-231"></span></p>
<h3>String vs. Symbol</h3>
<pre class="brush:ruby">puts :hello  #symbol
puts "hello"   #string</pre>
<p>위의 두 문장 모두 &#8216;hello&#8217;라는 문자열을 출력한다. 이런 이유로 Symbol을 String의 오리타입(duck type) 클래스로 오해하기도 한다. 하지만, 아래 코드를 보면 알겠지만, Symbol은 절대로 String이 아니다.</p>
<pre class="brush:ruby">puts :hello.size  # NoMethodError
puts :hello[0,2]  # NoMethodError
puts "hello".size  #5
puts "hello"[0,2]  # "he"</pre>
<p>Symbol이 String의 사촌 쯤으로 오해받는 것은 Symbol을 immutable string 용도로 많이 사용하기 때문이다. 나처럼 기존 다른 언어에서 Symbol을 써본적이 없는 사람이라면, 대충 예제코드들을 보면서 그렇게 생각할 가능성이 매우 높다.</p>
<p>Java등의 언어에서는 String이 immutable Class이므로 Hash의 키로 사용했을 경우 그다지 문제될 것이 없지만, Ruby에서는 String이 mutable class이므로 Hash의 키로 쓰일 때, 키로 쓰인 객체의 값이 변경될 경우 rehash 메소드를 호출해줘야 하는 경우가 생긴다. 따라서 Ruby에서는 가능하면 Hash의 키로 String대신 Symbol을 쓰는 것을 선호한다. 그러니 나 같은 놈은 처음부터 <span style="font-weight: bold;">Symbol = Immutable String</span> 으로 이해하고는 그 뒤부터는 미궁으로 빠져버린다.</p>
<p>Symbol을 그 정의대로 &#8216;<span style="font-weight: bold;">이름을 가진, 그리고 그 이름에 대해서 유니크(unique)한 객체</span>&#8216;이다. String이 따옴표로 둘러쌓인 문자열로 표기되듯이, Symbol은 <span style="font-style: italic; font-weight: bold;">:name</span>의 형태로 표기되며, name이 문자열로 표기될 수 있지만 그 자체는 String이 아닌 Symbol객체이다. 그리고 아래 코드에서 보여지듯이, 특정 이름을 갖는 Symbol 객체는 유일하다.</p>
<pre class="brush:ruby">puts :hello.class   # Symbol
puts "hello".class   # String
puts :hello.object_id == :hello.object_id   # true
puts "hello".object_id == "hello".object_id   #false</pre>
<h3>Symbol의 용도</h3>
<p>그렇다면 Symbol은 언제 사용하는 것이 좋을까?<br />
Ruby의 String이 mutable 객체이므로, 성능상의 이유로 Symbol이 immutable string의 대체물로 사용되곤 하지만, Symbol은 garbage collect가 되지 않으므로 자칫하면 메모리 누수가 발생할 수 있음을 유의해야 한다. 대충 아래와 같은 용도로 사용하는 것이 추천된다.</p>
<ol>
<li>메소드의 argument list에 옵션 키워드의 이름
<pre class="brush:ruby">link_to 'Show', :action=&gt;'show', :id= product</pre>
</li>
<li>C의 enum와 같은 값을 표현할 때
<pre class="brush:c"> /* C code*/
enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;</pre>
<pre class="brush:ruby"># Ruby
original_status = :Open
current_status= :Closed</pre>
</li>
<li>Hash의 키 이름
<pre class="brush:ruby">foo = {
:host =&gt; 'localhost',
:port =&gt; 80 }</pre>
</li>
</ol>
<p>특히 위 3번의 예는 String을 사용하는 아래의 예보다, Symbol이 사용자 정의 식별자(user defined identifier)로 사용됨으로써 코드의 의도가 명확히 드러난다 점에서도 더 낫다.</p>
<pre class="brush:ruby"># String을 사용한 경우, 속성-값의 관계가 덜 명확해 보인다.
foo = {
'host' =&gt; 'localhost',
'port' =&gt; 80}</pre>
<p><strong>Reference</strong></p>
<ul>
<li><a href="http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#8" target="_blank" onclick="pageTracker._trackPageview('/outgoing/www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol_8?referer=');">13 Wasy of Looking at a Ruby Symbol</a></li>
<li><a href="http://onestepback.org/index.cgi/Tech/Ruby/SymbolsAreNotImmutableStrings.red" target="_blank" onclick="pageTracker._trackPageview('/outgoing/onestepback.org/index.cgi/Tech/Ruby/SymbolsAreNotImmutableStrings.red?referer=');">Symbols are not immutable strings</a></li>
<li><a href="http://microjet.ath.cx/WebWiki/2005.12.27_UsingSymbolsForTheWrongReason.html" target="_blank" onclick="pageTracker._trackPageview('/outgoing/microjet.ath.cx/WebWiki/2005.12.27_UsingSymbolsForTheWrongReason.html?referer=');">Using Symbols for wrong reason</a></li>
<li><a href="http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols" target="_blank" onclick="pageTracker._trackPageview('/outgoing/glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols?referer=');">Understanding Ruby Symbols</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/231/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RVM : Ruby Version Manager</title>
		<link>http://oddpoet.net/archives/217</link>
		<comments>http://oddpoet.net/archives/217#comments</comments>
		<pubDate>Mon, 12 Jul 2010 20:34:27 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[개발]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[RVM]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=217</guid>
		<description><![CDATA[간만에 다시 Ruby를 가지고 놀고 있습니다. 1.9.x 버전에서 달라진 부분도 있고해서 macport로 1.9.1버전을 설치하는데 컴파일 오류가 나더군요. 구글링 결과 openssl 업데이트와 관련된 문제임을 파악했으나, 공식 패치버전이 macport repository에 올라오는데 몇 일은 걸릴 것 같더군요. 다른 대안을 찾다가 RVM(Ruby Version Manager)라는 멋진 녀석을 찾았습니다. 당분간 1.8.x, 1.9.x 사이를 오가면서 놀텐데 RVM으로 각 루비 버전들을 설치 및 [...]]]></description>
			<content:encoded><![CDATA[<p>간만에 다시 Ruby를 가지고 놀고 있습니다.<br />
1.9.x 버전에서 달라진 부분도 있고해서 macport로 1.9.1버전을 설치하는데 컴파일 오류가 나더군요.<br />
구글링 결과 openssl 업데이트와 관련된 문제임을 파악했으나, 공식 패치버전이 macport  repository에 올라오는데 몇 일은 걸릴 것 같더군요.</p>
<p>다른 대안을 찾다가 <a href="http://rvm.beginrescueend.com/" onclick="pageTracker._trackPageview('/outgoing/rvm.beginrescueend.com/?referer=');">RVM(Ruby Version Manager)</a>라는 멋진 녀석을 찾았습니다.<br />
당분간 1.8.x, 1.9.x 사이를 오가면서 놀텐데 RVM으로 각 루비 버전들을 설치 및 관리하면 편할 것 같습니다.</p>
<p><span id="more-217"></span></p>
<h3>RVM 설치</h3>
<p>설치는 매우 간단합니다.</p>
<pre class="brush:bash">bash &lt; &lt;( curl http://rvm.beginrescueend.com/releases/rvm-install-head )</pre>
<p>쉘에서 위와 같이 입력하고, .profile 마지막에 아래 라인을 추가해주면 됩니다.</p>
<pre class="brush:bash">[[ -s "$HOME/.rvm/scripts/rvm" ]] &amp;&amp; source "$HOME/.rvm/scripts/rvm"</pre>
<h3>Ruby 설치</h3>
<p>원하는 Ruby 버전은 쉘에서 다음과 같이 입력하여 설치할 수 있습니다.</p>
<pre class="brush:bash"># MRI 1.9.1 버전 설치
rvm install 1.9.1

# MRI 1.8.7-p160 버전 설치
rvm 1.8.7-p160

# MRI 1.8.7-p174 버전 설치
rvm 1.8.7-p174  

# JRuby 설치
rvm install jruby</pre>
<p>패치버전 단위까지 따로 관리할 수 있고, 공식 버전(MRI), JRuby, IronRuby 등등 다양한 Ruby Interpreter를 지원합니다.</p>
<h3>사용할 Ruby 변경하기</h3>
<pre class="brush:bash"># 설치된 ruby 목록 보기
rvm list

# ruby 변경하기 (현재 쉘에서만)
rvm 1.9.1

# 기본 ruby 변경하기
rvm 1.9.1 --default

# 시스템 기본 ruby 사용하기
rvm system --default</pre>
<p>macport와는 별도로 ruby 패키지를 관리한다는게 썩 내키지는 않지만, 여러 버전의 ruby를 간편하게 관리하고 스위칭할 수 있으니 좋네요. 그리고 기본적으로 <em>~/.rvm/</em> 밑에 소스 및 설치파일들을 관리하기 때문에 서버에서 rvm에 의한 영향을 특정 계정으로 한정지을 수 있다는 점도 장점일 수 있겠습니다.</p>
<p>간만에 Ruby 세상으로 돌아오니 좋군요. ^^</p>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/217/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>XStream에서 null값을 xml/json으로 serialize하기</title>
		<link>http://oddpoet.net/archives/192</link>
		<comments>http://oddpoet.net/archives/192#comments</comments>
		<pubDate>Wed, 14 Apr 2010 07:09:01 +0000</pubDate>
		<dc:creator>OddPoet</dc:creator>
				<category><![CDATA[개발]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[null]]></category>
		<category><![CDATA[xml serialize]]></category>
		<category><![CDATA[XStream]]></category>

		<guid isPermaLink="false">http://oddpoet.net/?p=192</guid>
		<description><![CDATA[XStream은 Java Object에 대한 xml serialize를 제공하는 Libraray입니다. 물론 deserialize도 지원하며, json 변환 역시 지원하지요. 문제상황 하지만 XStream은 Object의 property 값이 null 인 경우에는 해당 property를 serialize 하지 않습니다. 예를 들면&#8230; @XStreamAlias("Person") class Person { public String firstName; public String lastName; public String middleName; } ...... Person jobs = new Person(); jobs.firstName = "Steve"; jobs.lastName [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://xstream.codehaus.org/" onclick="pageTracker._trackPageview('/outgoing/xstream.codehaus.org/?referer=');">XStream</a>은 Java Object에 대한 xml serialize를 제공하는 Libraray입니다. 물론 deserialize도 지원하며, json 변환 역시 지원하지요. </p>
<h3>문제상황</h3>
<p>하지만 XStream은 Object의 property 값이 null 인 경우에는 해당 property를 serialize 하지 않습니다. 예를 들면&#8230;<br />
<span id="more-192"></span></p>
<pre class='brush:java'>
@XStreamAlias("Person")
class Person {
	public String firstName;
	public String lastName;
	public String middleName;
}
......
Person jobs = new Person();
jobs.firstName = "Steve";
jobs.lastName = "Jobs";

XStream xstream = new XStream();
xstream.processAnnotations(Person.class);
String xml = xstream.toXML(jobs);
System.out.println(xml);
</pre>
<p>위와 같은 코드는 아래와 같은 XML을 출력하게 됩니다. </p>
<pre class='brush:xml>
<Person>
  <firstName>Steve</firstName>
  <lastName>Jobs</lastName>
</Person>
</pre>
<p>즉, null 값을 가진 middleName은 아예 serialize 되지 않습니다. 아마도 XStream이 Object를 deserialize 할 때 null과 공백문자열 간의 모호함을 제거하기 위해서 null을 serialize 하는 것으로 생각됩니다. (근거는 없습니다.) JSON으로 serialize 할 때도 마찬가지 결과를 보여줍니다.</p>
<p>어찌 됐던, 위와 같은 코드에서 아래와 같은 결과를 얻고 싶은 경우 XStream의 기본 기능만으로 해결할 수 없습니다. </p>
<pre class='brush:xml'>
<Person>
  <firstName>Steve</firstName>
  <lastName>Jobs</lastName>
  <middleName/>
</Person>
</pre>
<h3>해결방법(XML) : Converter 구현 </h3>
<p>null value serialize를 위해서 여러가지 접근 방법이 있겠지만, Converter를 구현해서 사용하는게 가장 심플합니다.<br />
아래코드는 null value를 빈 element로 변환하도록 하는 Converter 구현입니다. </p>
<pre class='brush:java'>
/**
 * NullConverterHack.
 *
 *


 * {@link XStream}으로 serialize 할때 기본적으로 null value를 skip처리하므로,
 * 빈 엘리먼트를 삽입하도록 하는 {@link Converter} 클래스임.
 * 

 *


 * 아래와 같은 형태로 사용한다. 반드시 LOW PRIORITY로 사용해야 함.
 * <code>
 *  xstream.registerConverter(new NullConverterHack(xstream.getMapper()), XStream.PRIORITY_LOW);
 * </code>
 * 

 *
 * (주의) unmarshal을 지원하지 않는다.
 *
 * @see JsonWriterHack Json으로 serialize 할 경우에는 {@link NullConverterHack}과 {@link JsonWriterHack}을 함께 사용한다.
 * @author oddpoet
 */
public class NullConverterHack implements Converter {
	private Mapper mapper;

	public NullConverterHack(Mapper mapper) {
		this.mapper = mapper;
	}

	/**
	 * 모든 클래스에 대해서 작동.
	 *
	 * @param type
	 * @return
	 * @see com.thoughtworks.xstream.converters.ConverterMatcher#canConvert(java.lang.Class)
	 */
    @SuppressWarnings("unchecked")
	public boolean canConvert(Class type) {
        return true;
    }

    /**
     * marshalling.
     *
     * @param source
     * @param writer
     * @param context
     * @see com.thoughtworks.xstream.converters.Converter#marshal(java.lang.Object, com.thoughtworks.xstream.io.HierarchicalStreamWriter, com.thoughtworks.xstream.converters.MarshallingContext)
     */
    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
    	Field [] fieldSet = source.getClass().getDeclaredFields();

		for (Field field : fieldSet) {
    		Object member;

			if (!mapper.shouldSerializeMember(source.getClass(), field.getName())) {
    			continue;
			}

    		field.setAccessible(true);
			try {
				member = field.get(source);
				String name = mapper.serializedMember(field.getDeclaringClass(), field.getName());

				if (member == null) {
	    			writer.startNode(name);
	    			// 값이 null이면 내용은 채우지 않고 startNode(), endNode()만 호출
	    			writer.endNode();
	    		} else {
	    			ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, member.getClass());
	    			context.convertAnother(member);
	    			writer.endNode();
	    		}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}

    	}
    }

    /**
     * unmarshal을 지원하지 않는다.
     *
     * @param reader
     * @param context
     * @return
     * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, com.thoughtworks.xstream.converters.UnmarshallingContext)
     */
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        return null;
    }
}
</pre>
<p>Converter 구현 코드가 길지 않으니 자세한 설명은 생략하겠습니다. 실제 사용방법은 아래와 같습니다. </p>
<pre class='brush:java'>
Person jobs = new Person();
jobs.firstName = "Steve";
jobs.lastName = "";
jobs.middleName = null;

XStream xstream = new XStream();
// Converter를 등록한다. 단, PRIORITY_LOW로 등록하도록 한다.
xstream.registerConverter(new NullConverterHack(xstream.getMapper()), XStream.PRIORITY_LOW);

xstream.processAnnotations(Person.class);
String xml = xstream.toXML(jobs);
System.out.println(xml);
</pre>
<p>위와 같은 방법으로 객체를 XML로 serialize 하면 아래와 같은 결과를 얻을 수 있습니다. 공백문자와 null이 다른 형식으로 표현되고 있음에 유의하세요. </p>
<pre class='brush:xml'>
<Person>
  <firstName>Steve</firstName>
  <lastName></lastName> <!-- 공백문자는 이렇게 -->
  <middleName/> <!-- null은 이렇게 ... -->
</Person>
</pre>
<h3> JSON의 경우 </h3>
<p>위와 같이 Converter를 추가해서 사용할 경우 XML 변환시 null 값에 대한 빈 element 출력은 가능합니다만, JSON 출력의 경우 아래와 같이 null 이 아닌 빈 객체({})로 출력되는 문제가 있습니다. </p>
<pre class='brush:java'>
Person jobs = new Person();
jobs.firstName = "Steve";
jobs.lastName = "";
jobs.middleName = null;

XStream xstream = new XStream(new JsonHierarchicalStreamDriver() {

	@Override
	public HierarchicalStreamWriter createWriter(Writer out) {
		return new JsonWriter(out, JsonWriter.DROP_ROOT_MODE | JsonWriter.STRICT_MODE);
	}

});
xstream.registerConverter(new NullConverterHack(xstream.getMapper()), XStream.PRIORITY_LOW);
xstream.processAnnotations(Person.class);
String xml = xstream.toXML(jobs);
System.out.println(xml);
</pre>
<p>즉 위와 같은 코드는 아래와 같은 JSON을 출력하게됩니다. null 대신 {}이 출력되지요. </p>
<pre class='brush:javascript'>
{
  "firstName": "Steve",
  "lastName": "",
  "middleName": {}
}
</pre>
<p>Conveter의 Interface를 보면 알 수 있겠지만, JSON 출력시의 null 문제를 풀기 위해서는 Conveter 레벨에서 처리가 안됩니다. 결국 Writer 레벨에서 null 처리를 해야합니다. 그런데 기존 JsonWriter를 상속받아서 해결할 수 없기 때문에, 깔끔하지는 않지만 기존 JsonWriter 소스를 복사해서 새로운 클래스를 만들고 관련된 부분을 수정합니다. 수정해야할 부분은 <strong>endNode()</strong> 함수입니다. (아래 소스 참고)</p>
<pre class='brush:java'>
/**
 * JsonWriterHack.
 *
 *


 * XStream 패키지에 있는 {@link JsonWriter}에 수정을 가한 클래스이며, (상속받아서 해결 불가능함)
 * {@link NullConverterHack}과 함께 사용할 경우, null 값을 Json의 null로 변환해준다. 당연히 공백문자열("")과는 구분된다.
 * 

 *


 * 수정된 부분은 {@link #endNode()} 함수이며, 수정한 부분에 comment를 달아놓았다.
 * 하지만 변경된 JsonWriter에 의해 사용시 예기치 못한 JSON 변환이 이루어질 수 있으므로, 주의하도록한다.
 * 

 *
 * @see NullConverterHack {@link NullConverterHack}과 함께 사용해야 한다.
 * @author oddpoet
 */
public class JsonWriterHack implements ExtendedHierarchicalStreamWriter {
	// ... 나머지 코드는 JsonWriter와 동일하게 그대로 두고, endNode() 함수만 수정한다.
	public void endNode() {
		depth--;
		Node node = (Node)elementStack.pop();

		if (node.clazz != null &#038;&#038; node.isCollection) {
			if (node.fieldAlready) {
				readyForNewLine = true;
			}

			finishTag();
			writer.write("]");
		} else if (tagIsEmpty) {
			readyForNewLine = false;
			writer.write("null");  // <- 이부분을 수정. 원래는 writer.write("{}"); 였음.
			finishTag();
		} else {
			finishTag();

			if (node.fieldAlready) {
				writer.write("}");
			}
		}

		readyForNewLine = true;

		if (depth == 0 &#038;&#038; ((mode &#038; DROP_ROOT_MODE) == 0 || (depth > 0 &#038;&#038; !node.isCollection))) {
			writer.write("}");
			writer.flush();
		}
	}
	// 나머지 코드는 그대로 유지...
}
</pre>
<p>자 그럼, JsonWriter 대신 위에서 만든 JsonWriterHack으로 대체하면 원하는 null 출력을 볼 수 있습니다. </p>
<pre class='brush:java'>
Person jobs = new Person();
jobs.firstName = "Steve";
jobs.lastName = "";
jobs.middleName = null;

XStream xstream = new XStream(new JsonHierarchicalStreamDriver() {

	@Override
	public HierarchicalStreamWriter createWriter(Writer out) {
		return new JsonWriterHack(out, JsonWriterHack.DROP_ROOT_MODE | JsonWriterHack.STRICT_MODE);
	}

});
xstream.registerConverter(new NullConverterHack(xstream.getMapper()), XStream.PRIORITY_LOW);
xstream.processAnnotations(Person.class);
String xml = xstream.toXML(jobs);
System.out.println(xml);
</pre>
<pre class='brush:javascript'>
{
  "firstName": "Steve",
  "lastName": "",
  "middleName": null
}
</pre>
<p>Json에서의 null serialize 의 경우, 기존 구현체를 Rewrite해야해서 그다지 깔끔한 방법은 아닙니다. XStream으로 JSON 출력제어를 하는데 제약이 있으므로, 다른 object &#8211; JSON 매핑 library를 사용하는게 좋을 수도 있습니다. </p>
]]></content:encoded>
			<wfw:commentRss>http://oddpoet.net/archives/192/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

