반응형

HTML 5에 추가된 새로운 요소 (한글)

구조와 의미

developerWorks
문서 옵션
수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기

토론

영어원문

영어원문


제안 및 의견
피드백

난이도 : 초급

Elliotte Harold, 교수, Polytechnic University

2008 년 4 월 01 일

HTML 5는 20세기 이후 처음으로 HTML에 새로운 요소를 추가했습니다. 새 구조 요소로는 aside, figure, section이 있으며 새 인라인 요소로는 time, meter, progress가 있습니다. 또한 새로운 내장 요소로는 videoaudio가 있으며 새로운 대화형 요소로는 details, datagrid, command가 있습니다.

HTML은 1999년 HTML 4를 마지막으로 발전을 멈추었다. W3C는 HTML 기반 문법을 SGML(Standard Generalized Markup Language)에서 XML로 변경하고 SGV(Scalable Vector Graphics), XForm, MathML 등 완전히 새로운 마크업 언어를 개발하는 데 노력을 기울였다. 브라우저 개발업체는 탭과 RSS 리더 같은 브라우저 기능에 초점을 맞추었다. 웹 디자이너들은 CSS와 자바스크립트를 배운 후 기존 프레임워크 상에서 Ajax(Asynchronous JavaScript + XML)를 사용하여 응용 프로그램을 개발하기 시작했다. 하지만 HTML 자체는 지난 8년 동안 아무런 진전도 보이지 않았다.

자주 사용하는 약어
  • CSS: Cascading Style Sheets
  • HTML: Hypertext Markup Language
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

그런데 최근에 이르러 잠자던 사자가 깨어났다. 브라우저 시장에서 주요 3대 주자인 애플, 오페라, 모질라 재단이 WhatWG(Web Hypertext Application Technology Working Group)를 결성하여 기존 HTML을 개선하겠다고 나섰다. 더욱 최근에는 W3C가 이러한 움직임을 감지하고 비슷한 구성원을 모아 차세대 HTML 개발 대열에 동참했다. 결국은 두 대열이 하나로 합쳐지리라 생각한다. 아직까지 구체적인 내용은 논쟁할 여지가 많이 남았지만, 다음 HTML 버전의 전체적인 윤곽은 점차 자리를 잡아가고 있다.

(HTML 5 혹은 Web Applications 1.0이라 부르는) 새 HTML 버전은 1999년 얼음에 동결되었다가 막 해동된 원시인 웹 디자이너들도 금방 익숙해지리라 생각한다. 새 버전은 이름 공간(namespace)이나 스키마(schema)가 없다. 요소를 닫지 않아도 괜찮다. 브라우저는 오류에 관대하다. p는 여전히 p이고 table은 여전히 table이다.

그렇지만 동시에 막 해동된 웹 디자이너들은 다소 혼란스럽고 새로운 요소도 접하게 된다. 그렇다. div 같은 옛 친구는 그대로지만 section, header, footer, nav가 새롭게 등장했다. em, code, strong도 그대로지만 meter, time, m이 생겨났다. imgembed도 그대로 사용하지만, 이제 videoaudio도 있다. 그러나 해동되었던 원시인이라도 조금만 꼼꼼히 들여다 보면, 새로운 요소가 그리 새로운 개념은 아니라는 사실이 드러난다. 대다수는 1999년에도 필요했으나 존재하지 않았던 요소일 뿐이다. 기존 요소와 비교하면서 살펴보면 새 요소를 배우기가 전혀 어렵지 않다. 오히려 Ajax나 CSS보다 이해하기 더 쉽다.

마지막으로, (HTML과 마찬가지로 1999년에서 멈춘) 윈도우 98이 돌아가는 300MHz 노트북에서 새 HTML 페이지를 띄워보면 Netscape 4나 Windows® Internet Explorer® 5에서도 페이지가 문제 없이 뜬다. 놀랍지 않은가. 물론 브라우저가 새 요소 태그를 제대로 인식하지 못하겠지만 그래도 페이지는 뜨고 내용은 모두 담겨 있다.

이것은 우연의 일치가 아니다. 처음부터 설계를 이렇게 했다. 브라우저가 HTML 5를 지원하지 않는 경우 HTML 5는 우아하게 자신의 버전을 낮춘다. 이유는 간단하다. 우리 모두가 1999년에서 막 해동된 원시인이기 때문이다. 현재 브라우저는 탭, CSS, XmHttpRequest를 지원하지만 HTML 렌더링 엔진은 1999년 기술 그대로다. 기존 사용자 기반을 고려하지 않고서는 웹을 진화시키기 어렵다. HTML 5는 이러한 사실을 이해한다. 그래서 HTML 5는 현재 페이지 작성자들에게 실질적인 이익을 제공함과 동시에 사용자들이 브라우저를 서서히 판올림하면서 그 수가 늘어갈 미래 페이지 작성자들에게 더 큰 이익을 약속한다. 이러한 사실을 염두에 두고 이제 HTML 5를 둘러보자.

구조

흔히 형식이 잘 갖춰진(well-formed) HTML 페이지라도 구조가 부실하기에 처리하기가 쉽지 않다. 처음부터 페이지를 분석하여 구역(section)이 나뉘는 지점을 찾아내야 한다. 일반적으로 사이드바, 바닥글, 머리글, 탐색 메뉴, 기본 내용 구역, 개별 내용은 모두 다용도 div 요소로 나눠진다. HTML 5는 자주 사용하는 구조를 표현하도록 새로운 요소를 제공한다.

  • section: 책에서 부나 장, 장에서 절 등 근본적으로 HTML 4에서 heading을 사용하던 내용은 무엇이나 가능하다.
  • header: 페이지에 표시하는 페이지 머리글로, head 요소와 다르다.
  • footer: 잔글씨로 표시하는 페이지 바닥글로, 전자편지 서명 등이 들어간다.
  • nav: 다른 페이지를 가리키는 링크 모음이다.
  • article: 블로그, 잡지, 개요 등에 포함되는 개별 항목을 가리킨다.

예를 들어 오늘날에 사용하는 전형적인 블로그 페이지를 살펴보자. Listing 1에서 보듯이 페이지 상단에는 머리글, 하단에는 바닥글, 항목 몇 개, 탐색 메뉴, 사이드바가 있다.


Listing 1. 오늘날 전형적인 블로그 페이지
                
<html>
  <head>
    <title>Mokka mit Schlag </title>
 </head>
<body>
<div id="page">
  <div id="header">
    <h1><a href="http://www.elharo.com/blog">Mokka mit Schlag</a></h1>
  </div>
  <div id="container">
    <div id="center" class="column">               
      <div class="post" id="post-1000572">
        <h2><a href=
      "/blog/birding/2007/04/23/spring-comes-and-goes-in-sussex-county/">
      Spring Comes (and Goes) in Sussex County</a></h2>
        
        <div class="entry">
          <p>Yesterday I joined the Brooklyn Bird Club for our
          annual trip to Western New Jersey, specifically Hyper
          Humus, a relatively recently discovered hot spot. It
          started out as a nice winter morning when we arrived
          at the site at 7:30 A.M., progressed to Spring around
          10:00 A.M., and reached early summer by 10:15. </p>
        </div>
      </div>


      <div class="post" id="post-1000571">
        <h2><a href=
          "/blog/birding/2007/04/23/but-does-it-count-for-your-life-list/">
           But does it count for your life list?</a></h2>
        
        <div class="entry">
          <p>Seems you can now go <a
          href="http://www.wired.com/science/discoveries/news/
          2007/04/cone_sf">bird watching via the Internet</a>. I
          haven't been able to test it out yet (20 user
          limit apparently) but this is certainly cool.
          Personally, I can't imagine it replacing
          actually being out in the field by any small amount.
          On the other hand, I've always found it quite
          sad to meet senior birders who are no longer able to
          hold binoculars steady or get to the park. I can
          imagine this might be of some interest to them. At
          least one elderly birder did a big year on TV, after
          he could no longer get out so much. This certainly
          tops that.</p>
        </div>
      </div>

      </div>
    
    <div class="navigation">
      <div class="alignleft">
         <a href="/blog/page/2/">&laquo; Previous Entries</a>
       </div>
      <div class="alignright"></div>
    </div>
  </div>

  <div id="right" class="column">
    <ul id="sidebar">
      <li><h2>Info</h2>
      <ul>
        <li><a href="/blog/comment-policy/">Comment Policy</a></li>
        <li><a href="/blog/todo-list/">Todo List</a></li>
      </ul></li>
      <li><h2>Archives</h2>
        <ul>
          <li><a href='/blog/2007/04/'>April 2007</a></li>
          <li><a href='/blog/2007/03/'>March 2007</a></li>
          <li><a href='/blog/2007/02/'>February 2007</a></li>
          <li><a href='/blog/2007/01/'>January 2007</a></li>
        </ul>
      </li>
    </ul>
  </div>
  <div id="footer">
    <p>Copyright 2007 Elliotte Rusty Harold</p>
  </div>
</div>
  
</body>
</html>

들여쓰기에 신경을 쓴다 해도 div가 여러 차례 중첩되어 꽤나 복잡하다. 반면 Listing 2에서 보듯이 HTML 5에서는 페이지가 좀더 구조적이다.


Listing 2. HTML 5에서 전형적인 블로그 페이지
                
<html>
 <head>
    <title>Mokka mit Schlag </title>
 </head>
<body>
  <header>
    <h1><a href="http://www.elharo.com/blog">Mokka mit Schlag</a></h1>
  </header>
  <section>               
      <article>
        <h2><a href=
        "/blog/birding/2007/04/23/spring-comes-and-goes-in-sussex-county/">
         Spring Comes (and Goes) in Sussex County</a></h2>
        
        <p>Yesterday I joined the Brooklyn Bird Club for our
        annual trip to Western New Jersey, specifically Hyper
        Humus, a relatively recently discovered hot spot. It
        started out as a nice winter morning when we arrived at
        the site at 7:30 A.M., progressed to Spring around 10:00
        A.M., and reached early summer by 10:15. </p>
      </article>


      <article>
        <h2><a href=
          "/blog/birding/2007/04/23/but-does-it-count-for-your-life-list/">
          But does it count for your life list?</a></h2>
        
          <p>Seems you can now go <a
          href="http://www.wired.com/science/discoveries/news/
          2007/04/cone_sf">bird watching via the Internet</a>. I
          haven't been able to test it out yet (20 user
          limit apparently) but this is certainly cool.
          Personally, I can't imagine it replacing
          actually being out in the field by any small amount.
          On the other hand, I've always found it quite
          sad to meet senior birders who are no longer able to
          hold binoculars steady or get to the park. I can
          imagine this might be of some interest to them. At
          least one elderly birder did a big year on TV, after
          he could no longer get out so much. This certainly
          tops that.</p>
      </article>    
    <nav>
      <a href="/blog/page/2/">&laquo; Previous Entries</a>
    </nav>
  </section>

  <nav>
    <ul>
      <li><h2>Info</h2>
      <ul>
        <li><a href="/blog/comment-policy/">Comment Policy</a></li>
        <li><a href="/blog/todo-list/">Todo List</a></li>
      </ul></li>
      <li><h2>Archives</h2>
        <ul>
          <li><a href='/blog/2007/04/'>April 2007</a></li>
          <li><a href='/blog/2007/03/'>March 2007</a></li>
          <li><a href='/blog/2007/02/'>February 2007</a></li>
          <li><a href='/blog/2007/01/'>January 2007</a></li>
        </ul>
      </li>
    </ul>
  </nav>
  <footer>
    <p>Copyright 2007 Elliotte Rusty Harold</p>
  </footer>
  
</body>
</html>

더 이상 div가 필요하지 않다. class 속성으로 구역 종류를 나타낼 필요도 없다. 표준 이름을 보면 무슨 구역인지 바로 드러난다. 이는 오디오, 모바일 폰, 기타 비표준 브라우저에서 특히 중요하다.




위로


의미적 블록 요소

구조적 요소 외에도 HTML 5는 순전히 의미적으로 사용하는 블록 요소를 제공한다.

  • aside
  • figure
  • dialog

나는 책과 기사에서 첫 두 요소를 항상 사용한다. 세 번째 요소는 개인적으로 별로 사용하지 않으나 텍스트에 많이 사용한다.

aside

aside 요소는 참고, 팁, 사이드바, 독립 인용(pullquote), 괄호 삽입구 등 본문 흐름에서 벗어나는 문장을 나타낸다. 예를 들어 Listing 3에서 보듯이 developerWorks 기사는 사이드바를 흔히 표로 구현한다.


Listing 3. developerWorks HTML 4 사이드바
                
<table align="right" border="0" cellpadding="0" cellspacing="0" width="40%">
<tbody><tr><td width="10">
<img alt="" src="//www.ibm.com/i/c.gif" height="1" width="10"></td>
<td>
<table border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody><tr><td bgcolor="#eeeeee">
<p><a name="xf-value"><span class="smalltitle">.xf-value</span></a></p>
<p>
The <code type="inline">.xf-value</code> selector used here styles the input
field value but not its label. This is actually inconsistent
with the current CSS3 draft. The example really should use the
<code type="inline">::value</code> pseudo-class instead like so:
</p>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody><tr><td class="code-outline">
<pre class="displaycode">input::value { width: 20em; }
#ccnumber::value { width: 18em }
#zip::value { width: 12em }
#state::value { width: 3em  }</pre>
</td></tr></tbody></table><br>

<p>
However, Firefox doesn't yet support this syntax. 
</p>
</td></tr></table>

HTML 5에서는 Listing 4처럼 좀더 합리적으로 구현할 수 있다.


Listing 4. developerWorks HTML 5 사이드바
                
<aside>
<h3>.xf-value</h3>
<p>
The <code type="inline">.xf-value</code> selector used here styles the input
field value but not its label. This is actually inconsistent
with the current CSS3 draft. The example really should use the
<code type="inline">::value</code> pseudo-class instead like so:
</p>
  
<pre class="displaycode">input::value { width: 20em; }
#ccnumber::value { width: 18em }
#zip::value { width: 12em }
#state::value { width: 3em  }</pre>

<p>
However, Firefox doesn't yet support this syntax. 
</p>
</aside>

사이드바가 놓일 위치는 CSS에서 힌트를 얻어 브라우저가 판단한다.

figure

figure 요소는 이미지 블록과 제목을 표현한다. 예를 들어 developerWorks 기사는 Listing 5와 같은 HTML 코드를 많이 쓴다. 결과는 그림 1과 같다.


Listing 5. developerWorks HTML 4 그림
                
<a name="fig2"><b>Figure 2. Install Mozilla XForms dialog</b></a><br />
<img alt="A Web site is requesting permission to install the following item: 
   Mozilla XForms 0.7 Unsigned" 
  src="installdialog.jpg" border="0" height="317" hspace="5" vspace="5" width="331" />
<br />


Figure 1. Mozilla XForms 다이얼로그 설치
A Web site is requesting permission to install the following item: Mozilla XForms 0.7 Unsigned

HTML 5에서는 Listing 6과 같이 좀더 의미 있게 구현할 수 있다.


Listing 6. developerWorks HTML 5 그림
                
<figure id="fig2">
  <legend>Figure 2. Install Mozilla XForms dialog</legend>
  <img alt="A Web site is requesting permission to install the following item: 
    Mozilla XForms 0.7 Unsigned" 
    src="installdialog.jpg" border="0" height="317" hspace="5" vspace="5" width="331" />
</figure>

가장 중요하게, figure 요소를 사용하면 브라우저는 (특히 스크린 리더는) 이미지와 이미지 제목을 분명하게 연관짓게 된다.

figure 요소를 반드시 이미지에만 사용하라는 법은 없다. audio, video, iframe, object, embed 등과 같은 요소에 제목을 붙일 때도 figure 요소를 사용하면 편리하다.

dialog

dialog 요소는 여러 사람 간에 일어나는 대화를 표현한다. HTML 5는 dt 요소를 연설자로, dd 요소는 연설로 중복정의(overload)한다. 따라서 HTML 5를 지원하지 않는 브라우저에서도 페이지가 어느 정도 모양새를 갖춘다. Listing 7은 갈렐레오의 “주요한 2대 체계에 대한 대화(Dialogue Concerning the Two Chief World Systems)”라는 다소 유명한 연설이다.


Listing 7. HTML 5로 표현한 갈렐레오의 대화
                
<dialog>
	<dt>Simplicius </dt> 
    <dd>According to the straight line AF,
	and not according to the curve, such being already excluded
	for such a use.</dd>

	<dt>Sagredo </dt> 
    <dd>But I should take neither of them,
	seeing that the straight line AF runs obliquely. I should
	draw a line perpendicular to CD, for this would seem to me
	to be the shortest, as well as being unique among the
	infinite number of longer and unequal ones which may be
	drawn from the point A to every other point of the opposite
	line CD. </dd>

	<dt>Salviati </dt> 
    <dd><p> Your choice and the reason you
	adduce for it seem to me most excellent. So now we have it
	that the first dimension is determined by a straight line;
	the second (namely, breadth) by another straight line, and
	not only straight, but at right angles to that which
	determines the length. Thus we have defined the two
	dimensions of a surface; that is, length and breadth. </p>

	<p> But suppose you had to determine a height—for
	example, how high this platform is from the pavement down
	below there. Seeing that from any point in the platform we
	may draw infinite lines, curved or straight, and all of
	different lengths, to the infinite points of the pavement
	below, which of all these lines would you make use of? </p>
	</dd>
</dialog>

dialog 요소의 정확한 문법은 아직도 논의가 분분하다. 어떤 사람들은 dialog 요소 안에 (무대 지시문처럼) 대화가 아닌 문장을 추가하고 싶어 한다. 어떤 사람들은 dtdd를 중복정의(overload)해서는 안 된다고 주장한다. 그러나 대화를 의미적으로 표현하는 요소가 필요하다는 사실에는 대다수가 동의한다. 정확한 문법에는 동의하지 못할지라도 말이다.




위로


의미적 인라인 요소

HTML 4에는 var, code, kbd, tt, samp라는, 컴퓨터 코드를 살짝 변형한 인라인 요소 5개가 있었다. 그러나 시간이나 숫자나 야유 등과 같이 기본적인 속성을 표현할 방법은 없었다. HTML 5가 제공하는 새 인라인 요소는 기술자와 보통 저작자 사이에 존재하는 불균형을 바로 잡아준다.

mark

m 요소는 강조할 필요까진 없으나 “주목”해야 할 문구를 표현한다. 책에서 형광색 펜으로 표시하는 부분에 해당한다. 구글 “저장된 페이지”가 아주 적당한 예다. “저장된 페이지”를 열면 검색 용어가 다른 색으로 표시된다. 예를 들어 “Egret”을 검색하면 구글 “저장된 페이지”는 다음 코드를 사용할 수 있다.

The Great <m>Egret</m> (also known as the
American <m>Egret</m>)  is a large white wading bird found worldwide.
The Great <m>Egret</m> flies with slow wing beats. The
scientific name of the Great <m>Egret</m> is <i>Casmerodius
albus</i>.

요소 이름은 아직 논쟁 중이다. 명세가 나오기 전에 mmark로 바꿀지도 모른다.

time

time 요소는 특정한 시각을 표현한다. 예를 들어 5:35 P.M., EST, April 23, 2007을 다음과 같이 표현할 수 있다.

<p>I am writing this example at
<time>5:35 P.M. on April 23rd</time>.
</p>

time 요소를 사용하면 브라우저나 기타 응용 프로그램이 HTML 페이지에서 시각을 인식할 수 있다. 요소 내용은 어떤 형식이라도 상관이 없다. 그러나 각 time 요소는 datetime 속성을 사용하여 시스템이 인식하는 시각 형식을 지정해야 한다.

<p>I am writing this example at
<time datetime="2007-04-23T17:35:00-05:00">5:35 P.M. on April 23rd</time>.
</p>

시스템이 인식하는 시각 형식을 사용하면 검색 엔진, 달력 프로그램 등과 같은 응용 프로그램에서 시각을 인식하기 쉬워진다.

meter

meter 요소는 특정한 범위에 속하는 숫자 값을 표현한다. 예를 들면 월급, 프랑스 선거에서 르펜의 득표율, 테스트 점수 등에 사용한다. 여기서는 Software Development 2007에서 구글 프로그래머가 제공한 자료에 meter를 사용했다.

<p>An entry level programmer in Silicon Valley 
can expect to start around <meter>$90,000</meter> per year.
</p>

meter 요소를 사용하면 브라우저나 기타 응용 프로그램이 HTML 페이지에서 양을 인식할 수 있다. 요소 내용은 어떤 형식이라도 상관이 없다. 그러나 각 meter 요소는 최대 6개 속성을 사용하여 시스템이 인식하는 숫자 값을 지정해야 한다.

  • value
  • min
  • low
  • high
  • max
  • optimum

각 속성에는 관련 범위를 묘사하는 숫자를 지정해야 한다. 예를 들어 기말 고사 점수는 다음과 같이 표현할 수 있다.

<p>Your score was 
<meter value="88.7" min="0" max="100" low="65" high="96" optimum="100">B+</meter>.
</p>

위 코드는 총점 100점에 실제 점수는 88.7이라는 사실을 나타낸다. 가능한 최저 점수는 0인데 실제 최저 점수는 65점이다. 이상적인 최고 점수는 100점이지만 실제 점수는 96점이다. 응용 프로그램은 이 정보를 측정기에 표시하거나 툴팁에 추가하는 등 다양하게 표현할 수 있다. 하지만 대개는 다른 인라인 요소와 같은 방식으로 표현하리라 생각한다.

progress

progress 요소는 현재 진행 중인 상태를 표현한다. GUI 응용 프로그램에서 사용하는 진행 상태 표시 막대와 동일하다. 예를 들어 파일을 다운로드한 정도나 영화가 남은 시간을 progress 요소로 표현하면 간편하다. 다음 코드는 다운로드를 33% 완료했음을 나타낸다.

<p>Downloaded: 
  <progress value="1534602" max="4603807">33%</progress>
</p>

value 속성은 현재 값을 나타내며, max 속성은 최대 값을 나타낸다. 위 코드는 총 460만 3807바이트 중 현재 153만 4602바이트를 다운로드했음을 뜻한다.

max 속성을 생략하면 무한한 진행 상태 막대가 표시된다.

작업이 계속됨에 따라 진행 상태 막대를 동적으로 갱신하려면 자바스크립트를 사용한다. progress 요소는 동적으로 표현해야 흥미롭다.




위로


내장된 미디어

웹에서 비디오는 커다란 인기를 끌고 있지만, 대다수 웹 비디오 재생기는 독점적인 기술을 사용한다. 유튜브는 플래시를, 마이크로소프트는 윈도우 미디어(Windows Media®)를, 애플은 퀵타임을 사용한다. 그러다 보니 모든 브라우저에서 돌아가는 비디오 내장 마크업이 없다. 그래서 WhatWG는 임의의 비디오 형식을 내장하는 새 video 요소를 제안했다. 예를 들어 내 퀵타임 영화를 다음과 같이 내장할 수 있다.

<video src="http://www.cafeaulait.org/birds/sora.mov" />
  

특정 형식과 코덱을 선호할지 여부는 아직도 논의 중이다. 아마 Ogg Theora를 필수로 만들거나 적어도 강력히 권장할 가능성이 크다. 퀵타임과 같은 독점 형식이나 MPEG-4와 같이 특허가 걸린 형식은 선택으로 남겨두리라 생각한다. 하지만 GIF, JPEG, PNG 형식이 BMP, X-Bitmap, JPEG 2000 등 다른 형식을 제치고 img 요소가 선호하는 이미지 형식으로 자리 잡았듯이, video 요소가 선호하는 비디오 형식도 결국은 시장에서 판가름 나리라 믿는다.

video에 상응하는 audio 형식도 제안되었다. 예를 들어 다음과 같이 웹 페이지에 배경 음악을 깔 수 있다.

<audio src="spacemusic.mp3"
    autoplay="autoplay" loop="20000" />
  

autoplay 속성은 페이지를 브라우저에 올리자마자 사용자 요청을 기다리지 말고 음악을 시작하라는 뜻이다. 그런 다음 루프를 2000번 돌 때까지 (혹은 사용자가 윈도우를 닫거나 다른 페이지로 이동할 때까지) 음악을 계속 연주한다. 물론, 브라우저는 내장 미디어에 대해 소리를 없애거나(mute) 재생을 일시중지하는(pause) 기능을 제공해야 한다. 페이지 작성자가 페이지에 이런 기능을 넣었든 넣지 않았든 상관 없이 브라우저는 기본적으로 두 기능을 제공해야 한다.

브라우저는 WAV 형식을 지원해야 한다. 또한 MP3와 같은 형식을 지원해도 좋다.

기존 브라우저는 audio/video 마크업을 지원하지 않으며 시각/청각 장애자는 비디오/오디오를 시청/청취하지 못하므로, 비디오와 오디오 내용을 설명하는 마크업이 추가될지도 모른다. 이 정보는 검색 엔진이 반기리라 생각한다. 이상적으로는 비디오 대본이나 오디오 전사본이면 가장 좋다. 예를 들어 Listing 8은 존 F. 케네디 대통령의 취임 연설을 구현한 코드다.


Listing 8. HTML 5로 표현한 존 F. 케네디 취임 연설
                
<audio src="kennedyinauguraladdrees.mp3">
    <p>
    Vice President Johnson, Mr. Speaker, Mr. Chief Justice,
    President Eisenhower, Vice President Nixon, President Truman,
    Reverend Clergy, fellow citizens:
    </p>
    
    <p>
    We observe today not a victory of party, but a celebration of
    freedom -- symbolizing an end, as well as a beginning -- 
   signifying renewal, as well as change. For I have sworn before
    you and Almighty God the same solemn oath our forebears
    prescribed nearly a century and three-quarters ago.
    </p>
    
    <p>
    The world is very different now. For man holds in his mortal
    hands the power to abolish all forms of human poverty and all
    forms of human life. And yet the same revolutionary beliefs for
    which our forebears fought are still at issue around the globe -- 
    the belief that the rights of man come not from the
    generosity of the state, but from the hand of God.
    </p>
    
    <p>
    ...
    </p>
    
    </audio>




위로


상호작용

HTML 5는 Web Applications 1.0의 그늘에 속한다. HTML 5는 그 언저리에 몇 가지 새 요소를 추가해 웹 페이지와 사용자 사이 상호작용 정도를 높인다.

  • details
  • datagrid
  • menu
  • command

위 요소는 사용자 동작과 선택에 따라 페이지 내용을 바꾼다. 이 때 서버에서 새 페이지를 읽어들이지 않는다.

details

details 요소는 기본적으로 표시되지 않는 상세정보를 가리킨다. 선택적인 legend 요소로 상세정보를 요약해도 좋다. details의 적합한 사용처는 각주(footnote)나 후주(endnote)다. 예를 들어 다음 코드를 보자.

The bill of a Craveri's Murrelet is about 10% thinner 
than the bill of a Xantus's Murrelet. 
<details>
<legend>[Sibley, 2000]</legend>
<p>Sibley, David Allen, The Sibley Guide to Birds, 
(New York: Chanticleer Press, 2000) p. 247
 </p>
</details>

페이지를 표시하는 방법은 정확히 명시되지 않았다. 브라우저에 따라 1) 각주, 2) 후주, 3) 툴팁으로 표시할 수 있다.

details 요소에 open 속성을 지정해도 된다. open 속성을 지정하면 상세정보가 기본으로 표시된다. open 속성이 없으면 사용자가 요청할 때까지 상세정보는 숨겨진다. 어느 쪽이든 사용자가 아이콘/표시기를 클릭하여 상세정보를 표시하거나 숨길 수 있다.

datagrid

datagrid 요소는 그리드 컨트롤을 제어한다. 주로 정적 자료를 표현하는 기존 표와는 달리 사용자나 스크립트가 동적으로 갱신하는 트리, 목록, 표에 사용한다.

datagrid는 초기 자료를 요소 내용에서 가져온다. 요소 내용은 table, select 등 HTML 그룹 요소라면 무엇이든 가능하다. 예를 들어 Listing 9는 datagrid로 성적표를 구현한다. 여기서는 table에서 초기 자료를 가져온다. 좀더 단순한 1차원 자료라면 table 대신 select를 사용해도 좋다. 다른 HTML 요소를 사용하면 datagrid는 자식 하나를 행 하나로 가져온다.


Listing 9. 성적표 datagrid
                
<datagrid>
  <table>
    <tr><td>Jones</td><td>Allison</td><td>A-</td><td>B+</td><td>A</td></tr>
    <tr><td>Smith</td><td>Johnny</td><td>A</td><td>C+</td><td>A</td></tr>
    <tr><td>Willis</td><td>Sydney</td><td>C-</td><td>D</td><td>F</td></tr>
    <tr><td>Wilson</td><td>Frank</td><td>B-</td><td>B+</td><td>A</td></tr>
  </table>
</datagrid>

datagrid가 일반 표와 다른 점이라면, 이제 사용자는 행과 열과 셀을 선택하고, 행과 열과 셀을 축소하고, 셀을 편집하고, 행과 열과 셀을 삭제하고, 그리드를 정렬하는 등 브라우저 내에서 자료를 직접 조작할 수 있다. 자바스크립트 코드는 수정된 내용을 기억한다. Listing 10에서는 이러한 기능을 지원하기 위해 DOM(Document Object Model)에 HTMLDataGridElement 인터페이스를 추가했다.


Listing 10. HTMLDataGridElement
                
interface HTMLDataGridElement : HTMLElement {
           attribute DataGridDataProvider data;
  readonly attribute DataGridSelection selection;
           attribute boolean multiple;
           attribute boolean disabled;
  void updateEverything();
  void updateRowsChanged(in RowSpecification row, in unsigned long count);
  void updateRowsInserted(in RowSpecification row, in unsigned long count);
  void updateRowsRemoved(in RowSpecification row, in unsigned long count);
  void updateRowChanged(in RowSpecification row);
  void updateColumnChanged(in unsigned long column);
  void updateCellChanged(in RowSpecification row, in unsigned long column);
};

DOM을 사용해 자료를 동적으로 메모리에 올려도 된다. 즉 datagrid에 초기 자료를 제공하는 table이나 select가 없어도 좋다. 대신, Listing 11과 같이 DataGridDataProvider 개체로 자료를 제공한다. 이 방법을 사용하면 데이터베이스로부터 XmlHttpRequest나 자바스크립트가 통신할 수 있는 어느 곳에서든 자료를 가져올 수 있다.


Listing 11. DataGridDataProvider
                
interface DataGridDataProvider {
  void initialize(in HTMLDataGridElement datagrid);
  unsigned long getRowCount(in RowSpecification row);
  unsigned long getChildAtPosition(in RowSpecification parentRow, 
      in unsigned long position);
  unsigned long getColumnCount();
  DOMString getCaptionText(in unsigned long column);
  void getCaptionClasses(in unsigned long column, in DOMTokenList classes);
  DOMString getRowImage(in RowSpecification row);
  HTMLMenuElement getRowMenu(in RowSpecification row);
  void getRowClasses(in RowSpecification row, in DOMTokenList classes);
  DOMString getCellData(in RowSpecification row, in unsigned long column);
  void getCellClasses(in RowSpecification row, in unsigned long column, 
      in DOMTokenList classes);
  void toggleColumnSortState(in unsigned long column);
  void setCellCheckedState(in RowSpecification row, in unsigned long column, 
      in long state);
  void cycleCell(in RowSpecification row, in unsigned long column);
  void editCell(in RowSpecification row, in unsigned long column, in DOMString data);
};

menu와 command

menu 요소는 적어도 버전 2부터 HTML에 존재했다. HTML 4에서 사용하지 않게 되었으나 HTML 5에서 새로운 모습으로 돌아왔다. HTML 5에서 menucommand 요소를 포함하는데, 각 요소는 즉각적인 동작을 유발한다. 예를 들어 Listing 12는 팝업 경고를 띄우는 메뉴다.


Listing 12. HTML 5 메뉴
                
<menu>
    <command onclick="alert('first command')"  label="Do 1st Command"/>
    <command onclick="alert('second command')" label="Do 2nd Command"/>
    <command onclick="alert('third command')"  label="Do 3rd Command"/>
</menu>

checked="checked" 속성을 지정하면 명령은 확인란이 된다. radiogroup 속성을 지정하면 확인란이 라디오 버튼(radio button)으로 바뀐다. 라디오 버튼 그룹을 정의할 때는 같은 그룹으로 묶을 명령에 같은 radiogroup 속성 값을 지정한다.

간단한 명령 목록 외에 도구 모음(toolbar)이나 팝업 컨텍스트 메뉴에도 menu 요소를 사용한다. 이 때는 type 속성을 toolbarpop으로 지정한다. 예를 들어 Listing 13은 WordPress와 같은 블로그 편집기에서 흔히 사용하는 도구 모음이다. icon 속성은 버튼에 그림을 입힌다.


Listing 13. HTMl 5 도구 모음
                
<menu type="toolbar">
    <command onclick="insertTag(buttons, 0);"  label="strong" icon="bold.gif"/>
    <command onclick="insertTag(buttons, 1);"  label="em" icon="italic.gif"/>
    <command onclick="insertLink(buttons, 2);" label="link" icon="link.gif"/>
    <command onclick="insertTag(buttons, 3);"  label="b-quote" icon="blockquote.gif"/>
    <command onclick="insertTag(buttons, 4);"  label="del" icon="del.gif"/>
    <command onclick="insertTag(buttons, 5);"  label="ins" icon="insert.gif"/>
    <command onclick="insertImage(buttons);"   label="img" icon="image.gif"/>
    <command onclick="insertTag(buttons, 7);"  label="ul" icon="bullet.gif"/>
    <command onclick="insertTag(buttons, 8);"  label="ol" icon="number.gif"/>
    <command onclick="insertTag(buttons, 9);"  label="li" icon="item.gif"/>
    <command onclick="insertTag(buttons, 10);" label="code" icon="code.gif"/>
    <command onclick="insertTag(buttons, 11);" label="cite" icon="cite.gif"/>
    <command onclick="insertTag(buttons, 12);" label="abbr" icon="abbr.gif"/>
    <command onclick="insertTag(buttons, 13);" label="acronym" icon="acronym.gif"/>
</menu>

메뉴 제목은 label 속성을 사용한다. 예를 들어 Listing 14는 편집 메뉴다.


Listing 14. HTML 5 편집 메뉴
                
<menu type="popup" label="Edit">
    <command onclick="undo()"   label="Undo"/>
    <command onclick="redo()"   label="Redo"/>
    <command onclick="cut()"    label="Cut"/>
    <command onclick="copy()"   label="Copy"/>
    <command onclick="paste()"  label="Paste"/>
    <command onclick="delete()" label="Clear"/>
</menu>

메뉴를 다른 메뉴 아래 정의해 메뉴 계층을 만들어도 무방하다.




위로


결론

HTML 5는 미래 웹의 일부다. HTML 5가 제공하는 새로운 요소는 마크업을 더욱 간단하고 명료하게 만든다. 그만큼 웹 페이지도 더욱 명료해진다. 앞으로도 여전히 divspan을 사용하겠지만, 예전만큼 광범위하게 사용하지는 않으리라 생각한다. 많은 경우 더 이상 div나 span을 사용할 필요가 없을 테니까.

당장은 모든 브라우저가 새로운 요소를 지원하지는 않겠지만, 처음 HTML이 창안된 이후에 나왔던 img, table, object도 모두 같은 과정을 거쳤다. 점차 새로운 요소를 지원하는 브라우저가 늘어나리라 믿는다. 비록 새로운 요소를 지원하지 않아도 기존 브라우저에서 HTML 5 페이지를 읽기에 무리가 없다. 인식하지 못하는 요소를 무시하는 HTML 방식 덕택이다. 최신 브라우저를 갖춘 사용자는 좀더 멋진 경험을 하겠지만, 누구도 페이지를 못 읽는 불편함은 겪지 않으리라.

새 기능이 나오기까지 8년은 참으로 긴 시간이었다. 특히나 급격히 변하는 웹 세상에서는 더욱 오랜 시간이었다. HTML 5는 넷스케이프나 마이크로소프트 등 초창기 업체가 격주로 새 요소를 내놓던 초기 시절의 흥분을 어느 정도 되살렸다. 그러면서도 모두가 사용하기 쉽도록 상호운용성을 충분히 고려하여 아주 신중하게 요소를 정의했다. 미래가 밝아 보인다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Elliotte Rusty Harold는 뉴 올리언즈 태생이다. 원조 검프 스프 맛을 찾아 뉴 올리언즈를 자주 방문하지만, 현재 브룩클린 근처 프로스펙트 하이츠에서 아내 베스와 고양이 두 마리를 키우며 폴리테크닉 대학 전자계산학과 조교수로 자바와 객체 지향 프로그래밍을 강의하고 있다. 그가 운영하는 Cafe au Lait 사이트는 인터넷에서 가장 인기 있는 자바 사이트 중 하나가 되었고, 또 다른 사이트 Cafe con Leche 역시 가장 대중적인 XML 사이트 중 하나가 되었다. 올 봄에는 애디슨 웨슬리 출판사를 통해 Refactoring HTML을 출판할 예정이다.

Posted by 1010
반응형
<div style="border:1px solid gray; width:450px; margin-bottom: 1em; padding: 10px">
Div 박스처리
</div>
Posted by 1010
90.개발관련문서2008. 7. 7. 17:03
반응형

Q : 파일 다운로드시 한글파일의 경우 다운로드 되지 않거나 글자가 깨집니다.
A : 익스플로러 "도구 > 인터넷 옵션 > 고급 "에서 고급 UTF8옵션 해제하시면 됩니다.

최종수정일자 : 2006.12.22 22:38:20

Posted by 1010
90.개발관련문서2008. 7. 7. 16:24
반응형
웹개발 문서 모음

웹사이트 개발시에 필요한 문서 목록입니다.
해당 문서목록의 의견,문제점,궁금하신 점은 Q&A게시판에 올려주십시요.
공유를 원하는 유용한 웹개발문서를 받고 있습니다. 많이 많이 여기로 보내주세요.(다양한 업종의 스토리보드와 제안서 강추)

Q : 파일 다운로드시 한글파일의 경우 다운로드 되지 않거나 글자가 깨집니다.
A : 익스플로러 "도구 > 인터넷 옵션 > 고급 "에서 고급 UTF8옵션 해제하시면 됩니다.


* 버전 : v2.0
* 최종수정일자 : 2007.03.06 15:59:50

Posted by 1010
반응형
<BASE target="_son">
초보 웹 프로그래머를 귀찮고 당황하게 하는 경우를 하나 소개하고자 한다.

<상황>
이미 라이브된 웹 사이트에서 사용자가,
특정한 경우에 자바스크립트 에러가 난다는 것을 리포팅했다.
팀장이 그 이야기를 듣고 당장 고쳐놓으라고 주문한다.

급하게 javascript 파일인 js파일을 열어서 수정한 다음 서버에 업로드를 마쳤다.
그리고 팀장에게 '수정 완료했습니다.' 보고를 한다.

그런데 왠일인지 팀장이 확인을 해보더니
'아직 그대론데?"라고 한다.

살펴보니 아까 그 페이지를 보았을 때 사용된 js파일을
팀장의 웹 브라우저가 웹 캐쉬에 저장해두었고
그 캐쉬를 그대로 사용하여 페이지를 불렀기 때문에 갱신이 안된 것이다.

'Ctrl+F5눌러서 캐쉬 무시하고 리로드 해보세요'

그렇게 하더니 팀장은 '어.. 이제 되네.. 수고했어..'라고 한다.

그런데 그렇게 만만한 팀장은 아니다.
'근데 말이야, 다른 사용자들도 Ctrl+F5 안누르면 나처럼 다 에러나는거 아냐?'
맞는 말이다.

그래서 개발자는 이야기한다.
'시간이 좀 지나면 서서히 갱신이 될겁니다... IE가 다 그렇죠 뭐~~~'

하지만 역시 만만한 팀장은 아니다.
'무슨 소리를 하는거야, 무슨 수를 써서든 사용자들이 갱신된 js파일을 적용받도록 해!'


이런 일, 웹 개발자들이 한번씩 경험하는 일이라고 생각하는데...

이 때의 어떻게 해결하는 것이 가장 현명할까?

1. js 파일명을 바꾸어서 서버에 저장하고, HTML에서도 수정한다.
수정전 : <script type="text/javascript" src="/js/scripts.js"></script>
수정후 : <script type="text/javascript" src="/js/scripts_v2.js"></script>

이렇게 하면 당장 해결은 되겠지만,
이런 일이 반복되다 보면 서버측에 는 script파일들이 잔뜩 쌓이게 되고 버전 관리가 복잡하게 된다.
서버에서 해당 디렉토리를 열어보면,
script.js, script_v2.js, script_20071206.js, script.bk.js, script_last.js...
이렇게 되어있기 십상이다.

그러다가 어느 날 큰맘먹고 최신 버전 이외의 파일을 싹 delete하면,
갑자기 갑자기 몇개의 페이지에서 에러가 발생하기 시작한다.

'어, 여기 제가 담당하는 페이지 에러가 나는데요? 누가 script_the_last.js 파일 삭제했어요?'
'아니, 제가 지웠는데요, 아직도 그 파일쓰고 있었어요? 3개월전에 script_real_last.js로 바꿨어요~~'

차라리 이렇게 발견이라도 되면 다행이다.
발견되지 않고 몇주동안 에러가 나는 페이지를 고객에게 서비스하게 되는 일이 일어나지 말라는 법이 없다.

그래서 이방법은 비추천.
(특히 계절마다 갑자기 책상을 말끔히 정리하고 싶은 충동이 드는 사람들에게 비추천^^)

2. 웹 서버 설정을 바꾸어서, 또는 다른 방법으로, JS 파일이 캐쉬를 타지 않도록 한다.
어떤 이들은 스크립트가 호출되는 페이지를 no-cache로 하면 된다고 하기도 하는데,
해보면 잘 안될것이다. 그것은 스크립트파일을 no-cache하라는게 아니라, 그 페이지를 no-cache할 뿐

JS 파일자체를 no-cache로 해야 하며
이 파일이 있는 웹서버의 설정을 바꾸면 캐쉬를 타지 않게 할 수 있다.
그러면 원천적으로 사용자의 브라우저는 항상 JS파일을 서버에서 받아오게 된다.

하지만, 웹서버 숫자가 너무 많거나, 디렉토리 설계상 너무 복잡해서 힘들거나,
시스템 엔지니어링 팀과 평소 사이가 안좋아서 고쳐주지 않는 일이 발생할 수도 있다.
(서버에서 웹캐쉬 expire 주기를 조정하는 것도 방법이다)

그럴 때 생각해보는 방법
수정전 : <script type="text/javascript" src="/js/scripts.js"></script>
수정후 : <script type="text/javascript" src="/js/scripts.php"></script>

static파일인 js파일을 asp로 확장자를 바꾸었고,
해당 웹서버가 php확장자는 모두 php 웹스크립트로 인식하도록 되어있다면
위와 같이 바꾸면 일반적인 웹서버 설정, 브라우저 설정에서 캐쉬로 저장하는 것은
브라우저가 열려있는 동안이다. 브라우저 창 모두 닫고 새창 열어서 다시 들어가면 갱신된다는 뜻.

하지만, 위의 어떤 방법이든,
static파일로서 웹서버를 혹사시키지 않고 사용자 캐쉬를 사용해야 하는 파일을
강제로 항상 갱신하게 되는 결과가 되어, 사용자가 많은 사이트의 경우
서버 증설이 필요할 정도의 비효율이 발생할 수도 있다.

그래서 비추천.

3. 그래서 어떻게 하면 좋나?
수정전 : <script type="text/javascript" src="/js/scripts.js"></script>
수정후 : <script type="text/javascript" src="/js/scripts.js?version=20071207"></script>

1번과 2번 방법이 섞인것 같아 보이는가? 하지만 아니다.
서버에는 실제로 scripts.js 파일만 올라가 있다.
또, 수정후에 뒤에 붙은 변수인 version=20071207
php에서처럼 스크립트에 입력되는 request가 아니라 그냥 구분을 위해서 붙여놓은 것이다.
해단 request 변수는 js 파일에 영향을 미치지 않는다.
이렇게 해 두면 웹 브라우저는 버전별로 다른 웹 캐쉬를 생성하게 된다.

보통의 웹 브라우저는
/js/scripts.js?version=20071207
/js/scripts.js?version=20071208 이 있을 때
script.js파일이 변경이 없다고 하더라도 서로 다른 웹 캐쉬에 저장하도록 되어 있다

이것을 이용해서, 서버에는 scripts.js파일 하나만 존재하면서
사용자 브라우저에 남아있는 캐쉬가 갱신되어야 하는지 아닌지를 적절히 수정할 수 있다.
이런 이유로 1번 방법과는 달리 스크립트 파일의 버전관리를 쉽게 할 수 있다.

또한, 스크립트가 여러 페이지에 include되어 있는 경우라고 해도
어떤 페이지에서는
<script type="text/javascript" src="/js/scripts.js?version=20071207"></script>
또 다른 페이지에서는
<script type="text/javascript" src="/js/scripts.js?version=20071208"></script>
심지어는 어떤 페이지에서는
<script type="text/javascript" src="/js/scripts.js"></script>
와 같이 제각각으로 되어있더라도 서버에 script.js파일만 있으면 에러가 발생하지 않고,

또한 개발자가 의도하지 않은 경우에는 기본적으로 항상 인터넷 캐쉬가 정상동작하게 되어,
업그레이드시 발생할 수 있는 장애위험이 줄어들게 되고,
특히 스크립트 파일에 새로운 함수를 추가한 정도로만 변형하여 다시 갱신한 경우에 유용하다.


경험적으로 알고 있던 내용인데,
검색하면 쉬이 찾을 수가 없어서, 나라도 올려두면 누군가 찾아볼 수 있기를 바라는 마음에 올려본다.

내가 적용해 둔 것은 아니지만, 실제 예를 보고 싶으면,
2007년 12월 11일 현재, http://music.cyworld.com/ 에 가서 소스보기를 하면 상단에 다음과 같은 라인을 발견할 수 있을 것이다

<link rel="stylesheet" type="text/css" href="/include/css/music4_main.css?v=071205" media="screen" />

이런식이다
Posted by 1010
반응형
<script type=text/javascript>
var UserAgent = navigator.userAgent;
var AppVersion = (((navigator.appVersion.split('; '))[1].split(' '))[1]);

// default NSelect style
var SL_ActiveIDX = null;
var SL_Focused = null;
var SL_FocusedIDX = null;
var SL_Table = " cellspacing=0 cellpadding=0 border=0";
var SL_Text = "text-indent:2px;padding-top:3px;";
var SL_IPrefix = "http://sstatic.naver.com/search/images5/";
var SL_AImage = "arrow.gif";
var SL_BImage = "blank.gif";
var SL_SBLen = 10;
var SL_SBWidth = 18;
var SL_ScrollBar = ""
    +"scrollbar-face-color:#ffffff;"
    +"scrollbar-shadow-color:999999;"
    +"scrollbar-highlight-color:#E8E8E8;"
    +"scrollbar-3dlight-color:999999;"
    +"scrollbar-darkshadow-color:#EEEEEE;"
    +"scrollbar-track-color:#999999;"
    +"scrollbar-arrow-color:#E8E8E8;";

var SL_BGColor = "#FEFFCB";
var SL_BGColor_M = "#225688";
var SL_Border = "1px solid #AFB086";
var SL_FontSize = "10pt";
var SL_FontColor = "#000000";
var SL_Height = "18px";

SList = new Array();
document.write( "<div id=NSDiv style='position:absolute;top:-100px;z-index:3;'></div>" );

// set SL Style
function setEnv( pSrc, pBG, pBM, pBD, pAI )
{
    var oEnv = new Object();
    var oSrc = createObject( pSrc );

    if( oSrc.style.width ) {
    oEnv.Width = oSrc.style.width;
    } else {
    document.all.NSDiv.innerHTML = ""
        +"<table style='top:2px;'><tr><td height=1>"+pSrc+"</td></tr></table>";
    oEnv.Width = document.all.NSDiv.scrollWidth;
    }
    if( oSrc.style.height ) oEnv.Height = oSrc.style.height; else oEnv.Height = SL_Height;
    if( oSrc.style.fontSize ) oEnv.FontSize = oSrc.style.fontSize; else oEnv.FontSize = SL_FontSize;
    if( oSrc.style.color ) oEnv.FontColor = oSrc.style.color; else oEnv.FontColor = SL_FontColor;

    if( pBG ) oEnv.BGColor = pBG;    else oEnv.BGColor = SL_BGColor;
    if( pBM ) oEnv.BGColor_M = pBM;    else oEnv.BGColor_M = SL_BGColor_M;
    if( pBD ) oEnv.Border = pBD;    else oEnv.Border = SL_Border;
    if( pAI ) oEnv.AImage = pAI;    else oEnv.AImage = SL_AImage;

    return oEnv;
}

// parameter NSelect
function NSelect( HTMLSrc, KIN, BG, BM, BD, AI )
{
    if ( UserAgent.indexOf( "MSIE" ) < 0 || AppVersion < 5 ) {
    document.write( HTMLSrc );
    return;
    } else {
    var SE = setEnv( HTMLSrc, BG, BM, BD, AI );
    var SListObj = new setNSelect( HTMLSrc, KIN, SE );
    SListObj.append();

    return SListObj;
    }
}

function appendSList()
{
    document.write("<div id=TempDiv></div>\n");
    document.all.TempDiv.appendChild( this.Table );
    document.all.TempDiv.removeNode();

    return;
}

function MouseScrollHandler() {
    var f_titleObj = SList[SL_FocusedIDX].Title;
    var f_itemObj = SList[SL_FocusedIDX].Items;
    var idx_length = f_itemObj.options.length;
    var idx_selected = f_itemObj.options.selectedIndex ;

    CancelEventHandler( window.event );

    if( window.event.wheelDelta > 0 ) {
    idx_selected = Math.max( 0, --idx_selected );
    } else {
    idx_selected = Math.min( idx_length - 1, ++idx_selected );
    }

    if( f_itemObj.options.selectedIndex != idx_selected ) {
    f_itemObj.options.selectedIndex = idx_selected;
    SList[SL_FocusedIDX].ChangeTitle();
    if( f_itemObj.onchange ) f_itemObj.onchange();
    }

    return;
}

function ActiveIDXHandler() {
    if( SL_ActiveIDX == null ) {
    for( i = 0; i < SList.length; i++ ) {
        SList[i].List.style.display = "none";
        if( i == SL_Focused ) TitleHighlightHandler( i, 1 );
        else TitleHighlightHandler( i, 0 );
    }

    if( SL_Focused == null )
        document.detachEvent( 'onclick', ActiveIDXHandler );
    }
    if( SL_Focused == null ) document.detachEvent( 'onmousewheel', MouseScrollHandler );
    else document.attachEvent( 'onmousewheel', MouseScrollHandler );

    SL_ActiveIDX = null;
    SL_Focused = null;

    return;
}

function TitleClickHandler()
{
    SL_ActiveIDX = this.entry;

    for( i = 0; i < SList.length; i++ ) {
    if( i == SL_ActiveIDX ) {
        if( SList[i].List.style.display == "block" ) {
        SList[i].List.style.display = "none";
        TitleHighlightHandler( i, 1 );
        SL_Focused = i;
        SL_FocusedIDX = i;
        } else SList[i].List.style.display = "block";
    } else {
        SList[i].List.style.display = "none";
        TitleHighlightHandler( i, 0 );
    }
    }

    document.detachEvent( 'onclick', ActiveIDXHandler );
    document.attachEvent( 'onclick', ActiveIDXHandler );

    return;
}

function TitleMouseOverHandler()
{
    this.Title.children(0).cells(2).children(0).style.filter = "";
    if( !this.kin ) this.Title.style.border = "1 solid #999999";

    return;
}

function TitleMouseOutHandler()
{
    this.Title.children(0).cells(2).children(0).style.filter = "alpha( opacity=80 );";
    if( !this.kin ) this.Title.style.border = "1 solid #C3CACD";

    return;
}

function TitleHighlightHandler( entry, t )
{
    if( t ) {
    if( this.kin )
        SList[entry].Title.children(0).cells(0).style.background = SList[entry].env.BGColor;
    else
        SList[entry].Title.children(0).cells(0).style.background = SList[entry].env.BGColor_M;
    } else {
    SList[entry].Title.children(0).cells(0).style.background = SList[entry].env.BGColor;
    }

    return;
}

function ListMouseDownHandler( f )
{
    var tObj = this.Title.children(0);
    var length = this.Items.length;

    for( i = 0; i < length; i++ ) {
    this.Items.options[i].selected = false;
    if ( i == f.idx ) {
        this.Items.options[i].selected = true;
        this.ChangeTitle();
    }
    }
    if( this.Items.onchange ) this.Items.onchange();
    if ( this.kin && ( length - 1 ) == f.idx )
    location.href = this.Items.options[f.idx].value;

    this.List.style.display = "none";

    SL_Focused = this.entry;
    SL_FocusedIDX = this.entry;

    return;
}

function ListMouseOverHandler( f )
{
    if( this.kin ) f.style.color = "#FFFFFF";
    f.style.background = this.env.BGColor_M;
    return;
}

function ListMouseOutHandler( f )
{
    f.style.color = this.env.FontColor;
    f.style.background = this.env.BGColor;
    return;
}

function CancelEventHandler( e )
{
    e.cancelBubble = true;
    e.returnValue = false;

    return;
}

function ModifyDivHandler() {
    var width = parseInt( this.Title.style.width );

    this.Items.style.width = null;
    document.all.NSDiv.innerHTML = ""
    +"<table style='top:2px;'><tr><td height=1>"+this.Items.outerHTML+"</td></tr></table>";
    var scrollWidth = parseInt( document.all.NSDiv.scrollWidth );

    if( scrollWidth > width ) {
    this.Title.style.width = scrollWidth;
    this.List.style.width = scrollWidth;
    }

    return;
}

function ChangeTitleHandler() {
    var newTitle = this.Items.options[this.Items.options.selectedIndex].innerHTML ;
    this.Title.children(0).cells(0).innerHTML = "<nobr>"+newTitle+"</nobr>";

    return;
}

function ChangeListHandler() {
    var length = this.Items.length;
    var item = "";

    var listHeight = parseInt( this.env.Height ) * Math.min( SL_SBLen, length ) + 2;
    var overflowY = ( length > SL_SBLen ) ? "scroll" : "hidden";

    this.List.innerHTML = "";
    for( i = 0; i < this.Items.options.length; i++ ) {
    item = ""
        +"<DIV idx="+i+" style='height:"+this.env.Height+";"+SL_Text+"'"
        +"  onMouseDown='SList["+this.entry+"].ListMouseDown( this );'"
        +"  onMouseOver='SList["+this.entry+"].ListMouseOver( this );'"
        +"  onMouseOut='SList["+this.entry+"].ListMouseOut( this );'>"
        +"    <nobr>"+this.Items.options[i].innerText+"</nobr>"
        +"</DIV>";
    oItem = createObject( item );
    this.List.appendChild( oItem );
    }

    this.List.style.height = listHeight;
    this.List.style.overflowY = overflowY;

    return;
}

function AddOptionHandler( sText, sValue, iIndex ) {
    var oOption = document.createElement("OPTION");
    this.Items.options.add(oOption, iIndex);

    oOption.innerText = sText;
    oOption.value = sValue;

    return;
}

function setNSelect( pSrc, pKIN, pSE )
{
    this.entry = SList.length;
    this.lower = null;
    this.src = pSrc;
    this.env = pSE;
    this.kin = pKIN;

    // NSelect Object
    this.Items;
    this.Title;
    this.List;
    this.Table;

    // Create NSelect Element
    this.ItemObj = createObject;
    this.ListObj  = createList;
    this.TitleObj = createTitle;
    this.TableObj = createSList;

    // NSelect EventHandler
    this.TitleClick = TitleClickHandler;
    this.TitleMouseOver = TitleMouseOverHandler;
    this.TitleMouseOut = TitleMouseOutHandler;
    this.ListMouseDown = ListMouseDownHandler;
    this.ListMouseOver = ListMouseOverHandler;
    this.ListMouseOut = ListMouseOutHandler;
    this.CancelEvent = CancelEventHandler;

    // NSelect Function
    this.ModifyDiv = ModifyDivHandler;
    this.ChangeTitle = ChangeTitleHandler;
    this.ChangeList = ChangeListHandler;
    this.AddOption = AddOptionHandler;

    this.append = appendSList;
    this.Table = this.TableObj();

    SList[this.entry] = this;

    return;
}

function createObject( pSrc )
{      
    oObj = new Object();
    oObj.Div = document.createElement("DIV");
    oObj.Div.insertAdjacentHTML("afterBegin", pSrc);

    return oObj.Div.children(0);
}

function createTitle()
{
    var length = this.Items.length;

    for ( i = 0; i < length; i++ ) {
    if (this.Items.options[i].selected) {
        SIName = this.Items.options[i].innerText;
        SIValue = this.Items.options[i].value;
    }
    }

    this.Title = createObject(""
    +"<DIV id=title style='width:"+this.env.Width+";overflow-X:hidden;position:relative;left:0px;top:0px;"
    +"border:"+this.env.Border+";cursor:default;background:"+this.env.BGColor+";'"
    +"    onClick='SList["+this.entry+"].TitleClick( window.event );'"
    +"    onMouseOver='SList["+this.entry+"].TitleMouseOver( window.event );'"
    +"    onMouseOut='SList["+this.entry+"].TitleMouseOut( window.event );'"
    +">"
        +"<table height="+this.env.Height+" "+SL_Table+" style='table-layout:fixed;text-overflow:hidden;'>"
    +"<tr>"
        +"    <td style='width:100%;font-size:"+this.env.FontSize+";color:"+this.env.FontColor+";"+SL_Text+"'><nobr>"+SIName+"</nobr></td>"
        +"    <td style='display:none;'></td>"
        +"    <td align=center valign=center width="+SL_SBWidth+"><img src='"+SL_IPrefix+this.env.AImage+"' border=0 style='Filter:Alpha( Opacity=80 )'></td>"
    +"</tr>"
    +"</table>"
    +"</DIV>");

    oTitle_Sub = createObject(""
    +"<img style='position:absolute;top:1px;left:0;width:"+this.env.Width+";height:"+this.env.Height+";'"
    +"    ondragstart='SList["+this.entry+"].CancelEvent( window.event );'"
    +" src='"+SL_IPrefix+SL_BImage+"'>");

    this.Title.childNodes(0).cells(1).appendChild( this.Items );
    this.Title.childNodes(0).cells(2).appendChild( oTitle_Sub );

    return;
}

function createList()
{
    var ListDiv = ""
    +"<DIV id=list style='position:absolute;z-index:2;display:none;background:"+this.env.BGColor+";"
    +"border:"+this.env.Border+";font-size:"+this.env.FontSize+";color:"+this.env.FontColor+";cursor:default;"
    +"width:"+this.env.Width+";overflow-X:hidden;"+SL_ScrollBar+"'></DIV>";

    this.List = createObject( ListDiv );
    this.ChangeList();

    return;
}

function createSList()
{
    this.Items = this.ItemObj( this.src );

    this.TitleObj();
    this.ListObj();

    var table = createObject(""
        +"<table cellspacing=0 cellpadding=1 border=0>"
        +"<tr><td></td></tr>"
        +"</table>");

    table.cells(0).appendChild( this.Title );
    table.cells(0).appendChild( this.List );

    return table;
}


</script>
<script>
var yearSelect;
var monthSelect;

var todayDate;

if (typeof(headerfooter_time_year) != "undefined")
{
    /* 오늘의 날짜를 서버 날짜로 설정 */
    todayDate = new Date(
                    headerfooter_time_year, headerfooter_time_month - 1,
                    headerfooter_time_day, headerfooter_time_hour,
                    headerfooter_time_minute, headerfooter_time_second);
}
else
    todayDate = new Date();

function memorialDay(name, month, day, solarLunar, holiday, type)
{
    this.name = name;
    this.month = month;
    this.day = day;
    this.solarLunar = solarLunar;
    this.holiday = holiday;    /* true : 빨간날 false : 안빨간날 */
    this.type = type;    /* true : real time setting */
    this.techneer = true;
}

var memorialDays = Array (
    new memorialDay("신정", 1, 1, 1, true),
    new memorialDay("", 12, 0, 2, true, true),    /* 실시간으로 정해짐 */
    new memorialDay("설날", 1, 1, 2, true),
    new memorialDay("", 1, 2, 2, true),
    new memorialDay("삼일절", 3, 1, 1, true),
    new memorialDay("식목일", 4, 5, 1, true),
    new memorialDay("석가탄신일", 4, 8, 2, true),
    new memorialDay("어린이날", 5, 5, 1, true),
    new memorialDay("현충일", 6, 6, 1, true),
    new memorialDay("제헌절", 7, 17, 1, true),
    new memorialDay("광복절", 8, 15, 1, true),
    new memorialDay("", 8, 14, 2, true),
    new memorialDay("추석", 8, 15, 2, true),
    new memorialDay("", 8, 16, 2, true),
    new memorialDay("개천절", 10, 3, 1, true),
    new memorialDay("성탄절", 12, 25, 1, true),

    new memorialDay("정월대보름", 1, 15, 2, false),
    new memorialDay("단오", 5, 5, 2, false),
    new memorialDay("국군의날", 10, 1, 1, false),
    new memorialDay("한글날", 10, 9, 1, false),
    new memorialDay("625전쟁일", 6, 25, 1, false),
    new memorialDay("삼짇날", 3, 3, 2, false),
    new memorialDay("물의날", 3, 22, 1, false),
    new memorialDay("만우절", 4, 1, 1, false),
    new memorialDay("장애인의날", 4, 20, 1, false),
    new memorialDay("과학의날", 4, 21, 1 , false),
    new memorialDay("충무공탄신일", 4, 28, 1, false),
    new memorialDay("근로자의날·법의날", 5, 1, 1, false),
    new memorialDay("어버이날", 5, 8, 1, false),
    new memorialDay("스승의날", 5, 15, 1, false),
    new memorialDay("발명의날", 5, 19, 1, false),
    new memorialDay("바다의날", 5, 31, 1, false),
    new memorialDay("환경의날", 6, 5, 1, false),
    new memorialDay("유두", 6, 15, 2, false),
    new memorialDay("칠월칠석", 7, 7, 2, false),
    new memorialDay("중양절", 9, 9, 2, false),
    new memorialDay("철도의날", 9, 18, 1, false),
    new memorialDay("소방의날", 11, 9, 1, false)
);
   

function myDate(year, month, day, leapMonth)
{
    this.year = year;
    this.month = month;
    this.day = day;
    this.leapMonth = leapMonth;
}

// 음력 데이터 (평달 - 작은달 :1,  큰달:2 )
// (윤달이 있는 달 - 평달이 작고 윤달도 작으면 :3 , 평달이 작고 윤달이 크면 : 4)
// (윤달이 있는 달 - 평달이 크고 윤달이 작으면 :5,  평달과 윤달이 모두 크면 : 6)
var lunarMonthTable = [
[2, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 5, 2, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1],   /* 1901 */
[2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2],
[1, 2, 1, 2, 3, 2, 1, 1, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1],
[2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, 2],
[1, 2, 2, 4, 1, 2, 1, 2, 1, 2, 1, 2],
[1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1],
[2, 1, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2],
[1, 5, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 1, 1, 5, 1, 2, 2, 1, 2, 2],   /* 1911 */
[2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2],
[2, 2, 1, 2, 5, 1, 2, 1, 2, 1, 1, 2],
[2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2],
[1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1],
[2, 3, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1],
[2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 5, 2, 2, 1, 2, 2],
[1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2],
[2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2],   /* 1921 */
[2, 1, 2, 2, 3, 2, 1, 1, 2, 1, 2, 2],
[1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2],
[2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1],
[2, 1, 2, 5, 2, 1, 2, 2, 1, 2, 1, 2],
[1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2],
[1, 5, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2],
[1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2],
[1, 2, 2, 1, 1, 5, 1, 2, 1, 2, 2, 1],
[2, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 1],   /* 1931 */
[2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2],
[1, 2, 2, 1, 6, 1, 2, 1, 2, 1, 1, 2],
[1, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2],
[1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 4, 1, 2, 1, 2, 1, 2, 2, 2, 1],
[2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1],
[2, 2, 1, 1, 2, 1, 4, 1, 2, 2, 1, 2],
[2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 1, 2],
[2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1],
[2, 2, 1, 2, 2, 4, 1, 1, 2, 1, 2, 1],   /* 1941 */
[2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 1, 2],
[1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2],
[1, 1, 2, 4, 1, 2, 1, 2, 2, 1, 2, 2],
[1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1, 2],
[2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2],
[2, 5, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2],
[2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2],
[2, 2, 1, 2, 1, 2, 3, 2, 1, 2, 1, 2],
[2, 1, 2, 2, 1, 2, 1, 1, 2, 1, 2, 1],
[2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2],   /* 1951 */
[1, 2, 1, 2, 4, 2, 1, 2, 1, 2, 1, 2],
[1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 2, 2],
[1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2],
[2, 1, 4, 1, 1, 2, 1, 2, 1, 2, 2, 2],
[1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2],
[2, 1, 2, 1, 2, 1, 1, 5, 2, 1, 2, 2],
[1, 2, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2],
[1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1],
[2, 1, 2, 1, 2, 5, 2, 1, 2, 1, 2, 1],
[2, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2],   /* 1961 */
[1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 2, 3, 2, 1, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2],
[1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2],
[1, 2, 5, 2, 1, 1, 2, 1, 1, 2, 2, 1],
[2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, 2],
[1, 2, 2, 1, 2, 1, 5, 2, 1, 2, 1, 2],
[1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1],
[2, 1, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2],
[1, 2, 1, 1, 5, 2, 1, 2, 2, 2, 1, 2],   /* 1971 */
[1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 1],
[2, 2, 1, 5, 1, 2, 1, 1, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2],
[2, 2, 1, 2, 1, 2, 1, 5, 2, 1, 1, 2],
[2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 1],
[2, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1],
[2, 1, 1, 2, 1, 6, 1, 2, 2, 1, 2, 1],
[2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2],
[1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2],   /* 1981 */
[2, 1, 2, 3, 2, 1, 1, 2, 2, 1, 2, 2],
[2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2],
[2, 1, 2, 2, 1, 1, 2, 1, 1, 5, 2, 2],
[1, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2],
[1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1],
[2, 1, 2, 2, 1, 5, 2, 2, 1, 2, 1, 2],
[1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2],
[1, 2, 1, 1, 5, 1, 2, 1, 2, 2, 2, 2],
[1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2],   /* 1991 */
[1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2],
[1, 2, 5, 2, 1, 2, 1, 1, 2, 1, 2, 1],
[2, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2],
[1, 2, 2, 1, 2, 2, 1, 5, 2, 1, 1, 2],
[1, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2],
[1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 1, 2, 3, 2, 2, 1, 2, 2, 2, 1],
[2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1],
[2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1],
[2, 2, 2, 3, 2, 1, 1, 2, 1, 2, 1, 2],   /* 2001 */
[2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1],
[2, 2, 1, 2, 2, 1, 2, 1, 1, 2, 1, 2],
[1, 5, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2],
[1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1],
[2, 1, 2, 1, 2, 1, 5, 2, 2, 1, 2, 2],
[1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1, 2],
[2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2],
[2, 2, 1, 1, 5, 1, 2, 1, 2, 1, 2, 2],
[2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2],
[2, 1, 2, 2, 1, 2, 1, 1, 2, 1, 2, 1],   /* 2011 */
[2, 1, 6, 2, 1, 2, 1, 1, 2, 1, 2, 1],
[2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2],
[1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 2, 2, 1, 2, 1],
[2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2],
[2, 1, 1, 2, 3, 2, 1, 2, 1, 2, 2, 2],
[1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2],
[2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2],
[2, 1, 2, 5, 2, 1, 1, 2, 1, 2, 1, 2],
[1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1],   /* 2021 */
[2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 2],
[1, 5, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1],
[2, 1, 2, 1, 1, 5, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2],
[1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2],
[1, 2, 2, 1, 5, 1, 2, 1, 1, 2, 2, 1],
[2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2],
[1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1],
[2, 1, 5, 2, 1, 2, 2, 1, 2, 1, 2, 1],   /* 2031 */
[2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 5, 2],
[1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 4, 1, 1, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2],
[2, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1],
[2, 2, 1, 2, 5, 2, 1, 2, 1, 2, 1, 1],
[2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1],
[2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2],   /* 2041 */
[1, 5, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2],
[1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2]];

/* 양력 <-> 음력 변환 함수
* type : 1 - 양력 -> 음력
*        2 - 음력 -> 양력
* leapmonth : 0 - 평달
*             1 - 윤달 (type = 2 일때만 유효)
*/
function lunarCalc(year, month, day, type, leapmonth)
{
    var solYear, solMonth, solDay;
    var lunYear, lunMonth, lunDay;
    var lunLeapMonth, lunMonthDay;    
    var i, lunIndex;

    var solMonthDay = [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    /* range check */
    if (year < 1900 || year > 2040)
    {
        alert('1900년부터 2040년까지만 지원합니다');
        return;
    }

    /* 속도 개선을 위해 기준 일자를 여러개로 한다 */
    if (year >= 2000)
    {
        /* 기준일자 양력 2000년 1월 1일 (음력 1999년 11월 25일) */
        solYear = 2000;
        solMonth = 1;
        solDay = 1;
        lunYear = 1999;
        lunMonth = 11;
        lunDay = 25;
        lunLeapMonth = 0;

        solMonthDay[1] = 29;    /* 2000 년 2월 28일 */
        lunMonthDay = 30;    /* 1999년 11월 */
    }
    else if (year >= 1970)
    {
        /* 기준일자 양력 1970년 1월 1일 (음력 1969년 11월 24일) */
        solYear = 1970;
        solMonth = 1;
        solDay = 1;
        lunYear = 1969;
        lunMonth = 11;
        lunDay = 24;
        lunLeapMonth = 0;

        solMonthDay[1] = 28;    /* 1970 년 2월 28일 */
        lunMonthDay = 30;    /* 1969년 11월 */
    }
    else if (year >= 1940)
    {
        /* 기준일자 양력 1940년 1월 1일 (음력 1939년 11월 22일) */
        solYear = 1940;
        solMonth = 1;
        solDay = 1;
        lunYear = 1939;
        lunMonth = 11;
        lunDay = 22;
        lunLeapMonth = 0;

        solMonthDay[1] = 29;    /* 1940 년 2월 28일 */
        lunMonthDay = 29;    /* 1939년 11월 */
    }
    else
    {
        /* 기준일자 양력 1900년 1월 1일 (음력 1899년 12월 1일) */
        solYear = 1900;
        solMonth = 1;
        solDay = 1;
        lunYear = 1899;
        lunMonth = 12;
        lunDay = 1;
        lunLeapMonth = 0;

        solMonthDay[1] = 28;    /* 1900 년 2월 28일 */
        lunMonthDay = 30;    /* 1899년 12월 */
    }

    lunIndex = lunYear - 1899;

    while (true)
    {
//        document.write(solYear + "-" + solMonth + "-" + solDay + "<->");
//        document.write(lunYear + "-" + lunMonth + "-" + lunDay + " " + lunLeapMonth + " " + lunMonthDay + "<br>");

        if (type == 1 &&
            year == solYear &&
            month == solMonth &&
            day == solDay)
        {
            return new myDate(lunYear, lunMonth, lunDay, lunLeapMonth);
        }   
        else if (type == 2 &&
                year == lunYear &&
                month == lunMonth &&
                day == lunDay &&
                leapmonth == lunLeapMonth)
        {
            return new myDate(solYear, solMonth, solDay, 0);
        }

        /* add a day of solar calendar */
        if (solMonth == 12 && solDay == 31)
        {
            solYear++;
            solMonth = 1;
            solDay = 1;

            /* set monthDay of Feb */
            if (solYear % 400 == 0)
                solMonthDay[1] = 29;
            else if (solYear % 100 == 0)
                solMonthDay[1] = 28;
            else if (solYear % 4 == 0)
                solMonthDay[1] = 29;
            else
                solMonthDay[1] = 28;

        }
        else if (solMonthDay[solMonth - 1] == solDay)
        {
            solMonth++;
            solDay = 1;    
        }
        else
            solDay++;


        /* add a day of lunar calendar */
        if (lunMonth == 12 &&
            ((lunarMonthTable[lunIndex][lunMonth - 1] == 1 && lunDay == 29) ||
            (lunarMonthTable[lunIndex][lunMonth - 1] == 2 && lunDay == 30)))
        {
            lunYear++;
            lunMonth = 1;
            lunDay = 1;

            lunIndex = lunYear - 1899;

            if (lunarMonthTable[lunIndex][lunMonth - 1] == 1)
                lunMonthDay = 29;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 2)
                lunMonthDay = 30;
        }
        else if (lunDay == lunMonthDay)
        {
            if (lunarMonthTable[lunIndex][lunMonth - 1] >= 3
                && lunLeapMonth == 0)
            {
                lunDay = 1;
                lunLeapMonth = 1;
            }
            else
            {
                lunMonth++;
                lunDay = 1;
                lunLeapMonth = 0;
            }

            if (lunarMonthTable[lunIndex][lunMonth - 1] == 1)
                lunMonthDay = 29;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 2)
                lunMonthDay = 30;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 3)
                lunMonthDay = 29;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 4 &&
                    lunLeapMonth == 0)
                lunMonthDay = 29;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 4 &&
                    lunLeapMonth == 1)
                lunMonthDay = 30;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 5 &&
                    lunLeapMonth == 0)
                lunMonthDay = 30;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 5 &&
                    lunLeapMonth == 1)
                    lunMonthDay = 29;
            else if (lunarMonthTable[lunIndex][lunMonth - 1] == 6)
                lunMonthDay = 30;
        }
        else
            lunDay++;
    }
}

function getWeekday(year, month, day)
{
    var weekday = Array("일", "월", "화", "수", "목", "금", "토");
    var date = new Date(year, month - 1, day);

    if (date)
        return weekday[date.getDay()];
}

function getPassDay(year, month, day)
{
    var date = new Date(year, month - 1, day);

    var interval = Math.floor((todayDate - date) / (1000 * 60 * 60 * 24) + 1);

    return interval;
}

function getDDay(year, month, day)
{
    var date = new Date(year, month - 1, day);

    var interval = Math.floor((date - todayDate) / (1000 * 60 * 60 * 24) + 1);

    return interval;
}

function getDateSpecificInterval(year, month, day, interval)
{
    var date = new Date(year, month - 1, parseInt(day) + parseInt(interval) - 1);

    return date;
}

function dayCalcDisplay(type)
{
    var startYear = parseInt(document.getElementById("startYear").value);
    var startMonth = parseInt(document.getElementById("startMonth").value);
    var startDay = parseInt(document.getElementById("startDay").value);

    if (!startYear || startYear == 0 ||
        !startMonth || startMonth == 0 ||
        !startDay || startDay == 0)
    {
        alert('기준일을 입력해주세요');
        return;
    }

    var startDate = new Date(startYear, startMonth - 1, startDay);
    var today = new Date(todayDate.getFullYear(),
                        todayDate.getMonth(), todayDate.getDate());

    switch (type)
    {
    /* 오늘은 몇일째 */
    case 1:
        if (today < startDate)
        {
            alert("기준일을 오늘보다 이전 날짜로 설정하세요");
            return;
        }

        var interval = getPassDay(startYear, startMonth, startDay);
        document.getElementById("day1").value = interval;
        break;
    /* x 일 되는 날은 */
    case 2:
        if (today < startDate)
        {
            alert("기준일을 오늘 이전 날짜로 설정하세요");
            return;
        }

        var day2 = document.getElementById("day2").value;

        if (day2 <= 0)
        {
            alert("0 보다 큰 수를 입력하세요");
            return;
        }

        var date = getDateSpecificInterval(startYear, startMonth, startDay, day2);

        document.getElementById("resultYear").value = date.getFullYear();
        document.getElementById("resultMonth").value = date.getMonth() + 1;
        document.getElementById("resultDay").value = date.getDate();

        break;
    /* D-Day */
    case 3:
        var targetYear = parseInt(document.getElementById("targetYear").value);
        var targetMonth = parseInt(document.getElementById("targetMonth").value);
        var targetDay = parseInt(document.getElementById("targetDay").value);
        var interval = getDDay(targetYear, targetMonth, targetDay);

        if (!targetYear || targetYear == 0 ||
            !targetMonth || targetMonth == 0 ||
            !targetDay || targetDay == 0)
        {
            alert('날짜를 입력해주세요');
            return;
        }

        var targetDate = new Date(targetYear, targetMonth - 1, targetDay);

        if (today > targetDate)
        {
            alert("기준일을 오늘 이후 날짜로 설정하세요");
            return;
        }

        document.getElementById("day3").value = interval;

        break;

    /* 요일 계산 */
    case 4:
        var year = parseInt(document.getElementById("weekdayYear").value);
        var month = parseInt(document.getElementById("weekdayMonth").value);
        var day = parseInt(document.getElementById("weekdayDay").value);
        var weekday = document.getElementById("weekday");

        if (!year || year == "0" ||
            !month || month == "0" ||
            !day || day == "0")
        {
            alert('날짜를 입력해 주세요');
            return;
        }
       
        if (year < 100)
        {
            alert('년도를 100년 이후로 입력해 주세요');
            return;
        }

        weekday.value = getWeekday(year, month, day) + "요일";

        break;

    /* 양력/음력 변환 */
    case 5:
        if (document.getElementById('solarLunar').value == 'solar')
        {
            var leapMonth = document.getElementById('leapMonth').checked;
            var date = lunarCalc(startYear, startMonth, startDay, 2, leapMonth);
        }
        else
        {
            var date = lunarCalc(startYear, startMonth, startDay, 1);
        }

        if (date)
        {
            document.getElementById('solarLunarYear').value = date.year;
            document.getElementById('solarLunarMonth').value =
                (date.leapMonth ? "윤" : "") + date.month;
            document.getElementById('solarLunarDay').value = date.day;
        }
        else
        {
            document.getElementById('solarLunarYear').value = "";
            document.getElementById('solarLunarMonth').value = "";
            document.getElementById('solarLunarDay').value = "";
        }

        break;
    }
}

function memorialDayCheck(solarDate, lunarDate)
{
    var i;
    var memorial;


    for (i = 0; i < memorialDays.length; i++)
    {
        if (memorialDays[i].month == solarDate.month &&
            memorialDays[i].day == solarDate.day &&
            memorialDays[i].solarLunar == 1)
            return memorialDays[i];

        if (memorialDays[i].month == lunarDate.month &&
            memorialDays[i].day == lunarDate.day &&
            memorialDays[i].solarLunar == 2 &&
            !memorialDays[i].leapMonth)
            return memorialDays[i];
    }

    return null;
}

function setStartDate(year, month, day)
{
    document.getElementById('startYear').value = year;
    document.getElementById('startMonth').value = month;
    document.getElementById('startDay').value = day;
}

function setCalendar(year, month)
{
    var i;
    var oYearSelect = document.getElementById('yearSelect');
    var oMonthSelect = document.getElementById('monthSelect');

    if (!year)
    {
        year = oYearSelect.value;
        month = oMonthSelect.value;
    }
    else
    {
        for (i = 0; i < oYearSelect.length; i++)
            if (oYearSelect[i].value == year)
            {
                oYearSelect.selectedIndex = i;
                break;
            }
   
        for (i = 0; i < oMonthSelect.length; i++)
            if (oMonthSelect[i].value == month)
            {
                oMonthSelect.selectedIndex = i;
                break;
            }
    }

    var monthDay = Array(31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

    /* set monthDay of Feb */
    if (year % 400 == 0)
        monthDay[1] = 29;
    else if (year % 100 == 0)
        monthDay[1] = 28;
    else if (year % 4 == 0)
        monthDay[1] = 29;
    else
        monthDay[1] = 28;


    /* set the day before 설날 */
    if (lunarMonthTable[year - 1 - 1899][11] == 1)
        memorialDays[1].day = 29;
    else if (lunarMonthTable[year - 1 - 1899][11] == 2)
        memorialDays[1].day = 30;


    var date = new Date(year, month - 1, 1);
    var startWeekday = date.getDay();

    /* clean all day cell */
    for (i = 0; i < 42; i++)
    {
        document.getElementById('dayCell' + i).innerHTML = "";
        document.getElementById('memoCell' + i).innerHTML = "";
    }

    /* fill day cell */        
    for (i = 0; i < monthDay[month - 1]; i ++)
    {
        var index = startWeekday + i;
        var dayHTML;
        var memoHTML;

        var solarDate = new myDate(year, month, i + 1);
        var lunarDate = lunarCalc(year, month, i + 1, 1);

        /* memorial day */
        var memorial = memorialDayCheck(solarDate, lunarDate);

        /* 쉬지않는 기념일 */
        var memorialDay = false;
        if (memorial && memorial.holiday == false)
            memorialDay = true;

        /* day print */
        dayHTML = "<span onClick=\"setStartDate(" +
                    year + ", " + month + ", " + ( i + 1 ) + ")\">" +
                    "<font id=ln2 color='COLOR' title='TITLE'>" +
                    "HIGHLIGHT_START" + ( i + 1 ) + "HIGHLIGHT_END" +
                    "</font></span>";

        /* decoration */
        if ((memorial && memorial.holiday) || index % 7 == 0)
            dayHTML = dayHTML.replace("COLOR", "#DD7403");
        else if (index % 7 == 6)
            dayHTML = dayHTML.replace("COLOR", "#3C8096");

        if (memorial)
            dayHTML = dayHTML.replace("TITLE", memorial.name);

        if (todayDate.getFullYear() == year &&
            todayDate.getMonth() + 1 == month &&
            todayDate.getDate() == i + 1)
        {
            dayHTML = dayHTML.replace("HIGHLIGHT_START", "<b>");
            dayHTML = dayHTML.replace("HIGHLIGHT_END", "</b>");
        }

        dayHTML = dayHTML.replace("TITLE", "");    
        dayHTML = dayHTML.replace("COLOR", "");
        dayHTML = dayHTML.replace("HIGHLIGHT_START", "");
        dayHTML = dayHTML.replace("HIGHLIGHT_END", "");


        document.getElementById('dayCell' + index).innerHTML = dayHTML;


        /* lunar calnedar print */
        if (lunarDate.day == 1 || lunarDate.day == 15)
        {
            memoHTML = "<img src=http://www.blueb.co.kr/SRC/javascript/image2/" + (lunarDate.month < 10 ? "0" + lunarDate.month : lunarDate.month) + (lunarDate.day < 10 ? "0" + lunarDate.day: lunarDate.day) + ".gif border=0>";

        }
        else
            memoHTML = "<table border=0 cellpadding=0 cellspacing=0><tr height=17><td></td><tr></table>";

        document.getElementById('memoCell' + index).innerHTML = memoHTML;


    }
}

function lunarMonthCheck()
{
    if (document.getElementById('solarLunar').value == "solar")
        document.getElementById('leapMonth').disabled = false;
    else
        document.getElementById('leapMonth').disabled = true;
}

var ayear = todayDate.getFullYear(), amonth = todayDate.getMonth() + 1;

function initCalendar()
{
    document.write("<table border=0 cellpadding=0 cellspacing=0 width=100%><tr><td><font id=ln6><font color=#cf4900>달력</font> : 날짜와 요일을 확인할 수 있고, 특정일을 입력하면 해당일의 요일을 알려 줍니다.</font></td></tr><tr><td></td><td height=5 nowrap></td></tr></table>");
    document.write("");
    document.write("<table width=100% border=0 cellpadding=0 cellspacing=0>");
    document.write("<tr>");
    document.write("    ");
    document.write("    <td nowrap valign=top>");
    document.write("    <!--------달력 들어갈곳----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=220>");
    document.write("    <tr>");
    document.write("        <td>");
    document.write("        <table border=0 cellpadding=0 cellspacing=0 width=100%>");
    document.write("        <tr>");
    document.write("            <td rowspan=2 width=1 nowrap bgcolor=C3CACD></td>");
    document.write("            <td width=100% bgcolor=C3CACD></td>");
    document.write("            <td rowspan=2 width=1 nowrap bgcolor=A6ACAE></td>");
    document.write("            <td rowspan=2 width=2 height=2 nowrap bgcolor=ffffff></td>");
    document.write("        </tr>");
    document.write("        <tr>");
    document.write("            <td height=1 bgcolor=F1F6F8></td>");
    document.write("        </tr>");
    document.write("        <tr>");
    document.write("            <td width=1 nowrap bgcolor=C3CACD></td>");
    document.write("            <td width=100% bgcolor=F1F6F8 height=32 align=center>");
    document.write("            <!-----날짜 넣는 곳--->");
    document.write("            <table width=95% border=0 cellpadding=0 cellspacing=0>");
    document.write("            <tr>");
    document.write("                <td nowrap>");
    document.write("                <!--------년도---------->");
    document.write("                <table border=0 cellpadding=0 cellspacing=0>");
    document.write("                <tr>");
    document.write("                    <td>");

    var selectStr;
    var i, j;

    selectStr = "<select id=yearSelect style='width:80;font-size:9pt;' onChange='setCalendar()'>\n";

    for (i = 1900; i <= 2030; i++)
        selectStr += "<option value='" + i + "'>" + i + " 년</option>";

    selectStr += "</select>";

    document.write(selectStr);

    document.write("                    </td>");
    document.write("                </tr>");
    document.write("                </table>");
    document.write("                <!--------년도---------->            ");

    document.write("                </td>");
    document.write("                <td width=10 nowrap></td>");
    document.write("                <td nowrap>");
    document.write("                <!--------월---------->");
    document.write("                <table border=0 cellpadding=0 cellspacing=0>");
    document.write("                <tr>");
    document.write("                    <td>");


    selectStr = "<select id=monthSelect style='width:60;font-size:9pt' id=monthSelect onChange='setCalendar()'>\n";

    for (i = 1; i <= 12; i++)
        selectStr += "<option value='" + i + "'>" + i + " 월</option>";

    selectStr += "</select>";

    document.write(selectStr);

    document.write("                    </td>");
    document.write("                </tr>");
    document.write("                </table>");
    document.write("                <!--------월---------->        ");
    document.write("                </td>");
    document.write("                <td width=100%></td>");
    document.write("            </tr>");
    document.write("            </table>");
    document.write("            <!-----날짜 넣는 곳--->");
    document.write("            </td>");
    document.write("            <td width=1 nowrap bgcolor=A6ACAE></td>");
    document.write("            <td width=2 nowrap bgcolor=E0E4E6></td>");
    document.write("        </tr>");
    document.write("        <tr>");
    document.write("            <td width=1 nowrap bgcolor=C3CACD></td>");
    document.write("            <td width=100% height=1 background=http://www.blueb.co.kr/SRC/javascript/image2/date_line01.gif border=0></td>");
    document.write("            <td width=1 nowrap bgcolor=A6ACAE></td>");
    document.write("            <td width=2 nowrap bgcolor=E0E4E6></td>");
    document.write("        </tr>");
    document.write("        <tr>");
    document.write("            <td width=1 nowrap bgcolor=C3CACD></td>");
    document.write("            <td width=100% bgcolor=ffffff align=center>");
    document.write("            <!----달력 넣는곳------>");
    document.write("            <table border=0 cellpadding=0 cellspacing=0 width=100%>");

    document.write("            <tr>");
    document.write("                <td width=15% align=center><font id=ln6 color=DD7403>일</font></td>");
    document.write("                <td width=14% align=center><font id=ln6>월</font></td>");
    document.write("                <td width=14% align=center><font id=ln6>화</font></td>");
    document.write("                <td width=14% align=center><font id=ln6>수</font></td>");
    document.write("                <td width=14% align=center><font id=ln6>목</font></td>");
    document.write("                <td width=14% align=center><font id=ln6>금</font></td>");
    document.write("                <td width=15% align=center><font id=ln6 color=3C8096>토</font></td>");
    document.write("            </tr>");
    document.write("            <tr><td colspan=7 height=7 nowrap></td></tr>");


    for (i = 0; i < 6; i++)
    {
        document.write("<tr>");

        for (j = 0; j < 7; j++)
            document.write("<td align=center id='dayCell" + ( i * 7 + j )+ "'></td>");
        document.write("</tr>");

        document.write("<tr nowrap>");

        for (j = 0; j < 7; j++)
            document.write("<td align=center valign=top id='memoCell" + ( i * 7 + j ) + "'></td>");

        document.write("</tr>");
    }

    /////////////////////////////////////////////////////////////////////////// by Choi, Sungjoon

    if (typeof(rege_0_1) != "undefined" && 1900 <= rege_0_1 && rege_0_1 <= 2030)
    {
        ayear = rege_0_1;
        amonth = 1;
    }

    if (typeof(rege_0_2) != "undefined" && 1 <= rege_0_2 && rege_0_2 <= 12)
        amonth = rege_0_2;

    ///////////////////////////////////////////////////////////////////////////

    document.write("            <tr><td colspan=7 height=7 nowrap></td></tr>");
    document.write("            </table>        ");
    document.write("            <!----달력 넣는곳------>");
    document.write("            </td>");
    document.write("            <td width=1 nowrap bgcolor=A6ACAE></td>");
    document.write("            <td width=2 nowrap bgcolor=E0E4E6></td>");
    document.write("        </tr>");
    document.write("        </table>");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    <tr>");
    document.write("        <td>");
    document.write("        <table border=0 cellpadding=0 cellspacing=0 width=100%>");
    document.write("        <tr>");
    document.write("            <td colspan=2 width=100% height=1 bgcolor=A6ACAE></td>");
    document.write("            <td width=2 nowrap bgcolor=E0E4E6></td>");
    document.write("        </tr>");
    document.write("        <tr>");
    document.write("            <td width=2 height=2 nowrap></td>");
    document.write("            <td width=100% bgcolor=E0E4E6></td>");
    document.write("            <td width=2 nowrap bgcolor=E0E4E6></td>");
    document.write("        </tr>");
    document.write("        </table>    ");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    </table>");
    document.write("    <!--------달력 들어갈곳----->    ");
    document.write("    </td>");
    document.write("    <td width=20 nowrap></td>");

    document.write("    <td width=100% valign=top>");
    document.write("    <!--------날짜 계산하는 곳 ----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420>");
    document.write("    <tr>");
    document.write("        <td bgcolor=D8E4C5>");
    document.write("        <table border=0 cellpadding=3 cellspacing=1 width=100%>");
    document.write("        <tr>");
    document.write("            <td bgcolor=F5F9EF>");
    document.write("            <font id=ln6>");
    document.write("            &nbsp;<font color=275361><b>기준일</b></font>&nbsp; &nbsp;");
    document.write("            <input type=text size=7 maxlength=4 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=startYear value='" + todayDate.getFullYear() + "'> 년 &nbsp;");
    document.write("            <input type=text size=3 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=startMonth value='" + ( todayDate.getMonth() + 1 ) + "'> 월 &nbsp;");
    document.write("            <input type=text size=3 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=startDay value='" + todayDate.getDate() + "'> 일");
    document.write("            </font>");
    document.write("            &nbsp;<input type=checkbox id=leapMonth disabled><font id=ln6>윤달</font>");
    document.write("            </td>");
    document.write("        </tr>");
    document.write("        </table>");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    </table>");
    document.write("    <font size=1><br></font>");
    document.write("");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=1 background=http://www.blueb.co.kr/SRC/javascript/image2/date_line01.gif border=0></td></tr></table>");
    document.write("    ");
    document.write("    <!----양력/음력 계산기----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=7 nowrap></td></tr><tr><td nowrap><font id=ln6><font color=#cf4900>음력/양력 변환</font> : 기준일의 날짜를 음력 혹은 양력으로 변환해 줍니다.</font></td></tr><tr><td height=2 nowrap></td></tr></table>");
    document.write("        ");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420>");
    document.write("    <tr>");
    document.write("        <td>");
    document.write("        <table border=0 cellpadding=0 cellspacing=0>");
    document.write("        <tr>");
    document.write("            <td valign=top>");
    document.write("            <!--------양력/음력--------->");
    document.write("            <table border=0 cellpadding=0 cellspacing=0>");
    document.write("            <tr>");
    document.write("                <td>");
   
    selectStr = "<select style='width:50;font-size:9pt' id='solarLunar' onChange='lunarMonthCheck()'>\n";
    selectStr += "<option value='solar'>양력</option>";
    selectStr += "<option value='lunar' selected>음력</option>";

    NSelect(selectStr, 0, '#FFFFFF', '#EDEFF0', '1 solid #C3CACD', 'arrow_etcsrch.gif');


    document.write("                </td>");
    document.write("            </tr>");
    document.write("            </table>");
    document.write("            <!--------양력/음력--------->");
    document.write("            </td>");
    document.write("            <td valign=bottom><font id=ln6>으로</font></td>");
    document.write("            <td width=10 nowrap></td>");
    document.write("            <td>");
    document.write("            <font id=ln6>");
    document.write("            <a href=javascript:void(0) onClick='dayCalcDisplay(5)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/met_but01.gif border=0 align=absmiddle></a>");
    document.write("            <a href=javascript:void(0) onClick='dayCalcDisplay(5)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_arrow.gif border=0 align=absmiddle></a>");
    document.write("            <input type=text size=5 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=solarLunarYear readonly> 년&nbsp;");
    document.write("            <input type=text size=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=solarLunarMonth readonly> 월&nbsp;");
    document.write("            <input type=text size=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=solarLunarDay readonly> 일");
    document.write("            </font>");
    document.write("            </td>");
    document.write("        </tr>");
    document.write("        </table>");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    <tr><td height=7 nowrap></td></tr>");
    document.write("    </table>");
    document.write("    <!----양력/음력 계산기----->");
    document.write("    ");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=1 background=http://www.blueb.co.kr/SRC/javascript/image2/date_line01.gif border=0></td></tr></table>    ");
    document.write("    ");
    document.write("    <!----특정일 계산 ----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=7 nowrap></td></tr><tr><td><font id=ln6><font color=#cf4900>특정일 계산기</font> : 기준일로부터 오늘까지의 날짜를 계산합니다.</font></td></tr><tr><td height=2 nowrap></td></tr></table>");
    document.write("    ");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=97%>");
    document.write("    <tr><td colspan=2 height=3 nowrap></td></tr>");
    document.write("    <tr>");
    document.write("        <td nowrap valign=top><font id=ln6>오늘은 몇일째?</font></td>");
    document.write("        <td width=100%>");
    document.write("        <font id=ln6>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(1)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_but02.gif border=0 align=absmiddle></a>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(1)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_arrow.gif border=0 align=absmiddle></a>");
    document.write("        <input type=text size=6 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=day1 readonly> 일째");
    document.write("        </font>");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    <tr><td colspan=2 height=5 nowrap></td></tr>");
    document.write("    <tr>");
    document.write("        <td nowrap valign=top>");
    document.write("        <font id=ln6>");
    document.write("        <input type=text size=3 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=day2> 일 되는 날은?&nbsp; &nbsp;");
    document.write("        </font>");
    document.write("        </td>");
    document.write("        <td nowrap>");
    document.write("        <font id=ln6>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(2)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_but02.gif border=0 align=absmiddle></a>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(2)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_arrow.gif border=0 align=absmiddle></a>");
    document.write("        <input type=text size=5 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=resultYear readonly> 년&nbsp;");
    document.write("        <input type=text size=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=resultMonth readonly> 월&nbsp;");
    document.write("        <input type=text size=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=resultDay readonly> 일");
    document.write("        </font>");
    document.write("        </td>");
    document.write("    </tr>    ");
    document.write("    <tr><td colspan=2 height=7 nowrap></td></tr>");
    document.write("    </table>");
    document.write("    ");
    document.write("    <!----특정일 계산 ----->");
    document.write("");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=1 background=http://www.blueb.co.kr/SRC/javascript/image2/date_line01.gif border=0></td></tr></table>    ");
    document.write("    ");
    document.write("    <!----요일 계산기----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=7 nowrap></td></tr><tr><td><font id=ln6><font color=#cf4900>요일 계산기</font> : 특정일의 요일을 알려 줍니다.</font></td></tr><tr><td height=2 nowrap></td></tr></table>");
    document.write("");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420>");
    document.write("    <tr>");
    document.write("        <td>");
    document.write("        <font id=ln6>");
    document.write("        <input type=text size=5 maxlength=4 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=weekdayYear> 년&nbsp;");
    document.write("        <input type=text size=2 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=weekdayMonth> 월&nbsp;");
    document.write("        <input type=text size=2 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=weekdayDay> 일 의 요일은?&nbsp;");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(4)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_but01.gif border=0 align=absmiddle></a>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(4)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_arrow.gif border=0 align=absmiddle></a>");
    document.write("        <input type=text size=6 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=weekday readonly>");
    document.write("        </font></td>");
    document.write("    </tr>");
    document.write("    <tr><td height=7 nowrap></td></tr>");
    document.write("    </table>");
    document.write("    <!----요일 계산기----->");
    document.write("");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=1 background=http://www.blueb.co.kr/SRC/javascript/image2/date_line01.gif border=0></td></tr></table>");
    document.write("");
    document.write("    <!----D-Day 계산기----->");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420><tr><td height=7 nowrap></td></tr><tr><td><font id=ln6><font color=#cf4900>D-Day 계산기</font> : 오늘부터 특정일까지 남은 날짜를 계산합니다.</font></td></tr><tr><td height=2 nowrap></td></tr></table>");
    document.write("    ");
    document.write("    <table border=0 cellpadding=0 cellspacing=0 width=420>");
    document.write("    <tr>");
    document.write("        <td nowrap>");
    document.write("        <font id=ln6>");
    document.write("        <input type=text size=5 maxlength=4 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=targetYear> 년&nbsp;");
    document.write("        <input type=text size=2 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=targetMonth> 월&nbsp;");
    document.write("        <input type=text size=2 maxlength=2 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=targetDay> 일 까지 남은 날은?&nbsp;");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(3)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_but02.gif border=0 align=absmiddle></a>");
    document.write("        <a href=javascript:void(0) onClick='dayCalcDisplay(3)'><img src=http://www.blueb.co.kr/SRC/javascript/image2/date_arrow.gif border=0 align=absmiddle></a>");
    document.write("        <input type=text size=6 style='height:21; border:1 solid C3CACD; padding:3 0 0 2;' id=day3 readonly> 일");
    document.write("        </font>");
    document.write("        </td>");
    document.write("    </tr>");
    document.write("    <tr><td height=7 nowrap></td></tr>");
    document.write("    </table>    ");
    document.write("    <!----D-Day 계산기----->");
    document.write("    ");
    document.write("    <!--------날짜 계산하는 곳 ----->");
    document.write("    </td>");
    document.write("</tr>");
    document.write("</table>");
}

initCalendar();
setCalendar(ayear, amonth);
</script>
Posted by 1010
98..Etc/Error Log2008. 7. 7. 16:14
반응형

code too large for try statement 라는 에러..


이 에러를 난.. jsp페이지를 만들다가 봤다..


헐.. jsp 페이지가 64K를 넘으면 난다고 하는데.. 훔..


해결??


Weblogic.xml에..

<jsp-descriptor>.... </jsp-descriptor>

안에..


<jsp-param>
  <param-name>noTryBlocks</param-name>
  <param-value>true</param-value>
 </jsp-param>


를 넣어야 한다.

Posted by 1010
61.Linux2008. 7. 7. 15:45
반응형
yum install openssl-devel


wget http://dag.wieers.com/packages/lzo/lzo-1.08-4.2.el4.rf.i386.rpm
 wget http://dag.wieers.com/packages/lzo/lzo-devel-1.08-4.2.el4.rf.i386.rpm
 rpm -Uvh lzo*

 ## open vpn 받아서 rpm 만들고 설치
 wget http://openvpn.net/release/openvpn-2.0.7.tar.gz
 rpmbuild -tb openvpn-2.0.7.tar.gz
 rpm -Uvh  /usr/src/redhat/RPMS/i386/openvpn-2.0.7-1.i386.rpm

3. 인증서 생성 - 서버
  인증성 생성은 필수이다. 다음과 같이 생성한다.

 1) CA 생성 (상위 인증기관)
   cd /usr/share/doc/openvpn-2.0.7/easy-rsa/
   #vars 파일을 열어서 맨 마지막 줄을 수정한다.!!
     export KEY_COUNTRY=KR
     export KEY_PROVINCE=NA
     export KEY_CITY=BUSAN
     export KEY_ORG="superuser.co.kr"
     export KEY_EMAIL="doly@suidc.com"
  ####################################
  #인증서 생성시 마다 넣는게 귀찮아서 이렇게 정의 하는 것이니 하지 않아도 무관^^;
  . ./vars
  ## 위 명령은 , vars내용을 include한다는 명령이다.
  ./clean-all
  ## 기존에 생성된 것이 있으면 모두 삭제한다.
  ./build-ca
  ## CA 인증서를 생성한다.
  ## 이렇게하면 keys라는 폴더에 ca.key(개인키), ca.crt(공개인증서)가 생성된것을 확인한다.
  ## ca.crt파일은 모든 클라이언트에 배포. ca.key는 서버만 가지고 있음.

 2) 서버키 생성 (서버에 사용될 인증서 및 개인키)
   ./build-key-server server
  ## 뭐 많이 물어보는데 대충 대답하고 , y를 누른다.
Common Name 을 물어 오면 'server'를 입력한다. 그리고 다음 두개의 질문에 Yes 라고 답한다.
Sign the certificate? y/n
1 out of 1 certificate requests certified, commit? y/n
  # keys 디렉토리에 server.crt  server.key 등이 생긴것을 확인할수 있다.
  # 이 키들은 CA에 의해 사인된 인증서이다.
  # server.crt, server.key 모두 서버에만 사용


 3) 클라이언트키 생성 (클라이언트에 사용될 인증서)
   ./build-key client
  ## 뭐 많이 물어보는데 대충 대답하고 , y를 누른다.
Common Name 을 물어 오면 'client'를 입력한다. 그리고 다음 두개의 질문에 Yes 라고 답한다.
Sign the certificate? y/n
1 out of 1 certificate requests certified, commit? y/n
  # keys 디렉토리에 client.crt client.key 를 볼 수 있다.
  # 이 키들은 CA에 의해 사인된 인증서이다.
  # client.key, client.crt 모두 클라이언트에만 사용됨.

 4) Diffie Hellman 파라메터 생성(암호화에 필요한 놈)
  ./build-dh
  # keys디렉토리에 dh1024.pem 파일이 생긴것을 확인할 수 있다.
  # dh1024.pem은 서버에만 가지고 있는다.

 5) 클라이 언트용 파일 복사 및 보관
    mkdir -p /root/client-keys
    cp keys/ca.crt keys/client.* /root/client-keys
    cd /root
    zip client-keys.zip client-keys/*


4. 설정파일(server.conf)파일 복사 및 편집 - 서버
 1) 설정파일 및 키 복사
    cd /usr/share/doc/openvpn-2.0.7/
    cp sample-config-files/server.conf /etc/openvpn/
    cp easy-rsa/keys/server.* /etc/openvpn/
    cp easy-rsa/keys/dh1024.pem /etc/openvpn/  
    cp easy-rsa/keys/ca.* /etc/openvpn/
   
 2) 설정파일 편집.(/etc/openvpn/server.conf)
    server 10.1.1.0 255.255.255.0
    client-to-client
    duplicate-cn
    max-clients 100
    plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login

   ## 설명
   # sever 네트웍 설정은 10.1.1.0으로 한다.
   # client-to-client : 클라이언트 끼리 통신 가능하게
   # duplicate-cn : client인증서 하나로 여러대의 PC에서 사용할 수 있게한다.
   # max-clients 100 : 연결수를 100으로 제한한다.
   # plugin .... : user/pass인증을 받는다. (시스템 계정)


 3) G/W로 VPN서버를 쓰기 때문에 인터넷 공유 설정.
    echo 'iptables -t nat -A POSTROUTING -s 10.1.1.0/24 -o eth0 -j MASQUERADE' >> /etc/rc.d/rc.local
    iptables -t nat -A POSTROUTING -s 10.1.1.0/24 -o eth0 -j MASQUERADE
    iptables -t nat -L


참고자료 > VPN 구축 (via OpenVPN) 글쓴이 : doly™     날짜 : 07-03-06 15:56    

Posted by 1010
61.Linux2008. 7. 7. 15:43
반응형

리눅스 서버를 이용한 자바 개발 환경 구축

이 문서는 초보자도 쉽게 리눅스 서버를 이용한 자바 개발 환경을 구축할 수 있도록 돕기 위해서 작성되었습니다.
이 문서의 수정 및 재 배포가 가능하며 상업적 용도로 사용할 수 없습니다.

단 수정 및 재 배포 시 작성자의 이름 및 출처를 꼭 명시하기 바랍니다.

작성자 : 최광호(하이버즈)

작성일 : 2007년 07월 24일 수요일

최종 수정일 : 2008년 03월 28일 금요일

이메일 : hibuz@하이버즈.com

 

설치할 항목들

  • 리눅스서버 : SULinux 1.5 Server

    • 커널 : kernel-2.6.9
  • DB서버 :  MySQL 4.1
  • WAS 서버 :  JBoss 4.2.2
  • 형상관리 서버 :  Subversion 1.1.4
  • 이슈관리 서버 :  Trac 0.10.4

  1. 리눅스 서버운영 환경 구축

    1. 설치 순서 : 리눅스 -> JDK -> JBoss -> MySQL -> Subversion -> Trac
    2. 리눅스 설치
      로고

      • 버전 : SULinux 1.5 Server
      • 다운로드 : http://www.sulinux.net/1.5/download.php
      • 파일명 : SULinux-Server-1.5-i386.iso
      • 설치 후 yum repository site를 centos 4 로 변경합니다. (subversion 관련 패키지가 SULinux repository에는 없으므로)

        ]# vi /etc/yum.repos.d/SULinux-Base.repo
        1. ### SULinux-Base.repo 파일 ###[base]
          name=SULinux-$releasever - Base
          #mirrorlist=http://www.sulinux.net/mirrorlist/?release=1&arch=i386&repo=os  <- 주석처리
          baseurl=http://mirror.centos.org/centos/4/os/$basearch/ <- 추가
          gpgkey=http://mirror.centos.org/centos/4/os/$basearch/RPM-GPG-KEY-centos4 <- 추가
          #baseurl=http://ftp.sulinux.net/pub/SULinux/1/os/$basearch/
          gpgcheck=0
          #released updates
          [update]
          name=SULinux-$releasever - Updates
          #mirrorlist=http://www.sulinux.net/mirrorlist/?release=1&arch=i386&repo=updates <- 주석처리
          baseurl=http://mirror.centos.org/centos/4/updates/$basearch/  <- 추가
          #baseurl=http://ftp.sulinux.net/pub/SULinux/1/updates/$basearch/
          gpgcheck=0
      • 최신으로 update 한다

        ]# yum update
      • 시스템 점검 결과 보고와 관련된 /root/bin/sbin/system_check_port 스크립트 파일의 버그를 고친다.

        파일 중간에 lsof 명령 실행 부분을 절대경로로 바꿔준다.

        ]# vi /root/bin/sbin/system_check_port
        1. ### system_check_port 파일 ###
        2. lsof -P -> /usr/sbin/lsof -P
      • 참고: http://www.sulinux.net/bbs/board.php?bo_table=qna_1_0&wr_id=2707&page=9
      • FTP 를 이용해 자료를 업로드할 계정을 생성한다. (각자 원하는 사용자 계정 추가)

        ]# adduser hibuz

        ]# passwd hibuz

    3. JDK 설치
      Java

      • 버전 :  Java(TM) SE Development Kit 6 Update 5
      • 다운로드 : http://java.sun.com/javase/downloads/index.jsp
      • 파일명 : jdk-6u5-linux-i586.bin
      • 이전에 생성한 계정으로 다운로드 받은 파일을 ftp로 서버에 업로드 합니다. ex) user: hibuz
        설치할 경로로 파일을 옮깁니다. 여기서는 /usr/local 에 JDK를 설치할 것 입니다.

        ]# mv /home/hibuz/jdk-6u5-linux-i586.bin /usr/local/
      • 퍼미션을 확인하여 파일이 실행 가능하도록 권한을 설정합니다.

        ]# cd /usr/local/

        ]# chmod 755 jdk-6u5-linux-i586.bin

      • 파일을 실행시켜서 JDK를 설치합니다.

        ]# ./jdk-6u5-linux-i586.bin
      • 먼저 화면에 사용권 계약이 출력됩니다. [스페이스]를 누르면 페이지 단위로 이동합니다.
        끝까지 이동해서 Do you agree to the above license terms? [yes or no] 메시지가 보이면 y 또는 yes를 입력합니다.
        압축이 풀리면서 JDK가 설치되고 나면 설치 파일은 삭제 합니다.

        ]#  rm jdk-6u5-linux-i586.bin
      • /usr/local/jdk 디렉토리로도 접근 할 수 있도록 절대경로로 symbolic link 를 만듭니다.

        ]# ln -s /usr/local/jdk1.6.0_05 /usr/local/jdk
      • etc/profile 파일을 에디터로 열어서 파일 첫부분에 JAVA_HOME을 추가 합니다.

        ]# vi /etc/profile
        1. ### profile 파일 ###
        2. JAVA_HOME=/usr/local/jdk
        3. PATH=$PATH:$JAVA_HOME/bin
        4. export JAVA_HOME
      • 변경된 설정을 시스템에 적용시킵니다.

        ]# source /etc/profile
      • 임의의 위치에서 실행시켜 봤을때 java version "1.6.0_05" 메시지가 나오면 성공적으로 설치된 것입니다.

        ]# java -version
    4. JBoss 설치
      JBoss - A Division of Red Hat

      • 버전 : JBoss application server 4.2.2
      • 다운로드 : http://labs.jboss.com/jbossas/downloads
      • 파일명 : jboss-4.2.2.GA.zip
      • 다운로드 받은 파일을 ftp로 서버에 업로드 한 후 /usr/local/ 디렉토리로 파일을 옮깁니다.

        ]# mv /home/hibuz/jboss-4.2.2.GA.zip /usr/local/
      • 다운받을 주소를 알고 있을 경우 wget 명령어로 받아도 됩니다.

      • 압축을 해제한 후 압축 파일은 삭제 합니다.

        ]# cd /usr/local
        ]# unzip jboss-4.2.2.GA.zip

        ]# rm jboss-4.2.2.GA.zip

      • jboss_init_redhat.sh 파일을 수정한다.

        ]# vi /usr/local/jboss-4.2.2.GA/bin/jboss_init_redhat.sh
        1. ### jboss_init_redhat.sh 파일 ###
        2. JBOSS_USER=${JBOSS_USER:-"root"}
        3. JBOSSSH=${JBOSSSH:-"$JBOSS_HOME/bin/run.sh -c $JBOSS_CONF -b 0.0.0.0"}
      • jboss-4.2.2.GA 디렉토리를 jboss 디렉토리로도 접근 할 수 있도록 절대경로로 symbolic link 를 만듭니다.

        ]# ln -s /usr/local/jboss-4.2.2.GA /usr/local/jboss
      • 부팅시 JBoss가 자동으로 실행되도록 절대경로로 symbolic link 를 만듭니다.

        ]# ln -s /usr/local/jboss/bin/jboss_init_redhat.sh /etc/rc.d/init.d/jbossd

        ]# cd /etc/rc.d/rc0.d

        ]# ln -s ../init.d/jbossd K72jbossd

        ]# cd /etc/rc.d/rc3.d
        ]# ln -s ../init.d/jbossd S72jbossd

      • 시스템 체크 관련 설정 파일의 실행 프로세스 항목에 run.sh를 추가한다.

        ]# vi /root/conf/su_util.cfg
        1. ### su_util.cfg 파일 ###
        2. ############## 시스템 채크 관련 설정 ###################
        3. # 관리자 메일주소 (여러명에게 보낼시 공백으로 띄워주세요)# 시스템 점검 결과를 보낼 메일 주소
          system_mailto = test@hibuz.com <- 메일 주소를 적어주면 시스템 점검 결과가 하루마다 메일로 전달된다.
          proclist = run.sh <- 추가
      • JBoss 를 시작한다.

        ]# service jbossd start
      • 방화벽에서 8080 포트를 열어준다. (방화벽 설정 > 사용자 설정 > 그 외의 포트 > 맨끝에 8080:tcp 추가 > 확인 > 확인 > 종료)

        ]# setup
      • 웹 브라우져로 확인한다.

        http://(설치한 IP주소 또는 도메인):8080/
    5. MySQL 설치
      MySQL

      • 버전 : MySQL 4.1.20
      • yum update 명령으로 MySQL을 설치한다. (의존성을 가진 패키지들을 같이 설치한다.)

        ]# yum install mysql-server
      • 설치후 서비스를 자동으로 시작할 수 있도록 선택해준다. (시스템 서비스 > mysqld 선택 > 확인 > 종료)
        ]# setup
      • mysql을 시작한다.

        ]# service mysqld start
      • 초기 설치시 root 암호가 지정되어 있지 않아 보안상 위험하므로 비밀번호를 설정합니다.

        ]# mysqladmin -uroot -p password testpw <- 원하는 비밀번호 설정
      • mysql DB에 접속해 새로운 원격 사용자를 추가합니다. (호스트를 %로 하면 어떤 곳에서든 접속만 할 수 있음)

        ]# mysql -uroot -ptestpw mysql
        mysql> insert into user (host, user, password) values ('%', 'hibuz', password('testpw'));  <- 원하는 아이이 및 비밀번호 설정
      • 새로운 DB를 생성해서 권한을 주고 테스트용 테이블을 생성합니다.
        (호스트를 %로 해서 어떤 곳에서든 접속할 수 있고 hibuz 사용자에게 hibuzdb의 모든권한 부여)

        mysql> create database hibuzdb; <- 원하는 DB 생성

        mysql> insert into db values('%','hibuzdb','hibuz', 'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');

        mysql> use hibuzdb;
        mysql> create table test_table (
            ->   id int not null auto_increment primary key,
            ->   foo varchar(25),
            ->   bar int);
        mysql> insert into test_table values(null, 'hello', 12345);
        mysql> flush privileges; <- MySQL 서버 재시작 없이 사용자 정보가 반영되도록 하기 위함

    6. Subversion 설치
      Subversion

      • 버전 : Subversion 1.1.4
      • yum install 명령으로 subversion을 설치한다. (아파치 웹서버를 포함한 의존성을 가진 패키지들이 같이 설치된다.)

        ]# yum install mod_dav_svn
      • https 프로토콜을 사용할 수 있도록 mod_ssl 패키지도 같이 설치한다.

        ]# yum install mod_ssl
      • 설치후 서비스를 자동으로 시작할 수 있도록 선택해준다. (시스템 서비스 > httpd 선택 > 확인 > 종료)

        ]# setup
      • SULinux 의 환경설정 파일을 고쳐준다.

        ]# vi /root/conf/su_util.cfg
        1. ### su_util.cfg 파일 ###
        2. ################# mysql 관련 셋팅 ###################
          # mysql bin 디렉토리를 설정합니다. 예) mysql_bin_dir = /usr/local/mysql/bin
          mysql_bin_dir = /usr/bin
        3. ## mysql 데이터 디렉토리
          mysql_data_dir = /var/lib/mysql
        4. ################# 아파치 관련 셋팅 ################
          ## apache bin 샐행 스크립트 위치 예) /usr/local/apache/bin/apachectl
          apache_bin_ctl = /etc/rc.d/init.d/httpd
        5. ## apache conf 디렉토리 예) /usr/local/apache/conf
          apache_conf_dir = /etc/httpd/conf
        6. ## htpasswd 명령어 위치
          htpasswd_path = /usr/bin/htpasswd
        7. ## 결과를 생성할 웹 절대 경로
        8. web_dir = /var/www/html/ssu
        9. ## 웹인증 아이디 (웹인증을 yes 했을때만 사용)
          htuser = admin <- 웹 시스템 점검 페이지(http://설치 IP/ssu) 에 접근시 계정 설정 [system_check 명령을 실행시키면 확인가능]
          ## 웹인증 패스워드 (웹인증을 yes 했을때만 사용)
          htpass = sulinux
      • 설정 변경후 이를 적용해 준다.

        ]# system_check_init --all
      • 설치 후 svnadmin 명령어로 파일 시스템을 이용한 저장소를 만들고 권한을 변경합니다.

        ]# mkdir /home/svnroot

        ]# cd /home/svnroot

        ]# svnadmin create repos1 --fs-type fsfs

        ]# chown -R apache.apache repos1

      • svn 사용자 계정을 만든다.

        ]# cd /etc/httpd/conf

        ]# htpasswd -c passwd hibuz  <- https프로토콜을 이용해 svn을 이용할 별도의 계정, 처음 추가할 때만 -c옵션을 사용하고 다음 계정을 추가할 때는 -c 옵션을 빼고 사용한다.

        NewPassword:

        Re-type new password:

      • https 프로토콜로 svn에 접근할 수 있도록 환경을 설정합니다.
        /etc/httpd/conf.d/ssl.conf 파일의 마지막 부분의 </VirtualHost> 태그 안쪽에 <Location>을 다음과 같이 추가 합니다.
        접근은 아무나 가능하고 commit만 사용자 계정이 필요한 경우임

        ]# vi /etc/httpd/conf.d/ssl.conf
        1. ### ssl.conf 파일 ###
        2. <Location /svnroot>
            DAV svn
        3.   SVNParentPath /home/svnroot
        4.     AuthType Basic
        5.     AuthName "Hibuz's Repository"
        6.     AuthUserFile /etc/httpd/conf/passwd
        7.         <LimitExcept GET PROPFIND OPTIONS REPORT>
        8.             Require valid-user
        9.         </LimitExcept>
        10. </Location>
        11. </VitualHost>
      • 아파치를 시작 한다.

        ]# service httpd start
      • 웹 브라우져로 확인한다.

        https://(Subversion과 Apache를 설치한 IP주소 또는 도메인)/svnroot/repos1
    7. Trac 설치
      Trac

      • 버전 : Trac 0.10.4
      • yum install 명령으로 trac 을 설치하기 위해 Red Hat EL Repository를 추가한다.

        ]# vi /etc/yum.repos.d/SULinux-Base.repo
        1. [dag]
          name=Dag RPM Repository for Red Hat Enterprise Linux
          baseurl=http://apt.sw.be/redhat/el4/en/$basearch/dag
          gpgcheck=0
          enabled=0
          includepkgs=clearsilver python-clearsilver trac
      • Trac을 설치하고 아파치와 연동하기 위해 mod_python 패키지도 같이 설치한다.

        ]# yum --enablerepo=dag install trac

        ]# yum install mod_python

      • 설치 후 trac-admin 명령어로 파일 시스템을 이용한 저장소를 만들고 권한을 변경합니다. (묻는 과정에서 모두 엔터를 치고 Path to repository 에서 /home/svnroot/repos1 을 입력한다)

        ]# mkdir /home/trac

        ]# cd /home/trac

        ]#  trac-admin /home/trac/myproject initenv

        ]# chown -R apache.apache trac

      • 웹에서 접근 가능하도록 Location 을 등록한다.

        ]# vi /etc/httpd/conf.d/trac.conf
        1. #Alias /trac/ <- 첫줄은 주석처리
        2. <Location /trac>
             SetHandler mod_python
             PythonHandler trac.web.modpython_frontend
             # "/svn/trac/foobar" is the folder you gave to trac-admin initenv earlier
             PythonOption TracEnv /home/trac/myproject
             # "/trac" is the same as the Location above
             PythonOption TracUriRoot /trac
             # "/tmp" should be some writable temporary directory
             SetEnv PYTHON_EGG_CACHE /tmp
             # "trac" can be any string, but must be the same for all
             # Trac instances on the same Apache install
             PythonInterpreter trac
          </Location>
          <Location /trac/login>
             AuthType Basic
             AuthName "hibuz"
             AuthUserFile /etc/httpd/conf/passwd
             Require valid-user
          </Location>
      • Web Admin 플러그인 설치를 위한 권한 설정 후 확인해 본다.

        ]# trac-admin /home/trac/myproject permission add hibuz TRAC_ADMIN

        ]# trac-admin /home/trac/myproject permission add hibuz TICKET_ADMIN

        ]# trac-admin /home/trac/myproject permission list

      • Python 2.3 버전으로 Web Admin plug-in 을 다운로드 받는다.
      • Download for Trac 0.9.3 or later (Python 2.3) xmlrpcplugin.zip
      • 서버에 올린후 확장자를 제거하고 압축을 해제한다.

        ]# mv TracWebAdmin-0.1.2dev_r4240-py2.3.egg.zip TracWebAdmin-0.1.2dev_r4240-py2.3.egg

        ]# unzip xmlrpcplugin.zip

      • easy_install 을 사용하기 위해 python 스크립트를 서버에 올린 후 설치한다. [다운로드 : http://hibuz.springnote.com/pages/596254/attachments/420486?download=true]

        ]# python ez_setup.py
      • 플러그인을 설치한다.

        ]# easy_install TracWebAdmin-0.1.2dev_r4240-py2.3.egg

        ]# easy_install xmlrpcplugin/0.10/

      • trac.ini에 내용 추가

        ]# vi /home/trac/myproject/conf/trac.ini
        1. [components]
        2. webadmin.* = enabled
          tracrpc.* = enabled
      • 웹서버를 재시작한다.

        ]# service httpd reload
      • 웹 브라우져로 확인한다. (hibuz 계정으로 로그인 시 admin 탭이 보여야 한다.)

        http://(Subversion과 Apache를 설치한 IP주소 또는 도메인)/trac
      • 이메일 통보 기능을 사용하려면 trac.ini 에서 smtp를 활성화 하고 saslpasswd2 명령을 통해 인증 사용자를 추가한다.

        ]# saslpasswd2 -c root  <- 추가할 이메일 계정 사용자 -c 옵션은 최초 사용자 추가시 만 사용한다. /etc/sasldb2 파일을 생성해 준다.

        ]# vi trac.ini

        1. smtp_enabled = true
          smtp_password = password
          smtp_user = root
      • 이클립스 플러그인 업데이트 에서 Mylyn 을 선택해서 trac connector를 포함한 플러그인들을 추가해서 설치한다.
      • task repositories 에서 Add Task Repository 로 http://(Subversion과 Apache를 설치한 IP주소 또는 도메인)/trac 을 추가해서 task를 생성할 수 있다.
      • 참고사항

        Subversion Repository 변경사항 동기화 명령어 : trac-admin /home/trac/myproject resync

Posted by 1010
01.JAVA/Java2008. 7. 7. 15:40
반응형
 
정말 자바 웹 프로그래머가 알고있어야 할 기본입니다.
굉장히 방대한 분량이 간략히 정리되어있습니다.
 
=================================================================
 
마소 2005년 1월호 기고
1 제목
2 발문
3 필자 소개
4 본문
4.1 서론, 어떻게 공부할 것인가
4.2 본론
4.2.1 web.xml
4.2.2 예외 처리
4.2.3 로깅
4.2.4 예외 추적
4.2.5 한글 문제
4.2.6 URL 인코드
4.2.7 클래스패스의 리소스 사용법
4.2.8 서블릿/액션 멤버 변수 공유 문제
4.3 결론, 생각하기
4.4 참조

1 제목 #

자바 웹 프로그래머의 기본

2 발문 #

프로그래밍 초보자가 능히 한 사람 몫을 할 정도로, 혼자 코딩하도록 내버려둬도 다른 사람들이 불안에 떨지 않을 만큼 성장하는 가장 빠른 방법은 무엇일까? 디자인 패턴을 공부하고 최신 기술을 익히고 실전 프로그래밍을 많이 해보는 것? 그것도 물론 중요하다. 그러나, 이보다 훨씬 더 중요한 것은 기초를 다지는 것이다. 슬램덩크에서 강백호는 농구부 입단 후 2주일 간 드리블 연습만 했고 이것이 그가 빠른 시간 안에 한 사람 몫을 해내는데 밑거름이 되었다. 잠시 더블 클러치 연습은 멈추고 드리블을 해보자. 복잡한 이론, 어려운 신기술은 잠시 접어두고 프로그래머로서의 기본을 재점검해보자.

3 필자 소개 #

박영록 refactorer@naver.com code for human, not for programmer. 인간다운 프로그래머, 게으른 프로그래머를 지향한다. 현재 NHN에서 프레임웍 개발과 서버 관리를 담당하고 있다.

4 본문 #

4.1 서론, 어떻게 공부할 것인가 #

4년 전, 학교에서 어느 벤처 경영인의 강연을 들은 적이 있다. 미국에서 벤처를 시작해서 어느 정도의 성공을 거둔 기업가였다. 그는 강연 내내 기본을 강조했다. 미국과 한국의 기업 문화의 차이를 비교하면서 미국의 벤처들은 대체로 경영인으로서의 기본적으로 지켜야할 것들을 잘 지키는 반면 한국의 벤처는 기본적인 것들을 제대로 지키지 못하고 그로 인해 실패하는 경우가 많다고 했다. 벤처 붐이 일 때 수많은 학생 벤처가 경영에 대한 무지로 가진 기술을 펼쳐보지도 못하고 망한 현상에 대해 벤처는 경영이며 경영을 하려면 경영에 대해 배워야하는 것은 기본인데 그 기본이 지켜지지 않았기 때문이라고 했다. 당시 부도덕한 벤처 기업가들의 행태가 사회적으로 논란이 되고 있었는데 이에 대해서는 사회인으로서의 기본적인 소양이 갖추어져 있지 않기 때문이라고 했다. 그는 모든 것을 기본이란 말 하나로 설명했다. 기본이 물론 성공의 충분조건은 아니다. 그러나, 기본을 지키지 않고는 성공할 수 없다. 어떤 분야든 이것은 예외가 없을 것이다.

그렇다면 프로그래머, 그 중에서도 자바 웹 프로그래머의 기본은 무엇일까? 당연히 자바 언어에 대해서 잘 아는 것이다. 웹 프로그래밍이라는 것도 결국 사용하는 API가 다른 것 뿐, 좋은 자바 웹 프로그래머가 되려면 먼저 좋은 자바 프로그래머가 되어야한다. 너무도 당연한 말 같지만 현실은 그렇지 않다. 여러 자바 커뮤니티에 가보면 자바에 대한 정말 기본적인 질문들이 수도 없이 올라오며, 현업 프로그래머 중에도 기초가 부족한 사람이 너무나도 많다. 자바 프로그래머라면 자바에 관한 기본서 하나 정도는 마스터하고 시작하도록 하자. 자바 기본서들은 대체로 내용이 충실하므로 아무 거나 사도 나쁜 선택은 아닐 것이다. 그래도 추천이 필요하다면 Thinking in Java를 추천한다. 프로그래밍에 처음 입문하는 거라면 예제들을 직접 따라해보는 것도 좋을 것이다.

자바에 익숙해졌다면 다음 단계는 웹 기술이다. 웹 프로그래밍의 기본은 웹과 관련된 스펙(Specification)에 대한 지식, 구체적으로 Servlet/JSP 스펙, HTTP 스펙(RFC 2068), HTML ?W3C 스펙 등이다. 이 스펙들에 대해 상세히 다 알 필요는 없지만 웹 프로그래밍에서 사용하는 API들이 어떤 스펙에 기반하고 있는지, 자세히 알고 싶으면 무엇을 찾아야하는지는 알아야한다. 공대생이 공학수학의 내용을 전부 알고 있을 필요는 없지만 미분방정식을 풀고 싶으면 어느 페이지를 찾아봐야하는지는 알고 있어야하는 것처럼 어떤 요구사항이 발생했을 때 그 요구사항을 구현하려면 어떤 스펙을 찾아봐야하는지 정도는 알고 있어야한다. 그리고 의외로 많은 웹 프로그래머들이 HTML, CSS에 익숙지 않은데 이 때문에 웹사이트의 브라우저 호환성이 떨어질 뿐만 아니라 지저분한 코드를 양산하게 된다. HTML 코드 역시 유지보수 대상이 되는 코드이며 자바 코드 못지 않게 깔끔하게 유지할 수 있어야함을 기억하자. 이를 위해서는 HTML과 CSS에 대해 상세히 알아둘 필요가 있다. XML은 이제 프로그래머의 기본이니 언급할 필요도 없을 것이다. XML 파일을 이용하는 것이 편하게 느껴질 정도가 되면 코드의 유연성을 높일 좋은 방법들을 많이 생각해낼 수 있을 것이다.

스펙을 실제로 활용하는 것은 API(Application Programming Interface)를 통해서이다. Servlet/JSP API는 스펙과는 달리 실제로 API를 통해서 무엇을 할 수 있는지를 상세하게 알고 있어야한다. 이것은 비단 Servlet/JSP API 뿐 아니라 Java 기본 API, 각종 라이브러리의 API도 마찬가지다. 필자가 이제껏 자바에 관해 받아본 질문 중 대부분은 API 문서만 잘 들여다보면 해결되는 것이었다. API 문서를 자주 찾아보는 습관을 들이자. 리눅서들은 매뉴얼을 읽지 않고 질문하는 사람에게 RTFM(Read The Fucking Manual)이라는 대답을 해준다. 자바 역시 RTFM이 필요하다. ?J2EE 기본서를 하나 사서 보는 것도 좋을 것이다. ?J2EE 기본서에는 웹 관련 스펙 중 중요한 부분들, Servlet/JSP 스펙 및 API들이 잘 정리되어 있다. Java Server Programming, ?J2EE Edition 정도면 훌륭한 참고서가 될 것이다.

이제부터 이런 기본적인 지식 중에 중요하지만 간과하기 쉬운 것들, 간단하지만 알면 도움이 되는 정보들, 자주 부딪히게 되는 고민들 등 몇 가지 작은 문제들을 짚어볼 것이다. 모두 기본 학습 과정을 잘 거쳤다면 자연스럽게 알 수 있는 내용들이다. 이런 하나하나의 지식들을 통해 자신에게 부족한 점을 되짚어볼 수 있는 계기를 마련할 수 있기를 바란다.

4.2 본론 #

4.2.1 web.xml #
배치 서술자(deployment descriptor)라고 부르는 web.xml은 웹 프로젝트를 구성하는데 있어 필수적이면서 웹 애플리케이션의 동작을 여러 가지로 조정하는 역할을 한다. 스트러츠를 사용하는 경우도 스트러츠를 사용하기 위한 설정은 web.xml에 하게 되는데 그 설정들이 무슨 의미를 가지고 있는지 정도는 상식으로 알아두는 것이 좋을 것이다. 다음의 실제 스트러츠 설정 예제를 보자. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"><servlet> <servlet-name>action</servlet-name> <servlet-class> org.apache.struts.action.ActionServlet </servlet-class> <init-param> <param-name>config</param-name> <param-value> /WEB-INF/struts-config.xml </param-value> </init-param> <load-on-startup>1</load-on-startup></servlet><servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern></servlet-mapping></PRE>PHP, ASP 등의 다른 서버 사이드 스크립트나 JSP 페이지는 페이지를 호출하는 경로에 실제 스크립트 파일이 존재해야하지만 서블릿은 이와 달리 web.xml의 설정을 이용해서 URL을 특정 서블릿으로 매핑시킬 수 있다. 위의 설정은 호출된 URL을 스트러츠의 Action으로 매핑시키기 위한 설정이다. servlet 설정에서 action이라는 이름의 서블릿을 org.apache.struts.action.?ActionServlet 클래스로 등록하고 아래의 servlet-mapping 설정에서 *.do라는 URL로 호출된 페이지들을 action이라는 이름의 서블릿으로 매핑시킨다. url-pattern 값을 *.nhn으로 바꾼다면 *.nhn으로 호출된 요청들이 ?ActionServlet으로 매핑될 것이다. 스트러츠는 이 ?ActionServlet에서 요청을 각 Action으로 분기시켜준다. init-param은 서블릿을 초기화할 때 사용할 파라미터값이며 getInitParameter 메쏘드를 통해서 읽어올 수 있다. load-on-startup은 서블릿 엔진이 스타트될 때 로드될 우선 순위를 지정하는 값이다.

인덱스 페이지를 지정하는 것도 web.xml에서 할 수 있다. 많은 웹사이트들이 구체적인 경로 지정 없이 도메인명까지만 써줘도 페이지를 표시한다. 이를테면 http://www.hangame.com으로 호출할 경우 다음과 같이 설정해두면 www.hangame.com의 /index.jsp를 호출하게 만들 수 있다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"><welcome-file-list> <welcome-file>index.jsp</welcome-file></welcome-file-list></PRE>태그명에서 짐작할 수 있듯이 인덱스 페이지는 여러 개를 둬서 순서대로 검색하게 할 수 있다. 예를 들어 index.html과 index.jsp가 순서대로 지정된다면 서블릿 엔진은 index.html이 있으면 index.html을 보여주고 없으면 index.jsp를 호출한다. 이것도 없으면 404 에러가 나거나 디렉토리 목록이 보이게 된다. 이 인덱스 페이지는 모든 경로에 대해서 동작한다. 위와 같은 설정의 경우 http://www.hangame.com/login/을 호출한다면 http://www.hangame.com/login/index.jsp를 찾게 되는 것이다. 이 설정은 사실 아파치 등의 웹서버에서도 해줄 수 있으나 보통 웹 서버에서는 인덱스 페이지가 실제 파일로 존재해야 보여줄 수 있는데 서블릿 엔진에서는 실제 파일로 존재하지 않고 서블릿 매핑으로 지정만 되어 있어도 보여줄 수 있다는 장점이 있다.

접근 권한도 설정할 수 있다. 권한 체계가 간단한 웹 애플리케이션이라면 web.xml만으로도 충분한 권한 설정을 해줄 수 있다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"><security-constraint> <web-resource-collection> <web-resource-name>retail</web-resource-name> <url-pattern>/acme/retail/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>CONTRACTOR</role-name> <role-name>HOMEOWNER</role-name> </auth-constraint></security-constraint></PRE>위의 예는 서블릿 스펙 문서에 있는 예다. 이것의 의미는 GET이나 POST로 /retail/*와 같은 요청은 CONTRACTOR와 HOMEOWNER라는 role을 가진 사용자에게만 허락하겠다는 뜻이다. 이외의 사용자는 권한이 없다는 401 에러 페이지를 보게 된다. 이런 접근 제한 뿐 아니라 로그인 처리도 login-config 설정을 이용하면 가능하다. 실제 톰캣의 admin과 manager 애플리케이션은 이 설정을 이용해서 인증과 권한 처리를 한다. 자세한 스펙은 서블릿 스펙 문서에 정의되어 있으나 실제 활용하기엔 다소 부족한 감이 있고 톰캣의 실제 활용 예를 보는 것이 도움이 될 것이다. 이외에도 서블릿 필터 설정, 세션 설정, 리소스 설정 등 여러 가지 유용한 설정을 해줄 수 있고 공통적인 에외 처리를 위한 에러 페이지 설정도 가능하다. 에러 페이지 설정 부분은 이후 예외 처리에서 자세히 다룰 것이다.

4.2.2 예외 처리 #
자바의 강점 중 하나가 편리한 예외 처리 방식이다. C 언어 등 예외 처리 문법이 없는 언어를 먼저 접한 프로그래머에게는 생소한 개념일 수 있겠지만 알면 알수록 편리한 것이 자바의 예외 처리이다. 하지만 의외로 많은 자바 프로그래머들이 예외 처리를 어려워하고 예외 처리를 제대로 하지 않아 여러 가지 문제를 발생시킨다. 기본이라고 할 수도 있는 부분이긴 하나 사실 이것은 자바의 예외 처리 문법만 배운다고 되는 문제는 아니며 예외 처리에 대한 많은 고민이 필요하다. 특히 웹 애플리케이션의 예외 처리는 프로그래머를 위한 부분과 웹사이트 방문객을 위한 부분 두 가지를 모두 고려해야한다.

먼저 프로그래머의 입장을 살펴보자. 예외가 발생하면 어디까지는 그냥 던지고 어디서 캐치하는 것이 좋을까? 자바의 예외는 자바 코드의 모든 영역에서 발생할 수 있다. 이 모든 영역에 다 try-catch를 걸고 예외를 잡을 수는 없는 일이다. 대부분의 예외는 일단 그냥 던지는 것이 좋다. 자바의 예외가 좋은 것은 꼭 예외가 발생한 그 지점에서 처리를 하지 않아도 된다는 것 때문이다. 예외를 던짐으로서 예외를 처리하기에 적절한 위치에서 처리하게 만들 수 있다. 어떻게 처리해야할지 잘 모르겠다면 그냥 그대로 던지도록 하는 것이 좋다. 예외를 잡아서 처리해야하는 곳은 일반적으로 사용자에게 화면을 보여주기 직전이며 이것은 웹 애플리케이션이 MVC(Model-View-Controller) 패턴으로 작성되어 있다면 컨트롤러에서 이 역할을 하게 된다. 컨트롤러에서 예외를 보고 판단을 해서 사용자에게 보여줄 화면을 결정하는 것이다. 쇼핑몰에서 마일리지 적립액으로 상품을 구매하는 과정을 예로 들어보자. 만약 고객이 자신의 마일리지보다 더 많은 금액의 상품을 구매하려한다면 구매를 수행하는 모델 객체에서 예외가 발생할 것이다. 그러면 이 모델 클래스에서 예외를 바로 잡지 말고 던져서 구매 프로세스의 컨트롤러 객체에서 이를 잡아서 예외 페이지로 포워드를 시켜서 예외 메시지를 보여주는 식으로 코딩하면 된다.

웹사이트 방문객을 위해 중요한 것은 자바 예외가 발생했을 때 이해할 수 없는 시스템 에러 메시지나 스택트레이스 등의 황당한 화면이 아닌 친절한 에러 메시지를 표시해주는 것이다. 이를 위해서는 컨트롤러에서도 처리하지 못하고 던져진, 정말 예상 밖의 예외를 모두 끌어모아서 처리하는 부분이 필요하다. Servlet/JSP에서는 이런 부분의 처리를 위한 기능을 여러 가지로 제공하고 있고 스트러츠 등의 프레임웍에서도 다양한 방법을 제공하고 있다. JSP의 에러 페이지 설정이 그 한 예다. 그러나, JSP의 에러 페이지 설정 방식은 모든 JSP 페이지에 설정해야 작동한다는 단점이 있다. 만약 에러 페이지 지정을 빠뜨린 페이지에서 예외가 발생한다면 서블릿 엔진의 에러 메시지가 그대로 웹사이트 방문객에게 전달되고 만다. 이런 부분을 쉽게 처리하기 위한 방법이 있다. 이것은 위에서 설명했던 web.xml의 에러 페이지 설정을 이용하는 것이다. 우선 다음의 예를 보자. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"><error-page> <exception-type>java.lang.Exception</exception-type> <location>/common/error.jsp</location></error-page><error-page> <error-code>404</error-code> <location>/common/error.jsp</location></error-page></PRE>이렇게 설정해두면 웹 애플리케이션 전반에서 발생하는 예외 중 java.lang.Exception을 상속한 예외는 모두 잡혀서 /common/error.jsp 페이지에서 처리하게 된다. 예외가 발생하면 request 객체에 예외 상황에 대한 정보가 attribute로 저장된 후 /common/error.jsp로 포워딩되어 이곳에서 request에 담긴 정보들을 바탕으로 에외 처리를 해줄 수 있다. 이 곳에서는 일반적인 에러 메시지를 사용자에게 보여주면 된다. 자바 예외 뿐 아니라 HTTP 에러 코드도 잡아낼 수 있다. 이를테면 없는 페이지를 호출해서 404 에러가 나는 경우 이를 잡아서 페이지가 없다는 에러 메시지를 좀더 친절한 메시지로 보여줄 수 있다. 덧붙여, 이 에러 처리 페이지는 가급적 순수한 서블릿으로 만드는 것이 좋다. 스트러츠의 Action으로 에러 페이지를 구성해본 적이 있었는데 설정 상의 문제로 스트러츠의 ?ActionServlet 로딩이 실패할 경우 예외를 제대로 표시하지 못한다. JSP로 만드는 것도 나쁘진 않으나 복잡한 로직이 들어갈수록 서블릿이 더 코딩하기 더 편할 수 있다. 만약 이 에러페이지 자체에서 또다시 예외가 발생하면 찾기 힘든 경우가 많기 때문에 주의를 많이 기울여야한다.

4.2.3 로깅 #
에러 페이지에서 해야할 또 하나 중요한 일은 예외 상황에 대한 로그를 남기는 것이다. 에러 페이지까지 왔다는 것은 이미 개발자의 예상을 벗어난 동작을 하고 있다는 것이므로 이 사실은 개발자에게 빨리 전달되어야한다. 때문에 로그를 제대로 남겨서 조회하기 편한 시스템을 구축해야한다. 로깅 API는 여러 가지가 있고 JDK 자체에도 포함되어 있지만 log4j가 가장 널리 사용되고 성능, 기능, 안정성 등 여러 가지 면에서 다른 것들보다 낫다. 여러 가지 로깅 API를 바꿔가면서 사용할 수 있게 해주는 자카르타의 commons-logging 프로젝트도 쓸만하다. 로거 객체는 일반적으로 클래스 당 하나를 클래스의 전체 이름으로 생성해서 사용한다. 다음은 commons-logging을 사용하는 예다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black">package com.hangame.avatar;import ...public class Avatar { private static Log log = LogFactory.getLog(Avatar.class); public void changeBackgroud() { log.debug("avatar changing.."); }}</PRE>이러면 로그 객체는 Avatar 클래스의 전체 이름, com.hangame.avatar.Avatar로 생긴다. 만약 여기에 log4j를 붙여서 사용한다면 다음과 같은 log4j 설정을 사용할 수 있다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"><?xml version="1.0" encoding="UTF-8" ?><log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="normal" class="org.apache.log4j.ConsoleAppender"> <param name="Threshold" value="DEBUG"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p] %m%n"/> </layout> </appender> <appender name="memory" class="com.nhn.logging.MemoryAppender" > <param name="Threshold" value="ERROR"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/> </layout> </appender> <logger name="com.hangame" additivity="false"> <level value="DEBUG"/> <appender-ref ref="normal"/> <appender-ref ref="memory"/> </logger> <logger name="org.apache" additivity="false"> <level value="INFO"/> <appender-ref ref="normal"/> </logger> <root> <level value="WARN"/> <appender-ref ref="STDOUT"/> </root></log4j:configuration></PRE>위의 설정은 com.hangame와 org.apache라는 이름의 로거를 두 개 생성하고 있다. 로거의 특성은 이름으로 상속된다. com.hangame.avatar.Avatar라는 이름의 로거는 com.hangame의 속성을 모두 상속 받게 된다. 그러면 com.hangame이 normal과 memory라는 두 개의 appender를 갖고 있기 때문에 com.hangame.avatar.Avatar 로거가 찍은 로그는 표준 출력으로도 나가고 메모리에도 남게 된다. log4j의 이런 특성을 이용하면 다양한 방식으로 로그를 남길 수 있고 로그를 선택적으로 켜고 끄는 것이 가능하다. 이런 기능들을 잘 활용하면 로그를 조회하기 쉽게 구성할 수 있다. 위에서 예를 든 것처럼 메모리에 최근 로그를 남겨두고 이를 조회할 수 있는 페이지를 만든다거나 데이터베이스에 로그를 쌓을 수도 있다. 그리고 주기적으로 이런 로그 조회 페이지를 모니터링하면서 로그 리포트를 개발자에게 메일 등으로 자동 발송해주는 시스템도 구상해 볼 수 있을 것이다.

4.2.4 예외 추적 #
예외 처리 시스템을 구축하고 예외 로그를 남겼으면 다음은 이 정보를 바탕으로 문제점을 찾아들어가는 것이다. 예외 추적의 출발점은 당연히 예외 스택 정보이다. 대부분의 문제는 예외 스택 정보만 가지고도 찾아낼 수 있다. 하지만 의외로 많은 프로그래머들이 예외가 발생했을 때 스택 정보를 보지 않고 자신의 경험에 의지해서 문제점을 예측하려 하곤 한다. 이런 실제 상황에 기반하지 않은 예측은 운 좋게 문제를 바로 짚어내는 경우도 있겠지만 대개의 경우 시간만 낭비하게 된다. 예외가 발생하면 반드시 스택 정보에 찍힌 소스의 라인부터 살펴보는 습관을 기르는 것이 좋다. 스택 정보는 가끔 수백 라인에 이를 정도로 길어지는 경우도 간혹 있다. 이 모든 정보를 다 찾아볼 필요는 없다. 스택 정보는 메쏘드가 호출된 역순으로 찍히므로 위에 있는 정보가 예외가 발생한 위치와 가까운 정보다. 그렇다고 늘 제일 위의 정보를 봐야하는 것은 아니다. 웹 애플리케이션의 경우 스택 정보는 자신이 작성한 클래스 뿐 아니라 서블릿 엔진을 포함한 여러 가지 클래스의 정보들이 같이 담겨 있다. 이런 정보들은 보통 볼 필요가 없고 스택 정보에서 자신이 작성한 클래스 중 제일 위에 있는 것, 이것이 예외가 발생한 지점이며 이곳을 찾아보면 대부분의 문제점은 정확하게 추적 가능하다.

또 한 가지 자바 초보자를 괴롭히는 문제는 ?NullPointerException이다. 사실 이것은 초보자에게는 아주 까다로운 문제지만 조금만 알면 가장 찾기 쉬운 문제 중 하나가 ?NullPointerException이다. ?NullPointerException은 객체의 멤버 변수나 메쏘드를 이용하려고 할 때 그 객체가 null인 경우에 발생한다. 따라서 ?NullPointerException이 발생하면 위의 방법대로 예외가 발생한 라인을 찾아들어간 다음 그 라인에서 멤버 지정 연산자(.) 앞에 있는 객체를 보면 된다. 이 사실만 알고 있어도 ?NullPointerException이 발생했을 때 어떤 객체가 null인지를 쉽게 찾아낼 수 있을 것이다.

간혹 ?NullPointerException이 싫어서 다음과 같은 코드를 작성하는 경우가 있다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"> if ("Y".equals(param)) doSomthing(); else doOther();</PRE>이런 코드는 조심해서 써야한다. param의 null 체크가 귀찮아서 이런 식의 코드를 쓰곤 하는데 만약 param의 값이 Y인 경우는 doSomething()을 실행하고 N이나 null이면 doOther()를 실행해야하는 경우라면 이 코드는 문제가 없다. 그러나, 만약 param은 null이면 안되는 상황이라면 어떻게 될까? 다른 부분의 버그로 param에 null이 들어와도 프로그래머는 이것을 알아차리지 못하고 넘어가게 된다. 즉, 버그를 은폐하는 코드가 된다. 당장의 문제를 발생하지 않더라도 이런 코드는 나중에 찾기 힘든 문제를 유발할 수 있다. 이런 경우는 그냥 ?NullPointerException이 발생하도록 내버려 두면 param에 null 값이 들어왔을 때 다른 부분에 버그가 있기 때문이라는 사실을 감지할 수 있다. 상황에 따라 위와 같은 코드를 써도 되는지를 신중히 검토한 후 사용해야한다. 예외 발생이 두려워서 버그를 은폐할 수 있는 코드를 만들지 말자.

4.2.5 한글 문제 #
웹 프로그래머들을 괴롭게 하는 문제를 꼽을 때 빠지지 않는 것이 한글 문제다. 한글 문제가 지금처럼 골치아프게 된 데는 역사적으로 복잡한 원인들이 얽혀 있는데 이런 문제는 접어두고 자바 웹 프로그래머로서 한글 문제를 해결하기 위해 알아야하는 것들을 살펴보자.

자바는 문자열과 바이트 스트림을 다르게 취급한다. 자바의 스트링은 유니코드의 문자셋을 사용하며 문자열을 파일에 쓰거나 네트워크로 전송하는 등 실제 입출력이 일어날 때는 문자열을 바이트 스트림으로 변환하게 된다. 이 때 바이트 스트림으로 변환하는 규칙이 인코딩이다. 따라서 바이트 스트림으로 전달된 것을 문자열로 바꾸거나 문자열을 바이트 스트림으로 전달할 때는 반드시 인코딩을 지정해야한다. 이런 인코딩 중 한글을 표현할 수 있는 인코딩은 자바에서 사용하는 이름을 기준으로 하면 EUC-KR, ?MS949, UTF-8, UTF-16 정도가 있다. EUC-KR은 ?KSC5601-1987에 기반한 인코딩으로 한글의 모든 문자를 다 표현할 수 없다. ?MS949는 EUC-KR을 확장해서 모든 한글을 표현할 수 있지만 비표준이고 코드 자체에 기술적인 결함이 많다. UTF-8과 UTF-16은 유니코드의 인코딩들이며 모든 한글을 표현할 수 있고 표준이며 한글 이외의 다른 캐릭터셋과 함께 표현이 가능하다. 보통 많이 쓰이는 EUC-KR은 RFC 표준 인코딩이긴 하나 한글의 확장 문자들을 제대로 표시하지 못한다. 그래서 자바 웹 프로그래밍에서는 ?MS949를 많이 쓰게 된다. 자바에서 스트링 객체를 생성할 때는 이 중에 하나로 인코딩을 줘서 생성해야 한글을 표현할 수 있게 인코딩된다.

웹 서버로 전달되는 요청은 클라이언트의 웹브라우저가 문자열을 바이트 스트림으로 인코딩하는데 이 때 사용하는 인코딩은 일반적으로 한글 윈도우의 기본 인코딩인 ?MS949다. 그런데, 서블릿 엔진에서 요청을 처리하는데 사용하는 기본 인코딩이 ISO-8859-1이기 때문에 아무 것도 지정하지 않으면 ?MS949로 인코딩된 바이트들을 ISO-8859-1 인코딩의 스트링 객체로 만들기 때문에 한글이 깨져보이게 된다. 따라서 기본 인코딩을 ?MS949로 지정해주면 인코딩이 보존된 상태로 한글이 깨지지 않게 된다. ?HttpServletRequest.setCharacterEncoding() 메쏘드에서 이것을 지정해줄 수 있다. 그러나, 이것에도 약간 문제가 있다. 서블릿 스펙상 이 메쏘드는 POST 요청에만 적용된다. 즉, POST 요청의 파라미터는 setCharacterEncdoing에서 지정한 인코딩으로 스트링 객체가 생성되기 때문에 한글을 보존할 수 있으나 GET 요청은 setCharacterEncoding의 적용을 받지 않기 때문에 GET으로 받은 파라미터는 인코딩 변환을 다시 해주어야한다. 다만, 이것은 서블릿 엔진에 따라 다르다. 톰캣의 경우도 4.1 버전과 5.0 버전이 다르게 동작하니 주의가 필요하다.

웹 서버에서 다시 클라이언트로 응답을 할 때는 반대의 과정이다. 자바의 스트링 객체가 바이트 스트림으로 변환되며 이 때 역시 인코딩을 지정해야한다. 이 인코딩은 JSP 페이지에서 페이지 지시자의 pageEncoding 속성을 통해 지정을 해줄 수 있고 서블릿 2.4 스펙에서는 ?HttpServletResponse.setCharacterEncoding을 사용할 수 있다. HTTP 요청을 읽는 과정과 역순이라고 생각하면 된다. 그리고, 웹 서버에서 요청을 읽을 때 ?MS949를 지정해 주듯이 클라이언트의 웹브라우저도 웹 서버에서 생성한 응답을 정확하게 읽으려면 어떤 인코딩을 사용해야하는지 알아야한다. 이것을 지정해주는 것이 HTML의 Content-Type이다. 다음과 같이 지정할 수 있다. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"> <meta http-equiv="Content-Type" content="text/html;charset=euc-kr" /></PRE>여기서 지정하는 charset은 원칙적으로는 당연히 웹 서버에서 응답 객체를 생성할 때 지정한 인코딩값과 같아야 제대로 한글로 읽을 수 있다. 그러나, 여기 지정하는 charset이 RFC 표준 문자셋이 아닐 경우 브라우저에 따라 인식을 못할 수도 있다. 그래서 ?MS949로 인코딩했다면 ?MS949를 지정해야 정상이지만 ?MS949가 RFC 표준이 아니기 때문에 문제가 생길 수 있다. 그렇다고 응답의 인코딩을 EUC-KR로 지정하게 되면 확장 한글을 표시할 수 없기 때문에 문제가 된다. 그래서 페이지 인코딩은 ?MS949로 하지만 Content-Type에는 euc-kr을 지정해주게 되는 것이다. 물론 이렇게 되면 경우에 따라 확장 한글이 깨질 수 있지만 다행스럽게도 대부분의 브라우저에서 이렇게 지정하면 잘 동작한다.

사실 이 부분은 응답 스트림에 적용되는 인코딩과 HTML Content-Type에 지정하는 인코딩이 같기만 하면 되기 때문에 굳이 ?MS949를 사용할 필요는 없고 UTF-8 등의 인코딩을 사용해도 무방하다. 따라서 응답 스트림의 인코딩도 UTF-8로 하고 Content-Type도 UTF-8로 지정하는 것이 가장 확실한 방법일 수 있다. 또한, HTML의 Content-Type에 UTF-8이 지정되어 있으면 이 페이지에서 폼을 전송할 경우에도 UTF-8로 인코딩되어 요청을 파싱하는 쪽에서도 UTF-8을 사용할 수 있다. 유니코드의 인코딩들인 UTF-8, UTF-16은 한 인코딩으로 다국어를 처리할 수 있기 때문에 다국어 지원이 필요한 웹 애플리케이션은 실제로 UTF-8로 작성된 것이 많다. 다국어 지원이 필요 없다고해도 UTF-8을 사용하는 것이 오히려 한글 문제를 더 쉽게 해결하는 방법이 될 수 있다.

웹 뿐 아니라 데이터베이스나 파일에 입출력을 할 때도 마찬가지의 원리가 적용된다. 사용하는 인코딩이 다르면 변환 과정을 거쳐야한다. 이것은 리눅스나 유닉스에서 문제가 될 수 있다. 리눅스는 ?MS949를 지원하지 않고 EUC-KR만 지원하기 때문이다. 따라서 윈도우에서 개발하고 리눅스에서 돌리는 경우 문제가 되는 경우가 간혹 있다. ?MS949가 또 하나 문제가 되는 영역은 XML 파서다. 현재 가장 널리 사용되는 XML 파서는 Xerces인데 이 파서는 RFC 표준 문자셋 외에는 지원하지 않기 때문에 ?MS949 인코딩은 파싱 에러가 난다. 그런 반면 JDK 1.4에 포함된 파서인 Crimson은 네임스페이스 파싱에 버그가 있다. ?MS949를 XML 인코딩으로 쓸 경우 XML 파서 선택이 문제가 될 수 있는 것이다. 다행스럽게도 JDK 5.0에 포함된 파서는 Xerces를 썬에서 패치한 것인데 이것은 아무 문제가 없다. 하지만 여전히 많은 오픈소스 라이브러리들이 Xerces를 사용하고 있기 때문에 문제가 되는 경우는 계속 나타날 수 있을 것이다. 이것 때문에라도 UTF-8을 사용할 필요가 있다.

자바에서의 한글 문제는 문자열과 바이트스트림의 변환에 인코딩이 주어져야한다는 사실만 생각하면 다 쉽게 해결가능하다. 역시 기본이 잘 갖춰져 있으면 한글 문제도 쉽게 해결할 수 있는 것이다.

4.2.6 URL 인코드 #
URL 인코딩이 필요한 것은 URL에 사용가능한 문자가 제한되어 있기 때문이다. URL 스펙(RFC 1738)에 정의된 바로는 URL에 사용할 수 있는 문자는 알파벳, 숫자와 몇 가지의 특수문자 뿐이다. 따라서 다양한 문자들을 URL로 전달하려면 URL에서 허용하는 문자로 변환시켜서 전달해야한다. 이것은 GET 요청의 파라미터로 값을 전달하려할 때 문제가 된다. 예를 들어 http://website.com/process.jsp에 로그인 안된 상태에서 접근하면 자동으로 로그인 페이지인 http://website.com/login.jsp로 리다이렉트된 후 로그인을 하면 원래 요청했던 페이지로 다시 리다이렉트되도록 해야한다고 하자. 그러면 /process.jsp에서는 로그인 페이지로 리다이렉트시키면서 파라미터로 현재 요청한 URL, 즉 /process.jsp를 넘겨주고 login.jsp에서는 로그인 처리가 끝난 후 이 URL로 다시 리다이렉트를 시키면 된다. 여기서 /process.jsp에서는 http://website.com/login.jsp?redirect=http://website.com/process.jsp와 같은 형식으로 리다이렉트를 해주면 될 것이다. 여기서 문제는 redirect 파라미터의 값이 URL이기 때문에 URL 안에 URL이 들어간 형태가 되어 제대로 파싱이 되지 않는다. 그래서 파라미터로 넘겨야하는 URL 부분을 ?URLEncoder로 인코딩을 해서 http://website.com/login.jsp?redirect=http%3A%2F%2Fwebsite.com%2Fprocess.jsp와 같은 형태로 넘겨야한다. 이 값을 받는 부분에서는 다시 디코딩을 해줄 필요가 없다. URL은 자동으로 웹 서버에서 파싱할 때 디코딩을 해주기 때문이다. URL을 통해서 GET 요청의 파라미터로 보내야하는 값은 반드시 URL 인코딩을 거쳐야한다는 사실만 기억하도록 하자. 참고로 자바스크립트에서도 escape, unescape 함수를 통해서 URL 인코딩, 디코딩과 유사한 작업을 수행할 수 있다.

4.2.7 클래스패스의 리소스 사용법 #
웹 애플리케이션은 보통 애플리케이션의 설정을 담고 있는 파일이 필요하다. web.xml, struts-config.xml 등의 설정 파일들은 보통 웹 애플리케이션의 /WEB-INF/에 위치하게 되는데 그 외에 애플리케이션에서 사용하는 파일들은 어디에 놓고 사용하는 것이 편리할까? 가장 관리하기 쉽고 부가적인 작업이 적은 방법은 클래스패스에 두는 것이다. /WEB-INF/classes에 두면 자바의 클래스로더를 이용해서 이런 파일들에 접근할 수 있다. log4j 등 많은 라이브러리들이 자신의 설정 파일을 클래스패스에서 가장 먼저 찾게 된다. 다음의 예제를 보자 <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black"> public File getFile(String name) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); return new File(loader.getResource(name).getFile()); } public void doSomeProcess() { File file = getFile("config.xml"); }</PRE>위의 코드는 클래스패스에서 config.xml을 읽는다. 웹 애플리케이션의 기본 클래스패스는 /WEB-INF/classes이므로 기본적으로 여기서 찾게 된다. 이것으로 jar 파일 안의 내용도 읽을 수 있다. 이 경우는 ?ClassLoader.getResourceAsStream을 통해서 스트림으로 파일 내용을 읽을 수 있다. 대부분의 IDE나 maven 등의 빌드 툴에서는 소스 경로에 있는 파일들 중 자바 소스가 아닌 파일들을 자동으로 클래스패스로 복사해주므로 이용하기도 편리하다. 자카르타의 commons-discovery 프로젝트는 이런 기능들을 모아서 편리하게 이용할 수 있게 제공하고 있다.

4.2.8 서블릿/액션 멤버 변수 공유 문제 #
JSP가 보급되기 시작하던 초기에 많이 발생하던 문제로 웹사이트의 이용자가 접속했을 때 자신의 정보가 아닌 다른 사람의 정보가 나타나면서 엉키는 경우가 있었다. 이것의 원인은 서블릿에 대한 이해가 부족해서 발생한 것이었다. 다음의 예제를 보자. <PRE class=wikiSyntax style="COLOR: #c0c0c0; FONT-FAMILY: FixedSys,monospace; BACKGROUND-COLOR: black">public class BadServlet extends HttpServlet { Map userInfo; protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String username = req.getParameter("name"); Map userInfo = UserManager.getUserInfo(username); req.setAttribute("userInfo", username); }}</PRE>얼핏 별 문제가 없어보이지만 이 코드는 심각한 문제가 있다. 서블릿은 보통 서블릿 엔진에서 하나만 생성되고 한 번 생성된 서블릿 객체가 계속 재활용된다. 때문에 A와 B라는 두 사용자가 동시에 이 서블릿을 호출하게 되면 A의 호출을 수행하는 중에 B의 호출이 userInfo의 값을 바꿔버릴 수 있다. 그러면 A는 B의 정보를 보거나 그 반대의 경우가 생길 수 있는 것이다. 혼자서 테스트할 때는 한 번에 한 쓰레드만 service 메쏘드를 호출하기 때문에 이런 문제가 잘 드러나지 않기 때문에 별 문제 없는 줄 알고 있다가 서비스를 오픈하고 나면 문제가 되는 경우가 있으므로 조심해야한다. JSP에서 <%! %>를 통해서 선언하는 내용도 마찬가지 문제가 발생하므로 주의하자. 이런 내용 역시 자바 클래스와 멤버 변수의 기본 개념을 이해하고 서블릿 스펙만 한 번 읽어본다면 금방 알 수 있는 내용이다.

4.3 결론, 생각하기 #

이 내용들을 읽으면서 모르는 내용이 하나도 없었다면 자바 웹 프로그래머로서 어느 정도 기본은 되어 있다고 할 수 있다. 이런 내용들은 그 하나하나에 대한 지식을 쌓는 것도 중요하지만 더 중요한 것은 이런 내용을 알아야한다는 사실을 아는 것이다. 무엇을 알아야하는가를 가르쳐주는 것은 스펙이다. 스펙 문서들은 대부분 영어이고 그다지 친절하게 되어 있진 않지만 해당 분야에 대해 가장 정확한 정보를 담고 있다. 자세한 내용을 다 알진 못하더라도 스펙에 어떤 내용이 있는가 정도는 알아야 그 내용 중 자신에게 필요한 내용을 찾아서 공부할 수가 있는 것이다. 이런 정보를 어디서 찾을 수 있는가를 알고 있는 것도 중요하다. 기본적으로 www.ietf.org, jcp.org, java.sun.com, www.w3.org 정도의 사이트에는 익숙해지는 게 좋을 것이다.

많은 프로그래머들이 실제로 자기 손으로 프로그래밍해보는 게 실력이 느는 제일 좋은 방법이라고 말하지만 필자는 여기에 동의하지 않는다. 물론, 실제 경험을 쌓는 것이 필수적인 과정이긴 하다. 그러나, 기본 지식을 등한시한 상태에서 코딩만 해보는 것으로는 실력이 잘 늘지 않는다. 코딩 기술은 늘 수 있겠지만 정말 실제 서비스를 해야하는 프로그래밍에서 중대한 실수를 저지르게 되거나 남들이 쉽게 쉽게 하고 있는 일들을 어렵게 빙 둘러가면서 하게될 수 있다. 그래서 기본기를 갖추는 것이 중요한 것이다.

거듭해서 기본의 중요성을 강조했는데 한 가지 덧붙이고 싶은 말은 이런 기본 지식 뿐 아니라 기본을 활용하는 능력을 키우는 것도 잊지 말아야한다는 것이다. 앞서 언급한 예외 처리 같은 내용은 기본이긴 하나 자바 문법만 잘 안다고 알 수 있는 내용들은 아니며 기본을 바탕으로 좋은 판단을 내릴 수 있는 능력이 있어야한다. 결국 좋은 프로그래머가 되려면 먼저 좋은 사고 능력을 가지고 있어야하는 것이다. 글짓기를 잘하는 방법으로 흔히 다독(多讀), 다작(多作), 다상량(多商量)을 이야기한다. 많이 읽고 많이 쓰고 많이 생각하라는 것이다. 프로그래밍도 이와 비슷하다. 각종 스펙들과 좋은 코드들을 많이 읽어보고 직접 코딩도 많이 해보면 분명 실력이 늘지만 이것으로는 충분치 않다. 프로그래밍을 하면서 끊임없이 생각해야한다. 지금 작성한 코드는 좋은 코드인가, 이렇게 코딩하면 불편한데 개선할 방법은 없을까, 이 API들로 무엇을 할 수 있을까, 좀더 개발 속도를 향상시키려면 어떻게 해야할까 등등 생각을 많이 해야 진짜 발전을 이룰 수 있다. 만일 손가락이 아플 정도로 하루 종일 키보드를 두드리고 있다면 좋은 프로그래머라고 할 수 없다. 생각하는데 좀더 많은 시간을 써야한다. 모니터를 구부정하게 들여다보면서 키보드를 두드리는 것은 것보다는 의자에 편안히 기대서 생각하는 시간을 늘리자. 복잡한 문제가 있으면 바깥 공기를 쐬면서 산책을 하면서 생각을 하는 것도 좋다. 굳이 건강을 생각하지 않더라도 걷는 것은 두뇌를 활성화시키기 때문에 해결책을 더 빨리 찾을 수 있게 해 준다. 남들이 보기에는 게을러보일 수 있지만 놀고 있는 게 아니라는 것은 결과로 충분히 보여줄 수 있다. 물론 이런 생각을 잘 이어나가기 위해서는 생각의 재료가 되는 기본에 충실해야함은 물론이다. 어둠침침한 구석에 앉아 키보드만 두드리는 geek가 아닌 보다 인간다운 프로그래머가 되자.

4.4 참조 #

<PRE></PRE>
Posted by 1010