tus 프로토콜
tus는 재개 가능한 파일 업로드를 위한 개방형 프로토콜입니다. 네트워크 중단에도 견딜 수 있는 안정적인 업로드에 사용하세요.
왜 tus인가?
- 재개 가능: 네트워크 장애 후 업로드 계속
- 효율적: 처음부터가 아닌 중단된 지점부터 재개
- 표준화됨: 많은 서버 구현이 있는 잘 정의된 프로토콜
- 청크 방식: 큰 파일을 자동으로 처리
설치
npm install @samithahansaka/dropup tus-js-client
tus-js-client는 피어 종속성입니다.
기본 사용법
import { useDropup } from '@samithahansaka/dropup';
import { createTusUploader } from '@samithahansaka/dropup/tus';
function TusUploader() {
const { files, actions, state, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://tusd.tusdemo.net/files/',
}),
onUploadComplete: (file) => {
console.log('Upload complete:', file.uploadedUrl);
},
});
return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>Drop files for resumable upload</p>
</div>
{files.map(file => (
<div key={file.id} style={styles.fileItem}>
<span>{file.name}</span>
<span>{file.progress}%</span>
<span>{file.status}</span>
</div>
))}
<button onClick={() => actions.upload()}>
Upload
</button>
</div>
);
}
구성 옵션
createTusUploader({
// 필수
endpoint: 'https://your-tus-server.com/files/',
// 청크 크기 (기본값: Infinity = 청크 없음)
chunkSize: 5 * 1024 * 1024, // 5MB 청크
// 재시도 구성
retryDelays: [0, 1000, 3000, 5000], // 0초, 1초, 3초, 5초 후 재시도
// 모든 요청에 대한 헤더
headers: {
'Authorization': 'Bearer token',
},
// 서버로 전송되는 메타데이터
metadata: {
filename: 'file.name',
filetype: 'file.type',
// 사용자 정의 메타데이터 추가
userId: 'user123',
},
// localStorage를 통한 재개 기능 활성화
storeFingerprintForResuming: true,
// 성공적인 업로드 후 fingerprint 제거
removeFingerprintOnSuccess: true,
// 병렬 업로드
parallelUploads: 3,
});
이전 업로드 재개
tus는 중단된 업로드를 재개할 수 있습니다:
const { files, actions } = useDropup({
upload: createTusUploader({
endpoint: 'https://your-tus-server.com/files/',
// 재개를 위한 업로드 URL 저장
storeFingerprintForResuming: true,
// 선택 사항: 사용자 정의 스토리지 키
urlStorageKey: 'my-app-tus-uploads',
onShouldRetry: (error, retryAttempt) => {
// 사용자 정의 재시도 로직
if (error.message.includes('network')) {
return retryAttempt < 5;
}
return false;
},
}),
});
// 파일은 다시 추가될 때 자동으로 재개될 수 있거나
// retry로 수동으로 재개:
actions.retry(['file-id']);
진행 상황 추적
function TusWithProgress() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://your-tus-server.com/files/',
}),
onUploadProgress: (file, progress) => {
console.log(`${file.name}: ${progress}%`);
},
onUploadStart: (file) => {
console.log(`Starting upload: ${file.name}`);
},
onUploadComplete: (file) => {
console.log(`Complete: ${file.uploadedUrl}`);
},
});
return (
<div>
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Drop files here</p>
</div>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
{file.status === 'uploading' && (
<div style={styles.progressBar}>
<div
style={{
...styles.progress,
width: `${file.progress}%`,
}}
/>
</div>
)}
<span>{file.status}</span>
</div>
))}
</div>
);
}
일시 중지 및 재개
function PausableUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://your-tus-server.com/files/',
storeFingerprintForResuming: true,
}),
});
return (
<div>
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Drop files here</p>
</div>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
<span>{file.progress}%</span>
{file.status === 'uploading' && (
<button onClick={() => actions.cancel(file.id)}>
Pause
</button>
)}
{file.status === 'paused' && (
<button onClick={() => actions.retry([file.id])}>
Resume
</button>
)}
{file.status === 'error' && (
<button onClick={() => actions.retry([file.id])}>
Retry
</button>
)}
</div>
))}
<button onClick={() => actions.upload()}>
Start All
</button>
</div>
);
}
서버 설정
tusd (공식 서버)
# tusd 설치
go install github.com/tus/tusd/cmd/tusd@latest
# 기본 옵션으로 실행
tusd -upload-dir=/uploads
# S3 스토리지로 실행
tusd -s3-bucket=my-bucket -s3-endpoint=https://s3.amazonaws.com
Node.js (tus-node-server)
const { Server } = require('@tus/server');
const { FileStore } = require('@tus/file-store');
const server = new Server({
path: '/files',
datastore: new FileStore({ directory: './uploads' }),
});
// Express와 함께
app.all('/files/*', server.handle.bind(server));
사용자 정의 메타데이터 처리
// 서버 측: 메타데이터 액세스
const server = new Server({
path: '/files',
datastore: new FileStore({ directory: './uploads' }),
onUploadCreate: async (req, res, upload) => {
// 클라이언트에서 보낸 메타데이터 액세스
console.log(upload.metadata);
// { filename: 'photo.jpg', filetype: 'image/jpeg', userId: 'user123' }
},
onUploadFinish: async (req, res, upload) => {
// 완료된 업로드 처리
console.log('Upload complete:', upload.id);
},
});
tus 확장
Dropup의 tus 통합은 다음 tus 확장을 지원합니다:
| 확장 | 설명 |
|---|---|
| Creation | 새 업로드 생성 |
| Termination | 업로드 취소 |
| Checksum | 무결성 확인 |
| Concatenation | 병렬 업로드 |
| Expiration | 자동 정리 |
오류 처리
const { files, actions } = useDropup({
upload: createTusUploader({
endpoint: 'https://your-tus-server.com/files/',
retryDelays: [0, 1000, 3000, 5000, 10000],
}),
onUploadError: (file, error) => {
if (error.code === 'TUS_OFFLINE') {
console.log('Network offline, will resume when connected');
} else if (error.code === 'TUS_EXPIRED') {
console.log('Upload expired, starting fresh');
actions.retry([file.id]);
} else {
console.error('Upload failed:', error.message);
}
},
});
대용량 파일 업로드
tus는 대용량 파일에 이상적입니다:
function LargeFileUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
maxSize: 10 * 1024 * 1024 * 1024, // 10GB 제한
upload: createTusUploader({
endpoint: 'https://your-tus-server.com/files/',
chunkSize: 50 * 1024 * 1024, // 50MB 청크
retryDelays: [0, 3000, 5000, 10000, 20000],
storeFingerprintForResuming: true,
}),
onUploadProgress: (file, progress) => {
// 대용량 파일에 대한 상세 진행 상황 표시
const uploadedMB = (file.size * progress / 100 / 1024 / 1024).toFixed(0);
const totalMB = (file.size / 1024 / 1024).toFixed(0);
console.log(`${uploadedMB}MB / ${totalMB}MB`);
},
});
return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Upload large files (up to 10GB)</p>
</div>
);
}
비교: tus vs 청크 업로드
| 기능 | tus | 사용자 정의 청크 |
|---|---|---|
| 재개 기능 | 내장됨 | 수동 |
| 서버 지원 | 많은 옵션 | 사용자 정의 |
| 프로토콜 오버헤드 | 높음 | 낮음 |
| 구성 | 표준화됨 | 유연함 |
| 최적 용도 | 대용량 파일, 불안정한 네트워크 | 간단한 업로드 |