빠른 시작#
제품을 설치한 뒤 샘플 페이지로 업로드와 다운로드가 동작하는지 확인합니다. 구성 파일과 옵션은 이후 장에서 자세히 설명합니다.
설치#
설치 파일의 구성과 라이선스, 서버, 프런트엔드 연결 방법을 설명합니다.
구성 파일#
<웹 루트>/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 실행 계정에 쓰기 권한을 줍니다. 운영에서는 웹 루트 밖에 둡니다.
mkdir -p /data/innorix/upload
chown -R tomcat:tomcat /data/innorix/upload
chmod 750 /data/innorix/upload프런트엔드 연결#
페이지에서 CSS, innorix.js, config.js 순서로 로드합니다. config.js는 화면 프리셋을 쓸 때만 필요합니다.
<link rel="stylesheet" href="/innorix/innorix.css">
<script src="/innorix/innorix.js"></script>
<script src="/innorix/config.js"></script>옵션은 내장 기본값, 프리셋, create() 인자 순으로 합쳐지므로 create()에 직접 쓴 값이 가장 우선합니다.
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로 직접 구성 |
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
boxStyle: 'list' // list, icon, preview, html
});
전송창 스타일#
transferWindowStyle |
모양 |
|---|---|
default |
기본 크기 전송창 |
mini |
레이어로 출력하는 전송창의 작은 버전 |
list |
레이어로 출력하며 진행도 위에 파일 목록을 함께 표시 |
compact |
레이어 없이 리스트 하단에 표시 |
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
transferWindowStyle: 'mini' // default, mini, list, compact
});전송창을 열지 않으려면 showTransferWindow: false를 지정합니다.

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

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

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

빠른 다운로드 확인#
- 웹 서버 URL로
exam/download-agent.html을 엽니다. 샘플은download.jsp와 같은 위치의Task_list.zip을 제공합니다.

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

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

업로드#
사용자가 고른 파일을 서버에 올리고, 저장 결과와 업무 데이터를 화면과 서버가 함께 다루는 방법을 설명합니다. 각 기능은 설명 → 함수, 옵션 → 예시 순서로 되어 있어 필요한 기능만 찾아 읽을 수 있습니다.
파일 선택#
게시판 글쓰기 화면의 "파일 추가" 버튼, 탐색기에서 끌어다 놓기, 코드로 경로 지정까지 모두 하나의 업로드 목록으로 모입니다. 이후 검증과 전송은 이 목록을 기준으로 진행됩니다.
파일 선택 대화상자 ─┐
폴더 선택 대화상자 ─┤
드래그 앤 드롭 ─────┼→ 업로드 목록 → 검증 → 전송
프로그램 경로 지정 ─┘파일, 폴더 선택 버튼#
버튼 클릭에 선택 대화상자를 연결합니다. transferMode가 download인 컨트롤에서는 동작하지 않고, 컨트롤 준비 전 호출은 무시됩니다.
| 메서드 | 설명 |
|---|---|
openFileDialog() |
여러 파일 선택 |
openFileDialogSingle() |
파일 하나 선택 |
openFolderDialog() |
폴더 선택 |
<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()를 호출하면 첨부 직후 자동 업로드가 됩니다. 연속 첨부 시 중복 호출되므로 한 번만 시작되게 막습니다.
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 |
켜면 묻지 않고 복원해 바로 업로드, 끄면 이어서 전송할지 사용자에게 확인 |

첨부 파일 설정#
회원 가입 서류 첨부에서 "최대 5개, pdf와 jpg만, 파일당 10MB"처럼 제한을 둬야 하는 경우입니다. 서버에 올린 뒤 거부하면 회선이 낭비되므로 파일이 목록에 들어오는 시점에 검증하고, 위반한 파일만 거부하며 addFileError로 알립니다.
선택한 파일 → 중복 → 개수 → 전체 용량 → 차단 확장자 → 허용 확장자 → 개별 용량 → 원본 존재 → 목록 추가
(처음 실패한 항목에서 거부, addFileError)개수, 전체 용량에 걸리면 같은 선택 묶음의 나머지 처리를 중단하고 앞서 통과한 파일은 목록에 남습니다. 확장자, 개별 용량 오류는 해당 파일만 건너뜁니다.
제한 옵션#
innorix.create()에 지정합니다. 지정하지 않은 항목은 검사하지 않습니다.
| 옵션 | 설명 |
|---|---|
maxFileCount |
파일 개수 상한(바이트 아님, 개수) |
maxFileSize, maxTotalSize |
개별, 전체 용량 상한(바이트) |
allowType |
허용 확장자(점 없이 소문자). 배열 외에 그룹 객체도 가능 |
denyType |
차단 확장자. allowType과 겹치면 denyType 우선 |
useSignature |
파일 앞부분으로 실제 형식 확인(확장자만 바꾼 파일 대응). 주의사항은 5. 고급 활용 > 보안 > 시그니처 검사 |
addDuplicateFile |
중복 첨부 정책 |
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가 발생하지 않으니 안내가 필요하면 그 안에서 직접 표시합니다.
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()로 제거합니다.
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를 반환합니다.

업로드 실행#
수백 장의 사진을 올리는 중 회선이 느려지거나 창을 닫아도, 일시 정지, 재개, 취소로 제어하고 이어서 올릴 수 있어야 합니다.
upload() → uploadBefore 확인 → 전송 (진행률 uploadProgress) → uploadComplete
│
일시 정지 / 재개 / 취소 / 오류 재시도파일은 목록 순서대로 시작하지만 여러 파일이 겹쳐 전송되므로 완료 순서는 목록 순서와 같지 않을 수 있습니다. 큰 파일은 여러 요청으로 나뉘어 전달되고, 끊겨도 받은 위치 이후부터 이어서 올립니다.
업로드 시작#
| 이름 | 설명 |
|---|---|
upload() |
전송 시작. 컨트롤 미준비, 대상 파일 없음, uploadURL 없음, 전송 중, uploadBefore가 false 반환이면 시작되지 않음 |
uploadBefore |
시작 직전 이벤트. false를 반환하면 시작 취소 |
transferStart |
upload를 auto(즉시 전송) 또는 manual(전송창의 시작 버튼)로 지정 |
startTransferProgress() |
manual일 때 전송창에서 전송 시작 |
<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로 순번을 보냅니다.
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가 있으면 요청 크기 제한도 확인합니다.
<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>
서버 저장#
게시판 첨부 파일이 서버의 어느 폴더에 어떤 이름으로 저장되는지 정하는 부분입니다. 서버는 InnorixUpload 하나로 구성하고, 요청 판별, 구간 이어 붙이기, 응답 작성은 run()이 처리합니다. 개발자는 run() 앞뒤에서 저장 경로, 파일명, 완료 후 처리를 지정합니다.
서버는 요청 한 건이 아니라 파일 한 개당 여러 번 호출됩니다. 전체 완료 요청은 없고 파일 단위로만 진행되며, run()을 쓰면 이어올리기가 함께 동작합니다.
요청 수신 → getFileInfo (저장 경로, 파일명 결정) → attachFile (구간 저장, 반복)
→ attachFileCompleted (파일 한 개 완료) → isUploadDone() 후처리최소 엔드포인트#
POST 요청에서만 InnorixUpload를 생성하고 run()을 호출합니다. 사전 요청(OPTIONS)에서는 생성하지 않도록 POST 조건을 유지합니다.
| 메서드 | 설명 |
|---|---|
run() |
요청 처리와 응답 작성. 응답 본문을 쓰므로 앞뒤로 공백이나 출력을 남기지 않음 |
runForSpring() |
Spring 환경용 |
runAction() |
응답을 쓰지 않음. 호출자가 응답을 직접 작성 |
isUploadDone() |
파일 한 개의 저장이 끝난 요청에서 true |
<%@ 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 |
한 페이지의 여러 컨트롤 구분 |
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입니다.
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 대신 메모리나 캐시에 기록합니다.
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) ...
}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 같은 인증 값은 서버 세션에서 읽고, 파일명, 경로에 쓰기 전에 검증합니다.
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();
}String boardId = uploader.getParameter("boardId");
String customValue = uploader.getParameter("customValue"); // 값이 없으면 null
String headerValue = request.getHeader("value");값 돌려받기#
서버는 run() 이후 isUploadDone() 또는 attachFileCompleted 블록에서 값을 지정해 보내고, 클라이언트는 uploadComplete의 파일 항목 customValue에서 읽습니다. 반환 값은 모두 문자열입니다.
| 메서드 | 설명 |
|---|---|
setCustomValue(키, 값) |
서버에서 화면으로 돌려줄 값 지정 (예: 첨부 ID, 변환된 경로) |
sendCustomValue() |
지정한 값 전송 |
String _run_retval = uploader.run();
if (uploader.isUploadDone()) {
uploader.setCustomValue("attachId", String.valueOf(newAttachId));
uploader.sendCustomValue();
}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() 또는 전송창의 재개 버튼으로 이어 올립니다.
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의 단축 메서드 |
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에서 합니다.
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);
});
다운로드#
서버에 있는 파일을 사용자 PC로 안정적으로 내려받는 방법을 다룹니다. 각 기능은 설명 → 함수, 옵션 → 예시 순서로 되어 있어 필요한 기능만 찾아 읽을 수 있습니다.
다운로드 파일#
문서관리 화면에서 사용자가 계약서 여러 건을 체크하고 한 번에 받는 경우입니다. 컨트롤은 서버에 어떤 파일이 있는지 알지 못하므로, 애플리케이션이 목록을 만들어 presetDownloadFiles()로 넘기면 컨트롤이 전송과 저장을 맡습니다. 목록은 컨트롤 생성이 끝난 뒤(loadComplete)에 전달합니다.
서버 목록 API → 파일 목록 JSON → presetDownloadFiles() → 컨트롤 목록 → 다운로드 실행파일 항목 만들기#
서버 목록을 컨트롤이 읽는 항목으로 바꿉니다. 사용자에게 보이는 파일명은 항상 printFileName이 정하며 서버의 저장 파일명과는 별개입니다.
| 이름 | 설명 |
|---|---|
printFileName |
목록 표시와 저장에 쓰는 파일명(확장자 포함) |
downloadUrl |
파일을 제공하는 서버 URL. 인증이 필요하면 쿠키, 세션이 함께 전달되어야 함 |
fileSize |
바이트 단위 숫자. "1.2MB" 같은 문자열은 사용 불가. 비우면 컨트롤이 HEAD 요청으로 크기 확인 |
rootName |
폴더 경로. 구분자는 /로 통일 |
skipFileSizeCheck |
크기 없이 항목 추가(변환 후 크기가 정해지는 파일) |
서버 목록 API 응답을 printFileName/fileSize/downloadUrl로 변환해 loadComplete에서 전달합니다.
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에 넣습니다.
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에서 크기를 함께 내려 주는 편이 좋습니다.
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을 브라우저 주소창에 직접 넣어 서버 응답부터 확인합니다.

파일 제공#
로그인한 사용자만 받아야 하는 급여명세서를 내려주는 경우입니다. 목록에서 숨기는 것은 보안이 아니므로 권한 검사는 컨트롤이 호출하는 서버 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입니다.
<%@ 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 클래스를 쓰면 구간 응답과 헤더 설정을 한 번에 처리합니다.
<%@ 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()를 호출합니다.
box.on('loadComplete', function () {
box.presetDownloadFiles(filesFromServer);
box.download();
});사용자 확인 후 시작하려면 downloadBefore 핸들러가 false를 정확히 반환하게 하여 전송을 막습니다.
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() |
저장 폴더 열기 |
저장 경로와 같은 이름 처리 방식을 생성 옵션으로 지정합니다.
box = innorix.create({
el: '#fileBox',
savePath: 'C:\\Downloads\\exabyter', // 백슬래시를 두 번 씀
downloadDuplicate: 'numbering',
boxConfig: box_config.download_agent
});경로 선택 대화상자를 열고, 유효한 경로를 고른 경우에만 전송을 시작합니다.
function downloadToChosenFolder() {
box.setDownloadPath(function (response) {
if (response.result == true) {
box.download();
}
});
}Windows 경로의
\는 JavaScript 문자열에서\\로 씁니다.
폴더 구조와 파일명#
addFolder가 켜져 있을 때만 rootName이 하위 폴더로 반영됩니다. 하위 폴더까지 받으면 경로가 길어지므로 목록 단계에서 깊이와 파일명 길이를 점검합니다. Windows에서 쓸 수 없는 문자(\ / : * ? " < > |)는 printFileName에서 미리 바꿉니다. 저장 후에는 저장 폴더를 열어 파일명, 하위 폴더, 충돌 처리 결과를 확인합니다.


오류 및 재처리#
출장 중 와이파이가 끊겨 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()로 전송을 제어합니다.
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으로 응답하면 파일 내용으로 저장되므로 쓰지 않습니다.
<%@ 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 이벤트를 받습니다.
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으로 응답합니다.
<%
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).
<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로 보낸 값으로 어느 컨트롤의 요청인지 구분합니다.
String slot = uploader.getParameter("slot");통합 전송은 여러 컨트롤의 파일을 한 컨트롤로 모아 한 번에 올립니다 (exam/combine-agent-3box.html).
function upload() {
box3.addFiles(box1.getAllFiles());
box3.addFiles(box2.getAllFiles());
box3.upload();
}여러 컨트롤에 같은 메서드를 한꺼번에 호출하려면 innorix.group()으로 묶습니다. 그룹에서 호출한 메서드는 묶인 컨트롤 각각에 같은 인자로 실행됩니다.
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이벤트 발생 후 컨트롤 속성을 삭제
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: 파일이 아닌 항목을 드롭했을 때 발생하는 이벤트
<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>태그 문자열
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이면 메뉴를 만들지 않고 브라우저 기본 메뉴가 나타납니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
useContextMenu: false // 컨텍스트 메뉴 비활성
});컨트롤에는 Del 키 삭제 기능과 이를 켜고 끄는 옵션이 없습니다. 필요하면 화면에서 keydown을 받아 선택 파일을 삭제하는 코드를 직접 추가하고, 이 핸들러를 붙이거나 떼서 활성, 비활성을 정합니다.
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 |
컨트롤 해제 |
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()를 확인합니다.
function syncButtons() {
document.getElementById('delBtn').disabled = box.getSelectedFileCount() === 0;
}
box.on('onSelectRows', syncButtons);
box.on('onUnSelectRows', syncButtons);
box.on('removeFiles', syncButtons);그 밖의 이벤트와 옵션은 6. API Reference를 참고합니다.

전송창, 리스트 상세 구성#
사내 결재 화면의 첨부 영역에 파일 목록을 좁게 넣고, 진행 상태는 화면 디자인에 맞춘 자체 바로 보여주고 싶은 경우입니다. 목록과 전송창은 옵션으로 구성하고, 필요한 부분만 직접 만든 화면으로 바꿀 수 있습니다.
화면은 두 영역입니다. 파일을 보여주는 **리스트(파일 목록)**와 전송 중에 나타나는 전송창입니다. 전송창 모양은 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 |
전송 중 닫기 버튼을 누르면 취소 여부를 한 번 더 확인 |
제목을 바꾸고, 수동 시작과 취소 확인을 켠 기본 전송창으로 구성합니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
transferWindowStyle: 'default', // 기본 전송창
transferWindowTitle: '첨부파일 업로드',
transferStart: { upload: 'manual' }, // 시작 버튼으로 시작
cancelConfirmation: true, // 닫을 때 취소 확인
boxConfig: box_config.upload_basic
});컨트롤이 다른 페이지에 임베드되어 있으면 호스트 페이지에 출력하도록 지정합니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
hostTransferWindow: true, // 전송창을 호스트 페이지에 출력
hostTransferWindowTarget: 'top', // 'top' 최상위 호스트, 'parent' 직전 상위 호스트
draggableTransferWindow: true, // 제목 줄을 끌어 이동
boxConfig: box_config.upload_basic
});
프리셋은 transferStart가 자동 시작이므로 수동 시작은 직접 덮어씁니다. hostTransferWindow를 쓰려면 출력할 호스트 페이지에 jQuery와 innorix.css가 로드되어 있어야 하며, 호스트 페이지와 임베디드 컨트롤은 같은 출처여야 합니다.
미니#
transferWindowStyle: 'mini'는 레이어로 출력하는 전송창의 작은 버전입니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
transferWindowStyle: 'mini', // 레이어로 출력하는 작은 전송창
boxConfig: box_config.upload_basic
});
목록 포함#
transferWindowStyle: 'list'는 레이어로 출력하며, 진행도 위에 파일 목록을 함께 표시합니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
transferWindowStyle: 'list', // 레이어로 출력, 진행도 위에 파일 목록 표시
boxConfig: box_config.upload_basic
});
컴팩트#
transferWindowStyle: 'compact'는 별도 레이어 없이 리스트 하단에 전송 상태를 표시합니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
transferWindowStyle: 'compact', // 레이어 없이 리스트 하단에 표시
boxConfig: box_config.upload_basic
});
숨김#
전송창을 아예 숨기려면 showTransferWindow: false로 끄고 uploadProgress/downloadProgress 이벤트로 진행 화면을 직접 만듭니다. 페이지의 지정 영역에 진행률, 상태, 버튼을 직접 그리는 방식도 같은 구성입니다.
| 항목 | 역할 |
|---|---|
showTransferWindow: false |
기본 전송창을 열지 않음 |
uploadProgress, downloadProgress |
전송 상태 객체(progress, speed, state 등)를 전달 |
transferPause(), transferResume(), transferCancel() |
일시정지, 재개, 취소 |
state는 Before, Ready, Transferring, Complete, Cancel, Error, Pause 중 하나이고, speed는 바이트/초 단위입니다. 전송창을 숨기면 사용자가 취소, 일시정지할 수단이 사라지므로 세 메서드를 자체 버튼에 반드시 연결합니다.
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 영역에 상태를 표시하고 버튼을 연결합니다.
<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>
전송 상태가 바뀔 때마다 이벤트가 호출되어 자체 화면이 갱신되고, 버튼이 동작하면 정상입니다. 다운로드는 downloadProgress로 같은 방식을 씁니다.
리스트 UI#
boxStyle로 리스트 모양을 고릅니다. 값은 list, icon, preview, html 네 가지입니다.
리스트형#
boxStyle: 'list'는 리스트형 목록입니다. 크기와 폴더, 대량 목록 옵션은 다음과 같습니다.
| 옵션 | 역할 |
|---|---|
boxWidth, boxHeight |
목록 크기(px). el 요소의 CSS가 아니라 이 옵션으로 지정 |
setSize(w, h) |
생성 후 크기 변경 |
folderAttach, showFolderItems |
폴더 첨부 허용과 폴더 안 항목의 트리 표시 |
maxMassFileListCount |
행으로 표시할 최대 개수(최대 1000). 넘으면 안내 행과 개수, 크기 요약 표시 |
massFileTransfer |
true이면 파일 수와 관계없이 대량 전송 |
리스트형으로 크기를 지정해 만들고, 폴더 트리와 대량 목록을 켭니다.
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);
목록이 지정한 크기로 표시되고, 폴더를 추가하면 하위 항목이 트리로 나타납니다. 표시되지 않는 파일은 행이 없으므로 removeAllFiles()로 일괄 정리합니다.
아이콘형#
boxStyle: 'icon'은 아이콘형 목록입니다.
box = innorix.create({
el: '#fileBox',
boxStyle: 'icon',
boxWidth: 550,
boxHeight: 200,
uploadURL: './upload.jsp',
boxConfig: box_config.upload_basic
});
미리보기#
boxStyle: 'preview'는 미리보기 패널을 함께 보여주는 목록입니다. 미리보기 출력 위치와 PDF 표시는 다음 옵션으로 정합니다.
| 옵션 | 역할 |
|---|---|
setPreviewDiv |
미리보기를 지정한 요소에 출력. [요소 ID, 너비, 높이] |
showPreviewPdf |
PDF 첫 페이지를 캔버스에 표시. 페이지에서 PDF.js(pdfjsLib)를 먼저 로드해야 함 |
미리보기를 컨트롤 밖의 #previewBox에 출력합니다.
<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>
인증이 필요한 이미지 URL은 쿠키 전달과 CORS 허용이 필요합니다. HEIC 미리보기는 지원하지 않습니다.
HTML형#
boxStyle: 'html'은 애플리케이션이 HTML로 목록을 직접 그리는 형입니다. 컨트롤은 화면에 숨기고 목록을 HTML 표로 그립니다. 컨트롤은 파일 선택과 전송을 맡습니다.
| 항목 | 역할 |
|---|---|
| 컨트롤 요소 | display:none으로 숨김 |
afterAddFiles |
인자 배열의 filePath, fileSize, id로 표 행 생성 |
removeFileById(id) |
표에서 행을 지우면서 컨트롤의 목록에서도 삭제 |
<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>
표 행 순서와 컨트롤의 파일 순서가 어긋나지 않도록 행 삭제는 반드시 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_로 구분합니다.
box.appendThumbnailProperty('ALL', 300, 200, 'HORIZONTAL');
box.upload();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"이면 적용되지 않은 것입니다.
var r = box.appendWatermarkProperty('ALL', './logo.png', 'LEFT|BOTTOM');
if (r === 'not_supported') alert('워터마크 이미지 형식을 확인하세요.');
box.upload();
전송 제어#
대용량 파일이나 제한된 회선에서 전송 속도, 압축, 암호화, 무결성 검사를 조절해야 하는 경우입니다. 업로드와 다운로드 옵션을 한곳에 모았습니다.
업로드 전송 옵션#
- 속도:
limitRate로 상한을 지정하고setLimitRate로 바꿉니다. 컨트롤은 서버 부하를 보고 속도를 바꾸지 않으므로 강제 제한은 서버나 프록시에서 합니다. - 압축:
useCompress는 텍스트, 로그에만 효과가 있고 zip, jpg, mp4에는 CPU만 씁니다. 서버는setAutoDecompress(해제, 원본삭제)로 자동 해제합니다. - 암호화:
useEncrypt는 클라이언트와 서버가 같은 키와 IV를 가져야 하며, 파일명까지 가리려면useEncryptMeta를 함께 지정합니다. 키는 소스에 직접 쓰지 않고, HTTPS를 대체하지 않습니다. - 무결성:
integrity를 지정하며, 실패하면 전송창에 "무결성 검사 오류"가 표시됩니다. - 대량 전송: 작은 파일이 매우 많으면
massFileTransfer를 사용합니다. 이 모드에서는 파일 목록 표시가 제한됩니다.
클라이언트에 속도, 압축, 암호화 옵션을 지정하고, 서버는 같은 키와 IV로 InnorixUpload를 생성합니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload-crypt.jsp',
installURL: '../install/install.html',
limitRate: 2000,
useCompress: true,
useEncrypt: true,
useEncryptMeta: true
});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입니다.
<%@ 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을 지정해 컨트롤을 만듭니다.
box = innorix.create({
el: '#fileBox',
transferMode: 'both',
downloadType: 'direct',
isPresignedUrl: true,
uploadURL: './s3_upload.jsp',
installURL: '../install/install.html'
});InnorixS3Upload가 사전 서명 URL을 응답하는 s3_upload.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을 돌려주는 엔드포인트를 지정합니다.
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입니다.
<%@ 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 호환 스토리지에 저장하는 생성 예입니다.
// 이어올리기 정보를 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을 지정합니다.
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는 애플리케이션이 구현합니다.
<%@ 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()로 세션 쿠키를 지정합니다.
<script>
box.on('loadComplete', function () {
box.setCookie('JSESSIONID=<%= session.getId() %>');
});
</script>전송이 시작되면 세션 갱신 요청을 보내고, 완료, 오류, 취소 시 멈춥니다.
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> 태그에서 토큰을 읽습니다.
<meta name="_csrf" content="${_csrf.token}">
<meta name="_csrf_header" content="${_csrf.headerName}">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 이벤트로 거부 사유를 안내합니다.
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입니다.
<%@ 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는 파일명, 경로를 암호화합니다.
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)를 지정합니다.
<%@ 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는 연계 대상의 호출부로, 애플리케이션이 구현합니다.
<%
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를 읽어 검사 결과를 표시합니다.
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가 필요합니다.
<%@ 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로 알립니다.
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와 함께 만듭니다.
box = innorix.create({
el: '#fileBox',
uploadURL: './upload.jsp',
installURL: '../install/install.html',
boxConfig: box_config.upload_agent
});
다운로드 전용 컨트롤#
설치가 보장된 환경에서는 설치 확인을 생략합니다. 설치되지 않은 PC에서는 전송 시점에 실패하므로 사내 환경에서만 사용합니다.
skipPluginCheck: true: 설치 확인 생략downloadDuplicate: 중복 파일 처리 방식
skipPluginCheck: true와 downloadDuplicate를 지정한 다운로드 전용 컨트롤입니다.
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 프리셋 위에 옵션을 덮어씁니다.
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 이후 버튼에서 메서드를 호출해 파일을 추가하고 업로드합니다.
<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()로 다운로드 목록을 등록합니다.
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를 등록합니다.
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을 읽어 폼에 채웁니다.
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()을 호출합니다.
<%@ 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) |
서버 저장 경로를 만들 때마다 | 비어 있지 않은 문자열을 반환하면 그 값이 저장 경로가 됨 |