FileCube 버전 0.0.3 (설치 방법 및 기타 관련 정보)

등록일시: 2003-05-26 09:31,  수정일시: 2018-04-07 23:28
조회수: 23,209
본문은 최초 작성 이후, 약 21년 이상 지난 문서입니다. 일부 내용은 최근의 현실과 맞지 않거나 동떨어져 있을 수 있으며 문서 내용에 오류가 존재할 수도 있습니다. 또한 본문을 작성하던 당시 필자의 의견과 현재의 의견에 많은 차이가 존재할 수도 있습니다. 이 점, 참고하시기 바랍니다.

목차

FileCube 버전 0.0.3 개요

본 사이트를 둘러보신 분들은 아마도 한 번쯤은 사용해본 적이 있으실 것으로 생각되는데, FileCube는 필자의 사이트에서 제공하는 일종의 파일 다운로드 프로그램이다. 아직 사용해본 적이 없으신 분들은 기본 모드에서는 좌측 메뉴에, Index Tree 모드에서는 우상단에 위치한 FileCube 링크를 클릭해보기 바란다. 또는, 다음 링크를 클릭해도 FileCube에 접근이 가능하다.

노트: 본 사이트가 ASP.NET MVC 기반으로 이행된 2013년 말 이후부터 더 이상 ASP 기반의 FileCube는 서비스되지 않고 있으므로 유의하기 바란다. FileCube 역시 ASP.NET MVC 기반으로 이행 중인 상태로 소스 공개 여부는 아직 확정되지 않았다. 참고로, ASP 기반의 마지막 FileCube 버전은 0.0.7 Prepare SP1이며 여기에서 다운로드 가능하다.

간단히 살펴보면 쉽게 알 수 있는 것처럼 프로그램 자체는 매우 단순한 구조를 갖고 있으며 파일 탐색기와 유사한 인터페이스를 보여준다. 이는 FileCube을 제작하면서 파일 탐색기를 기본 모델로 했기 때문이다. 제공되는 기능도 매우 단순한데, 사용자가 지정한 설치된 웹 서버의 특정 폴더를 루트 폴더로 삼고, 좌측 패널에는 해당 폴더의 하위에 존재하는 모든 폴더들을 트리 형태로, 우측 패널에는 좌측 트리에서 선택된 폴더의 하위 폴더와 파일들을 목록 형태로 보여준다. 그리고, 우측 패널에 출력된 파일 목록에서 임의의 파일을 선택하면 선택된 파일의 종류와 상관 없이 무조건 다운로드 대화 상자를 출력해서 사용자가 파일을 다운로드 받을 수 있도록 해준다. 결국, 파일 탐색기와 비슷한 인터페이스를 제공해주는 다운로드 전용 ASP 프로그램이라고 말해도 그리 틀린 말은 아니다.

개인적으로 재미있는 점은 FileCube의 기본이 되는 프로그램은 이미 2000년 경에 만들어졌으며 오히려 지금보다 더 다양한 기능을 갖고 있었다는 사실이다. 가령, 파일 업로드 기능을 비롯해서 폴더 추가, 폴더 및 파일의 복사, 이동, 삭제 등 오히려 현재의 FileCube보다 파일 탐색기의 기능을 더 충실히 구현한 기능을 갖추고 있었다. 그도 그럴 것이 이 프로그램의 초기 목적 자체가 브라우저에서 FTP 클라이언트가 제공해주는 대부분의 기능을 구현하는 것이었으므로 어찌보면 이는 당연한 일이다.

그에 비하면 현재의 FileCube는 본 사이트의 구축 초기에 프리랜서로 근무하던 필자가 잦은 이동 중 빈번하게 사용되는 문서나 프로그램의 소스를 올려놓고, 필요할 때마다 웹에서 바로 다운로드 받아서 사용하기 위한 목적으로 작성한 것으로 비교적 간단한 기능만 추려낸 것이다. 물론, 필자 스스로도 필요에 따라 지속적으로 FileCube의 기능들을 개선시켜 나가겠지만, 앞에서 얘기한 것 같은 기능들이 필요한 분은 스스로의 업무 요구에 적합하게 직접 기능을 구현해서 FileCube에 새로운 기능을 추가해보는 것도 나름대로 흥미로운 작업이 될 수 있을 것이라고 생각한다.

그리고, 근간이 되는 프로그램 자체가 조금 오래된 프로그램이다보니 이제 와서는 개선이 필요한 부분들도 제법 많다. 대표적인 예로, 루트 폴더 하위에 존재하는 모든 하위 폴더들의 목록을 얻어오는 재귀함수가 상당히 비효율적인 구조로 되어 있다. 그래서, 하위 폴더의 갯수가 많아지면 많아질수록 폴더 트리의 초기화 속도가 기하급수적으로 늦어진다. 보다 빠른 실행 속도를 얻기 위해서는 반드시 개선이 필요한 부분 중 하나라고 생각한다.

본문은 FileCube 버전 0.0.3의 설치 방법과 사용된 몇 가지 기술들, 그리고 관련된 기타 정보들에 관한 글로써 일종의 메뉴얼 같은 성격을 갖고 있는 글이다. 사실, 스스로도 FileCube가 그리 대단한 프로그램이라고 생각하지는 않는다. 앞에서도 얘기했지만 기능도 단순하고 반드시 개선되야 할 부분들도 많이 갖고 있다. 더 솔직히 얘기하자면 필자는 그동안 FileCube에 대해서 별다른 생각을 갖고 있지 않았다. 그런데, 예상 밖으로 게시판이나 메일을 통해서 FileCube에 관심을 가져주시는 분들이 상당히 많으셔서 다운로드 받을 수 있도록 소스 파일들을 정리하고 주석을 달던 중, 설치 방법이나 기타 몇 가지 기술적인 설명이 담긴 문서의 필요성을 느끼게 되어 본문을 마련하게 되었다.

FileCube 버전 0.0.3 다운로드 및 설치 방법

압축 파일로 제공되는 FileCube 0.0.3 버전의 소스 파일은 다음 링크나 사이트에서 실제로 서비스되고 있는 FileCube를 통해서 다운로드 받을 수 있다.

FileCube_ASP_0.0.3.zip (45k)

다운로드가 정상적으로 끝나면 설치하고자 하는 가상 웹 서버 하위의 적당한 폴더로 다운로드 받은 압축 파일을 이동한 후, 압축을 풀어준다. 이 때, 압축이 풀린 프로그램 소스 파일의 설치 위치에는 아무런 제약도 없지만, 소스 파일들이 위치한 각각의 폴더들은 반드시 다음과 같은 상대적 위치를 갖고 있어야 한다. 폴더들 간의 위치와 파일명, 폴더명만 동일하게 유지되면 어떤 위치에 설치되더라도 프로그램 실행에 영향을 주지 않는다.

FileCube FileCube의 루트에 해당하는 폴더로 폴더명은 자유롭게 변경 가능
image FileCube에서 사용되는 모든 이미지 파일들이 위치해 있는 폴더
lib Library 폴더, FileCube에서 공통으로 사용되는 함수 및 설정 정보 상수 등을 담고 있는 파일들이 위치해 있는 폴더
asp  
css  
js  
vbs  
Storage Root 실제 데이터 파일들과 하위 폴더들이 저장되는 폴더

이 폴더들을 하나 씩 살펴보면, 먼저 FileCube 폴더(이 그림에서 가장 상단에 위치한 폴더, 프로그램 자체를 말하는 것이 아님)는 프로그램 루트에 해당하는 폴더로 그 이름은 임의로 변경할 수 있으며, 프로그램의 핵심 부분에 해당하는 다섯 개의 ASP 파일과 한 개의 Text 파일을 포함하고 있다. 대부분 이 폴더의 이름은 URL에 그대로 드러나게 되므로 각자 사이트 환경에 맞게 변경해서 사용하는 것도 괜찮다. 이 폴더에 위치한 ASP 파일들에 대해서는 잠시 후에 자세히 살펴보도록 하자.

다음으로 image 폴더에는 폴더명 그대로 FileCube에서 사용되는 모든 이미지 파일들이 위치해 있다. FileCube 자체가 매우 간단한 프로그램이라서 사용되는 이미지 파일들도 그다지 많은 편은 아니다. 트리 형태를 구성하는 이미지 파일들 몇 개와 데이터 파일들의 아이콘을 나타내는 이미지 파일들로 구성되어 있다. 가령, 다음과 같은 이미지 파일들인데 아마 눈에 익숙한 이미지들이 많이 있을 것이다.

기본적으로 제공되는 아이콘 이미지 파일들은 대략 오십여 개 정도로, 필자가 임의로 인터넷에서 비교적 빈번하게 사용된다고 생각되는 파일들을 선정한 것이다. 물론, 여러분이 필요한 아이콘을 직접 등록해서 사용하는 것도 가능하며 그 방법은 잠시 후에 다시 자세히 살펴보도록 하겠다. 정상적으로 등록된 아이콘 이미지 파일들은 FileCube에서 사용자가 선택한 폴더에 존재하는 실제 파일들의 확장자에 따라 자동적으로 분류되어 해당 확장자에 할당된 아이콘 이미지를 출력할 때 사용된다.

세 번째 폴더인 lib 폴더는 일종의 Library 폴더로 asp 폴더, css 폴더, js 폴더, vbs 폴더, 이렇게 모두 네 개의 하위 폴더들을 갖고 있다. 이 하위 폴더들은 각각 폴더명과 동일한 파일 타입의 FileCube 공통 파일들을 포함하고 있으며, 역시 이 파일들에 대해서도 본문을 진행해나가면서 해당 파일에 대한 설명이 필요할 때마다 하나씩 자세히 살펴보도록 하겠다.

가장 마지막 폴더인 Storage Root 폴더는 데이터 파일들과 하위 폴더들이 저장되는 데이터 루트 폴더로, 이 폴더의 하위에 위치한 모든 폴더와 파일들이 FileCube에 출력된다. 물론, 이 데이터 루트 폴더의 위치도 원하는 대로 자유롭게 변경할 수 있다. 데이터 루트 폴더로 사용될 폴더에는 그 사용 목적에 부합하는 권한 설정이 되어 있어야 한다.

FileCube 버전 0.0.3 환경 설정

대부분 소스 파일을 복사하는 것만으로도 거의 모든 환경 설정이 끝난다. 유일하게 추가적인 환경 설정이 요구되는 부분이 바로 자신이 원하는 데이터 루트 폴더의 위치를 지정해주는 부분이다. lib 폴더의 하위 폴더 중에서 asp 폴더를 살펴보면 fc_constant_info.asp라는 파일이 있는데, 바로 이 ASP 파일이 FileCube 프로그램의 환경 설정 파일이다. 파일을 열어보면 다음과 같이 CONST_HOSTING_ROOT 상수를 설정하는 코드를 쉽게 찾아볼 수 있을 것이다.

  '****************************************************************
  '* CONST_HOSTING_ROOT 상수 : 자료 파일들이 저장되는 폴더들의 최상
  '* 위 폴더를 지정한다. 다음의 두 가지 유형 중 임의대로 한 가지 유
  '* 형을 선택하여 지정할 수 있다. 그러나 Server.MapPath() 메서드를
  '* 사용하는 경우에는 상수를 사용할 수 없으므로 일반 변수를 사용하
  '* 여 정보를 설정한다.
  '****************************************************************
  
  '** 유형 1.
  '** Dim CONST_HOSTING_ROOT
  '** CONST_HOSTING_ROOT = Server.MapPath("/download")
  
  '** 유형 2.
  Const CONST_HOSTING_ROOT = "C:\Inetpub"

바로 이 CONST_HOSTING_ROOT 상수에 데이터 루트 폴더의 전체 경로 정보를 저장하게 되는데, 주석에 설명된 것처럼 두 가지 유형의 방법 중 마음에 드는 방법을 임의로 선택해서 사용할 수 있다. 이 코드에서 볼 수 있듯이 주석을 최대한 상세하게 입력해놨기 때문에 처음 사용하는 분도 별다른 어려움 없이 설정을 마칠 수 있을 것이라고 생각한다. 만약, 기본으로 제공되는 Storage Root 폴더를 데이터 루트 폴더로 사용하지 않는다면 해당 폴더는 그냥 삭제해버려도 무방하다.

그리고, 이 파일에는 다음과 같이 CONST_HOSTING_ROOT 상수 말고도 CONST_ECF_FILENAME이라는 이름의 상수가 하나 더 정의되어 있다. 이 CONST_ECF_FILENAME 상수는 FileCube에서 내부적으로 사용되는 고유의 폴더 정보 파일인 ECF 파일의 기본 파일명을 저장한다.

  '****************************************************************
  '* CONST_ECF_FILENAME 상수 : 기본 ECF 파일명을 지정한다. ECF 파일
  '* 은 일반적인 HTML 문으로 구성된 텍스트 형태의 파일로서, 해당 폴
  '* 더에 대하여 추가적인 설명이나 주석등을 달고자 하는 경우 사용할
  '* 수 있다. 해당 폴더내에 ECF 파일이 존재하는 경우 FileCube 는 그
  '* ECF 파일의 내용을 읽어서 그대로 출력한다.
  '****************************************************************
  
  Const CONST_ECF_FILENAME = "_comment_.ecf"

즉, 사용자가 선택한 폴더에 ECF 파일 상수에 지정한 파일명과 같은 이름의 파일이 존재하는 경우, 이 파일은 우측 패널의 파일 목록에 출력되지 않는다. 그대신 FileCube는 ECF 파일을 읽고 다음과 같이 파일 목록 하단에 그 내용을 그대로 출력한다. 따라서, 특정 폴더에 대한 기타 정보나 각종 주의 사항 등의 내용을 표시하고 싶은 경우, CONST_ECF_FILENAME 상수에 지정한 파일명과 동일한 이름으로 텍스트 파일을 하나 만든 다음, 출력하고 싶은 내용을 그 파일에 입력해서 해당 폴더에 저장하면 된다. 물론, ECF 파일 기능을 사용하고 싶지 않다면 이 상수는 무시해도 무방하다.

ECF 파일이 출력된 상태

여기까지 모든 설정 과정을 정상적으로 마쳤다면 웹 브라우저를 통해서 FileCube의 동작을 직접 확인할 수 있을 것이다. 참고로, 기본 문서는 FileCube 폴더에 위치한 index.asp다.

FileCube 버전 0.0.3 아이콘 이미지 파일 관리

새로운 파일 형식을 위해서 아이콘 이미지를 추가하는 방법도 매우 간단하다. 먼저, 해당 파일의 아이콘 이미지 파일을 준비한다. 아이콘 이미지 파일은 투명 GIF 형식이어야 하고, 그 크기는 16 X 16 픽셀을 넘지 않는 편이 좋다. 그리고, 이 때 반드시 지켜야만 하는 규칙이 한 가지 있는데 아이콘 이미지 파일의 파일명은 언제나 'Icon + 확장자 + .gif'이어야 한다는 것이다. 가령, 확장자가 abc인 파일의 아이콘 이미지 파일의 파일명은 IconABC.gif이어야만 하는 것이다. 이미지 파일이 준비되었다면 해당 파일을 image 폴더에 저장한다.

이제 아이콘 이미지 파일의 준비가 마무리되었으므로 환경 설정만 마치면 FileCube에서 바로 해당 아이콘을 인식하게 된다. lib 폴더의 하위에 위치한 asp 폴더를 살펴보면 fc_icons_info.asp라는 파일이 있는데, 이 파일은 순수하게 파일 확장자 정보에 대한 설정만을 위해서 준비된 파일이다. 여기에는 objIconInfo라는 이름의 딕셔너리 개체 변수 하나가 준비되어 있으며 바로 이 변수에 모든 파일 확장자의 정보가 저장된다. 새로운 파일의 확장자 정보를 등록하려면 objIconInfo 딕셔너리 개체 변수에 Add() 메서드를 사용해서 파일의 확장자를 Key로, 파일의 설명글을 Value로 하는 새로운 Itme를 추가한다. 즉, 파일의 확장자가 smp이고 해당 파일의 설명글을 '샘플 프로그램 데이터 파일'로 표시하려고 한다면 다음과 같이

  .Add "SMP", "샘플 프로그램 데이터 파일"

라는 라인을 fc_icons_info.asp 파일에 한 줄 추가시켜주면 되는 것이다. 실제 코드에서는 With 문을 사용했기 때문에 objIconInfo 딕셔너리 개체 변수의 명시적인 호출이 생략됬다는 점에 주의하기 바란다. 그리고, 반대로 사용하지 않는 아이콘의 등록을 제거하는 방법은 지금까지의 등록 과정과 정확하게 반대로 처리하면 되므로 굳이 이 자리에서 따로 설명하지는 않도록 하겠다.

참고로, 확장자 정보가 등록된 파일들을 제외한 나머지 모든 파일들은 다음과 같이 UnKnown 파일로 표시된다.

UnKnown 파일 아이콘 'UnKnown 파일' 아이콘

FileCube 버전 0.0.3 적용 기술

개요

이미 몇 차례 언급했지만 FileCube 버전 0.0.3에는 그리 특별한 프로그래밍 기술이 사용되지는 않았다. 그럼에도 불구하고 굳이 이슈가 될만한 부분들을 억지로 찾아본다면, ADO의 Stream 개체를 이용해서 구현된 다운로드 부분과 JavaScript를 이용해서 구현된 좌측 패널의 트리 구조 정도를 생각해볼 수 있을 것이다. 그러나, 이 역시도 인터넷에 대부분 공개된 기술들이므로 새로운 기술이라고 말할 수는 없으며, 본문에서는 순수하게 FileCube에 사용된 해당 기술을 조금 상세하게 설명하는 정도로만 얘기를 마무리하도록 하겠다.

ADO의 Stream 개체를 이용한 다운로드

그럼 먼저 ADO의 Stream 개체를 이용한 다운로드 기술에 대해서 알아보자. 이 기술은 일견 간단하게 보일런지 모르지만 생각하는 것만큼 그리 만만하기만 한 기술은 아니라는 것이 필자의 개인적인 생각이다. 단적으로 말해서 HTTP 프로토콜의 헤더와 ADO의 Stream 개체, 그리고 Response.BinaryWrite() 메서드에 대한 충분한 지식 없이는 애시당초 단순히 코드를 복사해서 기계적으로 사용하는 것 이상의 이해는 불가능하다. 다운로드 처리 코드가 들어있는 파일은 FileCube의 루트 폴더에 위치한 download.asp 파일인데, 전체 코드 중에서 실제로 다운로드와 관련된 부분은 후반부의 이십여 라인 정도에 불과하다. 코드를 살펴보기 전에 먼저 웹 브라우저에서 이뤄지는 파일 다운로드의 개념에 대해서 생각해보자.

본질적으로 웹 브라우저를 이용한 파일 다운로드의 실체는 서버에서 읽어들인 파일을 HTTP 프로토콜을 사용해서 전송하고 웹 브라우저가 전송받은 파일을 로컬 머신에 저장하는 일련의 과정이다. 웹 브라우저가 하는 작업은 대부분 이 범주를 벗어나지 않는다. 가령, 웹 페이지를 불러오는 작업도 서버에서 읽어들인 HTML 파일을 HTTP 프로토콜을 사용해서 전송하고 웹 브라우저가 전송받은 HTML 파일을 화면에 렌더링해주는 것일 뿐이다.

두 작업의 차이점은 단 한 가지뿐인데, 어떤 경우에는 전송받은 파일을 로컬 머신에 저장하고 어떤 경우에는 그 대신 화면에 렌더링한다는 것 바로 그것뿐이다. 웹 브라우저는 이미 이 두 가지 기능을 다 갖고 있으며 단지 무언가 서버로부터 보내지는 특정 정보를 기준으로 저장을 할 것인지 아니면 화면에 렌더링을 할 것인지를 결정하게 된다. 그렇다면 웹 서버쪽에서는, 아니 보다 더 정확하게 말해서 ASP 프로그램에서는 바로 이 처리의 기준이 되는 정보에 파일을 저장하라는 약속된 어떤 값을 설정해야만 한다는 결론이 나온다.

그렇다면 이제 그 처리의 기준이 되는 정보가 무엇인지, 그리고 거기에 무슨 내용을 설정하면 다운로드가 이뤄지는지 등을 조사할 차례이다. 그러면, 이런 내용들은 과연 어디에서 얻어낼 수 있는 것일까? 웹 서버와 웹 브라우저간의 통신 프로토콜은 HTTP 프로토콜이므로 이에 관해 궁금한 점들은 당연히 HTTP 프로토콜의 표준 규약을 조사해보면 될 일이다. 다음 링크는 W3C.org(World Wide Web Consortium) 사이트에서 찾은 RFC 2616 문서다.

이 문서를 살펴보면 [19.5.1] 섹션에 Content-Disposition라는 헤더에 대한 설명이 있는데, 마지막 부분에서 다음과 같은 내용을 발견할 수 있을 것이다.

If this header is used in a response with the application/octet-stream content-type, the implied suggestion is that the user agent should not display the response, but directly enter a `save response as...' dialog.

웹 서버에서 웹 브라우저로 응답을 보낼 때, 컨텐트 타입을 Application/Octet-Stream으로 설정하고 Content-Disposition 헤더에 어떤 값을 설정해서 전송하면 사용자 에이전트는 응답을 출력하는 대신 save response as... 대화 상자를 띄워야 한다는 뜻이다. 가령, 인터넷 익스플로러를 기준으로 얘기하면, '파일 다운로드' 다이얼로그 박스가 나타나게 되는 것이다.

참고로 원문에서 웹 브라우저라는 용어 대신 사용자 에이전트(User Agent)라는 용어를 사용한 것은, 이 표준이 웹 브라우저만 대상으로 하는 것이 아니라 HTTP 프로토콜을 사용하는 모든 서버와 클라이언트를 대상으로 하기 때문이다. 즉 프로토콜은 통신 규약일 뿐 , 실제로 이런 처리를 구현하는 것은 해당 프로토콜을 사용하는 서버와 클라이언트라는 사실을 잊지 말아야 한다. 따라서 만약, 프로토콜을 사용하는 클라이언트가 프로토콜을 구현함에 있어서 잘못된 처리를 가지고 있는 경우, 프로토콜에 정의된 것과 다른 잘못된 처리를 보일 수도 있는 것이다. 그리고, 실제로 인터넷 익스플로러의 일부 버전은 이런 이유로 인한 약간의 버그를 갖고 있다. 이에 관해서는 잠시 후에 더 자세히 알아보기로 하자.

이제 이런 요구 사항들을 ASP 코드로 구현해보면 다음과 같은 코드를 쉽게 구성할 수 있을 것이다.

  Response.ContentType = "Application/Octet-Stream"
  Response.AddHeader "Content-Disposition", "Content-Disposition 헤더 정보"

그러나, 문제가 한 가지 더 남아있다. 아직 이 Content-Disposition 헤더에 어떤 내용을 설정해야 하는지 모르는 것이다. RFC 2616 문서의 본문을 자세히 읽어보면 RFC 1806 문서에 대한 언급이 나오는데, Content-Disposition에 대한 정의가 바로 RFC 1806 문서로부터 비롯되었다는 내용이다. W3C.org의 메인 페이지에서 RFC 1806에 대해서 검색한 결과 다음과 같이 해당 문서를 찾을 수 있었다.

이 문서에 따르면 Content-Disposition 헤더에는 inline과 attachment 중에서 한 가지 값을 지정할 수 있으며, filename 파라미터로 전송된 파일을 외부 매체에 저장할 때 해당 파일의 기본 파일명을 제안할 수 있다. 그리고, inline과 attachment 간의 차이점은 inline은 전송된 파일을 즉시 출력하기 위해서 시도하는 반면, attachment는 바로 출력하지 않고 사용자가 출력 여부를 선택할 수 있는 추가적인 기회, 즉 '파일 다운로드' 대화 상자를 출력해서 사용자에게 파일을 열 것인지, 아니면 저장할 것인지 등을 선택할 수 있는 기회를 제공한다는 점이다.

다만, inline 값을 설정했다고 해서 항상 파일의 내용이 출력되는 것은 아닌데, 일반적으로 웹 브라우저나 운영체제가 해당 파일의 MIME 타입이나 확장자를 이해하는 경우에만 그 내용이 출력된다. 그러나, 이 주제는 본문의 범위를 벗어나는 내용이므로 여기에서는 더 이상 언급하지는 않겠다. 관심이 있으신 분들은 각자 개인적으로 MIME 타입에 관해서 조사해보시기 바란다.

결국 지금까지의 내용을 종합해서 반영해보면 다음과 같은 코드가 구성된다.

  Response.ContentType = "Application/Octet-Stream"
  Response.AddHeader "Content-Disposition", "Attachment; Filename=" & Trim(sFileName)
  Response.AddHeader "Content-Length", lngFileSize

이 코드에서 sFileName 변수는 실제로 전송할 파일의 파일명을, 그리고 lngFileSize 변수는 그 파일의 크기를 의미한다. 마지막 라인에 추가된 Content-Length 헤더와 관련된 라인은 없어도 다운로드에는 지장이 없지만, 이 라인이 없으면 다운로드시 대화 상자에 파일 크기가 출력되지 않아 불편하므로 가급적 추가해주는 것이 바람직하다.

이것으로 파일을 다운로드하기 위한 기본적인 정보 설정이 모두 끝났다. 이제 남아있는 과정은 실제로 해당 파일을 읽어들인 후 클라이언트로 전송하는 작업뿐이다. 이론적으로는 거기까지 작업을 마침으로서 파일 다운로드의 전 과정이 끝나게 된다. 그러나, 언제나 그렇지만 현실이란 그렇게 낙관적이지만은 않으며 더군다나 익히 알고 있는 것처럼 IT의 세계에서는 그 정도가 더욱 두드러진다.

원칙적으로는 지금까지 설명한 파일 다운로드에 대한 접근 방법을 사용해서 프로그램을 구현할 경우 어떠한 상황에서도 이상적으로 전 과정이 실행되야만 한다. 그러나, 실제로 프로그램을 실행시켜보면 상황에 따라 다양한 오류가 발생하는 것을 볼 수 있는데, 이 오류들을 가급적 최소화하기 위해서는 추가적인 작업이 요구된다. 더욱 비관적인 사실은 웹 프로그래밍만 가지고서는 이 오류들을 완벽하게 해결하는 것이 불가능하다는 점이다. 일단, 이 점에 관해서는 파일 다운로드에 관한 이야기를 마저 마무리하고 나서 다시 자세히 살펴보도록 하겠다.

이제 다운로드 할 파일을 읽어들이고 해당 파일을 웹 브라우저로 전송해야 하는데, 여기서 우리가 미처 생각하지도 못했던 문제점이 또 한 가지 드러난다. 그것은 ASP 프로그램을 작성하면서 파일 관련 작업이 요구될 때마다 매우 편리하게 사용하곤 했던 FileSystemObject 개체를 가지고서는 텍스트 파일만 처리할 수 있을 뿐 이진 파일은 제대로 처리할 수가 없다는 점이다. 그렇다고 ASP에서 자체적으로 이진 파일을 읽고 쓰는 기능을 제공해주는 것도 아니기 때문에 문제가 심각해진다.

결국, 이진 파일을 정확히 처리할 수 있는 기능을 갖고 있지만 별도로 설치하거나 구매할 필요가 없는 적당한 컴포넌트가 필요한데, 다름아닌 ADO의 일부로 제공되는 Stream 개체가 이 조건에 정확하게 들어 맞았던 것이다. 수 많은 컴포넌트들 중에서 하필 ADO의 Stream 개체가 사용된 것은 뭔가 특별한 이유가 있었기 때문이 아니라 이처럼 약간 단순한 논리에서 비롯된 것이다. 실제로 이진 파일을 읽어들이고 그 내용을 변수에 담아 Response.BinaryWrite() 메서드에 넘겨줄 수만 있다면 어떤 컴포넌트라도 Stream 개체를 대신해서 사용할 수 있다.

  '****************************************************************
  '* 다운로드를 위해서 ContentType 을 설정하고, HTTP Header 값을 설
  '* 정한다. 이 기술에 대한 보다 자세한 설명은 Microsoft 의 다음 문
  '* 서에 찾아볼 수 있다.
  '* 
  '*  - HOWTO: Raise a "File Download" Dialog Box for a Known MIME Type
  '*    http://support.microsoft.com:80/support/kb/articles/Q260/5/19.ASP&NoWebContent=1
  '* 
  '* 그러나 일부 버전의 Internet Explorer 에서는 다음과 같은 에러가
  '* 발생한다. (주로 IE 5.5 계열)
  '* 
  '*  - IE 5.5 : 
  '*    Content-Disposition: 첨부 파일은 알 수 없는 확장에 대해 잘못된 파일 이름을 저장한다.
  '*    http://support.microsoft.com/default.aspx?scid=kb;KO;262042
  '* 
  '*  - IE 5.0, 5.01, 5.01 SP1 및 5.5 : 
  '*    큰 파일에 대해 여러 파일 다운로드를 취소하면 Internet Explorer 가 응답을 멈춘다.
  '*    http://support.microsoft.com/default.aspx?scid=kb;KO;266305
  '* 
  '*  - IE 5.5 : 
  '*    "Content-Disposition: Attachment"가 알려진 컨텐트 종류에 대해 실패한다.
  '*    http://support.microsoft.com/default.aspx?scid=kb;KO;267991
  '*
  '*  - IE 5.5 SP1 : 
  '*    Content-Disposition Attachment 헤더가 파일을 저장하지 않는다.
  '*    http://support.microsoft.com/default.aspx?scid=kb;ko;279667
  '* 
  '****************************************************************
      
  Response.ContentType = "Application/Unknown"
  Response.AddHeader "Content-Disposition", "Attachment; Filename=" & Trim(sFileName)
  Response.AddHeader "Content-Length", lngFileSize
      
  '****************************************************************
  '* Stream 객체를 열고 파일을 읽어들인다.
  '****************************************************************
  
  Set objStream = Server.CreateObject("ADODB.Stream")
  With objStream
      .Open
      .Type = 1                   '** Binary 모드
      .LoadFromFile FullPath
      sFile = .Read()             '** Binary 읽기
  End With
  Set objStream = Nothing
  
  
  '****************************************************************
  '* 다운로드
  '****************************************************************
  
  Response.BinaryWrite sFile

이 코드는 지금까지 설명한 내용들을 모두 반영한 최종 코드로, 변수 선언부라든가 쿼리스트링 처리 등 일부분을 생략한 것이다. 그런데, 코드를 살펴보면 지금까지 열심히 설명한 것과는 달리 컨텐트 타입을 Application/Octet-Stream 대신 Application/Unknown으로 설정하고 있다는 것을 알 수 있을 것이다. 이 역시 앞에서 설명했던 오류 때문인데 그러면 지금부터 이런 방식으로 구현한 다운로드의 문제점이 과연 무엇인지 알아보도록 하겠다.

결론적으로 모든 문제의 원인은 바로 HTTP 프로토콜을 구현하는 두 주체 중 하나인 웹 브라우저, 조금 더 정확하게 얘기하면 인터넷 익스플로러 때문에 발생한다. 이미 설명한 바와 같이 HTTP 프로토콜은 단지 프로토콜일 뿐이고, 프로토콜이란 본질적으로 약속에 불과한 것으로, 그 원인이 고의적이거나 혹은 실수거나와는 전혀 무관하게 약속을 실행하는 쪽에서 무시해버리면 결국 유명무실해질 수 밖에 없다. 지금도 결국 마찮가지인데 웹 서버 쪽에서 Content-Disposition 헤더에 Attachment 값를 설정하고 데이터를 웹 브라우저로 전송하면 바로 다운로드가 시작되야만 한다는 강제적인 규칙은 사실 이 세상 그 어디에도 존재하지 않는다. 단지, 웹 서버는 HTTP 프로토콜 표준에 그렇게 하기로 약속되어 있으므로 그저 웹 브라우저가 그 규약에 맞춰 동작하리라고 믿고서 규약에 따라 처리할 뿐이다.

더욱이 HTTP 프로토콜은 웹 서버나 웹 브라우저에만 국한된 것도 아닐뿐더러 HTTP 프로토콜을 사용해서 데이터를 주고받는 통신 주체들간의 규약일 뿐이므로, 프로토콜 그 자체는 통신 주체가 웹 서버인지 웹 브라우저인지 알지도 못하고 애시당초 관심도 없다.

그런데, 웹 브라우저가 HTTP 프로토콜을 제대로 이해하지 못한다면 어떻게 될까? 아니면 프로토콜을 이해하고서도 무시해 버린다면? 단도직입적으로 말해서 인터넷 익스플로러의 일부 버전에서는 버그 때문에 지금까지 설명한 Content-Disposition 헤더에 대한 처리를 제대로 하지 못하는 경우가 발생한다. 그리고, 그 문제점들을 가만히 살펴보면 오류 발생 패턴조차도 버전에 따라 그야말로 가지각색이라는 것을 알 수 있다. 먼저, 다음 링크를 살펴보도록 하자.

이 문서는 지금까지 설명한 것과 거의 동일한 내용을 다루고 있는 마이크로소프트 기술 자료다. 내용을 살펴보면 Content-Disposition 헤더에 관한 내용을 비롯해서 다운로드를 완벽하게 구현하기 위해서 Visual Basic으로 이진 파일을 읽을 수 있는 간단한 자체 컴포넌트를 제작하는 방법과 이를 사용하는 방법 등에 관한 또 다른 기술 자료도 소개하고 있다. 이 컴포넌트에 관심이 있는 분들은 이 문서의 해당 링크 또는 다음의 링크를 참고하기 바란다.

필자가 정말로 얘기하고 싶은 부분은 그 다음 부분인데 문서를 살펴보면 거의 마지막 부분에 특정 오류에 관해서 설명하고 있는 다른 기술 자료에 대한 링크가 눈에 띌 것이다. 그 기술 자료는 바로 Content-Disposition 헤더를 attachment 값으로 설정했음에도 불구하고, 인터넷 익스플로러 4.01을 사용하는 경우 기대하고 있는 것과 같이 다운로드가 실행되는 것이 아니라 곧바로 내용이 화면에 출력되어 버리는 오류에 관한 내용으로서 다음의 링크가 바로 그 기술 자료이다.

이 문서를 살펴보면 이 오류에 대해서 마이크로소프트가 제시한 해결 방법은 인터넷 익스플로러의 버전을 5.0으로 업그레이드를 하는 것 뿐이다. 결국 이는 지금까지 설명한 방법을 사용해서 구현된 다운로드용 웹 프로그램은 인터넷 익스플로러 4.01을 사용하는 사용자의 환경에서는 제대로 실행이 되지 않는다는 의미가 된다. 그렇다면 아쉬운대로 그 이상의 버전에서는 다운로드가 올바르게 실행되는 것일까? 안타깝게도 그 질문에 대한 대답 역시 '아니오'가 정답이다.

마이크로소프트의 또다른 기술 자료들을 살펴보도록 하자. 지금부터 살펴볼 기술 자료들은 위에서 살펴본 최종 코드의 주석에서도 볼 수 있는 것처럼, 실제 작업 중 참고가 용이하도록 소스 코드에 주석으로 URL과 제목을 간단히 기록해놨으므로 필요시 참고하기 바란다.

이 기술 문서들은 모두 인터넷 익스플로러 5.0, 5.01, 5.01 SP1, 5.5 및 5.5 SP1에서 Content-Disposition 헤더를 이용해서 구현된 다운로드를 사용하는 경우 발생할 가능성이 있는 잠재적 오류들에 관한 내용들이다. 구체적으로 말해서 아예 다운로드가 실행되지 않는다던가 잘못된 파일 이름이 제공되기도 하고 상당히 심한 경우에는 인터넷 익스플로러가 반응을 보이지 않는 등, 어느 것 하나 무심히 넘어갈 수 없는 문제들이다. 더군다나 제시되고 있는 해결책들도 모조리 다음 버전으로 업그레이드 하거나 서비스 팩의 설치를 요구하는 등 그 밖에는 근본적인 해결 방법이 존재하지 않는다. 결국 이 얘기는 인터넷 익스플로러의 해당 버전을 사용하고 있는 사용자들은 경우에 따라 정상적인 다운로드의 실행을 기대할 수가 없으며 최소한 문제를 해결하기 위해서는 반드시 인터넷 익스플로러 6.0 이상의 버전을 설치해야만 한다는 결론으로 이어진다. 그러나, 일반 사용자들로서는 인터넷 익스플로러를 업그레이드하면 문제가 해결될 수 있다는 사실을 알아내는 것 자체도 매우 힘든 일이다.

필자도 넷스케이프나 오페라 같은 다른 웹 브라우저들을 대상으로 테스트해보지 않았기 때문에 이들 웹 브라우저에 대해서는 뭐라고 자신있게 말할 수는 없지만, 현재 국내 인터넷 환경의 웹 브라우저 점유율 분포를 고려한다면 개인 사이트나 기타 상업성이 없는 사이트에서라면 모를까, 중요한 관공서나 기업용 사이트, 혹은 사용료를 받고 서비스를 하는 유료 사이트 등에서 전적으로 위와 같은 방법에 의존해서 파일 다운로드를 구현하는 것은 조금 문제가 있다고 말할 수 있을 것이다. 사용자들에게 억지로 웹 브라우저 업그레이드를 강요할 수는 없는 노릇이니 말이다.

개인적으로 이런 문제들을 ASP 프로그래밍과 클라이언트 측 스크립트 프로그래밍만으로 처리해보려고 다양한 시도를 해 봤으나 결국 모두 실패하고 말았다. 가령, 컨텐트 타입에 Application/Octet-Stream 대신 Application/Unknown이나 Application/X-MSDownload 등을 입력해본다든가 Content-Disposition 헤더 대신 사용할 수 있는 또다른 헤더가 존재하는지 혹은 마이크로소프트에서 문제에 대한 새로운 해결 방안이 제시되었는지 알아본다든지 하는 등의 시도들 말이다.

결국 클라이언트 측에서 실행되는 다운로드 전용 OCX 컴포넌트를 사용하지 않고서는 모든 종류의 웹 브라우저의 모든 버전에서 모든 종류의 파일에 대한 일률적인 다운로드를 구현하는 것은 불가능하다는 최종 결론을 내리게 됐는데, 현재까지 알아본 바로는 이것이 정설이고 그나마 웹 브라우저의 종류와 버전을 조사해서 그에 따라 오류를 최소화하는 방향으로 프로그램을 처리하는 것까지가 ASP 프로그래밍과 클라이언트 측 스크립트 프로그래밍을 사용해서 구현이 가능한 최선의 방법이다. 이 문제와 관련해서 새로운 정보를 갖고 계신 분이 있다면 필자에게 알려주시면 매우 감사하겠다. 결론적으로 FileCube의 다운로드 관련 기능 역시 이 범주에서 벗어나지 못하므로 사용시 이런 구체적인 사정을 염두에 두기 바라며, 상업적인 목적의 사이트나 기타 기업용 사이트 등에서 FileCube의 다운로드 관련 코드를 가감없이 사용하는 것은 그리 권장하지 않는다는 생각이다.

JavaScript로 구현한 트리 구조

나중에라도 혹시 오해하시는 분이 계실지도 모르기 때문에 미리 확실하게 얘기하고 넘어가도록 하겠다. 솔직하게 말해서 FileCube의 폴더 트리 구조의 구현에 사용된 핵심 JavaScript 코드 중 일부 코드는 필자가 직접 구현한 것이 아니다. 그래서, 필자가 이 코드에 관해서 이런저런 얘기를 하는 것이 과연 옳은 일인가에 대해서 많이 생각을 해 보았다. 게다가 코드의 원작자가 누구인지, 어디에서 얻은 코드인지를 정확하게 기억하지 못하고 있기 때문에 더욱더 조심스러울 수 밖에 없었다.

그렇다고 뭔가 불법적인 과정을 통해서 얻은 코드는 아니며, 다만 직접 작성한 코드도 아니면서 이런 사정을 미리 밝히지 않고 코드에 대해서 여러가지 이야기를 꺼낸다는 것이 도의적으로 올바른 자세가 아닌 것 같아서 하는 얘기다. 원본 코드 자체는 상당히 오래 전에 만들어진 것으로서 인터넷 익스플로러와 넷스케이프 네비게이터의 점유율이 거의 비슷하던 시절의 것이므로 무척이나 오래된 코드인 셈이다.

아직까지 기억하고 계시는 분들이 많겠지만, 당시 웹 프로그래밍에서 새롭게 대두된 이슈들 중 한 가지는 바로 DHTML이었다. 그 중에서도 인터넷 익스플로러와 넷스케이프 네비게이터 양쪽 모두에서 동작하는 크로스 웹 브라우저용 DHTML에 프로그래머들이 많은 관심을 갖고 있었는데, 이 코드도 역시 그 당시에 만들어진 것들 중 하나다. 그 무렵 인터넷에는 이 주제와 관련된 사이트가 상당히 많이 존재했었고 이 코드 역시 외국의 그런 사이트 어디에선가 다운로드 받았던 것으로 기억하고 있다.

필자의 경우 지금까지 개인적으로 JavaScript로 구현된 수 많은 트리 구조 구현 코드를 봤지만, 몇 년이 지난 지금까지도 이 코드보다 더 뛰어난 코드는 접해보지 못한 것 같다. 인터넷 익스플로러와 넷스케이프 네비게이터 양쪽 모두를 완벽하게 구현하고 있으며 프로그램 구조나 사용 편의성 등의 면에서 볼 때 가히 발군이라 할만 하다. 직접 이런 트리 구조 구현 코드를 작성하려고 들면 못할 것은 없겠지만 이처럼 구조적으로 만들어내기는 쉽지 않을 것 같다.

한 가지 안타까운 점은 필자도 현재 이 코드의 진짜 원본은 보유하고 있지 않다는 점인데 이 글을 준비하면서 원본 코드와 해당 사이트를 다시 찾아보려고 여러차례 노력해봤지만 결국 실패하고 말았다.

현재 FileCube에서 사용되는 코드는 원본 코드에서 넷스케이프 네비게이터 관련 부분을 제거해버리고 추가로 필요한 부분들을 첨삭한 것으로 거의 원본의 기본 골격만 남아있는 상태다. 본래 주석도 거의 존재하지 않았으며 영어로 일부 작성되어 있었던 것을 필자가 한글로 정리한 것이고, 기본적으로 같이 제공되던 폴더나 파일 등의 이미지들도 최근 사용되는 운영체제의 폴더와 파일등의 이미지들을 캡춰해서 새롭게 제작했다. 원본 코드는 애시당초 트리 구조 그 자체를 만들어내기 위한 기본적인 기능만을 제공하고 있으므로, 그 외에 폴더의 실제 구조와 동기화시키는 부분이라든가 우측 패널과 연동되는 등의 기능들은 필자가 직접 작성한 것이다.

이 자리에서는 사용자들이 해당 코드를 자신의 개발 또는 운영 환경에 알맞게 수정할 수 있는 정도의 수준까지만 다루도록 하겠다. 왜냐하면 DHTML 역시 그저 쉽기만 한 기술은 아니어서 하나 씩 차근차근 설명하려고 들면 상당한 분량의 글이 요구되기 때문에 본문에서는 도저히 마무리 지을 수가 없기 때문이다. 나중에라도 적당한 기회를 가질 수 있다면 DHTML의 보다 깊은 부분까지 다뤄보도록 하겠다.

트리 구조를 구현하는 핵심 JavaScript 코드는 lib 폴더의 하위 폴더 중, js 폴더에 filecube.js라는 파일명으로 준비되어 있다. 따라서, 트리 구조를 출력하고자 하는 모든 페이지는 반드시 다음과 같이 해당 파일을 링크해야만 하는데, 지금 FileCube의 경우에는 루트 폴더에 위치한 frame_left.asp 파일이 바로 그 페이지에 해당한다. 파일명을 보면 쉽게 알 수 있겠지만 바로 이 파일이 좌측 패널을 구현하는 ASP 파일이기 때문이다.

  <script language="JavaScript" src="lib/js/filecube.js" type="text/javascript"></script>

기본적인 환경이 준비되었으므로 filecube.js 파일에서 제공되는 함수들을 이용해서 폴더 구조를 정의해 줄 차례다. 그러려면 먼저 ASP 코드를 사용해서 데이터 루트 폴더 하위에 실제로 존재하고 있는 폴더들의 전체 목록을 가져와야 한다. lib 폴더의 하위 폴더 중, asp 폴더를 보면 fc_utility_class.asp라는 파일을 찾을 수 있는데, 이 파일에는 FileCubeUtil이라는 이름으로 선언된 간단한 VBScript 클래스가 하나 정의되어 있다. VBScript 클래스에 대해서 익숙하지 않으신 분들은 MSDN이나 필자의 이전글들 중에서 다음 글들을 참고하시면 그 내용을 쉽게 이해하실 수 있으실 것이라고 생각한다.

이 클래스의 인스턴스를 만들고 RootPath 속성에 데이터 루트 폴더의 전체 경로를 문자열 값으로 설정한 다음 재귀함수인 FolderListF() 메서드를 호출하면 실행 결과가 FolderList 속성을 통해서 리턴되는데, 이 리턴값은 세미콜론(;)으로 연결된 데이터 루트 폴더 하위에 존재하는 모든 하위 폴더들의 경로 문자열이다. 그리고, 이 클래스의 인스턴스를 만들 때 일반적인 경우와는 달리 New 연산자가 사용된다는 점에 유의하기 바란다.

  '****************************************************************
  '* 구분자 ; 로 연결된 Folder 들의 정보 문자열을 얻는다. 이때 사용
  '* 되는 FileCubeUtil 클래스는 lib/asp/fc_utility_class.asp 파일에
  '* 정의되어져 있다.
  '****************************************************************
  
  Set objFileCubeUtil = New FileCubeUtil
  With objFileCubeUtil
      .RootPath = CONST_HOSTING_ROOT
      .FolderListF
      sFoldersList = .FolderList
  End with
  Set objFileCubeUtil = Nothing

이제 이렇게 얻어낸 하위 폴더들의 경로 정보를 이용해서 JavaScript 코드를 동적으로 구성해서 트리 구조를 선언하기만 하면 되는데, ASP 프로그램으로 클라이언트 측 JavaScript 코드를 그때 그때의 상황에 알맞게 실시간으로 만들어내는 기술은 널리 알려진 것이므로 따로 설명하지 않도록 하겠다. 이 과정에서 사용되는 함수가 바로 filecube.js 파일에 정의되어 있는 gFld() 함수와 insFld() 함수다. 이 두 함수에 대해서 알아보도록 하겠다. 먼저 gFld() 함수는 새로운 폴더 객체를 만들어 내는 함수로 여기에 상당히 재미있는 코드가 사용되고 있다. 일단 함수 그 자체는 예상과는 달리 단지 두 줄의 코드만으로 이뤄진 매우 간단한 함수로 그 전체 코드는 다음과 같다.

  function gFld(description, reference) {
    
      var folder = new Folder(description, reference);
      return folder;
    
  }

그저 함수에 두 개의 인자값을 넘겨주면 그 인자값들을 Folder 개체 생성자에 초기값으로 넘겨주고 개체의 인스턴스를 생성하여 다시 리턴해준다. 두 인자 중, description 인자에는 단순히 트리에 출력될 텍스트 문자열을 설정하면 되고, reference 인자에는 해당 폴더를 클릭했을 때 링크될 URL이나 혹은 링크가 필요하지 않다면 공백 문자열을 넘겨주면 된다. 가령, 다음의 코드는 트리에 'EgoCube'라는 이름으로 출력되고 클릭했을 경우 'http://www.egocube.pe.kr/'로 링크되는 트리 항목을 만들어 내는 코드다.

  objFolder01 = gFld("EgoCube", "http://www.egocube.pe.kr/");        // 코드 

그런데 눈치 빠른 분들은 뭔가 한 가지 이상한 점이 있다는 것을 발견했을 것이다. 바로 Folder 개체가 문제의 핵심인데, 이 Folder 라는 개체에 대한 설명은 JavaScript 메뉴얼이나 기타 서적 또는 관련 문서 등을 아무리 뒤져봐도 찾을 수 없을 것이다. 당연한 것이 Folder 개체는 코드 수준에서 새롭게 선언한 사용자 정의 개체이기 때문이다. 이처럼 JavaScript에서도 개발자가 클래스 개체를 선언해서 사용하는 것이 가능한데, 의외로 이를 모르고 계시는 분들이 많다. 다음 코드는 filecube.js 파일의 거의 첫 부분에 위치해 있는 Folder 개체의 선언부다.

  //***************************************************************
  //* Folder 개체 정의 
  //***************************************************************
  
  function Folder(description, reference) {
  
      // Folder 개체의 멤버 변수 선언 및 초기화
      this.desc       = description;
      this.ref        = reference;
      this.children   = new Array;
      this.id         = -1;
      this.navObj     = 0;
      this.iconImg    = 0;
      this.nodeImg    = 0;
      this.isLastNode = 0;
      this.level      = 0;
      this.nChildren  = 0;
      this.isOpen     = true;
      this.iconSrc    = "image/folderopen.gif";
      
      // Folder 개체의 메서드 할당
      this.initialize  = initialize;
      this.setState    = setState;
      this.addChild    = addChild;
      this.createIndex = createIndex;
      this.hide        = hideFolder;
      this.show        = showFolder;
      this.draw        = drawFolder;
      this.totalHeight = totalHeight;
      this.subEntries  = subEntries;
      this.outputLink  = outputLink;
      
  }

겉보기에는 마치 함수 선언처럼 보이지만 이 코드는 클래스의 특징을 나름대로 모두 갖고 있다. 주석을 살펴보면 크게 두 가지 부분으로 나눠져 있는 것을 알 수 있는데, 멤버 변수 관련 부분과 메서드 관련 부분이다. 먼저 전반부의 멤버 변수 관련 부분을 살펴보면 this 키워드를 사용해서 멤버 변수 선언이 이루어졌으며, 인자 형태로 넘겨받은 값을 설정하거나 직접 초기값을 설정할 수 있다는 사실을 알 수 있다.

반면, 후반부의 메서드 관련 부분을 이해하려면 약간의 추가 설명이 필요하다. 역시 this 키워드를 이용해서 메서드를 선언한다는 점까지는 멤버 변수의 경우와 동일하지만, 그 뒤에 설정된 값들이 도무지 어디서 나온 값들인지 처음엔 이해할 수가 없을 것이다. 결론부터 말하자면 이 값들은 그 메서드가 실제로 구현된 함수 자체를 뜻한다. 가령 이 코드에서 hide() 메서드를 예로 들어보면, 메서드의 이름은 hide() 이고 실제로 이 메서드의 코드는 hideFolder()라는 이름의 함수에 구현되어 있다는 뜻이다. filecube.js 파일을 찾아보면 쉽게 hideFolder() 함수를 발견할 수 있는데, 결국 hide() 메서드를 호출하는 것은 hideFolder() 함수를 호출하는 것과 동일한 결과를 가져오게 된다.

결국, gFld() 함수를 호출해서(코드 ①) 얻은 objFolder01 변수에는 Folder 개체의 인스턴스 변수가 할당되어 있고, objFolder01.desc 멤버 변수에는 'EgoCube'라는 문자열 값이, 그리고 objFolder01.ref 멤버 변수에는 'http://www.egocube.pe.kr/'라는 URL 문자열 값이 저장되어 있는 셈이다. 만약, Folder 개체에 뭔가 다른 멤버 변수나 메서드를 추가하고 싶다거나 사용하지 않는 멤버 변수나 메서드를 제거하고 싶다면 이 Folder 개체를 선언하는 코드를 변경해주면 된다.

다음으로 해야 할 일은 이렇게 만든 각각의 Folder 개체들간에 상호 관계를 설정해주는 일이다. 즉, 어떤 개체가 어떤 개체의 부모 개체인지, 또는 특정 개체의 하위에 몇 개의 자식 개체가 있는지 등의 정보 설정이 필요하다. 이 작업에 사용되는 함수가 insFld() 함수로 gFld() 함수를 사용해서 만들어낸 각각의 Folder 개체들간의 해당 정보를 설정해주는 역할을 한다. insFld() 함수의 전체 코드는 다음과 같다.

  //*************************************************************//
  //* insFld() 함수 : gFld() 함수를 사용하여 만든 새로운 폴더 개체
  //* 를 부모 폴더에 할당하여 부모 자식간의 관계를 설정한다.
  //*************************************************************//
  
  function insFld(parentFolder, childFolder) {
      
      return parentFolder.addChild(childFolder);
      
  }

이 코드에서도 알 수 있는 것처럼 insFld() 함수는 두 개의 Folder 개체를 인자로 받아서, 그 중 부모에 해당하는 개체의 addChild() 메서드를 호출하고 자식 개체를 인자로 넘겨준다. 이번에도 실제 코드 구현이 알고 싶다면 addChild() 메서드를 실제로 구현한 함수를 살펴봐야 한다. Folder 개체의 선언을 살펴보면 addChild() 메서드의 구현 함수는 동일한 이름을 가진 addChild() 함수라는 사실을 알 수 있다. 다음 코드는 addChild() 함수의 전체 코드이다.

  //*************************************************************//
  //* Folder 개체의 addChild() 메서드 실제 구현 함수
  //*************************************************************//
  
  function addChild(childNode) {
      
      this.children[this.nChildren] = childNode;
      this.nChildren++;
      return childNode;
      
  }

이 함수는 배열형인 부모 개체의 children 멤버 변수에 인자로 넘겨받은 자식 Folder 개체를 추가하고, 부모 개체가 갖고 있는 자식 개체의 갯수를 의미하는 nChildren 멤버 변수 값을 하나 증가시킨다. 따라서, 지금까지 설명한 내용들을 종합해보면 손쉽게 다음과 같은 코드를 얻을 수 있다.

  objParents  = gFld("Parents Folder",  "parents.asp");    // 부모 폴더 개체 생성
  objChildren = gFld("Children Folder", "children.asp");   // 자식 폴더 개체 생성
  objChildren = insFld(objParents, objChildren);           // 두 폴더 개체간의 관계 설정

그런데, 이 코드는 논리적으로 일말의 문제점도 없으며, 아니 문제점은 커녕 오히려 직관적으로 이해하기에도 수월한 지극히 정상적인 코드이긴 하지만, 실제로 ASP 코드를 이용해서 동적으로 생성하기에는 조금 번거로운 스타일이다. 따라서, 조금 더 단순하고 반복적인 형태로 사용할 수 있는 스타일로 바꾸는 것이 좋을 듯 하다. 다음 코드는 이런 현실적인 요구를 반영한 형태다.

  objParents  = gFld("Parents Folder", "parents.asp");
  objChildren = insFld(objParents, gFld("Children Folder", "children.asp"));
                // 자식 폴더 개체를 생성하고 그 즉시 부모 폴더 개체와의 관계를 설정

그리고, 이런 형태의 코드는 최종적으로 다음과 같은 실제 코드를 이끌어 내게 된다. 폴더라는 기반 구조의 특성상 가장 최상위의 루트 폴더를 제외하고는 모두 자신의 부모 폴더와의 관계를 설정해야 하므로 이런 코드는 매우 당연한 결과다.

  <script language="javascript">
  <!--
  var foldersTree = gFld("<b><font color='#FFFFFF'>FileCube Root</font></b>", "frame_main.asp");
  aux1 = insFld(foldersTree, gFld('1. EgoCube 관련 자료', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1'));
  aux2 = insFld(aux1, gFld('1.1 ADSI Samples', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples'));
  aux3 = insFld(aux2, gFld('ADSI for IIS by C#', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CADSI+for+IIS+by+C%23'));
  aux3 = insFld(aux2, gFld('EgoCube.IISWebAdmin', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin'));
  aux4 = insFld(aux3, gFld('Setup Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin%5CSetup+Files'));
  aux4 = insFld(aux3, gFld('Source Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin%5CSource+Files'));
  aux3 = insFld(aux2, gFld('EgoCube.IISWebAdmin 1.2.0.60', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin+1%2E2%2E0%2E60'));
  aux4 = insFld(aux3, gFld('Setup Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin+1%2E2%2E0%2E60%5CSetup+Files'));
  aux4 = insFld(aux3, gFld('Source Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CEgoCube%2EIISWebAdmin+1%2E2%2E0%2E60%5CSource+Files'));
  aux3 = insFld(aux2, gFld('IISMimeType', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CIISMimeType'));
  aux3 = insFld(aux2, gFld('IISScriptMap', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E1+ADSI+Samples%5CIISScriptMap'));
  aux2 = insFld(aux1, gFld('1.2 ASP & Script Samples', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E2+ASP+%26+Script+Samples'));
  aux3 = insFld(aux2, gFld('FileCube 0.0.3', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E2+ASP+%26+Script+Samples%5CFileCube+0%2E0%2E3'));
  aux3 = insFld(aux2, gFld('Hanja2Hangul', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E2+ASP+%26+Script+Samples%5CHanja2Hangul'));
  aux3 = insFld(aux2, gFld('Regular Expression', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E2+ASP+%26+Script+Samples%5CRegular+Expression'));
  aux3 = insFld(aux2, gFld('URLTools', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E2+ASP+%26+Script+Samples%5CURLTools'));
  aux2 = insFld(aux1, gFld('1.3 VB COM Component Samples', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples'));
  aux3 = insFld(aux2, gFld('EgoCube.URLTools', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CEgoCube%2EURLTools'));
  aux4 = insFld(aux3, gFld('Setup Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CEgoCube%2EURLTools%5CSetup+Files'));
  aux4 = insFld(aux3, gFld('Source Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CEgoCube%2EURLTools%5CSource+Files'));
  aux3 = insFld(aux2, gFld('StringTools.Hanja2Hangul', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CStringTools%2EHanja2Hangul'));
  aux4 = insFld(aux3, gFld('Setup Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CStringTools%2EHanja2Hangul%5CSetup+Files'));
  aux4 = insFld(aux3, gFld('Source Files', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E3+VB+COM+Component+Samples%5CStringTools%2EHanja2Hangul%5CSource+Files'));
  aux2 = insFld(aux1, gFld('1.4 Windows Script 5.6', 'frame_main.asp?path=%5C1%2E+EgoCube+%B0%FC%B7%C3+%C0%DA%B7%E1%5C1%2E4+Windows+Script+5%2E6'));
  //-->
  </script>

이 코드는 실제로 현재 본 사이트에서 서비스되고 있는 FileCube로부터 복사해서 가져온 실제 코드로 지금까지 설명해온 모든 관련 내용들이 포함되어 있다. 이 코드의 첫 라인을 제외한 모든 라인들은 ASP 코드에 의해서 동적으로 작성된 것이다. 그리고, 각각의 폴더 개체를 의미하는 aux로 시작하는 변수들이 몇 번에 걸쳐서 재사용되고 있는 것을 볼 수 있는데, 이는 의도적인 것으로 오타가 아니므로 참고하기 바란다.

이런 과정을 통해서 폴더의 트리 구조 정의를 모두 마쳤으며 이번에는 정의된 내용에 따라 실제로 화면에 폴더 구조를 출력해야 할 차례다. 지금까지의 과정들은 순수하게 구조 정의에 관한 작업이었으므로 아직까지는 화면에 아무 것도 출력되지 않은 상태인 것이다. 폴더 트리를 출력하고 싶은 곳에서 다음과 같은 코드를 입력한다.

  <script language="javascript">
  <!--
  // Tree 를 초기화한다.
  initializeDocument();
  // 초기에 Open 된 상태로 출력될 Tree 의 노드 레벨을 지정한다.
  initializeOpenLevel(0);
  //-->
  </script>

이 코드에 사용된 두 가지 함수 중, 첫 번째 함수인 initializeDocument() 함수는 정의된 모든 폴더들을 실제로 화면에 출력하고 초기화한다. 그리고, 두 번째 함수인 initializeOpenLevel() 함수는 초기에 확장된 상태로 출력될 폴더 노드의 레벨을 지정하기 위한 함수다. 즉, 이 코드의 경우, 그 값이 0으로 설정되어 있으므로 가장 최상위 노드인 루트 노드만 확장된 상태로 출력된다. 따라서 초기 화면에는 루트 노드와 루트 노드의 직계 자식 노드들만 화면에 출력되는 셈이다. 이 함수들에 대한 실제 코드는 여러분들이 직접 분석해 보기 바란다.

이제 마지막으로 각각의 폴더 노드를 클릭했을 때 어떤 동작이 발생하는지 살펴보고 글을 마무리하도록 하겠다. 이 동작들은 생각보다는 간단하지 않은데, 가장 큰 이유는 마우스로 폴더 노드의 어느 위치를 클릭하느냐 등에 따라 여러가지 경우가 발생하기 때문이다.

첫 번째 경우는 폴더 노드의 텍스트 부분, 즉 폴더 이름이나 폴더 아이콘을 클릭하는 경우다. 이때 요구되는 동작들은 다음과 같다. 당연한 얘기지만 우선 해당 폴더 노드가 선택되어 반전되어야 하고, 우측 패널에는 해당 폴더 하위에 존재하는 모든 하위 폴더들과 파일들의 목록이 출력되야 한다. 그리고, 선택된 폴더 노드가 현재 확장되어 있지 않은 상태라면 확장이 되어 자식 폴더 노드들을 선택할 수 있도록 출력되야 한다. 그러나, 이미 확장되어 있는 폴더 노드의 경우에는 해당 폴더가 선택되어 반전만 될 뿐 다시 축소되거나 하지는 않는다.

두 번째 경우는 폴더 아이콘의 좌측에 있는 아이콘을 클릭하는 경우다. 이때 해당 폴더 노드가 확장되어 있지 않은 상태라면 단순히 확장만 이루어질 뿐 폴더가 선택되지는 않는다. 반면 클릭된 해당 폴더 노드가 이미 확장되어 있는 상태라면 해당 폴더가 선택되어 반전되면서 축소가 이루어진다. 왜냐하면, 만약 현재 선택되어 축소가 이루어질 폴더의 하위 노드 중 한 폴더가, 바로 전 단계에서 선택되어 이미 반전되어 있고 우측 패널에 그 폴더의 내용이 출력되어 있는 상태라면, 단순히 현재 선택된 폴더 노드만 축소시킬 경우, 전 단계에서 선택됐던 그 하위 폴더는 좌측 패널에서 사라지고, 사라진 그 폴더의 하위 폴더들과 파일들의 목록만 우측 패널에 그대로 남아있게 되므로 결국 좌측 패널과 우측 패널의 동기화가 깨지기 때문이다.

세 번째 경우는 첫 번째 경우와 거의 동일한 동작이 발생하는 경우인데, 유일하게 다른 점은 그 발생이 사용자의 폴더 노드 마우스 클릭에 의해서가 아니라 코드에 의해서 이뤄진다는 점과 경우에 따라서 그 작업 대상이 되는 폴더 노드에 반전이 발생하지 않는다는 점 뿐이다. 버튼 클릭 한 번으로 전 폴더 트리를 확장하고 싶다거나, 폴더의 이름 중 특별한 문자열이 들어간 폴더를 검색해서 자동적으로 그 폴더로 이동하고 선택되도록 기능을 구현하고 싶은 등의 경우에 발생하게 되는 상황이다.

지금까지 설명한 세 가지 경우에 대한 실제 코드 구현은 clickOnNode() 함수와 clickOnNodeExt() 함수, 그리고 clickOnFolder() 함수를 참고하면 쉽게 파악이 가능한데 비교적 간단한 코드로 구성된 함수이므로 별다른 어려움 없이도 분석이 가능할 것이라고 생각한다.

그리고, 마지막 네 번째 경우는 앞의 세 가지 경우와는 반대로 우측 패널에서 상위 폴더나 하위 폴더 링크를 클릭해서 다른 폴더로 이동하는 경우에 발생하며, 이 경우에는 우측 패널의 변화에 따라 좌측 패널의 트리 노드 상태를 동기화시켜줘야 한다. 이 동작은 몇 가지 단계를 거쳐서 최종적으로는 setFolder() 함수를 호출하게 된다. 이 함수 역시도 간단한 코드로 구현되어 있으므로 직접 분석해보기 바란다.