지난번에 파일 업로드 프로그레스바 구현에 대한 글을 포스팅 한적이 있었습니다. 당시에는 단일 파일 업로드만 구현 했는데

이번에는 다중 파일 업로드 구현에 대해서 써볼까 합니다. 단일 파일 업로드 기능을 구현할때도 그랬지만 웹에서 파일업로드

기능 구현은 거의 해본적이 없습니다. 보통 OCX를 통한 파일 업로드 방식만  많이 사용해서 봐서 삽질을 많이 했네요.

일단 구현전에 괜찮은 파일 업로드 모듈을 찾아 봤습니다. 아무래도 제가 직접 제작하는것도 좋지만 저보다 뛰어난 분들이

만든 소스가 더 믿음직스러우니까요. 하지만 찾아보시면 보통 JS + Flash나 Flex를 사용한 파일 업로드 모듈을 보시게

될겁니다. 일반 PC환경에서면 크게 거슬리진 않지만 상황이 많이 나아지긴 했어도 모바일 환경하에서는 Flash 플레이어를

설치하지 않는 경우가 훨씬 많고 따로 설치 해야하는것도 은근히 번거롭게 느껴졌습니다. 그리고 요즘에 출시되는

스마트폰들은 성능이 좋지만 제가 현재 쓰고 있는 갤럭시U 모델의 경우 일반 웹도 버겁게 느껴질때가 많기 때문에 Flash나

Flex를 사용한 모듈은 배제하고 찾아보기로 했습니다. 그리고 다양한 기능보다는 파일업로드에 중점을 맞춘 모듈을 찾아보게

됐는데..

첫번째로 찾게 된것은 위와 같습니다. 제가 원했던 심플한 기능이었고 가장 맘에 들었던 것은 sumit 부분은 알아서 컨트롤 할 수

있게 되있다는 것 이었습니다. 그러나 input file을 css로 디자인 하게 되면 기능이 제대로  실행되지 않는다는 글을 보게 되서

쓰지 않게 됐습니다. (확실한 정보는 아닙니다.) 또 하나 찾게 됐던것은..


위와 같은 모듈입니다. (http://blueimp.github.com/jQuery-File-Upload/) 하지만 위 모듈의 경우 모바일 환경에서 일단 UI

때문에도 그냥 사용할 수도 없고 소스는 받아봤지만 커스터마이징 해서 쓰기에 시간이 너무 오래 걸릴것 같아 포기했습니다.

기능 자체는 상당히 깔끔하고 좋습니다. 한개 더 찾은 것 중에 쓸만하다 생각된것이 바로 아래와 같습니다. UI도 수정하기

크게 까다롭지 않고 플러그인 JS자체도 분석하기 수월 했습니다만.. 특정 ID를 가진 Submit버튼을 두고 전송을 해야해서

저의 경우 jQuery.form 의 ajaxSubmit()을 사용해야 할 필요가 있어서 패스하게 됐습니다.

보통은 찾아봤던 모듈들이 위와 같이 파일 업로드 자체만의 기능을 가지고 구현되어 있어서 다른 기능들에 포함해서

구현하려면 우선 모듈을 분석하는것이 먼저라 복잡하게 느껴져서 그냥 전부 패스 하고 직접 만들어 보게 됐습니다.

처음에 구현하려고 생각했던것이 위와 같은 형태입니다. '파일' 버튼(input type 'file'이 스타일링 된것)을 누르면 업로드

리스트가 추가되고 전송 버튼을 눌러 전송하는 형태 입니다. 위와 같이 구현하기 위해서는 가장 귀찮은 것이 파일추가 input

을 스타일링 하는 것입니다. 위 버튼의 경우 input file바깥의 div에 absolute position을 줘서 초과범위는 hidden 시키는

방법으로 input file을 숨겨놓은 형태입니다. 두번째로 문제가 되는것은 업로드할 파일 리스트 입니다. 업로드를 구현하는

back end(JSP,JAVA,PHP)는 구현하기 나름이겠지만 아래의 리스트에 파일 목록만 보일뿐 전송시에 input file의 value를

건네주지 못해서 구현 하나마나가 됩니다. 그러면 구현시 input file을 동적으로 생성하고 뿌려준다음 input file의 value를

주면 되지 않을까가 바로 생각나겠습니다만 보안상 이유로 input file의 경우 readonly 입니다. 때문에 사용자가 직접 파일을

선택하여 추가한 상태가 아니라면 value를 직접 set하는 것은 불가능 합니다. 예제에서는 위와 같이 스타일링 된 파일 버튼으로

파일을 선택하면 input file이 다시 동일 위치에 생성되며 이전의 element는 숨겨지는 형태입니다. 따라서 '파일' 버튼으로

다섯번 파일을 선택했다면 눈에 보이진 않으나 input file element가 다섯개가 생성된 상태입니다. 해당 내용은 예제내의

$("div#debug").text() 를 사용해서 뿌려보시면 이해가 가실 겁니다. 전에 포스팅한 글에서 예제로 업로드를 수행하는

부분의 스프링 MVC컨트롤러를 추가해뒀으므로 이번에는 JSP만 올려놓도록 하겠습니다. 항상 그랬지만 구현이 목적으로

실사용에 문제가 될 수 있는 부분도 있습니다. 이렇게 구현할 수도 있겠다 하는 방법만 보시면 되겠습니다.


multiuprogress.jsp


구현하고 보니 웹이 많이 발전하긴 했으나 파일입출력에 있어서는 아직 지원이 미흡하다는 느낌이 많이 드네요.

여담으로 HTML5에서는 위와 같은 꼼수를 쓰지 않고도 다중 파일 업로드를 구현할 수 있어 무척 반갑네요.

<input type="file" value="" name="upload[]" multiple>

위와같이 multiple 속성 선언으로 input의 '찾아보기' 버튼을 눌렀을때의 윈도우 다이얼로그에서 여러개의 파일을 선택할 수

있습니다. 저는 파이어폭스만 테스트 해봤으나.. 최신버전의 파이어폭스, 사파리, 크롬에서 작동한다고 합니다.

신고

요즘 N,D포털을 비롯해서 거의 모든 사이트에서 Front-end 구현 기술에 빠지지 않고 포함되는 기술로 AJAX가 사용되는

것 같습니다. 사실 AJAX자체는 신기술이라 할 수는 없지만 jQuery같은 훌륭한 자바스크립트 라이브러리가 나오기 전에는

뷰페이지를 표현하는데 있어서 AJAX가 많이 사용됐던것 같진 않습니다. 물론 이전에 prototype.js와 같은 라이브러리가

있긴 했으나 지금처럼 어느곳에서나 눈에 띌정도로 많이 사용됐던 것 같진 않네요. 저 같은 경우에도 AJAX를 깊이있게

사용해본적은 그다지 없고 request중에 비동기적 기능호출이 필요한 경우에만 종종 사용해왔던 것 같습니다.

하지만 대세를 따르다 보니 이젠 AJAX, JSON사용은 필요보다는 필수가 되가고 있네요. JSON이 두루 사용되기 전에는

AJAX를 통해 XML데이터나 Text데이터를 호출해서 사용하는 방식이 주였으나 최근에는 JSON 방식이 주로 사용되는

것 같습니다. JSON표기를 어떻게 사용하는지 확인해 보겠습니다.

- JSON (JavaScript Object Notation)

 이름과 같이 자바스크립트 객체 표기법을 JSON이라고 합니다. 예를 들어 다음과 같은 데이터를 JSON객체로 표현한다고

보겠습니다.

ex) 데이터

            친구
                -학교
                   - 김친구(성격:더러움), 이친구(성격:안착함)
                -회사
                   - 오대리(성격:좋음), 이대리(성격:다혈질)

친구는 학교친구도 있고 회사 친구도 있습니다. 학교친구,회사친구들은 각각 집합으로 볼수 있으니 배열로 표현 할수 있겠죠.

JSON에서는 객체나 배열 안에 또다른 객체나 배열을 표기 할수 있습니다. 그래서 학교친구와 회사친구를 배열로 만들고

학교와, 회사라는 객체에 각각 넣어준뒤 학교친구,회사친구 배열이 들어간 학교,회사 객체를 친구 객체에 추가 한다고 생각

하시면 되겠습니다. 이를 JSON으로 다음과 같이 표현할 수 있습니다.

ex) JSON

  var friend =

              {
                  "school" : {
                      "person" : [{"name":"김친구","personallity":"더러움"},

                                      {"name":"이친구","personallity":"안착함"}]
                  },
                  "company" : {
                      "person" : [{"name":"오대리","personallity":"더러움"},{"name":"이대리","personallity":"다혈질"}]
                  }
              }
;

JSON에서는 표기시 {"key" : "value"} 형식으로 키이름과 키이름에 따른 데이터가 함께 있어야 합니다. 위에서 보시면

person 배열에는 또다른 학교,회사 친구들의 이름과, 성격이 객체로 들어가 있습니다. 여기에 또 다시 배열이나 객체가

들어가는것도 가능합니다. 아래 예를 보시죠.

ex) 데이터

            친구
                -학교
                   - 김친구(성격:더러움)(핸드폰:010-2222-3333,010-3333-4444), 이친구(성격:안착함)
                -회사
                   - 오대리(성격:좋음), 이대리(성격:다혈질)

ex) JSON

  var friend =

              {
                  "school" : {
                      "person" : [{"name":"김친구","personallity":"더러움",

                                       "mobiles":["010-2222-3333","010-3333-4444"]},

                                       {"name":"이친구","personallity":"안착함"}]
                  },
                  "company" : {
                      "person" : [{"name":"오대리","personallity":"좋음"},

                                      {"name":"이대리","personallity":"다혈질"}]
                  }
              }
;

김친구는 핸드폰이 두개가 있습니다. JSON데이터는 다시 XML로 표현하는것도 가능하며 김친구의 핸드폰과 같은 텍스트

노드들의 집합은 배열로 지정합니다. JSON데이터는 아래 예와 같이 XML이나 JavaScript로도 다시 표현 할 수 있습니다.

ex)XML

<friend>

<school>

<person>

<name>김친구</name>

<personallity>더러움</personallity>

<mobiles>

<mobile>010-2222-3333</mobile>

<mobile>010-3333-4444</mobile>

</mobiles>

</person>

<person>

<name>이친구</name>

<personallity>안착함</personallity>

</person>

</school>

<company>

<person>

<name>오대리</name>

<personallity>좋음</personallity>

</person>

<person>

<name>이대리</name>

<personallity>다혈질</personallity>

</person>

</company>

</friend>


ex) Java Script

    var friend = new Object();
    friend.school = new Object();
    friend.company= new Object();
   
    friend.school.person = new Array();
    friend.school.person[0] = new Object();
    friend.school.person[0].name="김친구";
    friend.school.person[0].personallity="더러움";
    friend.school.person[0].mobiles = new Array();
    friend.school.person[0].mobiles[0] = "010-2222-3333";
    friend.school.person[0].mobiles[1] = "010-3333-4444";
    friend.school.person[1] = new Object();
    friend.school.person[1].name="이친구";
    friend.school.person[1].personallity="안착함";
   
    friend.company.person = new Array();
    friend.company.person[0] = new Object();
    friend.company.person[0].name="오대리";
    friend.company.person[0].personallity="좋음";
    friend.company.person[1] = new Object();
    friend.company.person[1].name="이대리";
    friend.company.person[1].personallity="다혈질";   

그렇다면 JSON은 뭐하러 사용할까요? 파싱과정을 거쳐야 하는 불편함이 있을때가 있지만 일단 가독성이 좋기 때문이죠.

하지만 XML의 경우 파싱하여 사용할때 DOM Parser를 사용하는데 DOM(Document Object Model)은  XML 구조를

트리형태로 구성하여 메모리 상에 구조를 만들면서 작은양의 데이터가 오갈때는 큰 무리가 없지만 큰 데이터가 오갈때는

사용자의 PC가 느려질수 있기도 합니다. JSON의 경우 경량 데이터 교환 포맷으로 XML에 비해 가볍다라고 하긴 하지만..

체감상으로 느껴본적은 없는 것 같습니다. 아마도 요즘엔 PC사양이 대부분 듀얼코어 이상의 웹서핑등에 사용될때는

고사양이라 그런 듯 싶습니다. 어쨋든 JSON 표기법은 처음엔 어색하지만 위의 예와 같이 우선 문자열 자체가 줄어들어

코딩량이 줄어들어드는 장점이 있습니다. 하지만 단점으로는 위와 같은 예제에서는 줄을 나눠서 가독성이 좋지만 한줄로

표현하게 되면 가독성이 좋지 않고 데이터 가공까지는 그렇다 쳐도 출력시킬때는 꽤 헷갈릴때가 많습니다.

실제로 개발을 하다보면 위와같이 데이터를 표현하는 경우는 드물고 한줄로 쫙 뿌리는 경우가 많습니다.

ex)JSON 데이터를 한줄로 표현

  var friend = {"school" : {"person" : [{"name":"김친구","personallity":"더러움","mobiles":                     ["010-2222-3333","010-3333-4444"]},{"name":"이친구","personallity":"안착함"}]},
                     "company" : {"person" : [{"name":"오대리","personallity":"좋음"},{"name":"이대                      리","personallity":"다혈질"}]}};

ex)XML 데이터를 한줄로 표현

<friend><school><person><name>김친구</name><personallity>더러움</personallity>

<mobiles><mobile>010-2222-3333</mobile><mobile>010-3333-4444</mobile></mobiles>

</person><person><name>이친구</name><personallity>안착함</personallity></person>

</school><company><person><name>오대리</name><personallity>좋음</personallity>

</person><person><name>이대리</name><personallity>다혈질</personallity></person>

</company></friend>

보통 이와 같은 형태로 DB에 데이터를 일정형식으로 그대로 넣었다가 가져오는 경우도 있고 DB의 데이터를 JSON이나

XML형태로 가공하여 사용하기도 합니다. 그냥 얼핏 봐도 JSON 표기법의 경우 한줄로 표현할 경우 어디까지가 객체의 끝인지

배열의 끝인지를 알수가 없습니다. 물론 XML도 한줄로 표현하면 보기 힘든점은 마찬가지긴 합니다만 그래도 JSON표기에

비해서는 좀더 쉽게 구분이 갑니다. 예제에서야 간단한 데이터로 표현했지만 데이터량이 많아질수록 가독성이 떨어지는

것은 어쩔 수 없는 부분인것 같습니다. 그래도 우선 저의 경우는 XML의 경우 DOM Parser를 거쳐 파싱해야 하는 과정이

복잡하고 버겁게 느껴질때가 많아서 JSON사용이 주류가 되는것에 대해 환영하는 입장입니다. 그렇다고 XML이 JSON보다

나쁘다라고 할수는 없고 개발할때 느끼지만 항상 상황에 적당한 기술 사용이 더 중요한 것 같습니다.

※예제는 실제로 실행해 보지 않아서 확실히 맞는지는 모르겠습니다. JSON표기법을 어떻게 사용하는지 객체,배열의 매핑

방법이 이렇다 하는것만 알아보시고 실제로 브라우저로 테스트 해보시기 바랍니다.

신고

지난 10월 26일 윈도우8이 정식출시 되었습니다. 물론 관심 많으신 분들은 출시이전 컨슈머 프리뷰 버전을 통해서도

경험 해보셨을것이고 그 이전의 빌드를 설치해본 경험도 있으실 것 같습니다. 저 또한 윈도우8 개발 초기 당시인 2011년

중순께에 설치를 해봤고 시작화면의 어색함에 바로 삭제해버린 후 이번엔 정식 출시된 ProK버전을 가지고 설치를 해봤습니다.

설치과정이야 윈도우 XP이후로 거의 손댈 필요없이 간소해졌고 비슷하므로 굳이 소개하진 않겠습니다. 

일단 설치를 마친후 몇가지 자동으로 셋팅을 마친후 시작 화면이 뜹니다. 

처음에는 윈도우8이 제공하는 기본앱이 시작화면을 차지하고 있습니다. 위 화면에서는 기본 앱 몇가지를 제외하고 모두 삭제

후 기존에 사용하던 프로그램들을 설치하여 주로 사용하는 프로그램의 아이콘만 '시작화면에 고정 하기' 기능으로 고정 시킨

상태입니다. 처음에는 시작화면이 매우 어색했습니다. 또한 좌측 중간에 보이는 '데스크톱' 패널이 없다면 항상보던 바탕화면

또한 볼 수 조차 없으니 조금은 당황스러웠고 조작감도 매우 불편하게 느껴졌습니다. 하지만 설치후 어느정도 적응하고 나니

오히려 반응이 빨라서 이 화면이 맘에 들긴 하더군요. 또한 주로 쓰는 프로그램의 경우 작업표시줄에 고정 시켜서 사용하면

되므로 적응하고 나서는 꽤 유용하다고 생각 들었습니다. 다만 배치해놓은 프로그램을 어느정도 종류별로 배치 시켜서 사용

하고 싶으실텐데 가운데 화면상에서 배치하다보면 하나를 맞춰놓으면 하나가 어긋나는.. 퍼즐맞추기 하듯이 배치가 힘들어

오히려 산만하고 짜증나는 시작 메뉴로 느껴질 수도 있겠습니다.

이 시작 화면에서 빈 공간에 오른쪽 마우스 버튼을 클릭하면 우측 하단에 '앱 모두 보기' 메뉴가 나옵니다. 기존의 윈도우로

보자면 시작 버튼 → 프로그램 메뉴 내의 모든 프로그램을 보는것으로 생각하시면 됩니다.

많은 부분을 가려놓았습니다만.. 설치된 프로그램들의 목록은 다음과 같이 A~Z, 가~하 순으로 표시 됩니다. 이 메뉴는 사실

처음 보자마자 정말 부적절 하다고 생각했습니다. 바로 아래 사진과 같이 키보드만 치면 바로 검색창이 뜨기때문에 원하는

프로그램을 검색해서 사용할 수도 있지만 모든 사용자들이 항상 프로그램명을 기억하고 검색해서 사용하진 않습니다.

혹은 알고 있다고 하더라도 기존에 사용하던 패턴이라면 시작메뉴를 눌러서 프로그램을 찾아 실행하는것이 일반화 되있겠죠.

프로그램이 저렇게 난해하게 펼쳐져 있기때문에 저의 경우에는 저 메뉴를 일체 사용하지 않으며 프로그램 제거 시에만

프로그램 설치시 제공되는 언인스톨 아이콘으로 삭제하기 위해서 사용합니다.

시작화면의 '데스크톱' 패널을 누르면 위와같이 익숙한 바탕화면이 나타납니다. XP사용중 윈도우8로 업그레이드 하셨다면

이 화면도 생소 하시겠지만 윈도우7을 사용하신 분들이라면 익숙한 화면 입니다. 이제야 기존에 하던 탐색기를 통한 파일

탐색등을 할수 있겠네요. 위에서도 언급했지만 이 부분의 경우 패치가 필요하지 않나 싶습니다. '데스크톱' 패널이 한번눌린

후에는 화면의 좌측 하단에 마우스를 올리면 시작화면↔바탕화면을 전환시킬수 있으나 '데스크톱' 패널이 한번도 눌리지

않은 상태에서는 바탕화면을 볼 수 조차 없습니다. 물론 어떤 단축키 정도는 있겠지요. 아직 단축키가 있는지는 찾아보지

않았습니다.

위와 같이 우측화면의 최상단 부분에 마우스 오버시 메뉴가 하나 뜹니다. 보통 제어판에서 이뤄지던 작업의 일부분을 이곳을

통해 좀더 간단하게 할 수 있게 구성 되어 있고 직접 제어판을 호출할 수도 있습니다. 또한 우측 하단의 'PC 설정 변경' 메뉴를

통해 시작화면 앱에 관련된 설정을 할 수도 있습니다만 스마트폰에서 앱을 다운받아 설치하듯 스토어를 통해 설치된

프로그램과 윈도우8의 Metro 앱 프로그래밍 방식에 맞춰 만들어진 프로그램 외의 이전에 데스크탑에서 사용하던 프로그램의

경우는 설정 될수 있는 부분이 전혀 없습니다. 따라서 일반 프로그램들의 경우 앱 설정등은 무시하시고 그냥 사용하시면

되겠습니다. 앞으로 윈도우8의 Metro UI를 지원하는 프로그램들이 얼마나 나올지는 모르겠지만 개발자들의 입장에서는 상당히

애매한 개발 범위가 되지 않을까 싶습니다. 세번째 화면의 경우 듀얼 모니터 확장이나 디지털/아날로그 아웃풋 복제 기능등

기존의 윈도우7에서 화면해상도 메뉴에서 다중디스플레이 설정을 하던것을 메뉴로 지원해서 상당히 편리하게 느껴집니다.

PC설정 변경 화면입니다. 설정 부분을 보기에 앞서 상당히 불만인것이.. 처음에 윈도우를 설치하면 사용자 계정을 MS Hotmail

계정을 무조건 사용하도록 유도합니다. 물론 메일 계정은 아무렇게나 입력해도 상관 없습니다만 이 경우 입력한 사용자 이름

으로 사용자 폴더 (일반적으로 C:\Users\xxx) 가 생성 됩니다. 일반적으로 사용시에는 한글 폴더명이 들어가는것이

별 상관 없습니다만 가끔 이 사용자 폴더가 한글이어서 프로그램 설치시나 설정시에 문제가 생길 경우가 있어서 꺼려집니다.

처음 설치후에 윈도우8 기본 앱이나 설정(메일, 일정관리등, 동기화)이 Hotmail계정과 연동되어 사용될 수 있어서 유도하는지는

모르겠으나 선택권이 없게 만들어 버린것 같아서 불만스럽긴 하네요. 설치 후에는 로컬계정으로 변경할수 있으며 이 경우

앱 동기화 기능등은 사용할 수 없게 됩니다만.. 솔직히 윈도우 기본 어플은 아주 먼 옛날 윈도우3.1 버전 부터 지금까지 그다지

애용해본 기억이 없어서 오히려 저의 경우엔 방해가 됐습니다. 기타 설정 내용등은 별 특별한 점은 없고 PC에 익숙하지 않은

사용자가 사용하기에 괜찮은 기능으로 두번째 사진의 'PC복구','윈도우 다시 설치' 등이 큰 도움이 될 수 있겠습니다.

윈도우 비스타때의 악몽을 되살리고 싶었는지 이번에도 UAC(사용자 계정 컨트롤)기능이 기본적으로 켜져있습니다.

항상 이유야 파일 보안등을 위해서 라고 하는것이 MS의 입장이긴 합니다만 시스템적인 작업 (ex)펌웨어 업그레이드 등

이 이뤄질경우 UAC때문에 PC가 맛이 가버리는등의 위험성도 있고 설사 UAC를 끈다고 해도 완벽한 Super Administrator

권한이 있지 않아서 제일 싫어하는 부분중에 하나입니다. 수동으로 레지스트리를 변경하여 UAC를 해제하고 최고 관리자

권한을 획득할수는 있으나 이경우 윈도우 기본 Metro 앱은 사용할 수가 없습니다. 기본 앱이 크게 필요치 않다면 해제하고

쓰시는것이 좋을듯 합니다. (※주의 : 기존의 윈도우 비스타, 윈도우7의 경우의 UAC해제 방법을 그대로 윈도우8에서 사용

하시면 문제가 발생합니다.) 

윈도우8 초기 개발 발표시에 Active X사용 불가 여부로 이슈가 됐던 IE10입니다. 메트로 앱, 데스크톱용 IE로 구분되어 있으며

매트로 앱 IE 10의 경우 ActiveX가 실행되지 않습니다. 데스크톱용 IE의 경우 기존에 사용하던 ActiveX 실행이 가능하므로

염려하실 필요는 없습니다. 사진에 보시는대로 매트로 앱 IE10의 경우 IE가 실행된 상태에서 화면 가장상단에 마우스 오버시

손모양 포인터가 나오면 끌어서 바탕화면에 위치시켜 사용 할 수 있습니다. 이 기능은 큰 디스플레이를 쓰시는 분들에게

괜찮은 기능으로 생각됩니다.

윈도우8 설치후 셋팅해서 사용해 본후 간략하게 여기까지 둘러봤습니다. 일단 제 결론은 "윈도우7 사용자가 굳이 윈도우8로

돈들여서 옮겨올 필요는 없다." 입니다. 인터페이스나 UI가 바뀐점, 약간의 부가적인 기능이 추가된것 때문에 10만원 가량의

돈을 지불하고 사기에는 아까운 OS라고 생각됩니다. MS에서 기존의 타블렛 윈도우의 경쟁력을 높히기 위해 윈도우8을

제작 한것으로 보이나 데스크톱용으로 윈도우8을 쓰기엔 어색한 점이 많습니다. 첫번째로 사용자가 어떤 작업을 하는데 있어서

동선이 매우 깁니다. 터치스크린을 사용하는 것이 아닌이상 마우스를 한번이라도 더 움직여야 하고 위에서도 언급했지만

테스크톱 패널을 누르기 전엔 데스크톱 시작 화면으로 전환 조차 할 수 없습니다. 또한 난해한 전체 앱 배치화면은 아무리

검색이 가능하다고 한들 이전에 사용하던 버전의 윈도우 시작 메뉴에서 정렬된 상태의 프로그램 그룹을 선택할수 있었던것에

비해 매우 불편합니다. 그리고 저의 경우 프로그램 그룹을 따로 생성해서 사용하는 경우가 종종 있는데 이것또한 아이콘들이

저장되있는 폴더를 일일히 찾아가서 만들어야 하는 불편함이 있으며 편리하게 사용하라고 있는 단축키가 이제는 어느정도

숙지해야 하는 요소가 되버린 것 같습니다. 예를 들어 이전 윈도우에서 시작→실행 으로 명령어를 직접 넣어 실행 하던 메뉴의

단축키는 윈도우키+R 입니다. 이전에는 선택권이 있었지만 이제는 실행 메뉴도 단축키를 사용하지 않고는 찾아볼수가

없습니다. 물론 다른 기능들의 경우도 편하게 사용하기 위해선 기본 단축키 정도는 숙지해야 합니다.

ARM용으로 출시된 Windows RT의 경우 성공여부는 지켜봐야 겠지만 적어도 데스크톱 OS로서는 업그레이드 할만큼의

메리트는 그다지 없다고 볼 수 있겠습니다. 보통 MS에서는 기존 윈도우 제품군 출시후 어느정도 시간을 두고 새OS를

출시했는데 윈도우8이 이렇게 빨리 출시되는것을 보니 이제는 태블릿 시장에서의 애플, 구글을 간과할 수가 없나 보네요.

어쨋든 윈도우8을 몇일 사용해 보니 파워유저들이 사용하기엔 전작에 비해 크게 좋은점을 바라기는 힘들것 같고

(몇가지 프로그램들은 호환성 문제도 존재 합니다.)  라이트 유저의 경우에는 오히려 직관적인 인터페이스로 편리 할 수도

있겠습니다.

신고

파일 업로드 라이브러리 선택(COS,Apache Commons Upload)에 대해 포스팅 했던 글에 이어 이번에는 파일 업로드

구현 예제에 대해 올려보겠습니다. 파일 업로드시에 보통 작은 데이터의 경우 손쉽게 jQuery의 ajaxStart, ajaxStop function을

통해 AJAX 요청 시작시, 완료시에 대한 이벤트를 처리 할 수 있으므로 ajaxStart()시에 GIF이미지 등을 화면에 띄워줘서

처리 상태를 사용자에게 알릴 수 있습니다. 보통 큰 용량의 파일이 업로드 될때는 JSP등의 페이지로 처리 하기 보다는 별도의

업로드 모듈을 사용하는것이 낫겠지만 5MB~10MB내의 파일이 오갈때에는 굳이 그럴 필요없이 웹으로만 구현하는것도

충분하다고 생각합니다. PC의 경우 굳이 프로그레스바를 보여주는것 보다는 간단하게 위와 같은 방법으로 처리하는것이

나을지 모르겠으나 스마트폰의 전송속도는 3G망이나 4G나 일반 유선통신 속도와 비교할것이 못되기도 하고 잘 안터지는

지역에서는 스피너 이미지로 프로그레스바를 보이는것 만으로는 사용자가 작업 완료 여부를 알 수 없기 때문에 만들어

보게 됐습니다.


progressbarex.zip

※압축첨부한 예제 파일들의 경우 거의 그대로 적용하시면 실행 되실겁니다. JAVA파일의 경우 package를 환경에 맞게

추가 하시면 됩니다. 구현이 목적으로 성능등은 고민한 흔적같은 것 없습니다. 그냥 예제로 봐주세요.ㅋ

그리고 Spring MVC컨트롤러에 대한 이해가 없으신 분들은 *.do로 호출되는 기능들만 따로 떼서 구현해보셔도 되겠습니다.

예전에 JAVA의 Swing을 사용해서 Thread 구현하여 만들어본 프로그레스바 외에는 이제까지 웹으로 프로그레스바를

구현해본적이 없어서 바로 감이 오지는 않았고 사실 구현 전에는 적당한 예제 소스를 많이 검색해 봤습니다. 근데 괜찮다 싶은

모듈이어도 적용하기가 어려운 경우가 많고 모바일 웹 화면에서 사용하려면 UI도 수정해야 되는 경우가 많았고 대부분의

예제는 JSP → <form> action으로 서블릿 호출 → 서블릿에서 Apache Commons Upload를 통해

전송상태를 PrintWriter로 출력 하는 것이 대부분이었습니다. 또한 파일 업로드 자체는 XMLHttpRequest에서 지원하지

않아 jQuery.form 라이브러리를 사용한 ajaxForm()등을 사용하여 업로드 하는 방식을 많이 보게 됐는데 잘 적용되지

않았습니다. 저의 경우 Spring Web MVC를 사용하여 구현하고 있었는데 이 구조 특성상 request가 완료된후 페이지로

포워딩 하므로 중간에 프로그레스 상태를 listen하여 화면에 출력될때 어떻게 하면 좋을까 고민이 됐습니다. 한참 고민중에

세션이 생각이 나더군요. 제가 만든 예제의 대략적인 프로그레스바 출력 흐름은 다음과 같습니다.

progressbardemo.jsp 에서 파일 선택후 전송버튼 클릭시 AJAX로 fileupload.do

(스프링 컨트롤러(Controller.java)에 매핑된 주소) 호출 ,

호출시에 jQuery.ajaxStart()에서 uploadstatus.do를 호출하여 fileupload.do가 실행중의 프로그레스 상태를

얻어오기 위해 주기적으로 UploadProgressListener를 호출함

fileupload.do에서 Apache Commons Upload를 사용하여 업로드 처리 중

UploadProgressListener에 세션을 셋팅 →

UploadProgressListener의 update()메소드에서 업로드 진행상태를

세션에 셋팅해서 upstatus.jsp로 포워딩함 →

초기에 progressbardemo.jsp에서 ajaxStart()에서 uploadstatus.do가 호출될때 세션에 셋팅된 파일의 전송정보

(읽은 바이트수, 전체크기, 전송속도) 등을 JSON으로 출력함 →

전송완료후 clearInterval로 setInterval로 확인하던 업로드 상태를 중지시킴

jQuery가 AJAX요청시에 지원하는 표현 간단하면서 강력한 기능들이 많은 도움이 됐네요. 이걸 이전 방식으로 XMLHttpRequest

를 일일히 얻어와서 구현하는 형태로 만들었다면 소스량도 많아지고 많이 복잡했겠지요. 예제 내용은 사실 효율적으로 보면

매우 좋지 않습니다. 결과만 보기위해서 만들긴 했습니다만 일단 실 서비스에 적용해서 사용한다면 파일 업로드가 진행중일때

upstatus.jsp가 JSON String을 얻어오기 위해 계속해서 호출이 되므로 여러 사용자가 사용시 서버에 많은 부하가 걸릴 수

있습니다. 이 방법보다는 MVC컨트롤러가 호출될때 세션에 셋팅된 파일 전송 정보 JSON Object를 얻을수 있는 방법을

전에 어느분의 블로그에서 본것 같은데 기억이 가물하네요. 찾아서 한번 적용해 봐야겠습니다.

또한 Spring Framework 자체에서 Apache Commons Upload를 Injection해서 사용할 수 있는 방법도 봤습니다만

일단은 익숙해진 방법으로 구현했습니다. 좀 더 효율적인 방법으로 수정해서 사용하실 분은 공유 해주셨으면 좋겠네요.

보통 파일 업로드 모듈이 Flash나 Flex를 사용한 환경이 많아 모바일 환경에서 사용자체는 가능하나 잘 맞지 않고 현재 제가

만든 게시판의 경우 하이브리드 앱 형태로 웹뷰를 사용한 앱위에서 웹페이지를 호출하는 형태인데 자료를 찾다보니 이 또한

안드로이드 앱 자체에서 업로드 진행상태 구현 방법에 대해서만 많이 나와 있어서 구현해 보게 됐습니다. 다운로드의 경우

에도 구현할수는 있겠지만 보통 다운로드의 경우 브라우저에서 진행상태를 보여주는 경우가 많으므로 생략했습니다.

좀 더 효율적인 방법 제시해주시는 분들은 언제나 환영입니다^^


신고

얼마전 공부겸 해서 Spring Framework 2.5, jQuery, iBatis를 사용한 모바일 전용 게시판을 제작 했습니다.

처음에는 그냥 기본적인 기능만 만들고 다른것 좀 공부 하려고 했는데 만들다 보니 재미가 붙어서 파일 업로드 기능도

추가 하게 됐습니다. 다중 업로드도 구현하려고 했지만 귀찮아서 단일 업로드만 구현하게 됐네요.

여튼.. 파일 업로드를 워낙 오랜만에 구현하려다 보니 기억나는 것도 없고 뭘 사용할까 고민이 됐습니다. 그래서 검색하다보니

가장 많이 사용하는듯 한 라이브러리로 cos.jar가 눈에 띄었습니다.

(com.oreilly.servlet의 약자이죠. 오라일리 책 시리즈로 으로 유명한..)

하나는 Apache Commons Upload 였는데 둘다 구현하기가 까다로운 편은 아니나 cos.jar가 구현방식 자체는 상당히

간편해 보였습니다. 그래서 cos.jar를 사용해서 파일 업로드를 구현하기로 맘 먹게 됐습니다. 하지만 cos.jar를

사용해서 구현 후에 다시 Apache Commons Upload를 사용하게 되었는데 구현시의 다른 부가적인 조작 편의성에 대해

의견을 써볼까 합니다.

※구현 방법등에 대해선 다루지 않겠습니다. 다른 훌륭한 분들이 올리신 구현 소스들은 조금만 검색해보셔도 많으니까요.

   또한 사용예로 제가 중간에 넣은 소스들은 단지 비교를 위한 예제 이므로 동작하지 않습니다. 참고만 하세요.


기능구현은 위와 같이 간단히 하기로 했습니다. 파일 선택 부분은 기능 구현 완료시 게시판의 UI에 추가 하기 위해서 스타일링

했네요. cos.jar 를 사용하던 Apache Commons Upload를 사용하던 업로드 시에는 BInary 코드로 보내게 되므로

<form>을 통해 전송시 enctype="multipart/form-data", method="post" 를 추가해서 보내게 되어 있습니다.

이렇게 전송된 데이터는 cos.jar를 사용할 경우 HttpServletRequest자체를 넘겨받아 내부에서 업로드를 몽땅 처리 하도록 되어

있습니다.

사용예)

MultipartRequest multi = null; 
  try{
    multi = new MultipartRequest(request, uploadFilePath, uploadFileSizeLimit, "utf-8",

new DefaultFileRenamePolicy());

   }catch(Exception e){}

위와 같이 MultipartRequest 의 객체가 생성되면서 넘겨받은 HttpServletRequest, 업로드할 경로, 업로드할 사이즈 제한

값등을 넘겨주면 바로 파일이 업로드 됩니다. 간단히 기능 구현시 이와 같이 사용법이 너무 심플하고 편하기 때문에 많이

이용되고 있는것 같습니다. 그럼 이번에는 Apache Commons Upload의 구현 방식을 보겠습니다.

사용예)

boolean isMultipart = ServletFileUpload.isMultipartContent(request);

if( isMultipart ) {
    File temporaryDir = new File(uploadFilePath);
    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setSizeThreshold(factoryThresholdLimit);
    factory.setRepository(temporaryDir);
    ServletFileUpload upload = new ServletFileUpload(factory);

    List items = null;
    try {
        items = upload.parseRequest(request);
    } catch (FileUploadException fe){
        System.out.println(fe);
    }
    if(items!=null){
        Iterator iter = items.iterator();
        while (iter.hasNext()) {
            FileItem fileItem = (FileItem) iter.next();
            if (fileItem.isFormField()){
                //파일을 제외한 나머지 파라미터 처리
            }else{
                //파일 업로드 처리
                try{
                    File uploadedFile = new File(uploadFilePath, serverFileName);
                    fileItem.write(uploadedFile);
                } catch(Exception e){                      
                    System.out.println(e);
                }
            }
        }
    }
}

분석을 떠나서 cos.jar의 구현 방식에 비해 뭔가 좀더 복잡해졌다는 느낌부터 오실 것 같습니다. 물론 더 간단하게 구현은

가능합니다만 그냥 라인 수로만 봤을때 cos.jar가 더 심플합니다. 이제 구현 방식에 대해 비교를 좀 더 해보죠.

cos.jar를 HttpServletRequest를 통채로 넘겨받아 라이브러리 내부에서 전체적인 처리를 합니다. 보통 파일 업로드를

구현할때 단일 모듈로 파일 업로드 자체만을 구현할때도 있겠지만 보통은 파일정보 외의 다른 정보도 처리 해야 할때가

많이 있습니다. cos.jar사용시 기존의 HttpServletRequest로는 파일 정보외의 정보를 받을수가 없습니다. 위의 구현

예로 보시면 MultipartRequest multi 를 통해 다른 파라미터를 처리 할 수 있습니다.

ex) String name = multi.getParameter("name");

cos.jar의 경우 소스를 함께 제공하기 때문에 소스를 수정하여 여러가지 컨트롤을 개발자가 수정할수도 있겠지만

남이 만들어놓은 소스를 일일히 분석해서 구현하기에는 저같이 귀차니즘에 빠진 개발자분들에게는 무리라고 봅니다.

cos.jar가 업데이트가 자주 이뤄지진 않지만 혹시라도 업데이트가 이뤄지면 해당 내용에 맞춰서 다시 수정해야 하는

불편함도 감수해야 할수도 있겠죠. 제 경우에는 외부 라이브러리는 피치못할 사정이 없는한 수정하는 경우를 피하는

편입니다. 어쨋든 저렇게 MultipartRequest의 getParameter()를 사용해서 나머지 파라미터들을 처리 할수는 있으나

파일의 경우 보통 서버에 실 파일명으로 보존하는 경우는 그렇게 많지 않을것 같습니다. 저의 경우 직접 원본파일의

접근을 막기위해 중복되지 않는 난수 키값을 생성해서 원본파일명으로 저장 시키고 사용자가 웹에서 요청시에는

이 파일을 웹에서 접근가능한 context의 Temporary 디렉토리에서 읽어오도록 구현하는 것이 목적이었습니다.

하지만 cos.jar에서는 이와 같은 처리는 소스를 수정하지 않는한 불가능 하며 파일 업로드가 완료된후에 파일명을

바꿔주는 방식으로 밖에는 처리 할수가 없습니다. 혹시나 해서 찾아봤지만 제가 아는한은 수정밖에는 방법이

없었습니다. 그래서 Apache Commons Upload로 업로드 모듈을 교체 하게 됐습니다.

위의 예와 같이

            FileItem fileItem = (FileItem) iter.next();
            if (fileItem.isFormField()){ //
파일을 제외한 나머지 파라미터 처리 }

파일 정보외의 파라미터들을 처리 할수 있으며 이 정보를 HashMap에 담아 처리 할수도 있겠습니다. 물론 cos.jar 사용

시에도 파일정보외의 파라미터들을 처리하는데에는 불편한 점은 없었습니다.

            }else{
                //파일 업로드 처리
                try{
                    File uploadedFile = new File(uploadFilePath, serverFileName);
                    fileItem.write(uploadedFile);
                } catch(Exception e){                      
                    System.out.println(e);
                }
            }


하지만 위와같이 파일 정보에대한 가공처리 후 실제 디스크에 쓰기 작업을 하는 동작이 이뤄져야 할때

Apache Commons Upload를 사용하는 것이 상당히 맘에 들었습니다. 또한 구현하다보니 파일 다운로드 시에는 굳이

진행상태를 표시해줄수 있는 프로그레스바를 구현하지 않아도 진행상태를 알수있어서(모바일의 경우) 구현할 필요가 없었지만

업로드의 경우 프로그레스바가 필요했습니다. 그냥 스피너 프로그레스바로 진행중이라는 상태만 보여줄수도 있겠지만

누구나 내가 기능을 실행했을때 얼마나 진행됐는지를 알고싶어 하는것이 당연하겠지요. 그래서 결국 구현을 하다보니

cos.jar 사용(기본 라이브러리 상태)으로는 이 역시 불가능 하다는 결론을 내렸습니다.

(혹시 방법이 있으신분 있다면 피드백 주시면 대단히 감사하겠습니다!)

Apache Commons Upload의 경우 ProgressListener를 구현하여 프로그레스 상태를 Listen할수 있으므로 진행상태를

웹상에서 보여주는것이 가능합니다.

사용예)

           ProgressListener progressListener = new ProgressListener(){

                private long megaBytes = -1;
                public void update(long pBytesRead, long pContentLength, int pItems) {
                    long mBytes = pBytesRead / 1000000;
                    if (megaBytes == mBytes) {
                        return;
                    }
                    megaBytes = mBytes;
                    System.out.println("We are currently reading item " + pItems);
                    if (pContentLength == -1) {
                        System.out.println("So far, " + pBytesRead + " bytes have been read.");
                    } else {
                        System.out.println("So far, " + pBytesRead + " of " + pContentLength
                                           + " bytes have been read.");
                    }
                }
            };

            //프로그레스 리스너를 파일업로드시 추가한다.
            upload.setProgressListener(progressListener);

실제 디스크에 파일이 저장되기전 Apache Commons Upload의 경우 임시 파일 업로드를 진행합니다. 구현 하기에 따라

임시 업로드 디렉토리를 따뤄둬서 업로드 진행중 실패시 원본 저장소의 데이터를 해치지 않도록 할수도 있겠습니다.

또한 WEB-INF/web.xml 에 org.apache.commons.fileupload.servlet.FileCleanerCleanup 리스너를 추가하여

이와같은 쓰레기 파일들을 알아서 삭제하도록 할수도 있습니다.

성능상의 차이는 체감상 크게 느껴지지 않아서 잘 모르겠습니다. 두 가지 라이브러리가 사용용도에 따라서 훌륭한 모듈이라

모듈의 문제로 속도가 저하 된다거나 하기 보다는 I/O가 발생하는 Task이므로 하드웨어, 전송환경등이 더 중요하다고

생각됩니다. 글을 작성하다보니 cos.jar를 너무 무시 한듯 하네요. ㅎㅎ 정리하자면


cos.jar : 웹 to 웹으로 전송해야되는 간단한 데이터, 진행상태 표시 구현이 필요없는 파일 업로드 기능 구현시

매우 간단하게 구현 가능한 장점을 가짐, 소스가 제공되므로 개발자 편의에 따라 소스를 수정하여 사용할수도 있음.

(다만 얼핏 보기로는 cos.jar가 오픈소스는 아니라고 봤습니다. 이 부분에 대해서는 잘 판단해보시길..)

Apache commons upload : 일반적인 파일 업로드 기능 외에 파일명이나 저장되야할 위치에 대한 조작등 개발

의도에 따른 소스 구현이 좀 더 유연함. 업로드 진행상태를 알 수 있는 프로그레스바 구현을 ProgressListner를

통해 쉽게 구현 할 수 있음.

심플한 구현에 있어서는 cos.jar를 사용하는것이 감히 최고라고 말씀드릴수 있겠네요. 두개의 라이브러리 모두 장점이 있으니

적당한 라이브러리를 선택하셔서 개발시 도움이 됐으면 합니다. 자료를 찾다보니 구현방법은 많이 있는데 딱히 어떤 모듈을 구

현할때 사용햇으면 좋겠다 하는 내용이 없네요.


신고

요즘 외쿡에서 들어오는 스팸글이 많아 해당 글에 대한 IP차단 및 삭제를 진행할때 여러 글을 체크한뒤 삭제를 진행했더니

일시적 오류가 발생했다는 메시지 창과 함께 삭제처리가 안된것 처럼 보여서 몇번 재시도를 했습니다.(세네번 정도)

겉으로는 삭제가 안된것 처럼 보여 새로고침 해보니 방명록 데이터가 삭제가 됐네요;;

글 남기신 분들께는 죄송합니다. 고의로 삭제한 것은 아니에요~

※ P.S

개발자의 입장에서 봤을때 아마도 체크된 글에 대한 IP차단+삭제를 진행할때의 로직이 한번에 수행 되는것 같습니다.

방명록 글에 체크가 됐을때 해당 페이지의 체크박스의 인덱스를 그대로 가져가다 보니 삭제 및 IP차단 실패시에 계속해서

삭제 시도시 체크박스의 체크가 해제되지 않은 상태에서 뒤에 있는 글도 삭제 되버리는 현상이 발생하는것 같네요.

글 삭제+IP차단 기능수행이 성공시에는 체크박스의 체크를 모두 제거 시키기 때문에 이런일이 발생하지 않겠지만

실패시에는 IP차단이 성공하더라도 삭제는 계속해서 이뤄지고 성공여부 체크는 두가지 액션이 모두 성공해야 기능수행이

성공된것으로 간주하는 것 같습니다.

아무래도 IP차단이야 실패한다고 치더라도 삭제의 경우 사용자들이 민감해 할 수 있는 부분이니 IP차단이나 삭제가

실패할경우 체크박스의 체크가 모두 해제되게 한다던지 해서 이런 일이 방지 되야 할 것 같네요.

※ 2012.12.07 내용추가

  - 휴지통 기능이 있었네요. 태터툴즈에서 티스토리로 넘어간 다음부터는 관리 메뉴를 거의 본적이 없는것

    같은데 덕분에 방명록 글을 모두 다시 살려놓긴 했습니다만.. 위에서 말한대로 관리자 메뉴에서 보니 몇몇

    방명록에 글쓰신 분들의  IP는 차단되있고 삭제는 계속해서 이뤄지고 있었습니다 ㅡㅡ; 여튼 다시 돌려놨으니

    다행이네요 ㅎㅎ

신고