멀티 파일 업로더 (Multi-file uploader) – “업로드 금지 파일” 등록 기능 추가

2009.05.13 14:33

2008년 10월부터 공개했던 파일업로더를 기능 추가해 다시 공개합니다. 따로 ASdocs를 마련하지 않았기 때문에 불편하긴 하지만 차차 넣을 생각이고 응용프로그램도 몇개 더 만들 예정입니다.

 

이번에 추가된 기능은 “업로드 금지 파일”을 등록하는 기능입니다. 가령, 서버측에 등록되면 민감할 php, js, asp, jsp, pl등의 확장자를 가진 파일을 업로드하려고 할 때 클라이언트 측에서 업로드하는 것을 원천적으로 방지하는 기능입니다.

 

사용하는 방법은 예제 프로그램에서 uploader/html-template/index.template.html 안에 flashvars의 banfileExtensions를 참고하시면 되겠습니다. 금지할 파일 확장자를 “php;js;asp;” 형태로 만드시면 됩니다. 사용자가 여기에 등록된 파일을 올리려고 하는 경우 fileuploader_banFileExtension 이벤트가 발생하게 되며 이벤트가 발생할 때 처리를 하기 위해 FAService의 addEventListener()를 이용해 이벤트 핸들러 함수를 등록합니다. 이벤트 핸들러 함수에 넘어오는 인자값은 object형태로 아래와 같은 값이 넘어옵니다.

 

{”filename”:업로드금지 처리된 파일명, “banFileExtension”: 업로드 금지처리된 파일의 확장자, “banFileExtensions”: 등록된 업로드 금지 확장자들. ;로 구분됨 }

 

질문 및 버그에 대한 댓글은 언제든지 환영합니다.

 

멀티 파일 업로더 실행 동영상

 

왜 만들었나?

Flash Player 10이 나오면서 기존 Javascript-Flash 기반 다중 파일 업로더 기능이 제대로 동작하지 않아 이것을 쓰는 많은 사이트들이 발등에 불떨어지듯한 상황이 발생하게 되었다. 내가 참여하고 있는 스타플(http://starpl.com)도 예외는 아니다.

 

원인을 살펴보자면 Flash Player의 파일 업로드에 대한 보안(?)정책이 바뀐 것에 기인한다. Ajax(Javascript)만 가지고 실시간 다중 파일 업로드가 안되기 때문에 Ajax에서 직접 ExternalInterface로 Flash의 FileReference.browse() 메소드를 호출하여 파일을 업로드하고 그 결과를 다시 Ajax로 ExternalInterface를 이용해 반환하는 거였는데 여기서 문제될 만한 부분은 Ajax쪽에서 사용자 조작이 있어 browse() 메소드를 호출하면 바로 “Error #2176 팝업 창 표시와 같은 특정 동작은 마우스를 클릭하거나 버튼을 누르는 것과 같이 사용자가 조작하는 경우에만 발생합니다.”라는 에러가 발생한다. 즉, Flash Player 10부터는 Flash 내부가 아닌 외부 Ajax와 같은 사용자 조작으로는 browse() 메소드를 호출할 수 없다!

 

Flash Player 10 부터는 Flash에 버튼을 만들고 FileReference.browse()를 호출해야한다.

 

왜 공개했나?

소스 공개의 묘미는 함께 알아가는 가는 거다. 본인의 실력이 특출나기 때문이 아니라는 점을 강조하고 싶다. 오히려 부족함을 느끼기 때문에 블로깅을 하는 것과 동일하다. 블로깅을 하면 나도 모르게 몰랐던 것도 알아간다. 왜냐하면 블로깅을 통해 관심 분야의 사람들과 만날 수 있기 때문이다. 소스 공개도 마찬가지이다. 새로운 이슈에 대해 나만 알고 있다면 얼마나 이기적인가? 어짜피 알거면 같이 알아가면서 함께 발전하자는게 나의 생각이다.

 

그런 의미에서 여러분도 with 블로깅&소스공개?!

 

멀티 파일 업로더 (Multi - file uploader) 에 대해

사용하는데는 어려움이 많이 없을거라 생각합니다.


이미 swfuploader라는 좋은 툴이 있긴 하지만 학습과 쉬운 소스 수정을 위해 직접 만들었습니다.
공유해서 함께 지식을 넓혀가길 희망합니다.


이름은 Multi-file uploader이지만 실제 동작은 1개씩 업로드 되는 겁니다.

 

* author : 지용호 (Yongho, Ji)

* Q&A : jidolstar[at]gmail.com, http://blog.jidolstar.com)

* license : LGPL (수정시에는 소스를 공개합니다. 하지만 그대로 사용하는 것은 사용 출처만 밝혀주세요.)

* 최초제작일 : 2008.10.24

* 최종수정일 : 2009.03.02

* 제작언어 : Adobe ActionScript 3.0

* 제작환경 : Adobe Flex Builder 3 Professional. Flex SDK 3.2

* 구동환경 테스트 : IE6, FF, google chrome

* 제작배경
  Flash Player 10이 정식 릴리즈 됨에 따라 Javascript를 통해 FileReference.browse() 메소드를 호출을 방지하도록 되었기 때문에 이 방식을 사용한 것을 대체하려고…

 

* 첨부파일 설명

  1. FAService는 Javascript-SWF간 통신하기 위한 라이브러리이다.
  2. fileupload는 멀티파일업로더 핵심 라이브러리이다.
  3. fileuploader는 fileupload와 FAService를 이용해서 HTML환경에서 멀티파일업로드를 가능하게 만들어진 애플리케이션이다.
  4. fileuploader/html-template/index.template.html 부분처럼 사용하면 되겠다.
  5. fileuploader/php 에는 예제로 만들어진 php소스가 있다. jsp, asp로 비슷하게 만들어 쓰면 되겠다.

* 추가사항

  •  2008.11.14
         1. POST, GET 방식으로 Variables를 넘길 수 있도록 함
         2. requestHeaders를 추가할 수 있도록 함 (헤더를 보내는 경우 서버측 crossdomain.xml에 allow-http-request-headers-from 설정이 되어야 한다.)
         3. contentType(MINE Type)을 지정할 수 있도록 함
         이러한 방식은 AS3의 URLRequest에 있는 속성이므로 참고하길 바란다.
         사용예는  index.template.html의 flashvars 참고하면 된다.
  •  2009.03.02
         banfileExtensions 추가. flashvars에 추가된 속성으로 업로드를 거절할 확장자를 가진 파일을 등록한다. 대소문자는 자동으로 맞춰준다. 사용자가 업로드 금지 파일을 등록하려는 경우 “fileuploader_banFileExtension” 이벤트가 발생한다. 자세한 사용법은 index.template.html을 참고한다.  ex) banfileExtensions = “mp3;php;pl;”;

* 사용방법

  1. 자바스크립트를 통해 이벤트 핸들러를 등록한다.(FAService Flex-Ajax 통신 브릿지 이용, Flex-JS로 제작됨 , 본인 제작)
  2. 파일 업로드를 위한 설정 Flash Vars로 등록 한다.(가령 업로드할 서버 경로, 파일 사이즈, 필터, 버튼이미지 경로등…)
  3. 업로더 SWF를 HTML상에 붙인다. 예제에서는 swfobject.js를 이용했다.
  4. 서버쪽 프로그램을 만든다. 첨부된 php파일을 참고하면 되겠다. asp, jsp든 어떤 언어를 써도 동일하게 만들면 되겠다.
  5. 예제에선 정상적으로 동작하는 경우 textarea에 ready가 뜬다. 이벤트 핸들러가 호출되면 여기에 출력하도록 짜여졌다.
  6. 파일 선택후 ok하면 이벤트는 fileuploader_startAll, (fileuploader_start, fileuploader_step, fileuploader_end), fileuploader_endAll 순으로 진행된다. 중간에 ()안에 들어간것은 여러개의 파일의 경우에 진행상황에 따라서 번갈아가며 호출된다.
  7. 서버 접속이 원할치 않는다면  fileuploader_fail 이벤트가 발생한다.(보안 또는 IO Error)
  8. 파일 선택을 취소하면 fileuploader_cancel가 발생한다.
  9. 1개의 파일 사이즈가 정해진 크기보다 크면 fileuploader_fileSizeError 이벤트가 발생한다.
  10. . 선택한 파일의 갯수가 정해진 갯수보다 크면 fileuploader_fileCountError 이벤트가 발생한다.
  11. . 중간에 uploaderFAService.call( “stop”, null )을 호출하게 되면 업로드가 최소되고 fileuploader_stopAll 이벤트가 발생한다.
  12. 금지된 파일을 선택한 경우 fileuploader_banFileExtension 이벤트가 발생한다.

* 이벤트

이벤트 발생시 파라미터들은 JSON Object 형태이다.

  • ready  없음
  • fileuploader_startAll : {”totalCount”:total count of files, “totalSize”:total size of files(bytes) }
  • fileuploader_start : {”filename”:file name, “bytesTotal”:size of file(bytes)}
  • fileuploader_step : {”filename”:file name, “bytesTotal”:size of file(bytes), “bytesLoaded”:uploaded size of file(bytes)}
  • fileuploader_end : {”filename”:file name, “bytesTotal”:size of file(bytes), “uploadCompleteData”:…}  여기서 uploadCompleteData는 서버 개발자가 마음대로 값을 바꿀 수 있다. JSON 형태의 String값으로 넘겨주면 프로그램에서는 자동적으로 Object형태로 반환해준다.
  • fileuploader_fail : {”filename”:file name, “bytesTotal”:size of file(bytes, “msg”:error message}
  • fileuploader_endAll : {”failCount”: count of files upload failed, “endCount”: count of files upload successed , “totalCount”: count of files tried to upload }
  • fileuploader_stopAll : {”totalCount”:total count of files, “totalSize”:total size of files(bytes) }
  • fileuploader_fileSizeError : {”filename”:file name, “bytesTotal”:size of file(bytes), “maxFileSize”: maximum file size(bytes)}
  • fileuploader_fileCountError :{”totalCount”:total count of files, “maxFileCount”: maximum count of files }
  • fileuploader_banFileExtension : {”filename”:filename, “banFileExtension”: extension of selected file, “banFileExtensions”: registered ban extensions}

 

* 함수

  • uploaderFAService.call(’browse’,null );  Flash Player 9이하일때는 다음과 같은 방법으로 파일 browsing을 요청할 수 있다. 하지만 이 방법은 사용을 권장하지 않는다.

 

* FAService에 대해

  1. FAService는 Flex-Ajax 통신 라이브러리이다.(ActionScript 3 프로젝트로도 사용이 가능)
  2. 여기서는 소스도 함께 공개했다.
  3. FABridge와 비교할때 최소기능만 사용하도록 만들었다.
  4. 단순히 addEventListener, removeEventListener, call 만으로 Flex와 Ajax간에 통신합니다.
  5. addEventListener은 Flex에서 발생하는 이벤트명, JS이벤트 함수, 우선순위 값이 들어갑니다. 우선순위 값은 같은 이벤트 발생시 호출한 JS이벤트핸들러 함수의 호출 순서를 정합니다. 숫자가 클수록 등록순에 관계없이 먼저 호출된다.
  6. JS 이벤트 핸들러 함수의 파라미터 값들은 Flex에서 정해서 보내줍니다. Object형이 일반적이지만 Array, Boolean, int, float,String 형등 다양한 형태가 될 수 있다.
  7. removeEventListener은 기존에 등록한 이벤트 핸들러를 삭제해준다.
  8. call 함수는 Flex쪽에 “호출명”이 등록되어 있어야만 호출된다. 인수값은 Object, Array, Boolean, int, float,String 형등이 모두 가능하며 이 값은 Flex쪽에서 정한다.
  9. 만약 Flex에서 정한 형태로 만들어지지 않으면 JS Alert 창을 띄우게 된다.
  10. call 함수는 반드시 ready 이벤트가 발생한 시점 이후로 사용해야한다. 이전에는 적용되지 않습니다.(스텍을 이용해서 명령을 저장해두었다가 하는 방법도 모색하고 있음, addEventHandler는 그렇게 하고 있음)
  11. 등록되어진 이벤트 핸들러, 호출가능한 call 함수 목록등을 반환할 수 있는 함수를 만들 필요가 있다고 생각한다.

 

소스 다운로드

 

읽어볼만한 글

Adobe Flex / ActionScript 3.0 , , , , , , , ,

  1. Blog Icon
    준호

    감사합니다 소스 분석하면서 공부좀 하겠습니다 ^^

  2. Blog Icon
    열공맨

    잘보고 갑니당~ ㅎㅎ

  3. 안녕하세요 ^^ 지돌스타님..

    다시한번 방문했습니다.

    ICARUS X 게시판을 공식 배포하였습니다. 그런데....

    파일업로드 부분을 개선하고자 부탁좀 드리려고합니다 -_-;
    도무지... 관련프로그램을 건드릴줄 몰라 처리를 못하고있습니다

    다중선택시.. 각각의 패킷을 계산하기위한.. 패킷값에 대한.. 파일번호 부분과..
    모질라계열 업로드 않되는 부분이 문제입니다..

    두가지만 해결되면 파일업로드부분은 완료됩니다.. HELP ME ~~~ 꼭좀 도와주세용 -ㅅ-;;

  4. 엇.... 그래요? 모질라라면 FF등을 말씀하시는 건가요? 음.. 제가 테스트 할땐 문제 없었는데...

    패킷값에 대한 파일번호 부분은 무슨말인지 모르겠네요. 좀더 구체적으로 말씀해주시면 감사하겠습니다.

  5. 댓글 감사드립니다 ^^ 제가 글이 서툴러서요 ㅎㅎ

    우선 공개해주신 소스대로 ICARUS X 게시판에 맞도록 추가 프로그래밍만 한상태입니다.
    우선 제가 다시한번 제 개발환경에서 FF / 크롬에서 업로드를 체크해야할듯하네요.
    그리고.. 패킷값에 대한 파일번호 부분입니다..


    우선 1.jpg / 2.jpg / 3.jpg / 4.jpg / 5.jpg 이렇게 각 5개의 파일을 업로드합니다.

    1. 5개의 파일을 멀티선택합니다.
    2. 5개 파일에 대한 총값을 계산합니다.
    3. 5개 파일에 대해 파일업로드가 진행됩니다.
    4. 파일업로드 진행중..

    // upload start
    // obj= {"filename":file name, "bytesTotal":size of file(bytes)}

    해당 부분의 문제인데요..
    5개의 파일 총값을 그래프로 출력하기 위함과.. 업로드가 진행중인 파일에 대해서.
    각각 파일의 고유 번호를 추가하여..

    1,5,3,2,4 순으로 파일이 업로드 되어도.. 해당 파일이 얼마만큼 업로드 되었는지..
    그래프로 출력할수있도록 해야하는데요..

    파일이란게.. 한글명의 경우 치환을 해야하고..
    동일 파일명을 서로다른 폴더에서 선택할경우..

    당장 업로드된 파일이 1,2,3,4,5 번중 어떤 파일인지 알수가없습니다..

    따라서.. 1,2,3,4,5 파일이 업로드 진행전에..
    각각의 파일에 대한.. 구분번호를 추가해서..

    // upload start
    // obj= {"filename":file name, "bytesTotal":size of file(bytes)}

    의... 로그가 리턴시..

    // obj= {"fileindex",:file index,"filename":file name, "bytesTotal":size of file(bytes)}

    와 같이 처리가 된다면..

    1,5,3,2,4 순으로 업로드가 되어도.. 각각의 파일 패킷을 계산할수있고..
    5개 파일에 대해... 총 파일용량을 계산할 수 있을듯합니다.

    5. 5개의 파일에 대한 총값을 계산 그래프로 출력
    6. 파일종료처리



    4번 을 보시면.. 각각의 파일에 대한.. 고유번호를 추가하는 부분이에요 ^^;
    다중선택해서 업로드가 시작되면..
    파일 패킷계산중.. 뒤죽박죽 으로 업로드가 되어서..
    패킷계산이 않되는군요 ㅠ_ㅠ
    거기에.. 동일한 파일명이 존재할경우.. 더더욱 파일용량을 체크할수가없구요;;




    에고.. 어떻게 설명해 드려야하나 고심하다가 우선 작성해봅니다.
    관련 프로그램을 건드릴줄 몰라.. 소스를 만저보다가.. 해결이 않되서 도움요청드립니다 ^^;;

    부탁드립니다 (__)

  6. 그런 어려움이 있으셨군요.
    ㅎㅎ 그런 부분은 생각해보지 못한터라..
    제가 볼때 일단 제가 만든 소스는 variable를 서버측에 보낼 수는 있도록 만들어져 있습니다. 하지만 개별 파일마다 특정값을 삽입해서 나가는 것은 안되죠.

    그래서 일단
    FileUploadCore.as 부분을 수정해야합니다.

    1. private var _variables:URLVariables; 부분을 private var _variables:URLVariables = new URLVariables(); 로 수정합니다.

    2. onSelect(event:Event) 부분이 있습니다.
    여기에
    if( variables )
    {
    urlRequest.data = variables;
    }
    부분은 아래처럼 고칩니다.
    var fileindex:int = 0;
    if( variables )
    {
    var v:URLVariables = createClone(variables) as URLVariables;
    v.fileindex = ++fileindex;
    urlRequest.data = v;
    }

    3. 다음 함수를 추가합니다.
    private function createClone( source:* ):*
    {
    var className:String = getQualifiedClassName( source );
    try
    {
    getClassByAlias( className );
    }
    catch( e:ReferenceError )
    {
    registerClassAlias( className, getDefinitionByName( className ) as Class );
    }
    var clone:ByteArray;
    clone = new ByteArray;
    clone.writeObject( source );
    clone.position = 0;
    return clone.readObject();
    }

    4. 패키지를 추가합니다.
    import flash.net.getClassByAlias;
    import flash.net.registerClassAlias;
    import flash.utils.ByteArray;
    import flash.utils.getDefinitionByName;
    import flash.utils.getQualifiedClassName;

    1,2,3,4 을 다하셨으면 컴파일하시고 사용하시면 되요.

    fileindex 값으로 파일마다 서버로 보내주기 때문에 그 값을 다시 받아 다시 fileuploader로 보내주시면 됩니다.

    사실 테스트 안해보고 한거지만 거의 될거라 확신하긴 합니다 ^^

    아무래도 이를 이용한 다양한 애플리케이션을 만들어봐야겠네요. ^^

  7. 감사합니다 ^^

    정말 너무너무 감사드려요.
    해당부분 추가후 컴파일 해서 테스트 해봐야겠습니다 ^^