INNORIX

INNORIX PLATFORM

INNORIX Platform시스템 전체의 파일 전송을 하나의 플랫폼에서 운영합니다.
  • 파일 전송 자동화
  • 서버 간 파일 전송
  • 객체 스토리지 간 전송
  • 파일 배포 및 수집

WEB

Exabyter웹에서 대용량 파일을 빠르고 안정적으로 업로드하고 다운로드합니다.
  • 웹 대용량 파일 업로드 & 다운로드
  • 개발자 가이드

DEVELOPERS

앱 임베디드 파일 전송기존 애플리케이션에 파일 전송 기능을 통합합니다.
  • 앱 임베디드 파일 전송

도구

전송 빌더원하는 전송을 직접 구성합니다.전송 파인더업무에 맞는 전송을 찾습니다.

성능과 규모

  • 고속 파일 전송
  • 대용량 파일 전송
  • 대량 파일 전송

현대화

  • FTP, SFTP 마이그레이션

새로운 전송 환경

  • AI Data Delivery
  • Dynamic Endpoint Transfer

글로벌 대규모 전송

Hyperlane수백 TB에서 PB까지 국가와 대륙을 넘어 전송합니다.
개발자리소스고객사
무료 시작하기
INNORIX

LET FILES
MOVE THEMSELVES

INNORIX는 모든 시스템과 환경에서 파일 이동과 자동화를 제공하는 엔터프라이즈 파일 인프라 기업입니다.
5,000개 이상의 기업 및 공공기관에서 사용하고 있습니다.

파일 전송

INNORIX PLATFORM

INNORIX Platform
  • 파일 전송 자동화
  • 서버 간 파일 전송
  • 객체 스토리지 간 전송
  • 파일 배포 및 수집

PERFORMANCE & SCALE

  • 고속 파일 전송
  • 대용량 파일 전송
  • 대량 파일 전송

MODERNIZATION

  • FTP, SFTP 마이그레이션

WEB

Exabyter
  • 웹 대용량 파일 업로드 & 다운로드
  • 개발자 가이드

DEVELOPERS

  • 앱 임베디드 파일 전송

NEW WORKLOADS

  • AI Data Delivery
  • Dynamic Endpoint Transfer

HYPERLANE

  • 서비스 소개
  • 서비스 문의

개발자

  • 개발자 센터
  • 구현 예제
  • API 빠른 시작
  • 개발자 가이드
  • API 레퍼런스
  • GitHub

도구

  • 전송 빌더
  • 전송 파인더

리소스

  • 리소스 센터
  • 제품 가이드
  • 외부 서비스 연동
  • 배포 및 관리
  • 도움말 센터

고객사

  • 정부
  • 공공부문
  • 제조
  • 엔지니어링
  • 금융
  • 유통
  • IT/통신
  • 미디어
  • 의료
  • 교육

플랜

  • 가격 및 플랜

회사

INNORIX 소개

비전 AI 제품

Al.bert — 스마트 교통 AI

글로벌 오피스

  • 미국 뉴욕
  • 대한민국 서울
  • 베트남 호찌민
  • 오피스 위치 보기→

(C)2026 INNORIX. All rights reserved.

  • 보안
  • 상태
  • 이용약관
  • 개인정보 처리방침
  • 쿠키 정책
  1. Exabyter
  2. Exabyter Lite 매뉴얼

Exabyter Lite 매뉴얼

INNORIX Exabyter Lite 업로드 및 다운로드 통합 가이드.

  • Lite
  • Standard

빠른 시작#

제품을 설치한 뒤 샘플 페이지로 업로드와 다운로드가 동작하는지 확인합니다. 구성 파일과 옵션은 이후 장에서 자세히 설명합니다.

설치#

설치 파일의 구성과 라이선스, 서버, 프런트엔드 연결 방법을 설명합니다.

구성 파일#

<웹 루트>/innorix/
├── innorix.js        (라이선스 포함)
├── innorix.css
├── config.js         (화면 프리셋)
├── install/          (에이전트 설치 페이지, 파일)
└── exam/             (샘플, 운영 배포 제외)

<웹 애플리케이션>/WEB-INF/lib/
└── InnorixJAVA.jar (+ 의존 jar)
파일 역할
innorix.js 컨트롤과 전송 엔진. 라이선스가 포함됩니다.
innorix.css 컨트롤과 전송창 스타일
config.js 화면 프리셋(box_config)
install/ 에이전트 설치 페이지와 파일
exam/ 샘플 페이지. 운영 배포에서는 제외합니다.
InnorixJAVA.jar 서버 라이브러리. 의존 jar와 함께 WEB-INF/lib에 둡니다.

servlet-api.jar는 보통 WAS가 제공하므로 넣지 않습니다.

라이선스#

라이선스는 별도 파일이 아니라 innorix.js 상단에 들어 있습니다. 발급받은 innorix.js로 교체하면 적용됩니다. 라이선스는 접속 도메인, IP 등 발급 내용과 일치해야 합니다.

서버 라이브러리 배치#

jar를 웹 애플리케이션의 WEB-INF/lib에 복사합니다. jar를 교체한 뒤에는 WAS를 재시작합니다.

저장 디렉터리#

업로드 파일을 저장할 전용 디렉터리를 만들고 WAS 실행 계정에 쓰기 권한을 줍니다. 운영에서는 웹 루트 밖에 둡니다.

bash
mkdir -p /data/innorix/upload
chown -R tomcat:tomcat /data/innorix/upload
chmod 750 /data/innorix/upload

프런트엔드 연결#

페이지에서 CSS, innorix.js, config.js 순서로 로드합니다. config.js는 화면 프리셋을 쓸 때만 필요합니다.

html
<link rel="stylesheet" href="/innorix/innorix.css">
<script src="/innorix/innorix.js"></script>
<script src="/innorix/config.js"></script>

옵션은 내장 기본값, 프리셋, create() 인자 순으로 합쳐지므로 create()에 직접 쓴 값이 가장 우선합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: '/innorix/exam/upload.jsp',
  boxConfig: box_config.upload_basic,
  boxWidth: 800   // 프리셋 값(600)을 덮어씀
});

UI 구성#

컨트롤(파일 목록)의 모양은 boxStyle로, 전송창의 모양은 transferWindowStyle로 지정합니다.

컨트롤 스타일#

boxStyle 모양
list 리스트형
icon 아이콘형
preview 미리보기를 함께 표시
html 애플리케이션이 HTML로 직접 구성
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  boxStyle: 'list'          // list, icon, preview, html
});

Exabyter Lite — 이미지 01

전송창 스타일#

transferWindowStyle 모양
default 기본 크기 전송창
mini 레이어로 출력하는 전송창의 작은 버전
list 레이어로 출력하며 진행도 위에 파일 목록을 함께 표시
compact 레이어 없이 리스트 하단에 표시
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  transferWindowStyle: 'mini'   // default, mini, list, compact
});

전송창을 열지 않으려면 showTransferWindow: false를 지정합니다.

Exabyter Lite — 이미지 02

빠른 업로드 확인#

  1. 에이전트를 설치할 수 있는 PC에서 웹 서버 URL로 exam/upload-agent.html을 엽니다. 에이전트가 없으면 설치 안내가 나타납니다.

Exabyter Lite — 이미지 03

  1. 파일을 추가하고 업로드를 누릅니다.

Exabyter Lite — 이미지 04

  1. 전송창이 완료로 바뀌고, 저장 디렉터리에 같은 크기의 파일이 생기면 정상입니다.

Exabyter Lite — 이미지 05

빠른 다운로드 확인#

  1. 웹 서버 URL로 exam/download-agent.html을 엽니다. 샘플은 download.jsp와 같은 위치의 Task_list.zip을 제공합니다.

Exabyter Lite — 이미지 06

  1. 목록에서 파일을 선택하고 다운로드를 누릅니다.

Exabyter Lite — 이미지 07

  1. 전송창이 완료로 바뀌고, 받은 파일 크기가 원본과 같으면 정상입니다.

Exabyter Lite — 이미지 08

업로드#

사용자가 고른 파일을 서버에 올리고, 저장 결과와 업무 데이터를 화면과 서버가 함께 다루는 방법을 설명합니다. 각 기능은 설명 → 함수, 옵션 → 예시 순서로 되어 있어 필요한 기능만 찾아 읽을 수 있습니다.

파일 선택#

게시판 글쓰기 화면의 "파일 추가" 버튼, 탐색기에서 끌어다 놓기, 코드로 경로 지정까지 모두 하나의 업로드 목록으로 모입니다. 이후 검증과 전송은 이 목록을 기준으로 진행됩니다.

파일 선택 대화상자 ─┐
폴더 선택 대화상자 ─┤
드래그 앤 드롭 ─────┼→ 업로드 목록 → 검증 → 전송
프로그램 경로 지정 ─┘

파일, 폴더 선택 버튼#

버튼 클릭에 선택 대화상자를 연결합니다. transferMode가 download인 컨트롤에서는 동작하지 않고, 컨트롤 준비 전 호출은 무시됩니다.

메서드 설명
openFileDialog() 여러 파일 선택
openFileDialogSingle() 파일 하나 선택
openFolderDialog() 폴더 선택
html
<div id="fileBox"></div>
<input type="button" value="파일 추가"      onclick="box.openFileDialog();">
<input type="button" value="단일 파일 추가" onclick="box.openFileDialogSingle();">
<input type="button" value="폴더 추가"      onclick="box.openFolderDialog();">
<input type="button" value="업로드"         onclick="box.upload();">

<script>
  var box;
  window.onload = function () {
    box = innorix.create({
      el: '#fileBox',
      uploadURL: './upload.jsp',
      installURL: '../install/install.html',
      folderAttach: true,
      addFolder: true
    });
  };
</script>

폴더 옵션#

옵션 설명
folderAttach 폴더 첨부 허용
addFolder 폴더 구조를 서버에 유지. 끄면 폴더 정보 없이 파일만 올라감
openFolderItems, showFolderItems 목록에서 폴더를 접어 보일지 여부
addEmptyFile 0바이트 파일 포함 여부

드래그 앤 드롭#

컨트롤 영역은 기본으로 드롭을 받습니다(enableDropZone). 사용자 영역을 드롭존으로 쓰는 방법과 예제는 5. 고급 활용 > 화면 및 컨트롤 > 사용자 정의 드롭존을 참고합니다.

경로 지정 첨부와 자동 업로드#

이름 설명
addLocalFiles(filePath) 코드가 경로를 지정해 첨부. 검증 규칙은 대화상자 선택과 동일
afterAddFiles 한 번의 첨부가 끝날 때 발생. 인자는 실제로 목록에 추가된 파일 배열

afterAddFiles에서 upload()를 호출하면 첨부 직후 자동 업로드가 됩니다. 연속 첨부 시 중복 호출되므로 한 번만 시작되게 막습니다.

javascript
var started = false;  // 연속 첨부 시 upload()가 두 번 호출되지 않게 막음
box.on('afterAddFiles', function (files) {
  if (started) return;
  started = true;
  box.setPostData({ resourceId: document.getElementById('resourceId').value });
  box.upload();
});

이전 전송 복원#

창을 닫았다 다시 연 경우 끝나지 않은 전송을 목록에 되살립니다. 원본 파일이 바뀌었으면 해당 파일은 복원되지 않고 file_is_modified 오류가 전달됩니다.

옵션 설명
attachIncompleteFiles 컨트롤 로드 시 끝나지 않은 이전 전송 조회
autoLoadTransfer 켜면 묻지 않고 복원해 바로 업로드, 끄면 이어서 전송할지 사용자에게 확인

Exabyter Lite — 이미지 09

첨부 파일 설정#

회원 가입 서류 첨부에서 "최대 5개, pdf와 jpg만, 파일당 10MB"처럼 제한을 둬야 하는 경우입니다. 서버에 올린 뒤 거부하면 회선이 낭비되므로 파일이 목록에 들어오는 시점에 검증하고, 위반한 파일만 거부하며 addFileError로 알립니다.

선택한 파일 → 중복 → 개수 → 전체 용량 → 차단 확장자 → 허용 확장자 → 개별 용량 → 원본 존재 → 목록 추가
                                  (처음 실패한 항목에서 거부, addFileError)

개수, 전체 용량에 걸리면 같은 선택 묶음의 나머지 처리를 중단하고 앞서 통과한 파일은 목록에 남습니다. 확장자, 개별 용량 오류는 해당 파일만 건너뜁니다.

제한 옵션#

innorix.create()에 지정합니다. 지정하지 않은 항목은 검사하지 않습니다.

옵션 설명
maxFileCount 파일 개수 상한(바이트 아님, 개수)
maxFileSize, maxTotalSize 개별, 전체 용량 상한(바이트)
allowType 허용 확장자(점 없이 소문자). 배열 외에 그룹 객체도 가능
denyType 차단 확장자. allowType과 겹치면 denyType 우선
useSignature 파일 앞부분으로 실제 형식 확인(확장자만 바꾼 파일 대응). 주의사항은 5. 고급 활용 > 보안 > 시그니처 검사
addDuplicateFile 중복 첨부 정책
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  maxFileCount: 5,
  maxFileSize: 10 * 1024 * 1024,    // 파일당 10MB
  maxTotalSize: 50 * 1024 * 1024,   // 합계 50MB
  allowType: ['jpg', 'png', 'pdf'],
  denyType: ['exe', 'msi', 'bat'],
  useSignature: true
});

확장자가 없는 파일은 allowType을 지정하면 거부됩니다. 중복 판정은 파일 경로 기준이라 이름이 같아도 폴더가 다르면 별개 파일입니다. 데모 라이선스에서는 개수와 전체 용량이 고정되므로 제한이 의도와 다르면 라이선스 종류부터 확인합니다.

위반 처리 (addFileError)#

핸들러는 (errors, files)를 받고, 오류 객체에는 type, message, file이 있습니다.

type 의미
addDuplicateFile 중복 파일
maxFileCount, maxTotalSize, maxFileSize 개수, 용량 초과
denyType, allowType 확장자 위반
file_is_modified 복원 시 원본이 변경됨
  • 일반 검증 실패는 배열로 전달되지만 시그니처 검사 실패는 객체 하나로 전달되는 경로가 있으므로, 배열이 아니면 배열로 감싼 뒤 처리합니다.
  • 기본 안내 레이어는 showNotificationLayer: false로 끄고 type별 문구를 직접 표시합니다.
  • beforeAddFile에서 false로 거부한 파일은 addFileError가 발생하지 않으니 안내가 필요하면 그 안에서 직접 표시합니다.
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  maxFileSize: 10 * 1024 * 1024,
  denyType: ['zip'],
  showNotificationLayer: false
});

var MESSAGES = {
  maxFileSize:      '파일당 10MB까지 첨부할 수 있습니다.',
  denyType:         'ZIP 파일은 첨부할 수 없습니다.',
  addDuplicateFile: '이미 첨부된 파일입니다.'
};

box.on('addFileError', function (p) {
  var list = Array.isArray(p) ? p : [p];
  var e = list[0];
  alert(MESSAGES[e.type] || e.message);
});

하나라도 실패하면 전체 취소#

기본 동작은 통과한 파일은 첨부하고 실패한 파일만 거부하는 것입니다. 전체를 취소하려면 addFileError에서 실패를 기록하고, 이어서 발생하는 afterAddFiles에서 방금 추가된 파일을 removeFileById()로 제거합니다.

javascript
var batchFailed = false;

box.on('addFileError', function (p) {
  batchFailed = true;
  var e = (Array.isArray(p) ? p : [p])[0];
  alert(e.message);
});

box.on('afterAddFiles', function (added) {
  if (batchFailed) {
    added.forEach(function (f) { box.removeFileById(f.id); });
  }
  batchFailed = false;
});

서버에서 다시 검사#

클라이언트 제한은 JavaScript를 끄거나 요청을 직접 보내면 우회되므로 서버가 getFileInfo 단계에서 다시 검사합니다. 허용 목록 검사 코드는 5. 고급 활용 > 보안 > 확장자 검증과 저장 정책을 참고합니다. 로그인 권한에 따라 첨부를 막아야 할 때는 setControlDisabledState(true)로 컨트롤을 잠그거나 beforeAddFile에서 false를 반환합니다.

Exabyter Lite — 이미지 10

업로드 실행#

수백 장의 사진을 올리는 중 회선이 느려지거나 창을 닫아도, 일시 정지, 재개, 취소로 제어하고 이어서 올릴 수 있어야 합니다.

upload() → uploadBefore 확인 → 전송 (진행률 uploadProgress) → uploadComplete
                                     │
                          일시 정지 / 재개 / 취소 / 오류 재시도

파일은 목록 순서대로 시작하지만 여러 파일이 겹쳐 전송되므로 완료 순서는 목록 순서와 같지 않을 수 있습니다. 큰 파일은 여러 요청으로 나뉘어 전달되고, 끊겨도 받은 위치 이후부터 이어서 올립니다.

업로드 시작#

이름 설명
upload() 전송 시작. 컨트롤 미준비, 대상 파일 없음, uploadURL 없음, 전송 중, uploadBefore가 false 반환이면 시작되지 않음
uploadBefore 시작 직전 이벤트. false를 반환하면 시작 취소
transferStart upload를 auto(즉시 전송) 또는 manual(전송창의 시작 버튼)로 지정
startTransferProgress() manual일 때 전송창에서 전송 시작
html
<div id="fileBox"></div>
<input type="button" value="파일 추가" onclick="box.openFileDialog();">
<input type="button" value="업로드"    onclick="box.upload();">

<script>
  var box;
  window.onload = function () {
    box = innorix.create({
      el: '#fileBox',
      uploadURL: './upload.jsp'
    });
    box.on('uploadBefore', function () {
      if (!document.getElementById('agree').checked) {
        alert('약관에 동의해야 업로드할 수 있습니다.');
        return false;   // 시작 취소
      }
    });
    box.on('uploadComplete', function (p) { console.log(p.files); });
  };
</script>

선택 파일만 업로드와 정렬#

이름 설명
getSelectedFiles() 선택 항목 확인
removeFileById(id) 목록에서 제거. beforeRemoveFile에서 막을 수 있음
sortName, sortSize, sortType, sortModified upload() 전에 정렬

서버는 파일 순서에 의존하지 않게 설계하고, 순서가 필요하면 setFilePostDataByIndex로 순번을 보냅니다.

javascript
function uploadSelectedOnly() {
  var selected = box.getSelectedFiles();
  if (selected.length === 0) { alert('선택한 파일이 없습니다.'); return; }

  box.getUploadFiles().forEach(function (f) {
    if (!f.selected) box.removeFileById(f.id);
  });
  box.upload();
}

전송 옵션#

속도 제한, 압축, 암호화, 무결성 검사, 대량 전송 옵션은 5. 고급 활용 > 전송 제어를 참고합니다.

전송 제어와 이벤트#

구분 이름
메서드 transferPause(), transferResume(), transferCancel(), closeTransferWindow()
이벤트 uploadProgress, uploadPause, uploadResume, uploadCancel, uploadComplete
옵션 cancelConfirmation(취소 전 확인 창), uploadDuplicate(이어올리기/덮어쓰기 정책)
  • 취소, 재개는 전송이 한 번이라도 시작된 뒤에 호출합니다.
  • 일시 정지 후 재개하면 서버에 기록된 위치부터 이어 올립니다.
  • 전송창에서 취소를 확인하면 저장된 이전 전송 정보도 지워지지만, 서버에 이미 저장된 파일과 임시 파일은 지워지지 않습니다.
  • uploadComplete의 p.files에는 clientFileName, serverFileName, serverFilePath, fileState가 있습니다. 핸들러가 false를 반환하면 전송창이 자동으로 닫히지 않습니다.
  • 서버가 파일을 합치는 동안 진행률이 100에 머무를 수 있어 workingServer, workingServerStop 이벤트로 처리 중 표시를 할 수 있습니다.
  • 업로드 경로에 프록시나 WAF가 있으면 요청 크기 제한도 확인합니다.
html
<input type="button" value="일시 정지" onclick="box.transferPause();">
<input type="button" value="재개"      onclick="box.transferResume();">
<input type="button" value="취소"      onclick="box.transferCancel();">

<script>
  box.on('uploadPause',  function (p) { console.log('정지', p.progress + '%'); });
  box.on('uploadResume', function (p) { console.log('재개'); });
  box.on('uploadCancel', function (p) { console.log('취소'); });
</script>

Exabyter Lite — 이미지 11

서버 저장#

게시판 첨부 파일이 서버의 어느 폴더에 어떤 이름으로 저장되는지 정하는 부분입니다. 서버는 InnorixUpload 하나로 구성하고, 요청 판별, 구간 이어 붙이기, 응답 작성은 run()이 처리합니다. 개발자는 run() 앞뒤에서 저장 경로, 파일명, 완료 후 처리를 지정합니다.

서버는 요청 한 건이 아니라 파일 한 개당 여러 번 호출됩니다. 전체 완료 요청은 없고 파일 단위로만 진행되며, run()을 쓰면 이어올리기가 함께 동작합니다.

요청 수신 → getFileInfo (저장 경로, 파일명 결정) → attachFile (구간 저장, 반복)
         → attachFileCompleted (파일 한 개 완료) → isUploadDone() 후처리

최소 엔드포인트#

POST 요청에서만 InnorixUpload를 생성하고 run()을 호출합니다. 사전 요청(OPTIONS)에서는 생성하지 않도록 POST 조건을 유지합니다.

메서드 설명
run() 요청 처리와 응답 작성. 응답 본문을 쓰므로 앞뒤로 공백이나 출력을 남기지 않음
runForSpring() Spring 환경용
runAction() 응답을 쓰지 않음. 호출자가 응답을 직접 작성
isUploadDone() 파일 한 개의 저장이 끝난 요청에서 true
jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
if (request.getMethod().equals("POST")) {
    String directory = InnorixUpload.getServletAbsolutePath(request);
    directory = directory.substring(0, directory.lastIndexOf("/") + 1) + "data";
    int maxPostSize = 2147482624;   // bytes

    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);
    String result = uploader.run();

    if (uploader.isUploadDone()) {
        // 파일 한 개의 저장이 끝난 시점
    }
}
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
%>

요청 값 읽기와 단계 분기#

getParameter()로 요청 값을 읽고 _action으로 단계를 구분합니다. 경로와 파일명 변경은 getFileInfo 단계에서 run() 호출 전에 합니다.

값 설명
_action speedCheck, getServerInfo, getFileInfo, attachFile, attachFileCompleted
_orig_filename, _filesize 원본 파일명, 파일 크기
_transferId 전송 식별자
el 한 페이지의 여러 컨트롤 구분
jsp
String _action        = uploader.getParameter("_action");
String _orig_filename = uploader.getParameter("_orig_filename");
String _filesize      = uploader.getParameter("_filesize");
String _el            = uploader.getParameter("el");

if ("getFileInfo".equals(_action)) {
    // 사전 검사, 저장 경로, 파일명 결정
}

String result = uploader.run();

저장 경로와 파일명#

메서드 설명
setDirectory(path) 저장 폴더 지정
setFileName(name) 저장 파일명 지정
setOverwrite(true) 같은 이름이 있으면 기존 파일을 지우고 저장. 기본은 이름(번호).확장자
setSaveFolderTree 폴더 업로드 시 폴더 구조 보존
setHideServerPathInfo(true) 클라이언트에 서버 경로 숨김. 클라이언트가 serverFilePath로 파일을 찾는 구성에서는 켜지 않음
  • 저장 디렉터리는 웹 루트 밖에 둡니다. 안에 두면 업로드된 .jsp가 실행될 수 있습니다.
  • setDirectory()의 하위 폴더에는 사용자 입력 대신 세션 값이나 서버가 정한 ID만 씁니다.
  • 게시판 첨부는 저장 파일명을 UUID로 만들고 원본 이름은 DB에 따로 둡니다. \ / : * ? " < > |와 공백은 치환합니다.
  • 화면에 보여줄 이름은 clientFileName, 서버 파일명은 serverFileName입니다.
jsp
String _action        = uploader.getParameter("_action");
String _orig_filename = uploader.getParameter("_orig_filename");
String userId         = (String) session.getAttribute("userId");   // 서버 세션 값 사용

if ("getFileInfo".equals(_action) && _orig_filename != null) {
    String sub = new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date());
    uploader.setDirectory(directory + "/" + userId + "/" + sub);

    String ext = _orig_filename.contains(".")
        ? _orig_filename.substring(_orig_filename.lastIndexOf(".")) : "";
    uploader.setFileName(java.util.UUID.randomUUID().toString() + ext);
}
String result = uploader.run();

완료 처리와 DB 기록#

  • isUploadDone()은 파일 한 개의 저장이 끝난 요청에서 true입니다. 파일이 3개면 3번 실행되므로, 재시도로 두 번 통과해도 안전하게 저장 파일명을 DB 유일 키로 둡니다.
  • 전체 완료는 클라이언트 uploadComplete에서 확정 요청을 보내 처리합니다. 부분 실패 시에는 uploadError가 발생하므로 확정은 uploadComplete에서만 합니다.
  • 폼과 파일을 따로 저장할 때는 파일 먼저(임시 키 후 폼 제출 시 연결) 또는 폼 먼저(게시글 ID 발급 후 setPostData) 방식을 고릅니다.
  • 진행 상태를 서버에서 기록하려면 attachFile의 _start_offset, _end_offset을 씁니다. 구간 요청은 순서 없이 도착하므로 받은 구간의 합으로 계산하고, DB 대신 메모리나 캐시에 기록합니다.
jsp
String _run_retval = uploader.run();

if (uploader.isUploadDone()) {
    String orig  = uploader.getParameter("_orig_filename");
    String saved = uploader.getParameter("_new_filename");
    String size  = uploader.getParameter("_filesize");
    String board = uploader.getParameter("boardId");   // setPostData로 보낸 값

    // INSERT INTO attach(board_id, orig_name, saved_name, size) ...
}
javascript
box.on('uploadComplete', function (p) {
  // p.files: 이번 전송에서 올라간 파일 전체
  $.post('/board/attach/commit', { files: JSON.stringify(p.files) });
});

데이터 전달#

게시글 번호와 분류를 파일과 함께 보내고, 서버가 만든 첨부 ID를 화면에 돌려받는 경우입니다. 별도 요청 없이 업로드 요청에 값을 실어 보내고 응답으로 받습니다.

화면 → setPostData / setFilePostDataByIndex / setCustomHeader → 서버 getParameter / getHeader
서버 → setCustomValue + sendCustomValue → 화면 uploadComplete의 customValue

값 보내기#

메서드 설명 서버에서 읽기
setPostData(obj) 전송 전체 공통 값 (예: 게시글 번호) getParameter("키")
setFilePostDataByIndex(i, obj) 파일별 값 (예: 분류, 순번) getParameter("키")
setCustomHeader(obj) HTTP 헤더 값 (예: 인증 토큰) request.getHeader("키")
  • 모든 설정은 upload() 호출 전에 합니다.
  • 파일별 값의 인덱스는 getAllFiles() 호출 시점의 순서이므로 upload() 직전에 설정하고, 그 뒤에는 파일을 추가, 삭제하지 않습니다.
  • 값이 없으면 getParameter는 null이므로 확인한 뒤 씁니다.
  • 클라이언트가 보낸 값은 사용자가 바꿀 수 있으므로 사용자 ID 같은 인증 값은 서버 세션에서 읽고, 파일명, 경로에 쓰기 전에 검증합니다.
javascript
function upload() {
  box.setPostData({ boardId: '1024' });

  var files = box.getAllFiles();
  for (var i = 0; i < files.length; i++) {
    box.setFilePostDataByIndex(i, { customValue: files[i].fileSize });
  }

  box.setCustomHeader({ value: 'test' });   // 인증 토큰 등
  box.upload();
}
jsp
String boardId     = uploader.getParameter("boardId");
String customValue = uploader.getParameter("customValue");   // 값이 없으면 null
String headerValue = request.getHeader("value");

값 돌려받기#

서버는 run() 이후 isUploadDone() 또는 attachFileCompleted 블록에서 값을 지정해 보내고, 클라이언트는 uploadComplete의 파일 항목 customValue에서 읽습니다. 반환 값은 모두 문자열입니다.

메서드 설명
setCustomValue(키, 값) 서버에서 화면으로 돌려줄 값 지정 (예: 첨부 ID, 변환된 경로)
sendCustomValue() 지정한 값 전송
jsp
String _run_retval = uploader.run();

if (uploader.isUploadDone()) {
    uploader.setCustomValue("attachId", String.valueOf(newAttachId));
    uploader.sendCustomValue();
}
javascript
box.on('uploadComplete', function (p) {
  p.files.forEach(function (f) {
    console.log(f.customValue.attachId);
  });
});

한글이나 특수문자가 깨지면 서버에서 읽은 값의 인코딩을 확인합니다.

오류 및 재처리#

엘리베이터를 타거나 와이파이가 바뀌어 연결이 잠깐 끊기는 모바일 환경에서도 사용자가 처음부터 다시 올리지 않도록, 일시적인 오류는 자동 재시도하고 받은 위치부터 이어 올립니다. 서버가 정책상 거부하는 경우에는 사용자 정의 오류로 사유를 알립니다.

오류 발생 (uploadError)
   ├ 복구 불가 → 일시 정지 (uploadPause) → 사용자가 재개
   └ 복구 가능 → 자동 재시도 (uploadRetry) → 이어올리기
서버 거부 → InnorixCustomError → 전송창에 사유 표시

재시도 옵션과 오류 이벤트#

옵션 설명
retryCount, retryDelay 재시도 횟수, 간격. 크게 늘리면 장애 시 서버에 재시도가 몰림
autoRecovery 켜면 오류 시 자동으로 이어서 전송, 끄면 오류마다 재개 버튼 필요
maxErrorCount 허용 오류 횟수
skipErrorFile 오류 파일을 건너뜀. 정책 위반 파일은 재시도해도 같은 결과이므로 건너뛰거나 일시 정지로 알림
timeout 응답이 없는 연결 정리

이벤트 인자는 state, progress, retries, stopRetrying, statusMessage 등을 가진 상태 객체입니다. 일시 정지 상태에서는 transferResume() 또는 전송창의 재개 버튼으로 이어 올립니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  retryCount: 3,
  autoRecovery: true,
  maxErrorCount: 10,
  skipErrorFile: true
});

box.on('uploadRetry', function (p) { console.log('재시도', p.retries); });
box.on('uploadError', function (p) { console.log(p.statusMessage); });

오류 코드 구분#

statusMessage.errorCode는 다음처럼 나뉩니다.

코드 구분
0 클라이언트 오류
1399, 10001003 서버 오류
400~599 네트워크 오류
1004~1999 서버가 보낸 사용자 정의 오류 (서버가 지정한 문구 표시)
5000 라이선스 오류
10001 이상 파일 접근, 서버 저장, 무결성, 복호화 오류

서버 사용자 정의 오류#

권한 없음, 확장자 금지, 저장 공간 부족처럼 서버가 거부해야 할 때 씁니다. 사유를 사용자에게 보이려면 1004~1999 코드를 사용합니다. 압축 해제 실패는 코드 1002로 응답됩니다.

메서드 설명
InnorixCustomError.set(code, message, detail, confirm) 오류 코드와 사유 지정
run() 오류 응답. 이후 처리는 중단(return)
showCustomError(), setCustomError() InnorixUpload의 단축 메서드
jsp
InnorixCustomError err = new InnorixCustomError(response);
err.set("1006", "PathTooLong", "경로가 너무 깁니다.", false);
err.run();
return;

재처리 흐름#

일시 정지 후 재개하거나 페이지를 다시 열면 서버에 기록된 위치부터 이어 올립니다. 이를 위해 서버는 InnorixUpload.run()을 사용해야 합니다.

이름 설명
attachIncompleteFiles, autoLoadTransfer, uploadDuplicate 이전 전송 복원 제어. 거절하면 저장된 정보가 삭제되고 변경된 파일은 file_is_modified로 전달
closeTransferWindow() uploadError에서 일정 시간 뒤 전송창을 닫는 패턴에 사용
UploadInfoCallBack 서버가 여러 대일 때 이어올리기 정보 공유
  • 취소된 전송의 서버 임시 파일은 서버에서 정리합니다.
  • 한 사용자의 연결 수 제한은 컨트롤 옵션이 없으므로 서버, L4, WAF에서 합니다.
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  installURL: '../install/install.html',
  uploadDuplicate: true,        // 이어올리기
  attachIncompleteFiles: true,  // 로드 시 이전 전송 조회
  autoLoadTransfer: false       // false이면 사용자에게 확인
});

box.on('uploadError', function (p) {
  setTimeout(function () { box.closeTransferWindow(); }, 3000);
});

Exabyter Lite — 이미지 12

다운로드#

서버에 있는 파일을 사용자 PC로 안정적으로 내려받는 방법을 다룹니다. 각 기능은 설명 → 함수, 옵션 → 예시 순서로 되어 있어 필요한 기능만 찾아 읽을 수 있습니다.

다운로드 파일#

문서관리 화면에서 사용자가 계약서 여러 건을 체크하고 한 번에 받는 경우입니다. 컨트롤은 서버에 어떤 파일이 있는지 알지 못하므로, 애플리케이션이 목록을 만들어 presetDownloadFiles()로 넘기면 컨트롤이 전송과 저장을 맡습니다. 목록은 컨트롤 생성이 끝난 뒤(loadComplete)에 전달합니다.

서버 목록 API → 파일 목록 JSON → presetDownloadFiles() → 컨트롤 목록 → 다운로드 실행

파일 항목 만들기#

서버 목록을 컨트롤이 읽는 항목으로 바꿉니다. 사용자에게 보이는 파일명은 항상 printFileName이 정하며 서버의 저장 파일명과는 별개입니다.

이름 설명
printFileName 목록 표시와 저장에 쓰는 파일명(확장자 포함)
downloadUrl 파일을 제공하는 서버 URL. 인증이 필요하면 쿠키, 세션이 함께 전달되어야 함
fileSize 바이트 단위 숫자. "1.2MB" 같은 문자열은 사용 불가. 비우면 컨트롤이 HEAD 요청으로 크기 확인
rootName 폴더 경로. 구분자는 /로 통일
skipFileSizeCheck 크기 없이 항목 추가(변환 후 크기가 정해지는 파일)

서버 목록 API 응답을 printFileName/fileSize/downloadUrl로 변환해 loadComplete에서 전달합니다.

javascript
box.on('loadComplete', function () {
  $.getJSON('/board/1024/attachments', function (rows) {
    var list = rows.map(function (r) {
      return {
        printFileName: r.originalName,
        fileSize: r.size,   // 바이트 단위 숫자
        downloadUrl: '/download.jsp?fileID=' + encodeURIComponent(r.id)
      };
    });
    box.presetDownloadFiles(list);
  });
});

한글, 특수문자가 들어간 값은 encodeURIComponent()로 인코딩해 downloadUrl에 넣습니다.

javascript
box.presetDownloadFiles([{
  printFileName: '계약서 최종.pdf',
  fileSize: 4952305,
  downloadUrl: 'download.jsp?fileID=2&name=' + encodeURIComponent('계약서 최종.pdf')
}]);

검증에 실패한 항목만 목록에서 빠지고 addFileError 이벤트가 발생합니다.

오류 의미
invalid_download_file URL, 파일명, 크기 누락
duplicate_file 같은 downloadUrl이 이미 있음. 서로 다른 파일은 URL에 구분되는 파라미터를 넣음

폴더와 크기 다루기#

rootName을 지정하고 addFolder를 켜면 폴더 구조가 유지됩니다. 빈 폴더는 isFolder 항목으로 표현합니다. 항목이 많은데 fileSize를 생략하면 목록 구성이 느려지므로, 목록 API에서 크기를 함께 내려 주는 편이 좋습니다.

javascript
box = innorix.create({
  el: '#fileBox',
  addFolder: true,   // rootName을 하위 폴더로 저장
  boxConfig: box_config.download_agent
});

box.on('loadComplete', function () {
  box.presetDownloadFiles([
    { rootName: 'FolderA',         printFileName: 'Exabyter and Exchanger.png',
      fileSize: 281624,  downloadUrl: 'download.jsp?fileID=1' },
    { rootName: 'FolderA/FolderB', printFileName: 'Exabyter Brochure.pdf',
      fileSize: 4952305, downloadUrl: 'download.jsp?fileID=2' },
    // 빈 폴더
    { downloadUrl: 'test', printFileName: 'empty', fileSize: 0, isFile: false, isFolder: true }
  ]);
});

목록 개수는 getFileCount()로 확인합니다. 목록은 보이는데 받기가 실패하면 downloadUrl을 브라우저 주소창에 직접 넣어 서버 응답부터 확인합니다.

Exabyter Lite — 이미지 13

파일 제공#

로그인한 사용자만 받아야 하는 급여명세서를 내려주는 경우입니다. 목록에서 숨기는 것은 보안이 아니므로 권한 검사는 컨트롤이 호출하는 서버 URL에서 합니다. 컨트롤은 downloadUrl로 요청하고, 서버는 권한을 확인한 뒤 요청받은 구간의 바이트만 응답합니다(스트리밍, 기본 방식).

컨트롤 → downloadUrl 요청 → 서버(권한 검사 → 파일 조회 → 구간 응답) → Agent 저장
제공 방식 설명
스트리밍 다운로드(기본) 서버가 구간 요청을 받아 해당 바이트만 응답. 인증, 권한 검사가 필요할 때
직접 다운로드 downloadUrl을 그대로 요청. 정적 파일, 외부 스토리지, 사전 서명 URL. 대상 서버가 Range 요청을 지원하는지 먼저 확인
시작 알림 본 요청 전에 DownloadStart 요청을 먼저 보냄. 요청 시점에 파일을 만들거나 변환할 때
서버 중계 서버가 외부 시스템(ECM 등)에서 받아 대신 응답. 외부 인증을 브라우저에 노출하면 안 될 때

구간 응답 만들기#

서버는 요청 값을 읽어 해당 구간만 응답합니다.

요청 값 설명
_StartOffset, _EndOffset 요청 구간. 끝 위치를 포함하므로 길이는 end - start + 1
_Action 시작 알림을 켠 경우 DownloadStart(시작 전), DownloadComplete(완료 후)
응용 파라미터 fileID처럼 downloadUrl에 직접 넣은 값

응답에는 Accept-Ranges: bytes, Content-Disposition, Content-Length를 설정합니다. 구간 값이 없으면 전체를 보내 브라우저 직접 요청도 처리합니다.

요청 구간만 읽어 응답하는 download.jsp입니다.

jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.io.*" %>
<%
String szStart = request.getParameter("_StartOffset");
String szEnd   = request.getParameter("_EndOffset");
String fileID  = request.getParameter("fileID");

// fileID로 파일을 조회하고, 이 지점에서 권한을 검사합니다.
File file = lookupFile(fileID);
String orgFileName = lookupName(fileID);
orgFileName = java.net.URLEncoder.encode(orgFileName, "UTF-8").replaceAll("\\+", "%20");

response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Disposition", "attachment; filename=\"" + orgFileName + "\"");

long start = (szStart != null) ? Long.parseLong(szStart) : 0;
long end   = (szEnd   != null) ? Long.parseLong(szEnd)   : 0;
long length = (szStart != null || szEnd != null)
        ? end - start + 1      // 컨트롤 요청: 지정 구간
        : file.length();       // 브라우저 직접 요청: 전체
response.setHeader("Content-Length", String.valueOf(length));

InputStream in = null;
OutputStream outStream = null;
try {
    in = new BufferedInputStream(new FileInputStream(file));
    outStream = new BufferedOutputStream(response.getOutputStream());
    if (start > 0) in.skip(start);

    byte[] buf = new byte[8192];
    while (length > 0) {
        int read = in.read(buf, 0, (int) Math.min(buf.length, length));
        if (read == -1) break;
        outStream.write(buf, 0, read);
        length -= read;
    }
} finally {
    if (outStream != null) { outStream.flush(); outStream.close(); }
    if (in != null) in.close();
}
%>

InnorixDownload 클래스를 쓰면 구간 응답과 헤더 설정을 한 번에 처리합니다.

jsp
<%@ page import="com.innorix.transfer.InnorixDownload" %>
<%
InnorixDownload d = new InnorixDownload(request, response, "UTF-8", directory);   // directory는 / 로 끝남
d.setFileName(sysFileName);        // 서버의 저장 파일명
d.setPrintFileName(orgFileName);   // Content-Disposition 파일명
d.run();
%>

본문 앞에 공백, 줄바꿈이 나가면 파일이 손상되므로 response.getOutputStream()만 사용합니다. Content-Disposition 파일명은 URLEncoder로 인코딩한 뒤 +를 %20으로 바꿉니다.

권한 검사#

요청마다 세션(401)과 접근 권한(403)을 확인합니다. 구간 요청마다 같은 검사를 하며, 예제는 5. 고급 활용 > 보안 > 로그인 사용자만 다운로드를 참고합니다.

요청 시점에 파일 만들기#

시작 알림(sendDownloadTime)을 켜면 파일마다 본 요청 전에 _Action=DownloadStart가 옵니다(직접 다운로드에서는 오지 않음). 파일 변환, DRM 해제 후 실제 크기를 알리는 예제는 5. 고급 활용 > 외부 모듈 연계 > 다운로드 시 DRM 해제를 참고합니다.

다운로드 실행#

사진 관리 화면에서 사용자가 몇 장만 골라 받거나 "전체 받기"를 누르는 경우입니다. 시작 시점과 대상은 화면마다 다르므로 용도에 맞는 메서드를 고릅니다. 시작 직전에 downloadBefore 이벤트가 발생하며 여기서 시작을 막을 수 있습니다.

목록 구성 → downloadBefore(차단 가능) → 전송 시작 → 진행 → 완료

시작 메서드#

메서드 설명 언제 쓰나
download() 목록 전체를 받음 자동 시작, 일반 버튼
downloadAll() downloadBefore를 발생시킨 뒤 download() 호출 시작 전 확인이 필요한 전체 받기
downloadSelectedFiles() 선택한 항목만 받음 체크박스로 고르는 화면
downloadAndOpen() 파일 하나를 받은 뒤 열기. downloadBefore를 발생시키지 않음 미리 보기 성격의 화면
startTransferProgress() 대기 중인 전송 시작 transferStart를 수동으로 둔 경우

전송할 파일이 없으면 알림을 표시하고 중단합니다. 속도 제한은 5. 고급 활용 > 전송 제어를 참고합니다.

시작과 시작 전 확인#

자동으로 시작하려면 목록을 만든 직후 download()를 호출합니다.

javascript
box.on('loadComplete', function () {
  box.presetDownloadFiles(filesFromServer);
  box.download();
});

사용자 확인 후 시작하려면 downloadBefore 핸들러가 false를 정확히 반환하게 하여 전송을 막습니다.

javascript
box.on('downloadBefore', function () {
  return window.confirm('다운로드를 시작할까요?');
});

버튼을 누르면 전송 창이 열리고 진행됩니다. downloadStart, downloadProgress, downloadComplete 이벤트로 진행률과 속도를 확인합니다.

저장 처리#

사용자가 매번 같은 보고서를 받아 이미 같은 이름의 파일이 폴더에 있는 경우입니다. 저장 위치와 같은 이름 처리 정책을 미리 정해 두면 사용자에게 묻는 일이 줄어듭니다. 경로는 savePath 옵션이나 경로 선택 대화상자(setDownloadPath())로 정하고, 이름 충돌은 downloadDuplicate로 처리합니다.

저장 경로 결정 → 동일 파일명 확인 → 폴더 구조 반영 → 파일 저장

같은 이름 처리 (downloadDuplicate)#

값 설명 언제 쓰나
resume 미완료 파일이면 이어받기 큰 파일을 다시 받는 화면
overwrite 덮어쓰기 항상 최신 결과로 교체해야 할 때
numbering 번호를 붙여 따로 저장 기존 파일을 보존하면서 사용자 개입이 없어야 할 때
confirm 확인 대화상자 표시. 문구는 언어 리소스로 변경 가능 사용자가 직접 고르게 할 때

저장 경로 정하기#

이름 설명
savePath 지정한 경로에 저장. 지정하지 않으면 Agent의 기본 경로 사용
getDownloadPath() 현재 저장 경로 조회
pathChange 켜면 사용자가 경로를 바꿀 수 있음. 경로 변경은 전송 시작 전에만 가능
setDownloadPath(callback) 경로 선택 대화상자 열기
openDownloadFolder() 저장 폴더 열기

저장 경로와 같은 이름 처리 방식을 생성 옵션으로 지정합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  savePath: 'C:\\Downloads\\exabyter',   // 백슬래시를 두 번 씀
  downloadDuplicate: 'numbering',
  boxConfig: box_config.download_agent
});

경로 선택 대화상자를 열고, 유효한 경로를 고른 경우에만 전송을 시작합니다.

javascript
function downloadToChosenFolder() {
  box.setDownloadPath(function (response) {
    if (response.result == true) {
      box.download();
    }
  });
}

Windows 경로의 \는 JavaScript 문자열에서 \\로 씁니다.

폴더 구조와 파일명#

addFolder가 켜져 있을 때만 rootName이 하위 폴더로 반영됩니다. 하위 폴더까지 받으면 경로가 길어지므로 목록 단계에서 깊이와 파일명 길이를 점검합니다. Windows에서 쓸 수 없는 문자(\ / : * ? " < > |)는 printFileName에서 미리 바꿉니다. 저장 후에는 저장 폴더를 열어 파일명, 하위 폴더, 충돌 처리 결과를 확인합니다.

Exabyter Lite — 이미지 14

Exabyter Lite — 이미지 15

오류 및 재처리#

출장 중 와이파이가 끊겨 2GB 영상이 80%에서 멈춘 경우입니다. 처음부터 다시 받지 않고 끊긴 지점부터 이어받도록 구성합니다. 오류가 나면 downloadError가 발생하며, 자동 복구가 켜져 있으면 잠시 후 이어받고 꺼져 있으면 일시중지되어 사용자가 재개합니다. 이어받기는 서버가 구간 응답을 정확히 지원해야 동작하고, 브라우저를 닫았다 다시 열면 미완료 전송이 있을 때 이어받을지 묻습니다.

오류 발생 → downloadError → (autoRecovery 켜짐) 잠시 후 이어받기 → downloadRetry
                         → (autoRecovery 꺼짐) 일시중지 → 사용자 재개

재시도 옵션#

옵션 설명
retryCount, retryDelay Agent의 재시도 횟수와 간격
autoRecovery 오류 후 같은 전송을 자동 재개
maxErrorCount 허용하는 연속 오류 횟수. 넘으면 일시중지
skipErrorFile 오류 파일을 건너뜀(여러 파일 중 일부 실패 허용)
attachIncompleteFiles 컨트롤 생성 시 미완료 전송 조회(재방문 시 이어받기)

이 밖의 옵션은 6. API Reference를 참고합니다.

오류 이벤트 처리#

downloadError, downloadRetry, downloadPause, downloadCancel 핸들러를 등록해 화면에 상태를 보여 줍니다. 이벤트 매개변수의 statusMessage에서 상태 ID와 오류 코드를 확인합니다. 사용자는 transferPause(), transferResume(), transferCancel()로 전송을 제어합니다.

javascript
box.on('downloadError',  function (p) { console.log(p.statusMessage); });
box.on('downloadRetry',  function (p) { console.log('retry', p.retries); });
box.on('downloadPause',  function (p) { console.log('paused'); });
box.on('downloadCancel', function (p) { console.log('canceled'); });

서버 오류 응답#

서버는 오류를 HTTP 오류 코드 또는 InnorixCustomError로 응답합니다. 사용자 정의 코드는 1000~1999 범위를 쓰며 이 범위의 문구가 전송 창에 표시됩니다. 오류 페이지를 200으로 응답하면 파일 내용으로 저장되므로 쓰지 않습니다.

jsp
<%@ page import="com.innorix.transfer.InnorixCustomError" %>
<%
InnorixCustomError customError = new InnorixCustomError(response);
customError.set("1600", "customErrorTitle", "customErrorMessage", false);
customError.run();
return;
%>

오류 종류와 조치#

오류 확인할 것
서버 스크립트 오류 서버 로그
서버에 파일 없음 downloadUrl과 권한 검사 응답
무결성 검사 오류 서버 파일 변경 여부. 이어받기 정보 삭제 후 재시도
복호화 오류 키와 isCrypt 설정
네트워크, 소켓 오류 프록시, 세션, 인증
파일 열기, 쓰기, 경로 길이 오류 저장 폴더 권한, 용량, 경로 길이
  • 서버 파일이 교체되었는데 이어받으면 파일이 손상될 수 있습니다. 파일을 자주 교체하는 업무라면 deleteDownloadResumeInfo()로 재개 정보를 지우거나 overwrite/numbering을 사용합니다.
  • 실패한 전송의 임시 파일은 자동 삭제되지 않는 것으로 보고 정리 절차를 둡니다.
  • 계속 실패하면 같은 URL을 브라우저 주소창에서 호출해 서버 응답을 확인하고, 구간 호출이 요청 길이만큼의 Content-Length로 응답하는지 봅니다.

완료 처리#

다운로드가 끝나면 누가 어떤 파일을 받았는지 이력을 남겨야 하는 경우입니다. 전송이 끝나면 페이지에 downloadComplete 이벤트가 발생하고, 설정했다면 서버의 완료 알림 URL이 호출됩니다.

전송 완료 → downloadComplete 이벤트(페이지) + 완료 알림 URL 호출(서버) → 후속 처리

완료 이벤트와 서버 알림#

방식 설명 언제 쓰나
downloadComplete 이벤트 페이지에서 파일 목록을 받아 처리. 핸들러가 false를 반환하면 전송 창이 자동으로 닫히지 않음 화면 갱신, 외부 모듈 호출
downloadCompletedEvent 옵션 use와 url을 지정하면 완료 시 해당 URL 호출 서버에 다운로드 이력 기록
_Action=DownloadComplete 파일 제공 URL에서 _Action으로 분기해 처리 인증 로직을 한 곳에 두고 싶을 때

완료 알림은 클라이언트가 보내는 요청이므로 그대로 신뢰하지 않습니다. 과금이나 권한 차감은 서버가 구간 응답을 보낸 기록을 기준으로 합니다.

완료 알림 URL을 지정하고, 페이지에서는 downloadComplete 이벤트를 받습니다.

javascript
box = innorix.create({
  el: '#fileBox',
  downloadCompletedEvent: { use: true, url: '/innorix/download-complete.jsp' },
  boxConfig: box_config.download_agent
});

box.on('downloadComplete', function (p) {
  console.log(p.files);
});

서버는 _Action=DownloadComplete 요청을 받아 이력을 기록하고 200으로 응답합니다.

jsp
<%
String action = request.getParameter("_Action");
if ("DownloadComplete".equals(action)) {
    // 이력 기록
    response.setStatus(200);
    return;
}
%>

서버 로그에 완료 기록이 생기고 전송 창이 정상적으로 닫히면 정상입니다.

무결성과 암호화#

downloadIntegrity로 전송 후 무결성 검사를 켤 수 있으며, 서버 연동 사양은 별도 확인이 필요합니다. 암호화 저장 파일은 항목의 isCrypt로 표시합니다. 목록 첫 항목의 값이 전송 전체에 적용되므로 암호화 파일은 별도 전송으로 분리합니다.

고급 활용#

기본 업로드, 다운로드 이후에 필요한 화면 구성, 스토리지 연동, 보안, 외부 모듈 연계, Agent 설정을 다룹니다. 각 절은 하나의 상황에서 시작해 설정과 코드를 한 흐름으로 설명합니다.

화면 및 컨트롤#

한 화면에 "계약 문서"용 첨부와 "현장 사진"용 첨부를 따로 두고, 이렇게 컨트롤을 여러 개 배치할 때는 컨트롤 자체를 어떻게 구성할지 먼저 정해야 합니다.

컨트롤은 innorix.create()가 만드는 파일 전송 단위입니다. 컨트롤마다 고유한 el, 설정, 파일 목록, 이벤트를 따로 가집니다. 한 페이지에 컨트롤을 여러 개 만들 수 있고, 화면이 사라질 때 destroy()로 해제할 수도 있습니다.

create(el) → loadComplete → 파일 추가, 전송, 이벤트 수신 → destroy()
   └ 컨트롤 A (업로드)   ─┐
   └ 컨트롤 B (업로드)   ─┼→ 화면 하나, 목록, 이벤트는 컨트롤마다 독립
   └ 컨트롤 C (다운로드) ─┘

한 화면에 컨트롤 여러 개 두기#

컨트롤마다 el과 변수를 따로 두고 설정을 독립적으로 줍니다. 이벤트도 컨트롤 객체에 등록하므로 한 컨트롤의 이벤트가 다른 컨트롤에 전달되지 않습니다.

  • el: 컨트롤을 그릴 요소. 컨트롤마다 달라야 합니다.
  • allowType: 컨트롤별 허용 확장자
  • setPostData(): 서버가 컨트롤을 구분하도록 값을 함께 전송

개별 전송 예제입니다. 문서용과 사진용 컨트롤이 각자 자기 목록을 올립니다 (exam/upload-multiSample.html).

html
<div id="fileBox1"></div>
<div id="fileBox2"></div>

<script>
  var box1, box2;
  window.onload = function () {
    box1 = innorix.create({ el: '#fileBox1', uploadURL: './upload.jsp', allowType: ['pdf', 'mp4'] });
    box2 = innorix.create({ el: '#fileBox2', uploadURL: './upload.jsp', allowType: ['jpg'] });

    box1.on('afterAddFiles', function () { box1.setPostData({ slot: 'doc' });   box1.upload(); });
    box2.on('afterAddFiles', function () { box2.setPostData({ slot: 'photo' }); box2.upload(); });
  };
</script>

서버에서는 setPostData로 보낸 값으로 어느 컨트롤의 요청인지 구분합니다.

jsp
String slot = uploader.getParameter("slot");

통합 전송은 여러 컨트롤의 파일을 한 컨트롤로 모아 한 번에 올립니다 (exam/combine-agent-3box.html).

js
function upload() {
  box3.addFiles(box1.getAllFiles());
  box3.addFiles(box2.getAllFiles());
  box3.upload();
}

여러 컨트롤에 같은 메서드를 한꺼번에 호출하려면 innorix.group()으로 묶습니다. 그룹에서 호출한 메서드는 묶인 컨트롤 각각에 같은 인자로 실행됩니다.

js
var all = innorix.group(box1, box2);
all.removeAllFiles();            // 두 컨트롤의 목록을 모두 비움
var counts = all.getFileCount(); // [box1의 개수, box2의 개수]

group().upload()는 컨트롤마다 자기 목록을 각각 전송할 뿐 하나의 전송으로 합치지 않습니다. 하나로 합치려면 위의 addFiles() 방식을 씁니다.

컨트롤 동적 생성과 해제#

팝업, 탭, SPA처럼 화면이 바뀌는 곳에서는 화면이 열릴 때 컨트롤을 만들고 닫힐 때 해제합니다. el 요소가 DOM에 있을 때 create()를 호출해야 합니다.

  • innorix.create(option): 컨트롤 생성
  • box.setSize(w, h): 크기 변경
  • box.destroy(): 목록 UI를 제거하고 onDestroy 이벤트 발생 후 컨트롤 속성을 삭제
js
var box = null;

function openUploader() {
  box = innorix.create({
    el: '#fileBox',
    uploadURL: '/innorix/upload.jsp',
    boxWidth: 600,
    boxHeight: 300,
    boxConfig: box_config.upload_basic
  });
  box.on('uploadComplete', function (p) { console.log(p.files); });
}

function closeUploader() {
  if (box) {
    box.destroy();
    box = null;
  }
}

destroy() 후에는 같은 변수를 다시 쓰지 말고 innorix.create()로 새로 만듭니다. 같은 el에 중복 생성하지 말고 먼저 해제합니다. loadComplete 이전에 removeAllFiles() 등을 호출하면 false가 반환됩니다.

사용자 정의 드롭존#

컨트롤 밖의 임의 요소(게시판 본문, 별도 카드 등)를 드롭 영역으로 지정합니다. dragenter가 발생할 때마다 setDropZone(이벤트, 요소)로 대상 요소를 알립니다.

  • enableDropZone: 컨트롤 기본 드롭 영역 사용 여부 (기본 true)
  • setDropZone(evt, el): 드롭 영역 지정
  • setDropzoneError: 파일이 아닌 항목을 드롭했을 때 발생하는 이벤트
html
<div id="dropZone" style="width:500px; height:200px; border:1px dashed #999;">
  여기에 파일을 끌어놓으세요
</div>
<div id="fileControl" style="display:none"></div>

<script>
  var innoJquery = innorix._load('innoJquery');
  var control;

  innoJquery(document).ready(function () {
    control = innorix.create({ el: '#fileControl', uploadURL: './upload.jsp' });

    innoJquery('#dropZone').on({
      dragenter: function (evt) {
        control.setDropZone(evt, this);   // 이 요소를 드롭 영역으로 지정
      }
    });
  });
</script>

대상 요소가 스크롤되거나 확대, 축소된 상태이면 드롭 위치가 어긋날 수 있습니다.

임베디드 컨트롤의 전송창을 호스트 페이지에 출력#

컨트롤이 다른 페이지에 임베드된 구조(호스트 페이지 안의 임베디드 컨트롤)에서 전송창을 임베디드 영역 밖의 호스트 페이지에 띄우려면 hostTransferWindow를 켭니다.

  • hostTransferWindow: 전송창을 호스트 페이지에 출력 (기본 false)
  • hostTransferWindowTarget: 출력할 호스트 페이지. 'top'(최상위, 기본) 또는 'parent'(직전 상위)
  • hostTransferWindowTop, hostTransferWindowLeft: 전송창 위치 (default면 중앙)
  • hostTransferWindowCssURL: 호스트 페이지 <head>에 추가할 <link> 태그 문자열
js
box = innorix.create({
  el: '#fileBox',
  uploadURL: '/innorix/upload.jsp',
  hostTransferWindow: true,
  hostTransferWindowTarget: 'top',
  hostTransferWindowCssURL: '<link rel="stylesheet" href="/css/innorix.css">',
  boxConfig: box_config.upload_basic
});

임베디드 컨트롤과 호스트 페이지는 같은 출처여야 하고, 호스트 페이지에 전역 jQuery가 로드되어 있어야 합니다. 호스트 페이지에 innorix.css가 없으면 전송창이 깨져 보이므로 hostTransferWindowCssURL로 추가하거나 직접 포함합니다.

컨텍스트 메뉴와 Del 키 삭제#

목록에서 오른쪽 버튼으로 여는 메뉴는 useContextMenu로 켜고 끕니다. 기본값은 true이며 false이면 메뉴를 만들지 않고 브라우저 기본 메뉴가 나타납니다.

js
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  useContextMenu: false   // 컨텍스트 메뉴 비활성
});

컨트롤에는 Del 키 삭제 기능과 이를 켜고 끄는 옵션이 없습니다. 필요하면 화면에서 keydown을 받아 선택 파일을 삭제하는 코드를 직접 추가하고, 이 핸들러를 붙이거나 떼서 활성, 비활성을 정합니다.

js
function onDelKey(e) {
  if (e.key === 'Delete' && box.getSelectedFileCount() > 0) {
    box.removeSelectedFiles();
  }
}
document.addEventListener('keydown', onDelKey);       // Del 키 삭제 활성
// document.removeEventListener('keydown', onDelKey); // 비활성

삭제 전에 확인을 받으려면 beforeRemoveFile 이벤트에서 false를 반환합니다.

리스트 이벤트 받기#

목록의 생성 완료, 선택, 해제, 삭제는 컨트롤 객체의 이벤트로 받습니다.

이벤트 시점 인자
loadComplete 컨트롤 준비 완료(생성 완료)
afterAddFiles 파일이 목록에 추가된 직후 추가된 파일 배열
onSelectRows 행 선택 파일 배열
onUnSelectRows 행 선택 해제 파일 배열
beforeRemoveFile 삭제 직전 (false 반환 시 취소) 파일
removeFiles 삭제 완료 삭제된 파일 배열
onDestroy 컨트롤 해제
js
box.on('loadComplete', function () { console.log('컨트롤 준비 완료'); });
box.on('onSelectRows',   function (p) { console.log('선택', p); });
box.on('onUnSelectRows', function (p) { console.log('해제', p); });
box.on('removeFiles',    function (p) { console.log('삭제됨', p); });

선택한 파일이 없을 때 삭제 버튼을 비활성화하려면 세 이벤트에서 box.getSelectedFileCount()를 확인합니다.

js
function syncButtons() {
  document.getElementById('delBtn').disabled = box.getSelectedFileCount() === 0;
}
box.on('onSelectRows', syncButtons);
box.on('onUnSelectRows', syncButtons);
box.on('removeFiles', syncButtons);

그 밖의 이벤트와 옵션은 6. API Reference를 참고합니다.

Exabyter Lite — 이미지 16

전송창, 리스트 상세 구성#

사내 결재 화면의 첨부 영역에 파일 목록을 좁게 넣고, 진행 상태는 화면 디자인에 맞춘 자체 바로 보여주고 싶은 경우입니다. 목록과 전송창은 옵션으로 구성하고, 필요한 부분만 직접 만든 화면으로 바꿀 수 있습니다.

화면은 두 영역입니다. 파일을 보여주는 **리스트(파일 목록)**와 전송 중에 나타나는 전송창입니다. 전송창 모양은 transferWindowStyle, 리스트 모양은 boxStyle로 고릅니다. 기본 구성은 box_config.* 프리셋이 정하고, innorix.create()에 직접 넘긴 옵션이 프리셋을 덮어씁니다. 전송 상태는 이벤트로 전달되므로 전송창을 숨기고 자체 진행 화면을 만들 수도 있습니다.

컨트롤 생성 → 리스트(boxStyle, 크기, 폴더) → 전송 시작 → 전송창(transferWindowStyle)
                                                       └→ (또는) 숨김 + 이벤트 → 자체 진행 화면

전송창 UI#

transferWindowStyle로 전송창 모양을 고릅니다. 값은 default, mini, list, compact 네 가지입니다.

기본#

transferWindowStyle: 'default'는 기본 크기의 전송창입니다. 진행률, 속도, 남은 시간, 상태와 시작, 일시정지, 취소 버튼이 있는 제공 전송창을 그대로 씁니다.

옵션 역할
transferWindowStyle 전송창 모양. default, mini, list, compact
transferWindowTitle 제목 줄에 표시할 문구(기본 Exabyter)
hostTransferWindow 임베디드 컨트롤이 전송창을 호스트 페이지에 출력
draggableTransferWindow 제목 줄을 끌어 창 이동(기본 true)
transferStart 전송 시작 방식. 'auto'는 upload() 호출 즉시, 'manual'은 사용자가 시작 버튼을 눌러 시작
cancelConfirmation 전송 중 닫기 버튼을 누르면 취소 여부를 한 번 더 확인

제목을 바꾸고, 수동 시작과 취소 확인을 켠 기본 전송창으로 구성합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  transferWindowStyle: 'default',        // 기본 전송창
  transferWindowTitle: '첨부파일 업로드',
  transferStart: { upload: 'manual' },   // 시작 버튼으로 시작
  cancelConfirmation: true,              // 닫을 때 취소 확인
  boxConfig: box_config.upload_basic
});

컨트롤이 다른 페이지에 임베드되어 있으면 호스트 페이지에 출력하도록 지정합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  hostTransferWindow: true,            // 전송창을 호스트 페이지에 출력
  hostTransferWindowTarget: 'top',     // 'top' 최상위 호스트, 'parent' 직전 상위 호스트
  draggableTransferWindow: true,       // 제목 줄을 끌어 이동
  boxConfig: box_config.upload_basic
});

Exabyter Lite — 이미지 17

프리셋은 transferStart가 자동 시작이므로 수동 시작은 직접 덮어씁니다. hostTransferWindow를 쓰려면 출력할 호스트 페이지에 jQuery와 innorix.css가 로드되어 있어야 하며, 호스트 페이지와 임베디드 컨트롤은 같은 출처여야 합니다.

미니#

transferWindowStyle: 'mini'는 레이어로 출력하는 전송창의 작은 버전입니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  transferWindowStyle: 'mini',   // 레이어로 출력하는 작은 전송창
  boxConfig: box_config.upload_basic
});

Exabyter Lite — 이미지 18

목록 포함#

transferWindowStyle: 'list'는 레이어로 출력하며, 진행도 위에 파일 목록을 함께 표시합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  transferWindowStyle: 'list',   // 레이어로 출력, 진행도 위에 파일 목록 표시
  boxConfig: box_config.upload_basic
});

Exabyter Lite — 이미지 19

컴팩트#

transferWindowStyle: 'compact'는 별도 레이어 없이 리스트 하단에 전송 상태를 표시합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  transferWindowStyle: 'compact',   // 레이어 없이 리스트 하단에 표시
  boxConfig: box_config.upload_basic
});

Exabyter Lite — 이미지 20

숨김#

전송창을 아예 숨기려면 showTransferWindow: false로 끄고 uploadProgress/downloadProgress 이벤트로 진행 화면을 직접 만듭니다. 페이지의 지정 영역에 진행률, 상태, 버튼을 직접 그리는 방식도 같은 구성입니다.

항목 역할
showTransferWindow: false 기본 전송창을 열지 않음
uploadProgress, downloadProgress 전송 상태 객체(progress, speed, state 등)를 전달
transferPause(), transferResume(), transferCancel() 일시정지, 재개, 취소

state는 Before, Ready, Transferring, Complete, Cancel, Error, Pause 중 하나이고, speed는 바이트/초 단위입니다. 전송창을 숨기면 사용자가 취소, 일시정지할 수단이 사라지므로 세 메서드를 자체 버튼에 반드시 연결합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  showTransferWindow: false,   // 기본 전송창을 열지 않음
  boxConfig: box_config.upload_basic
});

box.on('uploadProgress', function (s) {
  document.getElementById('pg').value = s.progress;
  document.getElementById('st').innerText = s.state;
  document.getElementById('sp').innerText = Math.round(s.speed / 1024);
});

box.on('uploadComplete', function (p) {
  console.log(p.files);
});

본문의 #transferPanel 영역에 상태를 표시하고 버튼을 연결합니다.

html
<div id="transferPanel" style="border:1px solid #c0c0c0; padding:8px; width:400px;">
  <div>상태: <span id="st">대기</span> / 속도: <span id="sp">0</span> KB/s</div>
  <progress id="pg" value="0" max="100" style="width:100%"></progress>
  <input type="button" value="일시정지" onclick="box.transferPause();"/>
  <input type="button" value="재개" onclick="box.transferResume();"/>
  <input type="button" value="취소" onclick="box.transferCancel();"/>
</div>

Exabyter Lite — 이미지 21

전송 상태가 바뀔 때마다 이벤트가 호출되어 자체 화면이 갱신되고, 버튼이 동작하면 정상입니다. 다운로드는 downloadProgress로 같은 방식을 씁니다.

리스트 UI#

boxStyle로 리스트 모양을 고릅니다. 값은 list, icon, preview, html 네 가지입니다.

리스트형#

boxStyle: 'list'는 리스트형 목록입니다. 크기와 폴더, 대량 목록 옵션은 다음과 같습니다.

옵션 역할
boxWidth, boxHeight 목록 크기(px). el 요소의 CSS가 아니라 이 옵션으로 지정
setSize(w, h) 생성 후 크기 변경
folderAttach, showFolderItems 폴더 첨부 허용과 폴더 안 항목의 트리 표시
maxMassFileListCount 행으로 표시할 최대 개수(최대 1000). 넘으면 안내 행과 개수, 크기 요약 표시
massFileTransfer true이면 파일 수와 관계없이 대량 전송

리스트형으로 크기를 지정해 만들고, 폴더 트리와 대량 목록을 켭니다.

javascript
box = innorix.create({
  el: '#fileBox',
  boxStyle: 'list',
  boxWidth: 980,
  boxHeight: 465,
  uploadURL: './upload.jsp',
  folderAttach: true,           // 폴더 첨부 허용
  showFolderItems: true,        // 폴더 안의 항목을 표시
  maxMassFileListCount: 500,    // 500개까지만 행으로 표시
  boxConfig: box_config.upload_basic
});

// 생성 후 크기 변경
box.setSize(700, 300);

Exabyter Lite — 이미지 22

목록이 지정한 크기로 표시되고, 폴더를 추가하면 하위 항목이 트리로 나타납니다. 표시되지 않는 파일은 행이 없으므로 removeAllFiles()로 일괄 정리합니다.

아이콘형#

boxStyle: 'icon'은 아이콘형 목록입니다.

javascript
box = innorix.create({
  el: '#fileBox',
  boxStyle: 'icon',
  boxWidth: 550,
  boxHeight: 200,
  uploadURL: './upload.jsp',
  boxConfig: box_config.upload_basic
});

Exabyter Lite — 이미지 23

미리보기#

boxStyle: 'preview'는 미리보기 패널을 함께 보여주는 목록입니다. 미리보기 출력 위치와 PDF 표시는 다음 옵션으로 정합니다.

옵션 역할
setPreviewDiv 미리보기를 지정한 요소에 출력. [요소 ID, 너비, 높이]
showPreviewPdf PDF 첫 페이지를 캔버스에 표시. 페이지에서 PDF.js(pdfjsLib)를 먼저 로드해야 함

미리보기를 컨트롤 밖의 #previewBox에 출력합니다.

html
<div id="previewBox"></div>
<div id="fileBox"></div>

<script>
  box = innorix.create({
    el: '#fileBox',
    uploadURL: './upload.jsp',
    boxStyle: 'preview',
    setPreviewDiv: ['previewBox', 240, 180]   // [요소 ID, 너비, 높이]
  });
</script>

Exabyter Lite — 이미지 24

인증이 필요한 이미지 URL은 쿠키 전달과 CORS 허용이 필요합니다. HEIC 미리보기는 지원하지 않습니다.

HTML형#

boxStyle: 'html'은 애플리케이션이 HTML로 목록을 직접 그리는 형입니다. 컨트롤은 화면에 숨기고 목록을 HTML 표로 그립니다. 컨트롤은 파일 선택과 전송을 맡습니다.

항목 역할
컨트롤 요소 display:none으로 숨김
afterAddFiles 인자 배열의 filePath, fileSize, id로 표 행 생성
removeFileById(id) 표에서 행을 지우면서 컨트롤의 목록에서도 삭제
html
<div id="dropZone" style="overflow:auto; width:500px; height:200px; border:1px solid #c0c0c0;">
  <table id="fileTable">
    <thead>
      <tr><th>파일명</th><th>용량</th><th>구분</th><th>삭제</th></tr>
    </thead>
    <tbody></tbody>
  </table>
</div>
<div id="fileControl" style="display:none"></div>

<input type="button" value="파일추가" onclick="control.openFileDialog();">
<input type="button" value="업로드" onclick="control.upload();">

<script src="../innorix.js"></script>
<script>
  var innoJquery = innorix._load('innoJquery');
  var control;

  innoJquery(document).ready(function () {
    control = innorix.create({
      el: '#fileControl',
      boxStyle: 'html',
      uploadURL: './upload.jsp'
    });

    // 파일 추가 후 표에 행을 만든다
    control.on('afterAddFiles', function (p) {
      for (var i = 0; i < p.length; i++) {
        var name = p[i].filePath.replace(/\\/g, '/').split('/').pop();
        innoJquery('#fileTable > tbody').append(
          '<tr><td>' + name + '</td><td>' + p[i].fileSize + '</td><td>일반</td>' +
          '<td><input type="button" value="삭제" onclick="deleteRow(this, \'' + p[i].id + '\')"></td></tr>');
      }
    });
  });

  // 표 행과 컨트롤의 파일을 함께 삭제한다
  function deleteRow(btn, id) {
    var row = btn.parentNode.parentNode;
    row.parentNode.removeChild(row);
    control.removeFileById(id);
  }
</script>

Exabyter Lite — 이미지 25

표 행 순서와 컨트롤의 파일 순서가 어긋나지 않도록 행 삭제는 반드시 removeFileById()와 함께 처리합니다. 파일을 추가하면 HTML 표에 행이 나타나면 정상입니다.

이미지 처리#

상품 등록 화면에서 사진을 올리면 목록용 작은 이미지와 로고 워터마크가 함께 필요한 경우입니다. 별도 서버 변환 없이 컨트롤이 업로드 전에 썸네일을 만들고 워터마크를 넣어 원본과 함께 올립니다.

이미지 처리는 서버가 아니라 클라이언트에서 수행합니다. 서버는 썸네일을 일반 파일로 받습니다. 썸네일은 업로드 목록에 파일 한 개로 추가되므로 서버에서는 원본과 썸네일이 각각 한 번씩 저장 완료됩니다. 대상은 이미지 파일뿐이며 다른 파일은 건너뜁니다.

이미지 첨부 → appendThumbnailProperty / appendWatermarkProperty → upload()
            → 원본 + 썸네일 파일이 각각 서버에 저장

썸네일 생성#

파일을 추가한 뒤 upload() 호출 전에 appendThumbnailProperty(index, width, height, baseline)를 호출합니다.

인자 역할
index 파일 인덱스 또는 "ALL"
width, height 썸네일 크기
baseline HORIZONTAL(가로 기준), VERTICAL(세로 기준), FIX(고정 크기)

생성 시작, 종료는 thumbnailStart, thumbnailComplete 이벤트로 받습니다. 썸네일은 항상 JPEG이므로 원본이 PNG여도 형식이 다를 수 있습니다.

upload() 전에 썸네일 속성을 지정하고, 서버에서는 파일명의 _thumb_로 구분합니다.

javascript
box.appendThumbnailProperty('ALL', 300, 200, 'HORIZONTAL');
box.upload();
jsp
if (uploader.isUploadDone()) {
    String saved = uploader.getParameter("_new_filename");
    boolean isThumb = saved != null && saved.contains("_thumb_");
}

워터마크 삽입#

appendWatermarkProperty(index, imageUrl, position)를 호출합니다. 위치는 세로 값(TOP, BOTTOM, CENTER)과 가로 값(LEFT, RIGHT, CENTER)을 |로 조합합니다. 허용되지 않은 확장자의 이미지를 주면 "not_supported"를 반환하고 적용하지 않으므로 반환값을 확인합니다. 적용 결과는 JPEG입니다. 원본 변조 방지나 형식 통일처럼 서버에서 반드시 변환해야 하는 정책이면 클라이언트 처리에 의존하지 말고 완료 처리에서 서버 이미지 라이브러리로 처리합니다.

로고 이미지를 왼쪽 아래에 삽입합니다. 반환값이 "not_supported"이면 적용되지 않은 것입니다.

javascript
var r = box.appendWatermarkProperty('ALL', './logo.png', 'LEFT|BOTTOM');
if (r === 'not_supported') alert('워터마크 이미지 형식을 확인하세요.');
box.upload();

Exabyter Lite — 이미지 26

전송 제어#

대용량 파일이나 제한된 회선에서 전송 속도, 압축, 암호화, 무결성 검사를 조절해야 하는 경우입니다. 업로드와 다운로드 옵션을 한곳에 모았습니다.

업로드 전송 옵션#

  • 속도: limitRate로 상한을 지정하고 setLimitRate로 바꿉니다. 컨트롤은 서버 부하를 보고 속도를 바꾸지 않으므로 강제 제한은 서버나 프록시에서 합니다.
  • 압축: useCompress는 텍스트, 로그에만 효과가 있고 zip, jpg, mp4에는 CPU만 씁니다. 서버는 setAutoDecompress(해제, 원본삭제)로 자동 해제합니다.
  • 암호화: useEncrypt는 클라이언트와 서버가 같은 키와 IV를 가져야 하며, 파일명까지 가리려면 useEncryptMeta를 함께 지정합니다. 키는 소스에 직접 쓰지 않고, HTTPS를 대체하지 않습니다.
  • 무결성: integrity를 지정하며, 실패하면 전송창에 "무결성 검사 오류"가 표시됩니다.
  • 대량 전송: 작은 파일이 매우 많으면 massFileTransfer를 사용합니다. 이 모드에서는 파일 목록 표시가 제한됩니다.

클라이언트에 속도, 압축, 암호화 옵션을 지정하고, 서버는 같은 키와 IV로 InnorixUpload를 생성합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload-crypt.jsp',
  installURL: '../install/install.html',
  limitRate: 2000,
  useCompress: true,
  useEncrypt: true,
  useEncryptMeta: true
});
jsp
InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, "UTF-8",
        directory, true, ENCRYPT_KEY, ENCRYPT_IV);   // 32자 키, 16자 IV
uploader.setAutoDecompress(true, true);              // 압축 해제 후 원본 삭제
String result = uploader.run();

다운로드 속도 제한#

limitRate로 클라이언트별 상한을 둘 수 있습니다. 클라이언트 옵션이라 우회될 수 있으므로, 반드시 지켜야 하는 상한은 서버나 L4에서 적용합니다.

스토리지 연동#

회사 NAS를 업로드 저장소로 쓰거나, 영상 파일을 S3 버킷으로 바로 올려야 하는 경우입니다. 마운트 경로를 그대로 저장소로 쓰는 방식과, 사전 서명 URL(제한 시간 동안만 유효한 업로드, 다운로드 주소)로 스토리지에 직접 전송하는 방식이 있습니다.

마운트 스토리지는 서버의 저장 디렉터리(directory)에 마운트 경로를 지정하는 구조입니다. S3는 서버가 사전 서명 URL을 발급하고, 컨트롤이 그 주소로 스토리지에 직접 전송합니다. 서버를 거쳐 중계하지 않으므로 서버 부담이 줄어듭니다.

[마운트, NAS]  컨트롤 → upload 엔드포인트 → 마운트 경로(저장 디렉터리)

[S3]          컨트롤 → 서버 엔드포인트(사전 서명 URL 발급) → S3 직접 전송

필요한 것: 마운트는 WAS 실행 계정의 쓰기 권한과 여유 공간, S3는 버킷, 접근 정보, 버킷 CORS 설정과 InnorixS3Upload, AwsS3 라이브러리. 샘플은 s3_upload.html, s3_upload.jsp, s3_download1.html, s3_download2.html, s3_download.jsp입니다.

마운트 경로에 저장#

저장 디렉터리에 마운트 경로를 지정합니다.

  • 서버 쓰기 실패는 오류 40001, 40002로 표시됩니다.
  • 서버를 이중화했다면 모든 WAS가 같은 마운트 경로를 같은 경로명으로 사용합니다.

저장 디렉터리(directory)에 마운트 경로를 지정한 upload.jsp입니다.

jsp
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
if (request.getMethod().equals("POST")) {
    String directory = "/usr/local/mount/upload";   // 마운트 경로
    int maxPostSize = 2147482624; // bytes

    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);
    String result = uploader.run();
}
%>

업로드한 파일이 마운트 경로에 기록됩니다.

S3 업로드#

서버가 사전 서명 URL을 응답하고, 컨트롤이 그 주소로 전송합니다.

  • isPresignedUrl: true, uploadURL: 클라이언트 옵션
  • InnorixS3Upload, setExpirationTimeSecond: 서버에서 URL 발급과 유효 시간 지정

isPresignedUrl: true와 서버 주소 uploadURL을 지정해 컨트롤을 만듭니다.

javascript
box = innorix.create({
  el: '#fileBox',
  transferMode: 'both',
  downloadType: 'direct',
  isPresignedUrl: true,
  uploadURL: './s3_upload.jsp',
  installURL: '../install/install.html'
});

InnorixS3Upload가 사전 서명 URL을 응답하는 s3_upload.jsp입니다. 접근 정보는 환경 변수에서 읽습니다.

jsp
<%@ page import="com.innorix.transfer.InnorixS3Upload" %>
<%
if (request.getMethod().equals("POST")) {
    String S3_URI = "https://s3.amazonaws.com";
    String S3_REGION = "ap-northeast-2";
    String S3_ACCESS_KEY_ID = System.getenv("S3_ACCESS_KEY_ID");
    String S3_SECRET_KEY = System.getenv("S3_SECRET_KEY");
    String S3_BUCKET = "my-bucket";
    String directory = "upload";
    int maxPostSize = 2147482624; // bytes

    InnorixS3Upload uploader = new InnorixS3Upload(request, response, maxPostSize, "UTF-8",
        S3_URI, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_KEY, directory);
    uploader.setExpirationTimeSecond(60 * 60);   // URL 유효 시간(초)

    String result = uploader.run();
}
%>

유효 시간은 가장 큰 파일의 전송 시간보다 길게 정합니다. 접근 키는 환경 변수나 키 관리 서비스에서 읽고, 샘플의 키는 운영에서 쓰지 않습니다.

S3 다운로드#

downloadUrl이 사전 서명된 URL을 돌려주는 엔드포인트를 가리키게 합니다.

loadComplete 후 presetDownloadFiles()의 downloadUrl에 사전 서명 URL을 돌려주는 엔드포인트를 지정합니다.

javascript
box.on('loadComplete', function () {
  box.presetDownloadFiles([{
    printFileName: 'test4.zip',
    fileSize: 2147484672,
    downloadUrl: './s3_download.jsp?_filePath=' + encodeURIComponent('test4.zip')
  }]);
});

AwsS3.GetDownloadUrl(bucket, key, seconds)로 URL을 만들어 응답하는 s3_download.jsp입니다.

jsp
<%@ page import="com.innorix.transfer.AwsS3" %>
<%
String filePath = "upload/";
String name = java.net.URLDecoder.decode(request.getParameter("_filePath"));

AwsS3 awsS3 = new AwsS3();
awsS3.GetAmazonS3("https://s3.amazonaws.com", "ap-northeast-2",
    System.getenv("S3_ACCESS_KEY_ID"), System.getenv("S3_SECRET_KEY"));

String downloadUrl = awsS3.GetDownloadUrl("my-bucket", filePath + name, 60 * 60);
response.getWriter().write(downloadUrl);
%>

컨트롤은 응답받은 주소로 S3에서 직접 수신합니다.

이어올리기 정보와 S3 저장#

  • 서버를 여러 대 두면 UploadInfoCallBack을 구현해 이어올리기 정보를 DB 등 공유 저장소에 기록합니다. 지정하지 않으면 기록하지 않습니다.
  • S3 호환 스토리지는 InnorixS3Upload를 사용합니다. 클라이언트에서 isPresignedUrl: true를 지정하면 브라우저가 스토리지로 직접 올리며, 이때 버킷 CORS 설정이 필요하고 후처리는 별도 작업으로 연결합니다. 접근 키는 소스에 직접 쓰지 않습니다.

이어올리기 정보를 외부에서 관리하거나, S3 호환 스토리지에 저장하는 생성 예입니다.

jsp
// 이어올리기 정보를 DB 등 공유 저장소에 기록 (UploadInfoCallBack 구현체)
InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory, new MyUploadInfoCallBack());

// S3 호환 스토리지에 저장 (접근 키는 환경 변수 등에서 읽어 사용)
InnorixS3Upload s3Uploader = new InnorixS3Upload(
    request, response, maxPostSize, "UTF-8",
    S3_URI, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_KEY, directory);
s3Uploader.setExpirationTimeSecond(60 * 60);
String result = s3Uploader.run();

브라우저가 스토리지로 직접 올리려면 클라이언트에 isPresignedUrl을 지정합니다.

javascript
box = innorix.create({
  el: '#filebox',
  uploadURL: './s3_upload.jsp',
  isPresignedUrl: true
});

파일이 저장되지 않으면 권한, 경로, URL 만료, CORS 순으로 점검합니다.

보안#

로그인한 사용자만 계약서 첨부를 내려받게 해야 하는 경우입니다. 컨트롤은 사용자 권한을 알지 못하므로 권한 판단은 uploadURL, downloadUrl이 가리키는 서버 엔드포인트가 맡습니다. allowType, maxFileSize 같은 클라이언트 옵션은 안내용이며, 요청을 직접 만들어 보내면 우회됩니다.

보안은 네 겹으로 구성합니다. 전송 구간은 HTTPS로 보호하고, 요청은 쿠키, 토큰으로 인증합니다. 파일은 확장자와 시그니처(파일 내용의 형식 표식)로 검증하고, 저장소는 서버 경로를 숨겨 보호합니다.

사용자 → [HTTPS] → 컨트롤(쿠키, 토큰 전달) → 서버(인증, 권한, 형식 검사) → 저장소

보안 옵션#

방식 설명 언제 쓰나
HTTPS 페이지와 uploadURL, downloadUrl 모두 https 운영 환경 전체
쿠키, 토큰 전달 cookie, customHeader, postData 로그인 사용자만 허용할 때
CSRF 토큰 (useCsrf) 페이지 메타 태그 토큰을 요청 헤더에 첨부 CSRF 방어가 켜진 서버
확장자, 시그니처 검증 allowType, denyType, useSignature + 서버 재검사 위장 파일 차단
무결성 검사 integrity, downloadIntegrity 손상, 변조 감지
암호화 useEncrypt(내용), useEncryptMeta(파일명, 경로) 민감 파일

로그인 사용자만 다운로드#

컨트롤은 파일을 나누어 여러 번 요청하므로 로그인, 소유권 검사를 모든 요청에서 수행합니다.

  • 요청 파라미터를 경로로 쓰지 않고 파일 ID로 서버 내부 경로를 조회합니다.
  • 범위 값은 파일 크기로 검증합니다.
  • downloadType: stream을 쓰면 저장 경로가 클라이언트에 나가지 않습니다.
  • setHideServerPathInfo(true), setHideRootPath(true)로 업로드 응답의 서버 경로를 숨깁니다.

로그인 확인, 소유권 검사, 파일 ID로 경로 조회, 범위 검증 후 구간을 응답하는 download.jsp입니다. findDownloadable과 DownloadInfo는 애플리케이션이 구현합니다.

jsp
<%@ page contentType="application/octet-stream" trimDirectiveWhitespaces="true" %>
<%@ page import="java.io.*, java.net.URLEncoder" %>
<%
// 1. 로그인 확인
Long userId = (Long) session.getAttribute("userId");
if (userId == null) { response.sendError(401); return; }

// 2. 파일 ID로 조회하고 소유권 확인 (없거나 권한 없으면 null)
DownloadInfo info = findDownloadable(userId, request.getParameter("fileID"));
if (info == null) { response.sendError(404); return; }

File file = info.storedFile;   // 서버가 정한 경로
long size = file.length();

// 3. 범위 검증
long start = 0, end = size - 1;
try {
    String szStart = request.getParameter("_StartOffset");
    String szEnd   = request.getParameter("_EndOffset");
    if (szStart != null) start = Long.parseLong(szStart);
    if (szEnd   != null) end   = Long.parseLong(szEnd);
} catch (NumberFormatException e) { response.sendError(400); return; }

if (start < 0 || end < start || end >= size) {
    response.setHeader("Content-Range", "bytes */" + size);
    response.sendError(416);
    return;
}
long length = end - start + 1;

// 4. 헤더
String encoded = URLEncoder.encode(info.printName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Disposition", "attachment; filename=\"" + encoded + "\"");
response.setHeader("Content-Length", String.valueOf(length));
response.setHeader("Cache-Control", "private, no-store");

// 5. 해당 구간만 출력
RandomAccessFile raf = new RandomAccessFile(file, "r");
try {
    raf.seek(start);
    OutputStream os = response.getOutputStream();
    byte[] buf = new byte[8192];
    long remain = length;
    while (remain > 0) {
        int n = raf.read(buf, 0, (int) Math.min(buf.length, remain));
        if (n == -1) break;
        os.write(buf, 0, n);
        remain -= n;
    }
    os.flush();
} finally {
    raf.close();
}
%>

세션과 토큰 전달#

Agent는 별도 HTTP 클라이언트로 요청하므로 로그인 정보를 옵션으로 넘깁니다.

  • cookie: 기본은 document.cookie. 같은 도메인에서 컨트롤을 사용해야 합니다.
  • customHeader, postData: 쿠키를 쓰기 어려운 교차 출처, 웹뷰에서 토큰 전달
  • useCsrf: true: 켜지 않으면 CSRF 방어 환경에서 업로드가 403으로 거부될 수 있습니다.
  • 대용량 전송은 세션 만료보다 오래 걸릴 수 있으므로 전송 중에만 세션을 갱신합니다. 인증 실패는 리다이렉트하지 않고 401로 응답합니다.

loadComplete 이벤트에서 setCookie()로 세션 쿠키를 지정합니다.

jsp
<script>
  box.on('loadComplete', function () {
    box.setCookie('JSESSIONID=<%= session.getId() %>');
  });
</script>

전송이 시작되면 세션 갱신 요청을 보내고, 완료, 오류, 취소 시 멈춥니다.

javascript
var keepAliveTimer = null;

function startKeepAlive() {
  stopKeepAlive();
  keepAliveTimer = setInterval(function () {
    fetch('/session/ping', { credentials: 'same-origin', cache: 'no-store' });
  }, 5 * 60 * 1000);
}
function stopKeepAlive() {
  if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; }
}

box.on('uploadStart',    startKeepAlive);
box.on('uploadComplete', stopKeepAlive);
box.on('uploadError',    stopKeepAlive);
box.on('uploadCancel',   stopKeepAlive);

customHeader, postData로 토큰을 전달하고 useCsrf: true를 지정합니다. useCsrf는 페이지의 <meta> 태그에서 토큰을 읽습니다.

html
<meta name="_csrf"        content="${_csrf.token}">
<meta name="_csrf_header" content="${_csrf.headerName}">
javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: '/innorix/upload.jsp',
  customHeader: { 'X-Auth-Token': authToken },
  postData: { boardId: '1024' },
  useCsrf: true,
  boxConfig: box_config.upload_basic
});

확장자 검증과 저장 정책#

클라이언트는 안내, 서버는 최종 판정을 맡습니다.

  • 클라이언트: allowType, denyType, useSignature, addFileError 이벤트
  • 서버: 허용 목록 방식으로 재검사하고 showCustomError로 거부
  • 저장 파일명은 서버가 새로 만들고, 원본명은 DB에 보관해 다운로드의 Content-Disposition에만 씁니다.
  • 저장 디렉터리는 웹 루트 밖에 두고 실행 권한을 제거합니다.

allowType, useSignature를 지정하고 addFileError 이벤트로 거부 사유를 안내합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  allowType: ['jpg', 'png', 'pdf'],
  useSignature: true,
  boxConfig: box_config.upload_basic
});

box.on('addFileError', function (p) {
  if (p.type == 'allowType') {
    alert('허용되지 않는 형식입니다.');
  }
});

getFileInfo 단계에서 허용 목록으로 검사하고 저장 파일명을 새로 만드는 upload.jsp입니다.

jsp
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%!
static final java.util.Set<String> ALLOW =
    new java.util.HashSet<String>(java.util.Arrays.asList("jpg", "png", "pdf"));

static String ext(String name) {
    if (name == null) return "";
    int dot = name.lastIndexOf('.');
    return dot < 0 ? "" : name.substring(dot + 1).toLowerCase();
}
%>
<%
if (request.getMethod().equals("POST")) {
    String directory = "/data/upload";   // 웹 루트 밖의 경로
    int maxPostSize = 2147482624; // bytes

    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);
    String action = uploader.getParameter("_action");
    String orig   = uploader.getParameter("_orig_filename");

    if ("getFileInfo".equals(action)) {
        if (!ALLOW.contains(ext(orig))) {
            uploader.showCustomError("1700", "허용되지 않는 형식", ext(orig), false);
            return;
        }
        uploader.setFileName(java.util.UUID.randomUUID() + "." + ext(orig));
    }
    String result = uploader.run();
}
%>

시그니처 검사#

useSignature를 켜면 파일 앞부분을 읽어 실제 형식이 확장자와 맞는지 확인합니다. Office 문서(docx, xlsx, pptx)와 jar, apk는 ZIP 형식이라 zip을 차단하면 함께 막히므로 차단 목록은 실제 업무 파일로 테스트합니다. 시그니처 검사는 악성 코드 검사를 대신하지 않으며, MIME 타입 기반 검사는 제공하지 않습니다.

암호화와 무결성#

useEncrypt는 파일 내용, useEncryptMeta는 파일명, 경로를 암호화합니다. setAutoDecryption으로 서버가 수신 후 자동 복호화할 수 있습니다. 키와 IV는 소스 상수가 아니라 환경 변수에서 읽습니다. 무결성은 integrity, downloadIntegrity로 켜고 서버가 체크섬을 비교합니다. 세부 값은 암호화 샘플(upload-crypt.html, upload-crypt.jsp)을 기준으로 구성합니다.

useEncrypt는 파일 내용, useEncryptMeta는 파일명, 경로를 암호화합니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload-crypt.jsp',
  useEncrypt: true,
  useEncryptMeta: true,
  useSignature: true,
  denyType: ['zip'],
  installURL: '../install/install.html'
});

키, IV를 환경 변수에서 읽어 InnorixUpload 생성자에 전달하는 upload-crypt.jsp입니다. 암호화된 상태로 저장하려면 setAutoDecryption(false)를 지정합니다.

jsp
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
if (request.getMethod().equals("POST")) {
    String directory = "/data/upload";
    int maxPostSize = 2147482624; // bytes
    String key = System.getenv("INNORIX_CRYPT_KEY");   // 16, 24, 32자
    String iv  = System.getenv("INNORIX_CRYPT_IV");    // 16자

    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, "UTF-8",
        directory, true, key, iv);   // true: 메타정보 암호화(useEncryptMeta와 일치)

    uploader.setAutoDecryption(false);      // 복호화하지 않고 암호화된 상태로 저장
    uploader.setHideServerPathInfo(true);   // 응답에서 서버 경로 제외

    String result = uploader.run();
}
%>

외부 모듈 연계#

올라온 첨부 파일을 백신으로 검사하고, 내려받을 때는 DRM을 풀어 주어야 하는 경우입니다. Exabyter에는 이들 솔루션을 직접 호출하는 옵션이 없습니다. 서버 엔드포인트의 단계별 훅에서 외부 모듈을 호출하고, 결과를 컨트롤에 돌려주는 방식으로 연계합니다.

서버 엔드포인트는 파일 정보 확인, 저장 완료, 다운로드 시작 같은 단계를 거칩니다. 각 단계에서 외부 모듈을 호출할 수 있습니다. 검사 결과는 사용자 정의 값(customValue)에 담아 응답하고, 컨트롤은 uploadComplete 이벤트에서 읽어 화면에 표시합니다.

업로드 완료 → 서버 훅에서 외부 모듈 호출 → customValue로 결과 응답 → uploadComplete에서 결과 표시
연계 지점 용도
getFileInfo 사전 검사, 저장 파일명 결정
isUploadDone() 저장 직후 백신, DRM, 개인정보 검사
DownloadStart 권한 확인, DRM 해제 후 변경된 크기 알림

연계 방식#

방식 설명 언제 쓰나
저장 직후 동기 검사 isUploadDone()에서 바로 검사 후 응답 소용량 파일
임시 디렉터리 격리 후 비동기 검사 상태를 "검사 중"으로 기록, 통과 전 다운로드 차단 검사가 오래 걸리는 대용량
다운로드 시 DRM 해제 DownloadStart에서 해제 후 크기 알림 DRM 적용 파일

저장 후 검사하고 결과 응답#

저장된 파일을 외부 모듈로 검사하고 결과를 응답합니다.

  • setCustomValue, sendCustomValue: 결과 값을 응답에 담기
  • 압축을 서버에서 해제한다면 해제된 파일에도 같은 검사를 적용하고 ../ 경로에 주의합니다.
  • 개인정보 필터링 전용 API는 없으므로 자체 검사기를 훅에서 호출합니다.

저장이 끝난 파일(isUploadDone())을 외부 모듈로 검사하고 결과를 setCustomValue, sendCustomValue로 응답하는 upload.jsp입니다. scanner는 연계 대상의 호출부로, 애플리케이션이 구현합니다.

jsp
<%
InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);
String result = uploader.run();

if (uploader.isUploadDone()) {
    File saved = new File(directory, uploader.getParameter("_new_filename"));

    ScanResult r = scanner.scan(saved);   // 백신, DRM 등 외부 모듈 호출
    if (!r.clean) {
        saved.delete();
        uploader.setCustomValue("scan", "infected");
    } else {
        uploader.setCustomValue("scan", "clean");
    }
    uploader.sendCustomValue();
}
%>

uploadComplete 이벤트의 파일 정보에서 customValue를 읽어 검사 결과를 표시합니다.

javascript
box.on('uploadComplete', function (p) {
  p.files.forEach(function (f) {
    if (f.customValue.scan == 'infected') {
      alert(f.clientFileName + ': 차단된 파일입니다.');
    }
  });
});

다운로드 시 DRM 해제#

다운로드 시작 단계에서 권한을 확인하고 DRM을 해제합니다. 해제 후 파일 크기가 바뀌면 바뀐 크기를 알려 다운로드 목록에 반영합니다.

DownloadStart 단계에서 권한을 확인하고, DRM 해제 후 바뀐 크기를 InnorixCustomValue로 알리는 download.jsp입니다. 클라이언트에 sendDownloadTime: true가 필요합니다.

jsp
<%@ page import="com.innorix.transfer.InnorixCustomError" %>
<%@ page import="com.innorix.transfer.InnorixCustomValue" %>
<%
String action = request.getParameter("_Action");
String fileID = request.getParameter("fileID");

if ("DownloadStart".equals(action)) {
    if (!canDownload(userId, fileID)) {   // 애플리케이션이 구현
        InnorixCustomError err = new InnorixCustomError(response);
        err.set("1600", "권한 없음", "이 파일을 다운로드할 권한이 없습니다.", false);
        err.run();
        return;
    }
    InnorixCustomValue customValue = new InnorixCustomValue();
    customValue.setCustomValue("fileSize", String.valueOf(releasedSize));   // 해제 후 크기
    customValue.run(response);
    return;
}
if ("DownloadComplete".equals(action)) {
    response.setStatus(200);
    return;
}
%>

저장 공간 확인과 외부 후처리#

  • 디스크 여유 공간을 자동 검사하는 기능은 없습니다. getFileInfo에서 File.getUsableSpace()로 비교하고 부족하면 InnorixCustomError로 응답합니다.
  • 바이러스 검사, 개인정보 필터링, DRM은 내장 기능이 없습니다. isUploadDone() 이후에 외부 엔진을 호출하고, 오래 걸리면 별도 큐로 넘깁니다. 웹 서버, 프록시의 응답 대기 시간이 후처리보다 짧으면 연결이 끊깁니다.
  • 검사 결과를 화면에 알리려면 setCustomValue()와 sendCustomValue()를 씁니다.
  • 다른 스토리지로 옮길 때 renameTo는 파일시스템이 다르면 실패하므로 복사 후 삭제를 씁니다. 클라이언트가 보낸 경로를 믿지 않고 서버가 아는 임시 디렉터리 안의 파일인지 확인합니다.
  • 완료 전에 브라우저를 닫으면 일부 파일만 남으므로 임시 업로드 정리 작업을 계획합니다.

getFileInfo에서 여유 공간을 확인하고, 완료 후 외부 검사 결과를 setCustomValue로 알립니다.

jsp
if ("getFileInfo".equals(_action)) {
    long need = Long.parseLong(uploader.getParameter("_filesize"));
    long free = new java.io.File(directory).getUsableSpace();

    if (free < need + 1024L * 1024 * 1024) {      // 1GB 여유 확보
        InnorixCustomError err = new InnorixCustomError(response);
        err.set("1700", "NoSpace", "서버 저장 공간이 부족합니다.", false);
        err.run();
        return;
    }
}
String result = uploader.run();

if (uploader.isUploadDone()) {
    // 외부 검사 엔진 호출 (오래 걸리면 별도 큐로 넘김)
    uploader.setCustomValue("scan", "clean");
    uploader.sendCustomValue();
}

Agent#

수 GB 영상 파일과 사진 폴더를 브라우저 한계 없이 올려야 하는 경우입니다. Agent 모드에서는 사용자 PC에 설치된 Agent 프로그램이 전송을 수행하고, 웹 페이지의 컨트롤은 화면과 제어를 맡습니다. 이 매뉴얼의 예제는 모두 Agent 모드 기준입니다.

컨트롤을 만들 때 Agent 모드를 지정하면 Agent 설치 여부를 확인합니다. 설치되어 있으면 Agent가 서버와 직접 전송합니다. 미설치면 설치 페이지로 안내합니다. 쿠키, 저장 경로, 재시도, 타임아웃은 Agent 전송 명령에 전달됩니다.

웹 페이지(컨트롤) → Agent 설치 확인 → (미설치) 설치 페이지 → Agent ↔ 서버 직접 전송

설치, 실행 옵션#

방식 설명 언제 쓰나
설치 페이지 이동 (installMethod) 같은 창에서 설치 페이지 열기 설치 후 돌아오는 흐름이 단순할 때
설치 페이지 팝업 (installPopup) 팝업으로 열기 작성 중인 화면을 유지해야 할 때
설치 확인 생략 (skipPluginCheck) 설치 확인 건너뜀 설치가 보장된 사내 환경
저장 경로 (savePath) 다운로드 저장 위치 지정 저장 폴더를 고정할 때
모바일 설치 페이지 (installURLMobile) 모바일용 설치 주소 모바일 Agent 사용 시

Agent 모드로 컨트롤 만들기#

Agent 모드, 업로드 주소, 설치 페이지를 지정해 만듭니다.

  • installURL: 설치 페이지 주소
  • redirect 쿼리: 설치 페이지는 설치 후 이 값으로 이동하며 값을 검증하지 않으므로, 같은 출처 경로만 허용하도록 설치 페이지나 웹 서버에서 제한합니다.

Agent 모드 컨트롤을 uploadURL, installURL, boxConfig와 함께 만듭니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  installURL: '../install/install.html',
  boxConfig: box_config.upload_agent
});

Exabyter Lite — 이미지 27

다운로드 전용 컨트롤#

설치가 보장된 환경에서는 설치 확인을 생략합니다. 설치되지 않은 PC에서는 전송 시점에 실패하므로 사내 환경에서만 사용합니다.

  • skipPluginCheck: true: 설치 확인 생략
  • downloadDuplicate: 중복 파일 처리 방식

skipPluginCheck: true와 downloadDuplicate를 지정한 다운로드 전용 컨트롤입니다.

javascript
box = innorix.create({
  el: '#fileBox',
  boxStyle: 'list',
  skipPluginCheck: true,
  downloadDuplicate: 'numbering',
  installURL: '../install/install.html',
  boxConfig: box_config.download_agent
});

box.on('loadComplete', function () {
  box.presetDownloadFiles([{
    printFileName: '5GB.mp4',
    fileSize: 5393874944,
    downloadUrl: 'download.jsp?fileID=7'
  }]);
});

API Reference#

Exabyter 컨트롤의 설정 옵션, 메서드, 이벤트와 서버 API를 한곳에 모았습니다. 기능을 구현하다가 이름, 매개변수, 반환값을 빠르게 확인해야 할 때 사용합니다. 옵션 이름과 이벤트 이름은 대소문자를 구분합니다.

설정 옵션#

innorix.create()에 전달하는 옵션입니다. boxConfig 프리셋을 쓰면 프리셋 값이 먼저 적용되고, create()에 직접 쓴 옵션이 이를 덮어씁니다. 문자열 "true", "false"는 불리언으로 변환됩니다.

innorix.create()로 컨트롤을 만들고 boxConfig 프리셋 위에 옵션을 덮어씁니다.

javascript
box = innorix.create({
  el: '#fileBox',
  uploadURL: './upload.jsp',
  installURL: '../install/install.html',
  boxWidth: 980,                          // 프리셋 값을 덮어씀
  boxConfig: box_config.upload_agent
});

생성과 기본 동작#

옵션 타입 설명
el String 컨트롤을 그릴 요소의 선택자. 필수
boxConfig Object 프리셋 객체. 직접 지정한 옵션이 우선
config Object 옵션 묶음. create() 인자와 병합
transferMode String "upload", "download", "both"
skipPluginCheck Boolean Agent 연결, 버전 확인을 건너뜀
boxStyle String 리스트 모양. list(리스트형), icon(아이콘형), preview(미리보기 패널을 함께 보여주는 목록), html(애플리케이션이 HTML로 목록을 직접 그리는 형)
transferWindowStyle String 전송창 모양. default(기본 크기 전송창), mini(레이어형 작은 전송창), list(레이어로 출력, 진행도 위에 파일 목록), compact(리스트 하단 표시). 숨기려면 showTransferWindow: false
boxStyleTheme String 루트 요소에 테마 클래스를 추가
boxWidth, boxHeight Number / String 컨트롤 너비, 높이
controlLang String UI 문구 언어(ko, en)

서버 연결#

옵션 타입 설명
uploadURL String / Array 업로드 요청 URL
uploadCompletedEvent Object 업로드 완료 시 호출할 URL 설정 { use, url }
downloadCompletedEvent Object 다운로드 완료 시 호출할 URL 설정 { use, url }
sendLogUrl String 로그 전송 URL
isPresignedUrl Boolean S3 presigned URL 방식 사용
isS3Transfer Boolean S3 전송 모드
useProxy Boolean 프록시 사용
custom Object 사용자 정의 값 묶음. Agent 요청에 전달

파일 선택과 사전 검증#

옵션 타입 설명
allowType Array / Object 허용 확장자 목록(소문자, 점 없음)
denyType Array 차단 확장자 목록
maxFileCount Number 업로드 목록의 최대 파일 수
maxFileSize Number 파일 하나의 최대 크기(bytes)
maxTotalSize Number 업로드 목록 총 크기 한도(bytes)
useSignature Boolean 파일 시그니처로 확장자 위조를 검사
addDuplicateFile Boolean false이면 같은 경로의 중복 추가를 거부
addEmptyFile Boolean 0바이트 파일 추가 허용
addFolder Boolean 폴더 항목 추가
folderAttach Boolean 폴더 첨부 허용
isGetFolderSize Boolean 폴더 크기를 계산해 표시
massFileTransfer Boolean 대량 전송 모드
massAddFileMode Boolean 대량 추가 모드
enableDropZone Boolean Agent 드롭 영역 사용

파일 목록 화면#

옵션 타입 설명
showSelectBox Boolean 행의 선택 체크박스 표시
showFuncBtn Array 행의 기능 버튼("remove", "move")
showFileListHeader Boolean 목록 헤더 표시
showByteSize Boolean 크기를 바이트 단위 숫자로 표시
showPreviewPdf Boolean 선택한 PDF 미리보기(boxStyle: 'preview'와 함께 사용)
setPreviewDiv Array [요소ID, 너비, 높이]. 미리보기를 별도 요소에 표시
showFolderItems Boolean 폴더 하위 항목을 펼쳐서 표시
openFolderItems Boolean 폴더 클릭으로 하위 항목 펼침/접힘
showGraph Boolean 전송 그래프 표시
useContextMenu Boolean 우클릭 컨텍스트 메뉴 사용
showNotificationLayer Boolean 컨트롤 내부 알림 레이어 사용
fileListWindowStatus Boolean 목록에 파일별 상태 표시
pathChange Boolean 다운로드 전송창에 경로 변경 버튼 표시
transferWindowTitle String 전송창 제목
hideLogo Boolean 컨트롤의 로고 숨김

전송창과 임베드 환경#

옵션 타입 설명
showTransferWindow Boolean 전송창 표시
showTransferStatusIcon Boolean 전송창의 상태 아이콘 표시
draggableTransferWindow Boolean 전송창을 헤더로 드래그해 이동
cancelConfirmation Boolean 취소 시 확인창 표시
hostTransferWindow Boolean 임베디드 컨트롤이 전송창을 호스트 페이지에 표시
hostTransferWindowTarget String 전송창을 그릴 호스트 페이지("parent", "top")
hostTransferWindowTop, hostTransferWindowLeft String 전송창의 top, left 위치
hostTransferWindowMarginTop, hostTransferWindowMarginLeft String 전송창의 margin-top, margin-left
hostTransferWindowCssURL String 호스트 페이지 head에 추가할 내용

설치와 Agent#

옵션 타입 설명
installURL String 설치 페이지 URL
installPopup Boolean 설치 페이지를 팝업으로 열기
installPopupWidth, installPopupHeight Number 팝업 너비, 높이
installMethod String "layer"이면 설치 안내를 레이어로 표시, 그 외는 페이지 이동
showInstallBtn Boolean 설치 상태에 따라 설치/사용 버튼 표시
pluginURL String 설치 레이어에서 이동할 설치 파일 URL

전송 동작#

옵션 타입 설명
transferStart Object 전송 시작 방식 { upload, download }. "auto" 또는 수동
retryCount Number 재시도 허용 횟수
retryDelay Number 재시도 간격
maxErrorCount Number 누적 오류가 이 값을 넘으면 일시정지
autoRecovery Boolean 오류 시 자동 재시도
uploadDuplicate Boolean true이면 이어쓰기, false이면 덮어쓰기
downloadDuplicate String 다운로드 중복 처리 방식
resumeCondition Boolean true이면 업로드를 이어하기로 시작
attachIncompleteFiles Boolean 로드 시 미완료 전송을 조회해 복원 여부 확인
autoLoadTransfer Boolean 미완료 전송을 확인 없이 목록에 추가하고 자동 업로드
downloadType String "stream"(구간 요청) 또는 "direct"(downloadUrl로 바로 이동)
savePath String 다운로드 저장 경로
skipDownloadCheck Boolean 다운로드 목록 검증을 모두 통과 처리
checkDownloadTimeout Number 크기 없는 다운로드 항목의 HEAD 요청 타임아웃(ms)
sendDownloadTime Boolean 다운로드 시작, 완료를 서버에 요청으로 알림
postData Object 모든 업로드 요청에 붙는 추가 파라미터
customHeader Object 모든 요청에 붙는 추가 헤더
cookie String 전송 명령에 전달할 쿠키
charset String 전송 명령에 전달되는 문자셋
referer String 전송 명령에 전달되는 Referer
presetDownloadFiles Array 생성 옵션으로 다운로드 목록을 미리 지정

보안#

옵션 타입 설명
useEncrypt Boolean 전송 암호화. 서버 키 설정과 함께 사용
useEncryptMeta Boolean 메타데이터 암호화
useCompress Boolean 압축 전송
downloadIntegrity Boolean 다운로드 무결성 검사

메서드#

innorix.create()가 반환한 컨트롤 객체(box)의 메서드입니다. 컨트롤은 loadComplete 이벤트 이후에 사용할 수 있고, 그 전이나 파일 대화상자가 열려 있는 동안에는 false를 반환합니다. 상태를 바꾸는 메서드는 성공하면 this를 반환하므로 이어 쓸 수 있습니다.

loadComplete 이후 버튼에서 메서드를 호출해 파일을 추가하고 업로드합니다.

html
<input type="button" value="파일 추가" onclick="box.openFileDialog();"/>
<input type="button" value="폴더 추가" onclick="box.openFolderDialog();"/>
<input type="button" value="업로드" onclick="box.upload();"/>

컨트롤 생성, 초기화, 해제#

메서드 설명 반환
innorix.create(option) 컨트롤을 생성 컨트롤 객체
innorix.group(box1, box2, ...) 여러 컨트롤을 묶어 같은 메서드를 한 번에 호출 그룹 객체
innorix.setLanguage(lang) 화면 언어를 지정 innorix
innorix.addLanguageResource(lang, resource) 언어 리소스를 추가 innorix
innorix.checkAgentInstalled(callback) Agent 연결 가능 여부와 버전을 확인 없음
setOption(obj) 옵션을 병합하고 컨트롤을 다시 초기화. 전송 중에는 호출하지 않음 this
getOption() 현재 옵션 객체를 반환. 읽기용 Object
destroy() 컨트롤 화면을 제거하고 객체를 해제 없음
isAgentInstalled(callback) Agent 설치 여부를 확인 없음
getTransferMode() transferMode 값을 반환 String
isEnsureMode(mode) "upload" 또는 "download" 사용 가능 여부 Boolean

파일 선택, 추가, 삭제#

메서드 설명 반환
openFileDialog() 다중 선택 파일 대화상자를 엶 this / false
openFileDialogSingle() 한 파일만 선택하는 대화상자를 엶 this / false
openFolderDialog() 폴더 선택 대화상자를 엶 없음 / false
addFiles(files) 파일 정보 배열을 목록에 추가 없음 / false
setDropZone(event, element) 외부 요소를 드롭 영역으로 지정 없음 / false
selectAllFiles(), unselectAllFiles() 전체 선택, 전체 선택 해제 this
addSelectFilesById(ids) 지정한 id 배열의 파일을 선택 this
unSelectFilesById(ids) 지정한 id 배열의 선택을 해제 this
removeFileByIndex(index) 목록 위치로 한 파일을 삭제 this / false
removeFileById(id) id로 한 파일을 삭제 this / false
removeSelectedFiles() 선택한 파일을 모두 삭제 this / false
removeAllFiles([callback]) 목록을 모두 비우고 callback() 호출 this / false
clearFiles() 목록을 모두 비움 this / false
sortName(order), sortSize(order), sortType(order), sortModified(order) 이름, 크기, 종류, 수정 시각 기준 정렬. order는 "asc" 또는 그 외(내림차순) —

다운로드 목록 설정#

메서드 설명 반환
presetDownloadFiles(files) 다운로드할 파일 목록을 등록. 필수 필드는 printFileName, downloadUrl, fileSize. 기존 목록을 지우지 않고 추가 없음

loadComplete 안에서 presetDownloadFiles()로 다운로드 목록을 등록합니다.

javascript
box.on('loadComplete', function () {
  box.presetDownloadFiles([{
    printFileName: 'Exabyter Brochure.pdf',
    fileSize: 1048576,
    downloadUrl: 'download.jsp?fileID=2'
  }]);
});

파일 목록 조회#

메서드 설명 반환
getAllFiles([type]) 모든 파일 정보. "ORIGINAL"이면 썸네일 파일 제외 Array
getSelectedFiles() 선택된 파일 Array
getSelectedFileCount(), getSelectedFolderCount() 선택된 파일 수, 폴더 수 Number
getSelectSize() 선택한 파일 크기 합계(bytes) Number
getUploadFiles(), getDownloadFiles() 업로드 파일, 다운로드 파일 Array
getFileCount() 전체 파일 수 Number
getTotalFolderCount(), getTotalFolderNames() 폴더 수, 폴더 이름 배열 Number, Array
getTotalSize() 전체 파일 크기(bytes) Number
getUploadFilesSize(), getDownloadFilesSize() 업로드, 다운로드 파일 크기 합계(bytes) Number
getFileByIndex(index) 목록 위치로 파일 조회 파일 객체 / undefined
getFileById(id) id로 파일 조회 파일 객체 / undefined

파일 객체의 필드는 아래와 같습니다. 필드를 수정해도 서버에는 반영되지 않으므로 읽기용으로 사용합니다.

필드 타입 설명
id, rowID String 파일 식별자. 두 값은 같음
printFileName String 표시 파일명
fileSize Number 크기(bytes)
transferType, mode String "upload" 또는 "download"
selected Boolean 선택 여부
filePath String 클라이언트 전체 경로(업로드)
basePath String 첨부한 위치의 상위 경로(업로드)
boxId String 컨트롤 요소 ID(업로드)
uploadUrl String 업로드 URL(업로드)
isFile Boolean 폴더이면 false(업로드)
rootName, folderName String 폴더 경로
fileExt, fileExtType String 확장자 분류(picture, video, audio, document, pdf 등. 목록에 없으면 file)
downloadUrl String 다운로드 URL(다운로드)
validate Boolean 크기 검증 완료 여부(다운로드)
modificationTime Number 수정 시각(ms)
postData Object setFilePostDataByIndex(), setFilePostDataById()로 지정한 파일별 값
customValue Object 서버가 setCustomValue()로 돌려준 값
thumbnail, parentID Boolean, String 썸네일로 생성된 파일 여부, 원본 파일의 id

presetDownloadFiles()에 넣는 항목의 입력 필드는 다음과 같습니다.

필드 타입 필수 설명
downloadUrl String 예 파일을 제공하는 URL. 목록 안에서 유일해야 함
printFileName String 예 목록과 저장에 사용하는 파일명
fileSize Number 예 바이트 단위 크기
modificationTime Number 아니오 수정 시각(ms)
rootName String 아니오 폴더 구조로 저장할 때의 상위 폴더 경로
isFile Boolean 아니오 false이면 폴더 항목
id, rowID String 아니오 항목 ID. 생략하면 자동 생성
customValue Object 아니오 다운로드 요청 쿼리에 덧붙는 키, 값
thumbnailUrl String 아니오 미리보기용 썸네일 경로

업로드, 다운로드 실행#

메서드 설명 반환
upload() 목록의 업로드 파일을 모두 전송 this / false
download(), downloadAll() 목록의 다운로드 파일을 모두 받음 this / false
downloadSelectedFiles() 선택한 다운로드 파일만 받음 this / false
downloadAndOpen() 선택한 마지막 파일을 받은 뒤 엶 this / 없음
uploaddownload() both 모드에서 선택 파일을 종류별로 나눠 실행 없음
startTransferProgress() 전송창에서 전송을 시작. transferStart가 수동일 때 upload() 뒤에 호출 없음

취소, 중지, 이어전송#

메서드 설명 반환
transferPause() 진행 중인 전송을 일시 중지 this / false
transferResume() 일시 중지한 전송을 재개 this / false
transferCancel() 전송을 취소 this / false
closeTransferWindow() 전송창을 닫음 없음
getIncompleteTransfer() 미완료 업로드 정보를 조회해 이어받기 목록을 구성 없음
deleteUploadResumeInfo(), deleteDownloadResumeInfo() 저장된 이어전송 정보를 삭제 없음

크기, 그래프, UI 표시 제어#

메서드 설명 반환
setSize(width, height) 컨트롤 크기를 변경 this
showGraph(), hideGraph() 전송 그래프 표시, 숨김 없음
showNotificationLayer(type, title, message) 알림 레이어를 표시 없음
estimateUploadTime(callback) 업로드 예상 시간(초). 실패하면 false this
estimateDownloadTime(callback) 다운로드 예상 시간(초). 실패하면 false this
getDownloadPath([callback]) 다운로드 저장 경로를 조회 String / 없음
setDownloadPath(callback) 저장 경로 선택 대화상자를 엶 없음
openDownloadFolder() 다운로드 폴더를 엶 없음 / false

쿠키, 헤더, POST 데이터#

메서드 설명 반환
setCookie(cookieString) 전송 요청에 붙일 쿠키 문자열 설정 true
getCookie() 설정한 쿠키 문자열 반환 String
deleteCookie() 설정한 쿠키 삭제 true
setCustomHeader(obj) 전송 요청에 붙일 HTTP 헤더 설정. 전송 중에는 호출하지 않음 this
setPostData(obj) 업로드 전체에 붙는 값 설정. 이전 값을 교체 this
setFilePostDataByIndex(index, obj) 파일별 값을 목록 위치로 설정 true
setFilePostDataById(id, obj) 파일별 값을 id로 설정 true
setResumeType(type) 이어전송 방식(resumeType)을 변경 true

설정한 값은 서버에서 uploader.getParameter("키")로 읽습니다.

이미지 리사이징, 워터마크#

메서드 설명 반환
appendThumbnailProperty(index, width, height, baseline) 원본 이미지를 줄인 썸네일 파일을 업로드 목록에 추가. index는 위치 또는 "ALL", baseline은 "VERTICAL", "HORIZONTAL", "FIX" true
appendWatermarkProperty(index, imageUrl, position) 이미지에 워터마크를 합성. imageUrl은 jpg, jpeg, png. position은 "BOTTOM|RIGHT" 형식 true / "not_supported"

이벤트#

컨트롤이 발생시키는 이벤트입니다. box.on(name, handler)로 등록하고 box.off(name)으로 해당 이벤트의 핸들러를 모두 제거합니다. box.on({ 이름: 핸들러, ... })로 여러 개를 한 번에 등록할 수 있습니다. 일부 이벤트는 핸들러가 false를 반환하면 동작이 취소됩니다.

box.on()으로 loadComplete, addFileError, uploadComplete를 등록합니다.

javascript
box.on('loadComplete', function () {
  console.log('컨트롤 준비 완료');
});

box.on('addFileError', function (p) {
  console.log(p.type);
});

box.on('uploadComplete', function (p) {
  console.log(p.files);
});

반환값의 효과#

이벤트 false 반환 시
beforeAddFile 해당 파일을 목록에 추가하지 않음
beforeRemoveFile 해당 파일을 삭제하지 않음
uploadBefore, downloadBefore 전송을 시작하지 않음
uploadComplete, downloadComplete 전송창을 자동으로 닫지 않음

컨트롤 생성, 초기화 이벤트#

이벤트 발생 시점 p
loadComplete 컨트롤이 준비되어 메서드를 호출할 수 있게 된 직후 없음
onDestroy destroy() 호출 시 없음
installPopupBlocked 설치 페이지 팝업이 브라우저에서 차단됨 없음

파일 추가, 제거 이벤트#

이벤트 발생 시점 p
beforeAddFile 파일이 목록에 추가되기 직전 파일 객체
afterAddFiles 한 번의 추가 작업이 끝난 뒤 파일 객체 배열
addFileError 개수, 크기, 확장자 검사 등에 걸렸을 때 오류 객체 또는 배열
beforeRemoveFile 파일을 삭제하기 직전 파일 객체
removeFiles 삭제가 끝난 뒤 파일 객체 배열

addFileError의 오류 객체는 { type, message, file }입니다. 항상 배열로 바꿔 처리하세요. type 값은 다음과 같습니다.

type 원인
addDuplicateFile 같은 경로의 파일이 이미 목록에 있음
maxFileCount 파일 개수 초과
maxTotalSize 전체 크기 초과
maxFileSize 파일 하나의 크기 초과
denyType 차단된 확장자
allowType 허용 목록에 없는 확장자
attachFileError 파일이 존재하지 않음
invalid_download_file 다운로드 파일 정보에 downloadUrl, printFileName, fileSize 중 하나가 없음
duplicate_file 같은 downloadUrl이 이미 목록에 있음
file_is_modified 이어올리기 복원 시 원본 파일이 바뀌어 복원되지 않음

선택, 정렬 이벤트#

이벤트 발생 시점 p
onSelectRows 파일이 선택될 때 선택된 파일 객체 배열
onUnSelectRows 파일 선택이 해제될 때 해제된 파일 객체 배열
onDblClickRows 파일 행을 더블클릭할 때 파일 객체 하나
afterSortFiles 정렬 후 정렬된 전체 파일 객체 배열

드롭존 이벤트#

이벤트 발생 시점 p
dropzoneShow 파일을 끌어 드롭 영역 위에 올렸을 때 없음
dropzoneHide 끌던 파일이 영역을 벗어났거나 놓았을 때 없음
setDropzoneError setDropZone()에 파일이 아닌 항목을 끌어왔을 때 브라우저 이벤트
agentFileDialogShow, agentFileDialogHide Agent 파일 대화상자가 열리고 닫힐 때 없음

업로드 이벤트#

upload() 호출 후 uploadBefore → uploadStart → uploadProgress → uploadComplete 순으로 발생합니다.

이벤트 발생 시점 p
uploadBefore upload() 호출 직후, 전송 시작 전 true
uploadStart 전송이 시작될 때 전송 상태 객체
uploadProgress 전송 상태가 갱신될 때 전송 상태 객체
uploadAlways 상태가 갱신될 때마다 전송 상태 객체
uploadComplete 모든 파일 전송이 끝난 뒤 전송 상태 객체
uploadCancel 전송이 취소됐을 때 전송 상태 객체
uploadError 전송 중 오류가 발생했을 때 전송 상태 객체
uploadPausing, uploadPause, uploadResume 일시 중지 요청, 일시 중지, 재개 전송 상태 객체
uploadRetry 오류 후 자동 재시도를 시작할 때 전송 상태 객체

다운로드 이벤트#

업로드와 같은 구조이며 이름이 download로 시작합니다. p는 type: "download"인 전송 상태 객체입니다.

이벤트 발생 시점
downloadBefore download(), downloadSelectedFiles() 호출 직후. p는 true
downloadStart 전송 시작
downloadProgress 상태 갱신
downloadAlways 상태가 갱신될 때마다
downloadComplete 모든 파일의 다운로드 완료
downloadCancel 취소
downloadError 오류
downloadPausing, downloadPause, downloadResume 일시 중지 요청, 일시 중지, 재개
downloadRetry 자동 재시도 시작
changeDownloadPath 다운로드 저장 경로가 바뀔 때. p는 경로 문자열

전송 상태 객체#

모든 전송 이벤트가 같은 형태의 객체를 전달합니다.

필드 타입 설명
transferID String 전송 묶음 ID. 서버의 _transferId와 같음
type String "upload" 또는 "download"
state String Before, Ready, Transferring, Complete, Cancel, Error, Pause
progress Number 진행률(0~100)
speed Number 전송 속도(bytes/s)
totalSize Number 전체 크기(bytes)
transferSize Number 전송한 크기(bytes)
totalFileCount Number 전체 파일 수
transferCompletedFileCount Number 전송 완료된 파일 수
retries Number 재시도 횟수
stopRetrying Boolean 재시도를 중단했는지 여부
files Array 전송 대상 파일 객체 배열
client_ip String 서버가 확인한 클라이언트 IP
statusMessage Object { id, errorCode, customError }. 상태 식별자, 오류 코드(없으면 false), 서버의 사용자 정의 오류

uploadComplete의 p.files 항목에는 파일 객체 필드에 다음 필드가 더해집니다.

필드 타입 설명
clientFileName String 클라이언트 파일명
serverFileName String 서버에 저장된 파일명
serverFilePath String 서버 저장 경로

uploadComplete에서 p.files의 serverFileName을 읽어 폼에 채웁니다.

javascript
box.on('uploadComplete', function (p) {
  var form = document.getElementById('f_write');
  p.files.forEach(function (f) {
    var input = document.createElement('input');
    input.type = 'hidden';
    input.name = 'serverFileName';
    input.value = f.serverFileName;
    form.appendChild(input);
  });
  form.submit();
});

서버 API#

서버 라이브러리(com.innorix.transfer 패키지)가 업로드, 다운로드 요청을 처리합니다. 업로드 엔드포인트는 InnorixUpload.run() 하나로 모든 요청을 처리하며, 요청마다 새 인스턴스를 만듭니다. 사용자 값은 request.getParameter()가 아니라 uploader.getParameter()로 읽습니다.

InnorixUpload를 생성하고 getFileInfo에서 저장 파일명을 바꾼 뒤 run(), isUploadDone()을 호출합니다.

jsp
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
if (request.getMethod().equals("POST")) {
    String directory = InnorixUpload.getServletAbsolutePath(request);
    directory = directory.substring(0, directory.lastIndexOf("/") + 1) + "data";
    int maxPostSize = 2147482624; // bytes

    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);

    String action = uploader.getParameter("_action");
    if ("getFileInfo".equals(action)) {
        uploader.setFileName("new-" + uploader.getParameter("_orig_filename"));   // run() 전에 호출
    }

    String result = uploader.run();

    if (uploader.isUploadDone()) {
        // 파일 1개 저장 완료
    }
}
%>

업로드 요청 액션#

요청은 multipart/form-data POST이고 _action 값으로 단계를 구분합니다.

_action 처리 시점 서버가 하는 일
speedCheck 전송 시작 전 전송 속도 측정용 요청 처리
getServerInfo 속도 측정 후 서버 정보 응답
getFileInfo 파일마다 1회 수신 가능 여부 확인, 저장 파일명, 경로 결정, 이어하기 여부 판단
attachFile 구간마다 반복 받은 구간 데이터를 파일의 해당 위치에 저장
attachFileCompleted 파일마다 1회 병합, 무결성 검사, 압축 해제, 복호화 등 완료 처리. 이후 isUploadDone()이 true

업로드 요청 파라미터#

파라미터 액션 설명
_action 전체 요청 단계
_orig_filename getFileInfo, attachFile, attachFileCompleted 원본 파일명
_unique_filename 동일 클라이언트가 만든 고유 파일명(이어하기 식별용)
_new_filename attachFile, attachFileCompleted getFileInfo에서 결정한 저장 파일명
_filesize getFileInfo, attachFile, attachFileCompleted 파일 전체 크기(bytes)
_folder, _clientpath 동일 폴더 경로, 클라이언트 경로
_serverpath attachFile 서버 저장 경로
_filepath attachFileCompleted 서버에 저장된 파일 경로
_start_offset, _end_offset attachFile 구간의 시작, 끝 위치(끝 위치 포함)
_encrypt attachFile, attachFileCompleted 암호화 전송 여부
_compressed getFileInfo, attachFile, attachFileCompleted 압축 전송 여부
_check_integrity attachFileCompleted 무결성 검사 요청 여부
_transferId 전체 전송 묶음 ID. 클라이언트의 transferID에 대응
el 전체 요청을 보낸 컨트롤 요소 ID
사용자 키 전체 setPostData() 값, 파일별 값, 파일의 customValue

업로드 응답 규약#

컨트롤은 응답 본문에서 <태그>값</태그> 형태의 줄을 읽습니다. 응답은 run()이 작성합니다.

응답 태그 액션 의미
file_code 전체 0000이면 성공. 그 외이거나 없으면 실패
file_savefilename getFileInfo 서버 저장 파일명
file_savepath getFileInfo, attachFile 서버 저장 경로
file_neednewupload getFileInfo 0이면 이어서 전송, 1이면 새로 전송
file_customvalue getFileInfo, attachFile 서버가 보낸 사용자 값. 파일 객체의 customValue에 병합

다운로드 요청#

다운로드 엔드포인트는 downloadType이 "stream"이면 구간 요청을 받습니다. InnorixDownload를 쓰거나 직접 구간 응답을 구현할 수 있습니다.

파라미터 전달 조건 설명
_StartOffset, _EndOffset downloadType: "stream" 요청 구간(끝 위치 포함)
_Action sendDownloadTime: true DownloadStart(시작 전), DownloadComplete(완료 후)
사용자 키 파일의 customValue 키가 쿼리로 덧붙음

DownloadStart 요청에 서버가 줄 수 있는 응답은 다음과 같습니다.

응답 효과
일반 응답 다운로드 진행
InnorixCustomValue로 fileSize 설정 목록의 파일 크기를 그 값으로 교체
InnorixCustomError 다운로드를 중단하고 코드, 제목, 메시지를 표시

서버 클래스#

클래스 용도
InnorixUpload 업로드 요청 처리. run()이 모든 _action을 처리
InnorixS3Upload InnorixUpload를 상속한 S3 호환 스토리지용 업로드
InnorixTransfer InnorixUpload를 상속. Save()는 run()과 같고, Save(name)은 저장 파일명을 지정
InnorixDownload 지정한 구간의 파일 바이트를 응답으로 씀
InnorixCustomValue 서버가 클라이언트로 돌려줄 사용자 정의 값을 응답으로 만듦
InnorixCustomError 사용자 정의 오류를 응답으로 만듦
UploadInfoCallBack 이어전송 정보 저장 방식과 서버 저장 경로를 애플리케이션이 처리

업로드 요청 처리와 완료 판정#

InnorixUpload 인스턴스는 요청마다 새로 만들고, run() 하나로 모든 _action을 처리합니다.

메서드 용도
run() 요청 액션을 처리하고 응답을 작성. String 반환
runForSpring() run()과 같은 처리를 하고 결과 객체(getCode(), getXml())를 반환
isUploadDone() 파일 하나의 저장이 끝나 attachFileCompleted가 정상 처리되면 true

요청 값과 수신 파일 조회#

메서드 용도
getParameter(name), getParameterNames() 요청 파라미터, 파라미터 이름 목록
getFileNames(), getFilesystemName(name), getOriginalFileName(name), getContentType(name), getFile(name) 수신 파일 정보 조회
getFileStream() 저장된 파일 스트림

저장 경로와 파일명 지정#

getFileInfo 단계에서 run() 전에 호출합니다.

메서드 용도
getFileName(), setFileName(name) 저장 파일명 조회, 변경
getDirectory(), setDirectory(path) 저장 디렉터리 조회, 변경
isOverwrite(), setOverwrite(bool) 같은 이름 파일 덮어쓰기 여부
isSaveFolderTree(), setSaveFolderTree(bool) 폴더 구조 유지 저장
setHideServerPathInfo(bool) 응답의 서버 저장 경로를 빈 값으로 보냄
setHideRootPath(bool) 클라이언트가 되돌려 보내는 경로를 directory 기준 상대 경로로 해석

사용자 값과 오류 응답#

메서드 용도
setCustomValue(key, value), sendCustomValue() 서버 값을 클라이언트로 돌려줌
setCustomError(code, message, detail, confirm), showCustomError(...) 사용자 정의 오류 설정, 즉시 응답
setServerInfo(host, id, groupId, latitude, longitude) 서버 정보 응답에 덧붙일 값

압축, 암호화 처리#

메서드 용도
setAutoDecompress(bool, bool) 압축 전송 파일의 완료 시 자동 해제 여부, 해제 후 압축 파일 삭제 여부
setEncryptKey(key), setEncryptIV(iv) 암호화 키, IV
setAutoDecryption(bool) 암호화되어 도착한 파일을 완료 시 자동 복호화할지 지정
decryptFile(src, dest), encryptFile(src, dest) 파일 단위 복호화, 암호화

다운로드 응답과 보조 응답 클래스#

클래스 메서드 용도
InnorixDownload run() 구간 파라미터가 있으면 해당 구간만, 없으면 전체 파일을 응답
InnorixDownload setFileName(name) 응답할 파일 이름
InnorixDownload setPrintFileName(name) 응답 헤더에 표시할 파일명
InnorixCustomError set(code, title, message[, confirm]), run() 사용자 정의 오류를 응답
InnorixCustomValue setCustomValue(key, value), run(response) 키-값을 응답으로 전송. 다운로드 시작 단계에서 fileSize를 돌려줄 때 사용

이어올리기 정보 외부 저장#

UploadInfoCallBack을 생성자 마지막 인자로 InnorixUpload에 넘깁니다. 이어올리기 상태를 DB 같은 외부 저장소에 두거나 서버 저장 경로를 바꿀 때 사용합니다.

메서드 호출 시점 용도
onCreateUploadInfo(path, size) getFileInfo에서 새 업로드를 시작할 때 새 이어올리기 정보 생성
onLoadUploadInfo(path, size) getFileInfo에서 이전 전송을 찾을 때 이어올리기 정보를 문자열로 반환
onRemoveUploadInfo(path) attachFileCompleted가 정상 완료될 때 이어올리기 정보 삭제
onChangeServerFilePath(path) 서버 저장 경로를 만들 때마다 비어 있지 않은 문자열을 반환하면 그 값이 저장 경로가 됨
다음Standard

이 페이지에서

  • 빠른 시작
  • 설치
  • UI 구성
  • 빠른 업로드 확인
  • 빠른 다운로드 확인
  • 업로드
  • 파일 선택
  • 첨부 파일 설정
  • 업로드 실행
  • 서버 저장
  • 데이터 전달
  • 오류 및 재처리
  • 다운로드
  • 다운로드 파일
  • 파일 제공
  • 다운로드 실행
  • 저장 처리
  • 오류 및 재처리
  • 완료 처리
  • 고급 활용
  • 화면 및 컨트롤
  • 전송창, 리스트 상세 구성
  • 이미지 처리
  • 전송 제어
  • 스토리지 연동
  • 보안
  • 외부 모듈 연계
  • Agent
  • API Reference
  • 설정 옵션
  • 메서드
  • 이벤트
  • 서버 API