INNORIX

INNORIX PLATFORM

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

WEB

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

DEVELOPERS

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

도구

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

성능과 규모

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

현대화

  • FTP, SFTP 마이그레이션

새로운 전송 환경

  • AI Data Delivery
  • Dynamic Endpoint Transfer

글로벌 대규모 전송

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

LET FILES
MOVE THEMSELVES

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

파일 전송

INNORIX PLATFORM

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

PERFORMANCE & SCALE

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

MODERNIZATION

  • FTP, SFTP 마이그레이션

WEB

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

DEVELOPERS

  • 앱 임베디드 파일 전송

NEW WORKLOADS

  • AI Data Delivery
  • Dynamic Endpoint Transfer

HYPERLANE

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

개발자

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

도구

  • 전송 빌더
  • 전송 파인더

리소스

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

고객사

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

플랜

  • 가격 및 플랜

회사

INNORIX 소개

비전 AI 제품

Al.bert — 스마트 교통 AI

글로벌 오피스

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

(C)2026 INNORIX. All rights reserved.

  • 보안
  • 상태
  • 이용약관
  • 개인정보 처리방침
  • 쿠키 정책
  1. 개발자
  2. 구현 예제
  3. 웹 업로드, 다운로드

웹 업로드, 다운로드

INNORIX Transfer Builder를 사용해 웹 업로드 및 다운로드 파일 전송 흐름을 구성하고 애플리케이션용 설정을 생성합니다.

배포 및 관리
  • 한 곳으로 파일 보내기
  • 여러 곳으로 파일 보내기
  • 여러 곳에서 파일 수집
  • 시스템 간 파일 동기화
  • 웹 업로드, 다운로드

웹 업로드, 다운로드는 최종 사용자가 웹 브라우저에서 직접 파일을 올리고 받는 구성입니다. 에이전트를 설치하지 않은 외부 사용자도 대용량 파일을 주고받을 수 있고, 분할 전송, 재개, 진행률 표시는 웹 컨트롤이 처리합니다.

구성 요소는 세 가지입니다.

구성 역할
exabyter.js, exabyter.css 브라우저에 붙는 전송 컨트롤. 파일 선택 UI, 분할 전송, 진행률, 재개를 담당
upload.jsp / download.jsp 서버 수신, 송신 엔드포인트. 실제 저장 경로를 정하는 곳
WEB-INF/lib/InnorixJAVA.jar 엔드포인트가 사용하는 전송 라이브러리

⚠️ 배포 전에 보안 항목을 먼저 확인하세요. 서버 엔드포인트는 브라우저가 보낸 값을 그대로 받습니다. 특히 다운로드는 fileName 을 파일 경로에 붙이는 구조라 경로 검증이 없으면 저장소 밖의 파일이 노출될 수 있습니다. 바로 아래 보안 필수 항목을 먼저 읽으세요.

ℹ️ 코드 탭 안내 — 이 구성은 브라우저 스크립트 + 서버 엔드포인트로 이루어지므로 예제는 JavaScript(클라이언트)와 JSP(서버) 두 탭으로 제공합니다. 빌더가 생성하는 번들도 이 두 가지 파일로 구성됩니다.

시작하기#

빌더가 만들어 주는 것#

빌더에서 웹 업로드, 다운로드 탭의 옵션을 고르고 내려받으면 다음 구조의 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> 하나에 렌더링됩니다.

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
});
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
/* ============================================================================
 * Exabyter - upload receiving endpoint
 * The control slices the file and POSTs each slice; this page writes them into server storage.
 * ==========================================================================*/

// --- Authentication (required) ------------------------------------------
// This page has no authentication of its own. Check your session/token first.
// The preflight (OPTIONS) arrives without credentials, so only POST is checked.
String userId = (String) session.getAttribute("userId");
if (request.getMethod().equals("POST") && userId == null) {
    response.setStatus(401);
    return;
}

// The control sends a CORS preflight (OPTIONS) before POST. Handle uploads on POST only.
if (request.getMethod().equals("POST"))
{
    // --- Storage directory --------------------------------------------------
    // Default: the "data" folder next to this JSP. Use an absolute path in production.
    //   directory = "C:/exabyter/data";     // Windows
    //   directory = "/storage/exabyter";    // Unix
    String directory = InnorixUpload.getServletAbsolutePath(request);
    directory = directory.substring(0, directory.lastIndexOf("/") + 1) + "data";

    int maxPostSize = 2147482624;  // max request body size in bytes - match your proxy limits
    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);

    // --- Storage method (the builder's Storage method) ----------------------
    // One folder per transfer:
    //   uploader.setDirectory(directory + "/" + uploader.getParameter("_transferId"));
    // One folder per user:
    //   uploader.setDirectory(directory + "/" + userId);
    // One folder per user and per transfer:
    //   uploader.setDirectory(directory + "/" + userId + "/" + uploader.getParameter("_transferId"));

    /*
     * _action flags sent by the control:
     *   speedCheck          - transfer speed measurement
     *   getServerInfo       - server info probe
     *   getFileInfo         - file info probe
     *   attachFile          - slice upload in progress
     *   attachFileCompleted - file upload complete
     */
    String _action        = uploader.getParameter("_action");
    String _orig_filename = uploader.getParameter("_orig_filename");
    String _filesize      = uploader.getParameter("_filesize");
    String _transferId    = uploader.getParameter("_transferId");

    // Process the incoming slice.
    String _run_retval = uploader.run();

    uploader = null;
}

// --- CORS headers -----------------------------------------------------------
// List the allowed origins. Reflecting the request Origin lets any site call this endpoint.
java.util.List<String> allowedOrigins = java.util.Arrays.asList(
        "https://portal.example.com",     // <- replace with your service domain
        "http://localhost:8080");         // <- development only, remove in production
String origin = request.getHeader("Origin");
if (origin != null && allowedOrigins.contains(origin)) {
    response.setHeader("Access-Control-Allow-Origin", origin);
    response.setHeader("Access-Control-Allow-Credentials", "true");
}
response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, html5");
%>

다운로드#

다운로드는 제공할 파일 목록을 컨트롤에 넘기는 것이 핵심입니다. 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
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.innorix.transfer.InnorixTransfer" %>
<%@ page import="com.innorix.integrity.Integrity" %>
<%@ page import="java.io.*" %>
<%
/* ============================================================================
 * Exabyter - download sending endpoint
 * The control requests byte ranges in parallel; this page streams the requested range.
 * Integrity (MD5) checks and the start/complete signals arrive at the same endpoint.
 * ==========================================================================*/

out.clear();
out = pageContext.pushBody();

// --- Authentication (required) ----------------------------------------------
// This page has no authentication of its own. Check your session/token first.
if (session.getAttribute("userId") == null) {
    response.setStatus(401);
    return;
}

// --- Storage root -----------------------------------------------------------
// Use an absolute path in production.
//   filePath = "C:/exabyter/data/";   // Windows (use "/" as the separator)
//   filePath = "/storage/exabyter/";  // Unix
String saveDir = request.getSession().getServletContext()
        .getRealPath(request.getServletPath()).replace("\\", "/");
String filePath = saveDir.substring(0, saveDir.lastIndexOf("/") + 1) + "data/";

// Parameters the control adds automatically
String szStartOffset   = request.getParameter("_StartOffset");
String szEndOffset     = request.getParameter("_EndOffset");
String szPrintFileName = request.getParameter("fileName");
String szIntegrity     = request.getParameter("_Integrity");
String action          = request.getParameter("_Action");

// Start/complete signals - return with no body.
if (action != null && (action.equals("DownloadStart") || action.equals("DownloadComplete"))) {
    return;
}

// Decode the file name
String sysFileName = szPrintFileName;
if (szPrintFileName != null) {
    sysFileName = new String(szPrintFileName.getBytes("8859_1"), "UTF-8");
}

// --- Path validation (required) ---------------------------------------------
// fileName comes from the browser. Concatenating it directly lets "../" reach files
// outside the storage root, so always check that the canonical path stays inside it.
//
// Safer still: hand out a server-issued ID per file when you build the list
// (e.g. ./download.jsp?fileId=a1b2c3) and map ID -> real path here, never taking a path.
File root   = new File(filePath).getCanonicalFile();
File target = new File(root, sysFileName).getCanonicalFile();
if (!target.getPath().startsWith(root.getPath() + File.separator) || !target.isFile()) {
    response.setStatus(403);
    return;
}

// --- Integrity check: return the MD5 of the requested range -----------------
if (szIntegrity != null && szIntegrity.equalsIgnoreCase("true")) {
    long startOffset = szStartOffset != null ? Long.parseLong(szStartOffset) : 0;
    long endOffset   = szEndOffset   != null ? Long.parseLong(szEndOffset)   : 0;

    Integrity integrity = new Integrity();
    String _md5 = "";
    try {
        _md5 = integrity.getMD5FromFileSlice(target.getPath(), startOffset, endOffset);
    } catch (Exception e) { }

    response.setStatus(200);
    response.getWriter().write(_md5);
    response.getWriter().flush();
    response.getWriter().close();
    return;
}

// --- Stream the file (or range) ---------------------------------------------
// Only the validated target is used (never the raw user-supplied path).
response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
// This is the core part. Range math for _StartOffset/_EndOffset and the actual streaming
// are implemented in the bundle's download.jsp - use that file as-is.
%>

서버에서 꼭 손봐야 할 것#

위치 값 설명
upload.jsp directory 업로드 저장 루트. 기본값은 JSP 옆 data 폴더이므로 반드시 실제 경로로 바꾸세요.
upload.jsp maxPostSize 요청 본문 최대 크기(바이트). 앞단 프록시(nginx 등) 제한과 함께 맞춰야 합니다.
upload.jsp CORS 기본 코드는 요청 Origin 을 그대로 반사합니다. 운영에서는 허용 도메인으로 고정하세요.
download.jsp filePath 다운로드 원본 루트.
공통 인증, 경로 검증, 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"));

참고#

빌더 옵션 ↔ 생성 코드 매핑#

업로드 탭

빌더 항목 선택지 반영되는 곳
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 이 있는지 확인하세요.
이전시스템 간 파일 동기화

이 페이지에서

  • 시작하기
  • 빌더가 만들어 주는 것
  • 보안 필수 항목
  • 구현
  • 업로드
  • 다운로드
  • 서버에서 꼭 손봐야 할 것
  • 참고
  • 빌더 옵션 ↔ 생성 코드 매핑
  • 자주 겪는 오류