const needle = require('needle');
const fs = require('fs');
const path = require('path');
const [, scriptName, filename, uploadURL] = process.argv;
if (!filename || !uploadURL) {
console.error(`사용법: node ${path.basename(scriptName)} filename upload_url`);
process.exit(-1);
}
async function uploadFile(file, url) {
// rangeEnd는 파일의 마지막 바이트 인덱스, 즉 파일의 총 바이트 수입니다
const rangeEnd = (await fs.promises.stat(file)).size;
let options = {
headers: {
'Content-Range': `bytes */${rangeEnd}`,
},
};
const response = await needle('put', url, null, options);
switch (response.statusCode) {
case 200:
case 201:
console.log('업로드 완료');
return;
case 308:
return resumeUpload(response, file, url);
default:
console.log('예상치 못한 응답 코드 수신: ', response.statusCode);
return;
}
}
async function resumeUpload(response, file, url) {
console.log('업로드 미완료, 재개 중');
if (response.headers.range) {
let resumeOffset = Number(response.headers.range.split('-')[1]) + 1;
let options = {
headers: {
'Content-Range': `bytes ${resumeOffset}-${rangeEnd-1}/${rangeEnd}`,
'Content-Length': `${rangeEnd-resumeOffset}`,
},
};
let readStream = fs.createReadStream(file, {start: resumeOffset});
return needle('put', url, readStream, options);
} else {
console.log('업로드 시작 중');
let options = {
headers: {
'Content-Type': 'text/plain'
}
};
let readStream = fs.createReadStream(file);
return needle('put', url, readStream, options);
}
}
// 재개 가능한 세션 URL 요청
async function requestResumableSession(url) {
const options = {
headers: {
'Content-Type': 'text/plain',
'Content-Length': '0',
'x-goog-resumable': 'start',
},
};
const res = await needle('post', url, null, options);
if (res.statusCode === 201) {
const resumableSessionURL = res.headers['location'];
console.log('업로드 시작 위치: ', resumableSessionURL);
await uploadFile(filename, resumableSessionURL);
} else {
console.log('재개 가능한 세션 URI 생성 실패');
}
}
requestResumableSession(uploadURL).then(result => console.log('업로드 완료'));