Web upload / download 는 최종 사용자가 웹 브라우저에서 직접 파일을 올리고 받는 구성입니다. 에이전트를 설치하지 않은 외부 사용자도 대용량 파일을 주고받을 수 있고, 분할 전송·재개·진행률 표시는 웹 컨트롤이 처리합니다.
구성 요소는 세 가지입니다.
| 구성 | 역할 |
|---|---|
exabyter.js · exabyter.css | 브라우저에 붙는 전송 컨트롤. 파일 선택 UI, 분할 전송, 진행률, 재개를 담당 |
upload.jsp / download.jsp | 서버 수신·송신 엔드포인트. 실제 저장 경로를 정하는 곳 |
WEB-INF/lib/InnorixJAVA.jar | 엔드포인트가 사용하는 전송 라이브러리 |
⚠️ 배포 전에 보안 항목을 먼저 확인하세요. 서버 엔드포인트는 브라우저가 보낸 값을 그대로 받습니다. 특히 다운로드는
fileName을 파일 경로에 붙이는 구조라 경로 검증이 없으면 저장소 밖의 파일이 노출될 수 있습니다. 바로 아래 보안 필수 항목을 먼저 읽으세요.
ℹ️ 코드 탭 안내 — 이 구성은 브라우저 스크립트 + 서버 엔드포인트로 이루어지므로 예제는
JavaScript(클라이언트)와JSP(서버) 두 탭으로 제공합니다. 빌더가 생성하는 번들도 이 두 가지 파일로 구성됩니다.
시작하기
빌더가 만들어 주는 것
빌더에서 Web upload / download 탭의 옵션을 고르고 내려받으면 다음 구조의 zip 이 생성됩니다. 톰캣(또는 서블릿 컨테이너)의 webapps/ 아래에 그대로 풀면 바로 동작합니다.
exabyter-web-upload/
upload.html <- page built from the options you picked + exabyter.create({...})
upload.jsp <- receiving endpoint (set the storage path here)
exabyter.js
exabyter.css
img/ font/ <- assets referenced by the css
WEB-INF/lib/InnorixJAVA.jar
README.txt
다운로드 모드를 고르면 같은 구조에 download.html · download.jsp 가 들어갑니다.
배포 후 수정할 곳은 사실상 엔드포인트의 저장 경로 한 군데입니다.
upload.jsp—directory(업로드 저장 루트),maxPostSize(요청 본문 최대 크기)download.jsp—filePath(다운로드 원본 루트)
보안 필수 항목
빌더가 만들어 주는 번들은 동작 확인을 위한 최소 구성입니다. 외부 사용자에게 공개하기 전에 다음 네 가지는 반드시 적용하세요.
| 항목 | 그대로 두면 | 해야 할 일 |
|---|---|---|
| 엔드포인트 인증 | upload.jsp · download.jsp 에는 인증이 없어 URL 을 아는 누구나 호출합니다 | 페이지 맨 앞에서 세션·토큰을 검사하고 실패 시 401 로 종료 |
| 다운로드 경로 검증 | fileName 이 그대로 파일 경로가 되어 ../ 로 저장 루트 밖 파일을 읽을 수 있습니다 | 정규화(canonical) 경로가 저장 루트 안인지 확인. 더 안전하게는 파일 ID → 실제 경로 매핑을 서버에서 수행하고 경로 자체를 받지 않기 |
| 업로드 저장 경로 | 기본값이 JSP 옆 data 폴더라 웹으로 노출될 수 있습니다 | 웹 공개 범위 밖의 절대 경로로 변경 |
| CORS | 요청 Origin 을 그대로 반사하면 임의 사이트에서 호출할 수 있습니다 | 허용 도메인 목록으로 고정 |
아래 구현 예제에는 인증 검사 · 경로 검증 · 도메인 제한이 이미 들어 있습니다. 값만 서비스에 맞게 바꿔서 쓰세요.
구현
업로드
컨트롤은 빈 <div> 하나에 렌더링됩니다.
<link rel="stylesheet" type="text/css" href="./exabyter.css">
<script src="./exabyter.js"></script>
<div id="fileControl"></div>
<div class="toolbar">
<button onclick="control.openFileDialog()">Attach File</button>
<button onclick="control.directoryDialog()">Attach Folder</button>
<button onclick="control.upload()">Start Upload</button>
</div>
// upload.html - same shape as the bootstrap code the builder generates.
var control = exabyter.create({
setElementID: '#fileControl',
setViewType: 'ListView',
transferMode: 'upload',
setUploadURL: './upload.jsp', // receiving endpoint
setTransferStart: { upload: 'manual' }, // 'auto' = start as soon as files are selected
// How files are attached - the builder's Method
enableDropZone: true, // Drag & drop
addFolder: true, // allow folder attachment (Files and folders)
// File policy - the builder's File & storage rules
allowType: ['pdf', 'docx', 'xlsx', 'png'], // omit to allow every type
maxFileCount: 50,
maxFileSize: 1073741824, // 1 GB (bytes)
maxTotalSize: 10737418240, // 10 GB (bytes)
resumeType: 'numbering', // duplicate name: overwrite | numbering | relay (resume)
// Appearance
showTransferWindow: true,
showTransferStatusIcon: true,
enableComplexFolders: false, // hide the folder tree on the left (flat list)
hideClientPath: true,
transferWindowTitle: 'Exabyter',
controlLang: 'ko', // control UI language
});
// Control is ready
control.on('loadComplete', function () {
setStatus('Ready.', 'ready');
});
// All files attached - call upload() here to start automatically
control.on('afterAddFiles', function () {
// control.upload();
});
control.on('uploadStart', function () {
// Upload started
});
control.on('uploadProgress', function () {
// Progress changed
});
control.on('uploadComplete', function () {
setStatus('Upload complete.', 'done');
// Call your service API on completion (the builder's Call API)
fetch('/your/api', { method: 'POST', credentials: 'include' });
});
control.on('uploadError', function () {
setStatus('Upload error - see the console for details.');
});
control.on('uploadCancel', function () {
// Canceled by the user
});다운로드
다운로드는 제공할 파일 목록을 컨트롤에 넘기는 것이 핵심입니다. setDownloadList() 에 넘긴 항목이 화면 목록이 되고, 사용자가 고른 항목만 내려받습니다.
ℹ️ 아래 JSP 는 인증 검사 · 경로 검증 · 무결성 응답까지의 핵심 부분입니다. 바이트 구간 계산과 스트리밍을 포함한 전체 파일은 번들의
download.jsp에 들어 있습니다 (Get API Code 로 내려받습니다). 이 코드 상자만으로 다운로드 서버가 완성되지는 않습니다.
// download.html
var control = exabyter.create({
setElementID: '#fileControl',
setViewType: 'ListView',
transferMode: 'download',
setTransferStart: { download: 'manual' }, // 'auto' = start as soon as the list is set
downloadDuplicate: 'numbering', // duplicate name: overwrite | numbering | resume
downloadIntegrity: true, // verify file integrity
setVerification: 'enable',
showTransferWindow: true,
showTransferStatusIcon: true,
enableComplexFolders: false,
hideClientPath: true,
transferWindowTitle: 'Exabyter',
controlLang: 'ko',
});
control.on('loadComplete', function () {
setStatus('Ready.', 'ready');
});
control.on('downloadStart', function () { /* transfer started */ });
control.on('downloadProgress', function () { /* progress changed */ });
control.on('downloadComplete', function () {
setStatus('Download complete.', 'done');
});
control.on('downloadError', function () {
setStatus('Download error - see the console for details.');
});
control.on('downloadCancel', function () { /* canceled by the user */ });
// Files to offer - usually mapped straight from your service API response.
control.setDownloadList([
{
downloadURL: './download.jsp?fileName=report_2026Q3.pdf',
displayFileName: 'report_2026Q3.pdf', // name shown to the user
fileSize: 15728640, // bytes
isFolder: false,
},
{
downloadURL: './download.jsp?fileName=training_video.mp4',
displayFileName: 'training_video.mp4',
fileSize: 5368709120,
isFolder: false,
},
]);
// Wire up your buttons
// control.downloadSelectedFiles(); // selected items only
// control.download(); // everything서버에서 꼭 손봐야 할 것
| 위치 | 값 | 설명 |
|---|---|---|
upload.jsp | directory | 업로드 저장 루트. 기본값은 JSP 옆 data 폴더이므로 반드시 실제 경로로 바꾸세요. |
upload.jsp | maxPostSize | 요청 본문 최대 크기(바이트). 앞단 프록시(nginx 등) 제한과 함께 맞춰야 합니다. |
upload.jsp | CORS | 기본 코드는 요청 Origin 을 그대로 반사합니다. 운영에서는 허용 도메인으로 고정하세요. |
download.jsp | filePath | 다운로드 원본 루트. |
| 공통 | 인증 · 경로 검증 · CORS | 위 보안 필수 항목의 네 가지를 적용했는지 배포 전에 다시 확인하세요. |
사용자/전송 단위로 폴더를 나눌 때는 업로드 엔드포인트에서 이렇게 분기합니다.
// Per transfer
uploader.setDirectory(directory + "/" + uploader.getParameter("_transferId"));
// Per user (take the user id from the session)
String userId = (String) session.getAttribute("userId");
uploader.setDirectory(directory + "/" + userId);
// Per user and per transfer
uploader.setDirectory(directory + "/" + userId + "/" + uploader.getParameter("_transferId"));
참고
빌더 옵션 ↔ 생성 코드 매핑
업로드 탭
| 빌더 항목 | 선택지 | 반영되는 곳 |
|---|---|---|
| Method | Drag & drop | enableDropZone: true |
| File select button | enableDropZone: false + 첨부 버튼 | |
| Connect to existing UI | 버튼 없이 control.upload() 를 기존 UI 에 직접 연결 | |
| Start method | Click the upload button | setTransferStart: { upload: 'manual' } |
| Auto-start after selecting files | afterAddFiles 에서 control.upload() 호출 | |
| Selection target | Single file | maxFileCount: 1 |
| Files and folders | addFolder: true + 폴더 첨부 버튼 | |
| File type | Images / Documents / Videos / Custom | allowType: [...] |
| Max file count | 1 / 10 / 50 / 100 / Custom | maxFileCount |
| Max file size | 100MB / 1GB / 5GB / 10GB / Custom | maxFileSize (바이트) |
| Max total size | 1GB / 5GB / 10GB / 50GB / Custom | maxTotalSize (바이트) |
| Duplicate file name | Overwrite | resumeType: 'overwrite' |
| Save with a new name / Don't overwrite | resumeType: 'numbering' | |
| Storage method | Fixed folder | upload.jsp 의 directory 그대로 |
| Per-user / Per-upload / Per-user & per-upload | upload.jsp 에서 uploader.setDirectory(...) 로 분기 | |
| Completion message | 문구 입력 | uploadComplete 핸들러의 상태 문구 |
| Call API | URL + POST/GET | uploadComplete 에서 fetch(url, { method }) |
| Developer event hooks | 체크한 항목 | control.on('uploadStart' | 'uploadProgress' | 'uploadComplete' | 'uploadError' | 'uploadCancel', …) |
다운로드 탭
| 빌더 항목 | 반영되는 곳 |
|---|---|
| 제공할 파일 | control.setDownloadList([{ downloadURL, displayFileName, fileSize, isFolder }]) |
| 시작 방식 | setTransferStart: { download: 'manual' | 'auto' } |
| 중복 이름 | downloadDuplicate: 'overwrite' | 'numbering' | 'resume' |
| 무결성 | downloadIntegrity: true + setVerification: 'enable' |
| 이벤트 훅 | control.on('downloadStart' | 'downloadProgress' | 'downloadComplete' | 'downloadError' | 'downloadCancel', …) |
ℹ️ 화면 구성(파일 목록 표시 항목), 파일명 규칙, 접근 권한, 제공 기간 같은 항목은 서비스 쪽에서 목록을 만들 때 결정합니다. 컨트롤에는 최종 목록만 넘기면 됩니다. 파일은 목록에 담긴 그대로 하나씩 전송됩니다.
자주 겪는 오류
| 증상 | 원인과 해결 |
|---|---|
| 컨트롤이 안 보임 | setElementID 가 가리키는 <div> 가 스크립트 실행 시점에 없거나, exabyter.js 로드 전에 create() 를 호출했습니다. |
| 업로드 시작 직후 실패 | setUploadURL 경로가 틀렸거나 upload.jsp 가 배포되지 않았습니다. 브라우저 네트워크 탭에서 404 를 먼저 확인하세요. |
| CORS 오류 | 컨트롤은 POST 전에 OPTIONS 를 보냅니다. Access-Control-Allow-* 헤더가 OPTIONS 응답에도 나가야 합니다. |
| 큰 파일에서 413 | maxPostSize 와 앞단 프록시의 본문 크기 제한을 함께 올리세요. |
| 저장 경로가 이상한 곳 | directory 기본값이 JSP 옆 data 폴더입니다. 절대 경로로 바꾸세요. |
| 한글 파일명 깨짐 | JSP 의 pageEncoding="UTF-8" 과 컨테이너의 URI 인코딩 설정(URIEncoding=UTF-8)을 확인하세요. |
| 확장자 제한이 안 걸림 | allowType 은 점 없이 소문자 배열입니다(['pdf','png']). 클라이언트 검증이므로 서버에서도 한 번 더 확인하세요. |
| 다운로드 목록이 비어 있음 | setDownloadList() 를 loadComplete 이후에 호출했는지, 항목에 displayFileName 이 있는지 확인하세요. |