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('上传完成'));