PHP에서 논란이 많은 것 중에 하나가 Exception 이외에 핸들링하기 어려운 Error가 또 있다는 것이다. 작은 규모의 웹 애플리케이션에서는 별 문제가 되지않고, 오히려 장점도 있을 수 있겠지만, 규모가 조금 큰 웹 애플리케이션에서는 문제가 될 수 있고, 특히 서버 콘솔 프로그램을 만들었다면 치명적일 수 있는 문제이다.

물론 Error도 set_error_handler()를 사용해서 핸들링할 수 있다고는 하지만, Exception 처리와는 별개로 처리하다보면 코드 흐름이 엉망이 되기가 쉽기도 하고 해서 Exception으로 변환해서 처리를 하는게 낫다고 생각이 들때가 있는데, 그럴때는 아래와 같은 코드를 사용하면 된다.

set_error_handler(function ($errno, $errstr, $errfile, $errline) {

    throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);

});

참고 : http://www.sitepoint.com/forums/showthread.php?632913-Converting-native-PHP-errors-to-Exceptions

"개발자 좀 살려주세요!", "굿바이 IE6" 등의 캠페인 등으로 브라우저 업그레이드를 해왔지만, 그때뿐이고 Active-X로 점철된 금융권 사이트와 정부 사이트로 인해서 우리나라에서 IE는 쉽게 버전 업그레이드 할 수 없다. 때문에 최신 버전이 이미 10대를 넘었지만, 아직도 7을 써야만 하는 사이트가 있는 우리나라 현실에서는 아직도 IE 버전 체크가 필요한 경우가 자주 있다.

버전을 구분하는 방법은 여러가지가 있지만, 다음은 간단히 PHP 코드를 사용해서 구분하는 예제 코드이다.

if (preg_match('/(?i)msie [2-7]/',$_SERVER['HTTP_USER_AGENT']))
{
// IE 7 이하
}

elseif (preg_match('/(?i)msie [8-9]/',$_SERVER['HTTP_USER_AGENT']))
{
// IE 8~9
}
else
{
// IE 10 이상
}


Yeoman을 사용하여 프론트엔드 개발을 하면서 한가지 아쉬운 점이 있었다. 단일 페이지로 구성된 웹 애플리케이션이라면 문제가 없겠지만, 페이지가 많아지면서 중복 코드가 많아진다는 점이었다. HTML에도 include같은 기능이 있으면 좋겠지만, 그것은 불가능하니 자바스크립트의 힘을 빌리거나 조립해서 하나의 화면을 만들어줄 서버 프로그램이 필요하다. 하지만, 프론트엔드 개발 진행중에 서버 코드와 별개로 HTML/CSS로만 작업을 하려다보니 발생한 문제인데, Yeoman의 구성인 Grunt에서 사용할 수 있는 grunt-bake를 사용하면 해결할 수 있었다.

간단히 다음 명령으로 설치할 수 있다.

$ npm install grunt-bake --save-dev

이제 Gruntfile.js 파일을 수정해야 한다.

grunt.initConfig( { bake: { your_target: { options: { // Task 옵션을 추가한다. }, files: { // 파일을 아래와 같은 형식으로 추가한다.
// "결과 파일": "작업 파일" "dist/index.html": "app/index.html" // etc ... } }, }, } )

위와 같이 설정을 해주면, app/index.html 내용은 아래와 같은 형식으로 작성하게 된다.

<html> <head></head> <body> <!--(bake includes/header.html)-->
<div class="content"> ... </div> <!--(bake includes/footer.html)--> </body> </html>

물론, includes/header.html 파일과 includes/footer.html 파일도 각 부분에 들어갈 내용으로 작성해준다.

이제 빌드를 위한 Task 설정을 수정할 차례이다. server Task와 build Task에 아래와 같이 "bake:build"를 추가해준다.

grunt.registerTask('server', function (target) {
    if (target === 'dist') {
        return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
    }
    grunt.task.run([
        'clean:server',
        'bake:build',
        'concurrent:server',
        'connect:livereload',
        'open',
        'watch'
    ]);
});
grunt.registerTask('build', [
    'clean:dist',
    'bake:build',
    'useminPrepare',
    'concurrent:dist',
    'concat',
    'cssmin',
    'uglify',
    'copy:dist',
    'rev',
    'usemin'
]);

그리고 livereload를 위해서 watch 설정에 페이지 부분 내용이 들어갈 폴더를 추가해준다.

watch: {
    compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server']
    },
    styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['copy:styles']
    },
    bake: {
        files: [ "<%= yeoman.app %>/includes/**" ],
        tasks: "bake:build"
    },
    livereload: {
        options: {
            livereload: LIVERELOAD_PORT
        },
        files: [
            '<%= yeoman.app %>/*.html',
            '.tmp/styles/{,*/}*.css',
            '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
            '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
    }
}

이제 모든 설정이 끝났다. 페이지에서 중복된 코드는 반복하지 말고, 모두 부분 HTML로 작성하면 된다.

긴 웹페이지에서 A태그를 사용해서 섹션별로 이동할 수 있도록 만들었는데, fixed로 고정되어있는 헤더가 이동한 섹션 일부를 가리는 문제가 발생했다. 다음은 이런 문제가 생기는 예제 코드이다. (문서 내용은 한글 Lorem Ipsum을 사용했다.)

위의 코드에서 헤더에 있는 각 링크를 클릭하면 동일한 페이지의 해당하는 섹션으로 이동하는데, 상단의 헤더가 고정되어 가리게 된다. 이 문제를 해결하기 위해서 스크립트를 사용하지 않고 CSS 스타일만으로 해결되는 간단한 방법이 없을지 여러 방법들을 테스트하다가 아래와 같이 코드를 작성하였다.

이 해결 방법의 핵심은 이동할 섹션 바로 앞쪽에 보이지 않는 요소를 헤더와 동일한 높이로 만들어서 섹션 id를 지정한다는 것이다. 바로 아래와 같은 코드이다.

/* CSS */
.anchor{
display: block;
height: 100px; /* 헤더의 height와 동일한 값 */
margin-top: -100px; /* 헤더의 height와 동일한 값 */
visibility: hidden;
}
<!-- HTML -->
...
<span class="anchor" id="section1"></span>
<div class="section"> ...

참고 : http://pixelflips.com/blog/anchor-links-with-a-fixed-header

"평생 개발자로 먹고 살 수 있다"는 부제를 가진 이 책은 정말 제목대로 유지보수하기 어렵게 코드를 작성하는 방법이 나와있다. 그렇다고 부제처럼 자기만 알아볼 수 있는 코드를 작성해서 평생 짤리지 않도록 하는 방법을 다뤘다기 보다는, 나쁜 예를 보여서 이렇게는 하지 말자고 반성하게 하는 거울과 같은 역할을 하는 책이다.

읽다보니 정말 별의별 상황이 다 있는데, 어찌보면 내가 겪어본 것도 있고, (의도적이 아니었더라도) 내가 하고 있어서 반성하게 하는 부분도 있었다. 몇가지를 옮겨보면 아래와 같다.

"언어의 규칙이 허용하는 범위 내에서 클래스, 생성자, 메소드, 멤버 변수, 파라미터, 지역 변수에 같은 이름을 사용하자. 이에 안주하지 말고 더 나아가서 {} 블록 내에서 이미 사용되고 있는 지역 변수명을 재사용할 수 있는지 고민하자."

"이유는 빼고 어떻게에 대해서만 문서화하라"

"피트, 미터, 통과 같은 측정 단위를 변수, 입력, 출력, 매개변수에 문서화는 절대 하지 않는다."

"에러나, 기기 크래쉬, OS 결함을 처리하는 코드는 절대 테스트 하지 않는다. OS가 반환하는 코드도 검사하지 않는다. OS가 반환하는 코드는 실행에 아무 도움이 되지 않으며 우리 테스트 시간만 오래 걸리게 한다. 게다가 우리 코드가 디스크 에러, 파일 읽기 에러, OS 크래쉬와 같은 모든 경우를 적절하게 처리하는지 어떻게 일일이 테스트 할 수 있겠는가? 도대체 왜 컴퓨터 시스템을 신뢰할 수 없는 것처럼 생각하고 교수대 같은 것이 제대로 동작하지 않는지 테스트해야 하는지 이해할 수가 없다. 최신 하드웨어에서는 에러가 발생하지 않는다."

"프로그램이 좀 느리다고? 고객에게 더 빠른 컴퓨터를 사라고 말하자. 성능 테스트를 수행했다면, 문제가 일어나는 지점을 찾았을 것이다. 아마 문제를 해결하려면 알고리즘을 변경해야 할 것이고 제품 전체를 완전히 다시 설계해야 하는 경우도 생길 수 있다."

"개발 도구에 포함된 라이브러리를 모른척해야 한다. 비주얼 C++을 사용한다면 MFC나 STL의 존재를 무시하고 문자열이나 배열을 손수 작정할 수 있다. 이렇게 하면서 자신도 모르게 포인터 기술이 좋아지고 동시에 코드를 확장하려는 시도를 좌절시킬 수 있다."

이 책은 무료 e-book으로 공개되어 다운로드도 가능하고, 웹사이트에서 기사 형식으로도 공개되어있다.

e-Book


O'Reilly에서 개최하는 웹 기술 컨퍼런스인 Fluent 2013이 미국 샌프란시스코에서 5월말에 열린다. 거기를 내가 갈 수 있는 것은 아니지만, 온라인으로 맛보기 컨퍼런스, 이른바 Fluent 2013 Preview를 하기에 잠도 안자고 들어봤는데, 꽤 들을만한 주제들이었다.

발표한 주제와 발표자들은 아래와 같다.

  • "this" in JavaScript: How It Really Works - Martha Girdler
  • Hardware Access and Device APIs with JavaScript & HTML5 - Wes Bos
  • Prototyping à la Node with Express – Pam Selle
  • Principles of AngularJS – Brad Green & Shyam Seshadri

Martha의 발표는 자바스크립트가 어렵게 느껴지는 사람에게 꼭 필요한 내용이었고, Wes의 Device API소개는 이런 것도 된다는 데모 위주의 발표였다. Pam의 발표는 node.js와 express를 사용하면 쉽게 프로토타입을 만들 수 있다는 그냥 뻔하게 느껴지는 발표여서 좀 실망했지만, 그래도 (발표의 의도와는 동떨어졌지만) JustinmindMixture.io라는 프로토타이핑 툴을 알게된 것은 소득이었다. 마지막 AngularJS 발표는 뭔가 문제가 있는지 진행 중에 슬라이드가 제대로 넘어가지 않아서 내용에 집중하기 어려웠고, 시간이 짧다보니 몇몇 특징들만 소개하는 것으로 끝났다. 발표 슬라이드는 O'Reilly에서 배포한 아래 링크에서 PDF로 다운로드 가능하다.

Device API는 발표 내용을 바탕으로 한번 시험해보면 재미있을 것 같고, AngularJS는 실제로 사용할만한지는 아직도 의문이긴 하지만, 제대로 살펴봐야겠다.

웹개발을 하다보면, 테스트 용도로 잠시 간단한 웹서버가 필요할 때가 자주 있다. 하지만, 이럴때마다 APMSETUP이나 XAMPP같은 것들을 설치하기는 조금 부담스러울때가 있다. 다양한 기능이 필요할때는 어쩔수 없지만, HTTP 요청/응답을 위해서 저걸 받아서 설치하자니 삽으로 할 일을 대형 장비를 가져다 쓰는 느낌이다. 그럴때 파이썬만 설치되어있다면 다음과 같이 간단하게 웹서버를 띄울 수 있다.

$ python -m SimpleHTTPServer 8000

8000은 원하는 포트 번호로 바꿔주면 되고, 실행한 경로를 웹 루트로 설정한다.

'Developing' 카테고리의 다른 글

Fluent 2013 Preview  (0) 2013.04.05
PHP에서 PDF 만드는 방법  (2) 2012.07.09
WebSocket 채팅 프로그램 예제 - 패러디 웨이브 <4>  (0) 2010.12.15

PHP에서 PDF 문서를 만들기 위한 방법에는 다음과 같은 것이 있다.

1. PDFlib

PHP 공식 사이트의 문서에 나와있는 PDF 생성방법이다. PHP 공식 사이트에 소개되어있는 만큼 가장 문서화가 잘 되어있다고 봐야할 것 같고, 제공하는 기능도 괜찮아 보인다. 하지만, PDFlib 자체는 유료 소프트웨어이므로 정식으로 사용하기 위해서는 라이센스를 구매해야한다.(구매하지 않으면, 생성하는 모든 페이지 위에 데모 문구가 찍혀서 나온다고 한다.) 비 상업적 용도로 사용한다면, PDFlib Lite 7을 사용하는 방법도 있다.(현재 유지보수는 하지 않고 있으며, 무료답게 기술 지원도 없다고 한다. 참고로 현재 PDFlib의 버전은 8이다.)

참고

2. FPDF

FPDF는 무료로 용도에 상관없이 사용할 수 있다. PDFlib에서 라이브러리를 설치해야하는 것과는 달리, PHP 라이브러리만 설치하면 되기때문에 설치도 쉽다. 게다가 ubuntu를 사용한다면, 저장소에 이미 있기때문에 더욱 간편하게 끝난다. 문서는 FPDF 공식 사이트에서 함수 설명과 예제를 제공한다.

참고

3. TCPDF

FPDF처럼 TCPDF도 무료로 용도에 상관없이 사용가능한 자유소프트웨어이다. 설치도 다운로드해서 파일을 include해서 사용하는 간편한 방식이다. 다만, FPDF가 ubuntu 패키지로 존재하는 것에 비해서 TCPDF는 별도로 다운로드 받아야한다. 그러나, FPDF가 기본적으로는 유니코드를 지원하지 않는 것에 비해서, TCPDF는 기본 지원한다.(FPDF의 경우, 한글은 CP949 코드셋을 사용한다.) 문서는 공식 사이트에서 PHPDoc과 예제를 제공한다.

참고

"패러디 웨이브가 어떤 것인지 빨리 한번 돌려보고 싶은 사람들을 위해서 간단한 사용방법을 적어보려고 한다. 먼저 프로그램및 발표 자료를 첨부한다.

ParodyWave-0.1a.zip 패러디 웨이브 0.1a
websocket.pdf 발표자료

먼저 Node.js 설치가 필요하다. Linux 등을 사용한다면 설치에 큰 어려움이 없겠지만, Windows를 사용한다면, cygwin을 먼저 설치하는 등 손이 좀 갈 것이다. 설치방법은 Node.js 웹사이트를 참고하여 다음과 같이 하면 된다.(Windows에서 설치해보고 싶은 사람은 여기를 참고하자.)

./configure
make
make install

설치가 끝나면 "패러디 웨이브" 파일을 다운로드 받아서 적당한 곳에 풀어놓고 다음과 같이 실행한다.

node WebSocketServer.js

그 뒤에 웹브라우저를 열고, 주소표시줄에 'http://localhost:8888'을 입력해서 다음과 같은 화면이 나오면 성공이다.

혹시 8888포트를 다른 곳에서 사용중이거나 다른 서버를 다른 곳에서 실행해서 접속 정보를 바꾸고 싶다면 다음 코드를 수정하면된다.

WebSocketServer.js 175줄
server.listen(8888);
ParodyWave.html 66줄
ParodyWaveClient.conn = new WebSocket("ws://localhost:8888");


이번에는 마지막으로 클라이언트 부분이다. 이 부분은 HTML5에서 제공하는 WebSocket API대로만 작성하면 되기때문에 크게 어려운 부분은 없다.

<!doctype html> 
<html> 
<head> 
    <meta charset="UTF-8" />
    <title>패러디 웨이브 version 0.1a</title>
	<style type="text/css"> 
		* { margin: 0; padding: 0; font-family: Helvetica; font-size: 9pt; }
		body { width: 100%; height: 100%; color; #444; overflow: hidden; }
		ul { list-style: none; }
		li { padding-left: 5px; font-weight: normal; color:#aaa; border-bottom: 1px dotted #ddd; padding-top: 5px; padding-bottom: 5px; }
		li:first-child { font-weight: bold; color: #000; }
		#thread-list { margin-top: 20px; }
		#thread-list li { border-style: none; display: inline; margin-right: 10px; }
		#welcome { position: fixed; width: 100%; height: 60px; margin-top: 200px; text-align: center; background-color: #888; padding-top: 30px; background: -moz-linear-gradient(center top, #444 0%,#222 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444),color-stop(1, #222));}
		#home { position: fixed; width: 100%; height: 200px; margin-top: 200px; text-align: center; background-color: #888; padding-top: 30px; background: -moz-linear-gradient(center top, #444 0%,#222 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444),color-stop(1, #222));}
		#header { display: none; position: fixed; width: 100%; height: 30px; left: 0; top: 0; background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444),color-stop(1, #222));}
		#header > h1 { color: #fff; font-weight: bold; font-size; 16px; float: left; margin-left: 10px; margin-top: 8px;}
		#join-count { float: right; height: 12px; margin-right: 5px; margin-top: 4px; background-color: #666; color: #ddd; padding: 3px; padding-left: 10px; padding-right: 10px; -webkit-border-radius: 10px 10px; font-size: 10px; background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #111),color-stop(1, #555)); }
		#input-container { position: fixed; width: 90%; height: 30px; bottom: 0; left: 10px; }
		#thread-container { width: 100%; height: 100%; overflow: auto; background-color: #fff; padding: 10px; padding-top: 30px;}
		input[type=text] { border: 1px solid #ccc; height: 25px; font-size: 14px; }
		input[type=button] { padding: 5px; padding-left: 15px; padding-right: 15px; font-size: 14px; border: 1px solid #e0a903; border-radius: 6px 6px; text-shadow: rgba(255,255,255,.5) 0 1px 0; background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffc000),color-stop(1, #e0a903)); background: -moz-linear-gradient(center top, #ffc000 0%,#e0a903 100%);}
		#thread > div { display: block; float: left; width: 180px; height: 250px; margin-right: 10px; margin-top: 5px; border: 1px solid #ddd; -webkit-box-shadow: 5px 5px 5px #aaa; -webkit-border-radius: 10px 10px;}
		#thread > div .title { background-color: #eee; padding: 3px; border-bottom: 1px solid #bbb; -webkit-border-top-left-radius: 10px 10px; -webkit-border-top-right-radius: 10px 10px; background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff),color-stop(1, #ccc));}
		#thread > div .title .msg-count { float: right; height: 12px; margin-right: 5px; margin-top: 3px; background-color: #fff; border: 1px solid #bbb; color: #444; padding-left: 3px; padding-right: 3px; -webkit-border-radius: 10px 10px; font-size: 10px; }
		#thread > div .title .msg-count:hover { background-color: #444; border: 1px solid #222; color: #ccc; cursor: pointer;  }
		#thread > div .title > h1 { display: inline; text-align: right; margin-right: 10px; text-overflow: ellipsis; font-size: 14px; }
		#thread > div div.article-container { clear: both; padding: 5px; height: 205px; overflow: hidden; }
		#thread > div .article { font-size: 12px; }
		#thread > div .article .before { color: #bbb; }
		#thread > div .article .current { color: #444; font-weight: bold; }
		#thread > div .article hr { stroke-width: 1px; margin-top: 5px; margin-bottom: 5px; }
		#line { width: 90%; }
	</style> 
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
    <script> 
    var ParodyWaveClient = {
        conn: null,
        user: "",
        thread: false,
        line: null,
        writing: false,
        currentValue: "",
		threadStore: null
	}
 
    $(document).ready(function () {
		var docWidth = $(document).width();
		var docHeight = $(document).height() - 30;
		
		$("#thread-container").css("height", docHeight+"px");
		
        $("#new-thread").click(openThread);
        $("#connect").click(function () {
            if (! ParodyWaveClient.conn) login();
            else logout();
        });
    });
 
    window.onunload = function () {
        if (ParodyWaveClient.conn) ParodyWaveClient.conn.close();
    }
    function login() {
        if("WebSocket" in window) {
            debug("connecting...");
            ParodyWaveClient.conn = new WebSocket("ws://localhost:8888");
            ParodyWaveClient.conn.onopen = function () {
                debug("connection established");
                ParodyWaveClient.user = $("#userid").val();
                $("#welcome>input").attr("disabled",true);
                $("#connect").val("LogOut").attr("disabled",false);
                $("#home").show();
				$("#welcome").hide();
				
                sendMsg({cmd: "status"});
                sendMsg({cmd: "list"});
 
				ParodyWaveClient.threadStore = new Array();
				
                setInterval(function() {
                  ParodyWaveClient.conn.send('HB');
                }, 60000);
            }
            ParodyWaveClient.conn.onmessage = onWebSocketMessage;
            ParodyWaveClient.conn.onclose = onWebSocketClose;
        }
        else {
            alert("This web browser doesn't support WebSocket!");
        }
    }
    function logout() {
        ParodyWaveClient.conn.close();
    }
    function openThread() {
        sendMsg({cmd: "open", user: $("#userid").val(), title: $("#title").val()});
    }
    function joinThread(idx) {
        sendMsg({cmd: "join", user: $("#userid").val(), id: idx});
    }
    function debug(str) {
        if ($("#chk-debug").attr("checked"))
            console.log(str);
    }
    function sendMsg(msg) {
        if (ParodyWaveClient.conn) ParodyWaveClient.conn.send(JSON.stringify(msg));
    }
    function onWebSocketMessage(e) {
        console.log(e.data);
        var msg;
        try {
            msg = JSON.parse(e.data);
        }
        catch (e) {
            debug("Invalid message");
            msg = {type: "error"};
        }
        switch (msg.type) {
            case "status":
                $("#join-count").text("Connection: "+msg.connections);
                break;
            case "join":
                if (msg.data) {
                    for (var i = 0 ; i < msg.data.length ; i ++) {
						addThreadData("new", msg.data[i][0], i, msg.data[i][1], msg.data[i][2]);
					}
                }
                $("#input-container").show();
                $("#home").hide();
 
				$("#header > h1").html("패러디 웨이브 version 0.1a");
				$("#header").show();
				
                ParodyWaveClient.currentValue = "";
                
                $("#line").bind("keyup", function (event) {
                    cmd = {cmd: 'update'};
                    if (! ParodyWaveClient.writing) {
                        ParodyWaveClient.writing = true;
                        cmd.cmd = 'new';
                    } else if (event.keyCode == 13 && event.srcElement.value != "") {
                        cmd.cmd = 'submit';
                        event.srcElement.value = "";
                        ParodyWaveClient.writing = false;
                    } else if (ParodyWaveClient.currentValue == event.srcElement.value) return;
                    cmd.value = ParodyWaveClient.currentValue = event.srcElement.value;
                    sendMsg(cmd);
                });
                break;
            case "list":
                $("#thread-list").html("");
                for (var i = 0 ; i < msg.threads.length ; i ++) {
                    if (! msg.threads[i]) continue;
                    $("#thread-list").append("<li><input type='button' onclick='joinThread(" + msg.threads[i][0] + ")' value='"+msg.threads[i][1]+"방에 조인' /></li>");
                }
                break;
            case "update":
                if (typeof msg.value != "string") addThreadData("new", msg.id, msg.line, msg.user, msg.value);
                else addThreadData("update", msg.id, msg.line, msg.user, msg.value);
        }
    }
 
	function addThreadData(type, id, lineNo, user, msg) {
		if (msg == undefined) msg = "<img src='wait.png' align='absmiddle'>";
		if (!$.isArray(ParodyWaveClient.threadStore[id])) {
			ParodyWaveClient.threadStore[id] = new Array();
			ParodyWaveClient.threadStore[id].push(new Array(lineNo, user, msg));
		} else {
			var matchLineNo = false;
			
			for (i = 0; i < ParodyWaveClient.threadStore[id].length; i++) {
				if (ParodyWaveClient.threadStore[id][i][0] == lineNo) { 
					matchLineNo = true;
					ParodyWaveClient.threadStore[id][i][2] = msg;
					break;
				}
			}
			
			if (!matchLineNo) ParodyWaveClient.threadStore[id].push(new Array(lineNo, user, msg));
		}
		
		if (ParodyWaveClient.threadStore[id].length == 1 && type == "new") { // new 
	        $("#thread").append('<div id="fdata'+id+
				'"><div class="title"><img src="person.png" align="absmiddle"><h1>'+ParodyWaveClient.threadStore[id][0][1]+
				'</h1><div class="msg-count" boxid="'+id+'" onclick="toogleHistory(this);">1</div></div><div class="article-container"><ul class="article"><li class="line-'+lineNo+'">'+ParodyWaveClient.threadStore[id][0][2]+
				'</li></ul></div></div>');
		} else {
			for (i = 0; i < ParodyWaveClient.threadStore[id].length; i++) {
				if ($("#fdata"+id+" .article li.line-"+lineNo).length <= 0)
					$("#fdata"+id+" .article").prepend("<li class='line-"+lineNo+"'>"+ParodyWaveClient.threadStore[id][i][2] + "</li>");
				else 
					$("#fdata"+id+" .article li.line-"+lineNo).html(ParodyWaveClient.threadStore[id][i][2]);
			}
			
			$("#fdata"+id+" .title .msg-count").text(ParodyWaveClient.threadStore[id].length);
		}
	}
	
	function toogleHistory(f) {
		var idf = "#fdata"+$(f).attr("boxid")+" .article-container";
		if ($(idf).css("overflow-y") == "auto")
			$(idf).css("overflow-y", "hidden");
		else 
			$(idf).css("overflow-y", "auto");
	}
 
    function onWebSocketClose() {
        debug('Disconnected');
        $("#welcome>input").attr("disabled",false);
        
        // initiate text input
        $("#userid").val("");
        $("#title").val("");
        $("#line").val("");
        $("#connect").val("LogIn")
        
        // close others
        $("#home").hide();
        $("#input-container").hide();
        $("#thread").html("");
        $("#users").text("");
        $("#welcome").show();
 
        // initiate status
        ParodyWaveClient.conn = null;
        ParodyWaveClient.user = "";
        ParodyWaveClient.thread = false;
        ParodyWaveClient.line = null;
        ParodyWaveClient.writing = false;
        ParodyWaveClient.currentValue = false;
        
        // unbind event
        $("#line").unbind();
    }
    </script> 
</head> 
<body> 
    <div id="welcome"> 
 
        <input type="text" id="userid" placeholder="대화명을 입력해주십시오."/> 
        <input type="button" id="connect" value="로그인" /> 
        <input type="checkbox" id="chk-debug" title="Show Debug Message"/> 
    </div> 
 
    <div id="home" style="display:none;"> 
        <input type="text" id="title" placeholder="생성할 방제목을 입력해주십시오."/> 
        <input type="button" id="new-thread" value="Create!" /> 
        <div> 
 
            <ul id="thread-list"></ul> 
        </div> 
    </div> 
 
	<div id="header"> 
		<h1>패러디 웨이브</h1> 
		<div id="join-count"></div> 
	</div> 
 
	<div id="thread-container"> 
    	<div id="thread"></div> 
	</div> 
 
    <div id="input-container" style="display:none;"> 
        <img src="chat.png" align="absmiddle"> <input type="text" id="line"/> 
    </div> 
    <div id="status" style="display:none;"> 
        <p id="users"></p> 
 
    </div> 
</body> 
</html>

login 메소드가 WebSocket 접속을 시도하는 부분인데, 64줄의 조건문으로 웹브라우저가 WebSocket을 지원하는지 여부를 판단할 수 있다. 66줄에는 접속하는 호스트 주소가 들어가는데, 환경에 따라서 적당히 바꾸면 되겠다.

80줄에는 타이머 설정이 있는데, 접속 유지를 위한 HeartBeat 코드가 들어가 있다. 서버에서 임의로 적용해놓은 타임아웃 설정 이외에도 웹브라우저에서 기본으로 적용하는 타임아웃이 있기때문에 사용중에 접속이 끊겨버리는 일을 방지하기 위해서는 이러한 코드가 필요하다. 현재 WebSocket Protocol에는 타임아웃에 대한 처리는 없다.

67, 84, 85줄에서는 각각 open, message, close 이벤트에 대한 핸들러를 지정하고 있다. 역시 대부분의 동작은 message 이벤트 핸들러에서 수행하게 된다.

+ Recent posts