Web upload / download — 브라우저에서 올리고 받기

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/ 아래에 그대로 풀면 바로 동작합니다.

text
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.jspdirectory(업로드 저장 루트), maxPostSize(요청 본문 최대 크기)
  • download.jspfilePath(다운로드 원본 루트)

보안 필수 항목

빌더가 만들어 주는 번들은 동작 확인을 위한 최소 구성입니다. 외부 사용자에게 공개하기 전에 다음 네 가지는 반드시 적용하세요.

항목그대로 두면해야 할 일
엔드포인트 인증upload.jsp · download.jsp 에는 인증이 없어 URL 을 아는 누구나 호출합니다페이지 맨 앞에서 세션·토큰을 검사하고 실패 시 401 로 종료
다운로드 경로 검증fileName 이 그대로 파일 경로가 되어 ../ 로 저장 루트 밖 파일을 읽을 수 있습니다정규화(canonical) 경로가 저장 루트 안인지 확인. 더 안전하게는 파일 ID → 실제 경로 매핑을 서버에서 수행하고 경로 자체를 받지 않기
업로드 저장 경로기본값이 JSP 옆 data 폴더라 웹으로 노출될 수 있습니다웹 공개 범위 의 절대 경로로 변경
CORS요청 Origin 을 그대로 반사하면 임의 사이트에서 호출할 수 있습니다허용 도메인 목록으로 고정

아래 구현 예제에는 인증 검사 · 경로 검증 · 도메인 제한이 이미 들어 있습니다. 값만 서비스에 맞게 바꿔서 쓰세요.

구현

업로드

컨트롤은 빈 <div> 하나에 렌더링됩니다.

html
<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.jspdirectory업로드 저장 루트. 기본값은 JSP 옆 data 폴더이므로 반드시 실제 경로로 바꾸세요.
upload.jspmaxPostSize요청 본문 최대 크기(바이트). 앞단 프록시(nginx 등) 제한과 함께 맞춰야 합니다.
upload.jspCORS기본 코드는 요청 Origin 을 그대로 반사합니다. 운영에서는 허용 도메인으로 고정하세요.
download.jspfilePath다운로드 원본 루트.
공통인증 · 경로 검증 · CORS보안 필수 항목의 네 가지를 적용했는지 배포 전에 다시 확인하세요.

사용자/전송 단위로 폴더를 나눌 때는 업로드 엔드포인트에서 이렇게 분기합니다.

java
// 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"));

참고

빌더 옵션 ↔ 생성 코드 매핑

업로드 탭

빌더 항목선택지반영되는 곳
MethodDrag & dropenableDropZone: true
File select buttonenableDropZone: false + 첨부 버튼
Connect to existing UI버튼 없이 control.upload() 를 기존 UI 에 직접 연결
Start methodClick the upload buttonsetTransferStart: { upload: 'manual' }
Auto-start after selecting filesafterAddFiles 에서 control.upload() 호출
Selection targetSingle filemaxFileCount: 1
Files and foldersaddFolder: true + 폴더 첨부 버튼
File typeImages / Documents / Videos / CustomallowType: [...]
Max file count1 / 10 / 50 / 100 / CustommaxFileCount
Max file size100MB / 1GB / 5GB / 10GB / CustommaxFileSize (바이트)
Max total size1GB / 5GB / 10GB / 50GB / CustommaxTotalSize (바이트)
Duplicate file nameOverwriteresumeType: 'overwrite'
Save with a new name / Don't overwriteresumeType: 'numbering'
Storage methodFixed folderupload.jspdirectory 그대로
Per-user / Per-upload / Per-user & per-uploadupload.jsp 에서 uploader.setDirectory(...) 로 분기
Completion message문구 입력uploadComplete 핸들러의 상태 문구
Call APIURL + POST/GETuploadComplete 에서 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 응답에도 나가야 합니다.
큰 파일에서 413maxPostSize 와 앞단 프록시의 본문 크기 제한을 함께 올리세요.
저장 경로가 이상한 곳directory 기본값이 JSP 옆 data 폴더입니다. 절대 경로로 바꾸세요.
한글 파일명 깨짐JSP 의 pageEncoding="UTF-8" 과 컨테이너의 URI 인코딩 설정(URIEncoding=UTF-8)을 확인하세요.
확장자 제한이 안 걸림allowType 은 점 없이 소문자 배열입니다(['pdf','png']). 클라이언트 검증이므로 서버에서도 한 번 더 확인하세요.
다운로드 목록이 비어 있음setDownloadList()loadComplete 이후에 호출했는지, 항목에 displayFileName 이 있는지 확인하세요.