fetch
metodi bilan faylni yuklash ancha oson.
Ulanish uzilganidan keyin yuklashni qanday davom ettirish mumkin? Buning uchun o’rnatilgan opsiya yo’q, lekin buni amalga oshirish uchun kerakli qismlar bizda bor.
Qayta boshlanuvchi yuklashlar yuklash jarayonini ko’rsatish bilan birga kelishi kerak, chunki biz katta fayllarni kutamiz (agar davom ettirish kerak bo’lsa). fetch
yuklash jarayonini kuzatishga imkon bermagani uchun, biz XMLHttpRequest dan foydalanamiz.
Unchalik foydali bo’lmagan progress eventi
Yuklashni davom ettirish uchun ulanish uzilgunga qadar qancha yuklangani ma’lum bo’lishi kerak.
Yuklash jarayonini kuzatish uchun xhr.upload.onprogress
mavjud.
Afsuski, bu bizga yuklashni davom ettirishda yordam bermaydi, chunki u ma’lumotlar yuborilganda ishga tushadi, lekin ular server tomonidan qabul qilindimi? Brauzer buni bilmaydi.
Ehtimol, u mahalliy tarmoq proksi tomonidan buferga olingan, yoki masofaviy server jarayoni shunchaki o’lib qoldi va ularni qayta ishlay olmadi, yoki u o’rtada yo’qoldi va qabul qiluvchiga yetmadi.
Shuning uchun bu event faqat chiroyli progress bar ko’rsatish uchun foydali.
Yuklashni davom ettirish uchun server tomonidan qabul qilingan baytlarning aniq sonini bilishimiz kerak. Va buni faqat server aytishi mumkin, shuning uchun biz qo’shimcha so’rov qilamiz.
Algoritm
-
Birinchidan, yuklashimiz kerak bo’lgan faylni noyob identifikatsiya qilish uchun fayl id’sini yarating:
let fileId = file.name + '-' + file.size + '-' + file.lastModified;
Bu yuklashni davom ettirish uchun kerak, serverga nimani davom ettirayotganimizni aytish uchun.
Agar nom yoki hajm yoki oxirgi o’zgarish sanasi o’zgarsa, boshqa
fileId
bo’ladi. -
Serverga so’rov yuboring, u allaqachon qancha baytga ega ekanligini so’rang, masalan:
let response = await fetch('status', { headers: { 'X-File-Id': fileId } }); // Server shuncha baytga ega let startByte = +await response.text();
Bu server fayl yuklashlarini
X-File-Id
header bo’yicha kuzatadi deb taxmin qiladi. Server tomonida amalga oshirilishi kerak.Agar fayl hali serverda mavjud bo’lmasa, server javobi
0
bo’lishi kerak -
Keyin, faylni
startByte
dan yuborish uchunBlob
metodiningslice
dan foydalanishimiz mumkin:xhr.open("POST", "upload", true); // Fayl id'si, server qaysi faylni yuklayotganimizni bilishi uchun xhr.setRequestHeader('X-File-Id', fileId); // Davom ettirayotgan bayt, server davom ettirayotganimizni bilishi uchun xhr.setRequestHeader('X-Start-Byte', startByte); xhr.upload.onprogress = (e) => { console.log(`${startByte + e.total} dan ${startByte + e.loaded} yuklandi`); }; // file input.files[0] yoki boshqa manbadan bo'lishi mumkin xhr.send(file.slice(startByte));
Bu yerda biz serverga fayl id’sini
X-File-Id
sifatida yuboramiz, shuning uchun u qaysi faylni yuklayotganimizni biladi, va boshlang’ich baytniX-Start-Byte
sifatida yuboramiz, shuning uchun u biz uni dastlab emas, balki davom ettirayotganimizni biladi.Server o’z yozuvlarini tekshirishi va agar o’sha faylning yuklashi bo’lgan bo’lsa va joriy yuklangan hajm aynan
X-Start-Byte
ga teng bo’lsa, ma’lumotlarni unga qo’shishi kerak.
Mana Node.js da yozilgan mijoz va server kodi bilan demo.
Bu saytda faqat qisman ishlaydi, chunki Node.js Nginx nomli boshqa server orqasida joylashgan bo’lib, u yuklashlarni buferga oladi va ularni to’liq tugagandan keyin Node.js ga uzatadi.
Lekin siz uni yuklab olishingiz va to’liq namoyish uchun mahalliy ravishda ishga tushirishingiz mumkin:
let http = require("http");
let static = require("node-static");
let fileServer = new static.Server(".");
let path = require("path");
let fs = require("fs");
let debug = require("debug")("example:resume-upload");
let uploads = Object.create(null);
function onUpload(req, res) {
let fileId = req.headers["x-file-id"];
let startByte = +req.headers["x-start-byte"];
if (!fileId) {
res.writeHead(400, "No file id");
res.end();
}
// biz "hech bir joyda" fayllarni joylashtiramiz
let filePath = "/dev/null";
// Buning o'rniga haqiqiy yo'lni ishlatishi mumkin, masalan.
// let filePath = path.join('/tmp', fileId);
debug("onUpload fileId: ", fileId);
// yangi yuklashni ishga tushiring
if (!uploads[fileId]) uploads[fileId] = {};
let upload = uploads[fileId];
debug("bytesReceived:" + upload.bytesReceived + " startByte:" + startByte);
let fileStream;
// agar startByte 0 bo'lsa yoki o'rnatilmagan bo'lsa, yangi fayl yarating, aks holda hajmini tekshiring va mavjud faylga qo'shing
if (!startByte) {
upload.bytesReceived = 0;
fileStream = fs.createWriteStream(filePath, {
flags: "w",
});
debug("New file created: " + filePath);
} else {
// Ishonch hosil qilish uchun diskdagi fayl hajmini ham tekshirishimiz mumkin
if (upload.bytesReceived != startByte) {
res.writeHead(400, "Wrong start byte");
res.end(upload.bytesReceived);
return;
}
// mavjud faylga qo'shing
fileStream = fs.createWriteStream(filePath, {
flags: "a",
});
debug("File reopened: " + filePath);
}
req.on("data", function (data) {
debug("bitlar qabul qilindi", upload.bytesReceived);
upload.bytesReceived += data.length;
});
// so'rov tanasini faylga yuboring
req.pipe(fileStream);
// so'rov tugaganda va uning barcha ma'lumotlari yoziladi
fileStream.on("close", function () {
if (upload.bytesReceived == req.headers["x-file-size"]) {
debug("Yuklash yakunlandi");
delete uploads[fileId];
// bu yerda yuklangan fayl bilan boshqa biror narsa qilish mumkin
res.end("Success " + upload.bytesReceived);
} else {
// ulanish yo'qolsa, biz tugallanmagan faylni atrofida qoldiramiz
debug("Fayl tugallanmagan, toʻxtatilgan " + upload.bytesReceived);
res.end();
}
});
// kiritish-chiqarish xatosi bo'lsa - so'rovni yakunlang
fileStream.on("error", function (err) {
debug("fileStream xatoligi");
res.writeHead(500, "Fayl xatoligi");
res.end();
});
}
function onStatus(req, res) {
let fileId = req.headers["x-file-id"];
let upload = uploads[fileId];
debug("onStatus fileId:", fileId, " upload:", upload);
if (!upload) {
res.end("0");
} else {
res.end(String(upload.bytesReceived));
}
}
function accept(req, res) {
if (req.url == "/status") {
onStatus(req, res);
} else if (req.url == "/upload" && req.method == "POST") {
onUpload(req, res);
} else {
fileServer.serve(req, res);
}
}
// -----------------------------------
if (!module.parent) {
http.createServer(accept).listen(8080);
console.log("Server 8080 portida ishlamoqda");
} else {
exports.accept = accept;
}
class Uploader {
constructor({ file, onProgress }) {
this.file = file;
this.onProgress = onProgress;
// faylni noyob identifikatsiya qiladigan fileId yaratish
// agar foydalanuvchi sessiya identifikatori bo'lsa, uni ham qo'shib yanada noyob qilish mumkin
this.fileId = file.name + "-" + file.size + "-" + file.lastModified;
}
async getUploadedBytes() {
let response = await fetch("status", {
headers: {
"X-File-Id": this.fileId,
},
});
if (response.status != 200) {
throw new Error(
"Yuklangan baytlarni olish mumkin emas: " + response.statusText
);
}
let text = await response.text();
return +text;
}
async upload() {
this.startByte = await this.getUploadedBytes();
let xhr = (this.xhr = new XMLHttpRequest());
xhr.open("POST", "upload", true);
// fayl id'sini yuborish, server qaysi faylni davom ettirishni bilishi uchun
xhr.setRequestHeader("X-File-Id", this.fileId);
// davom ettirilayotgan baytni yuborish, server davom ettirilayotganini bilishi uchun
xhr.setRequestHeader("X-Start-Byte", this.startByte);
xhr.upload.onprogress = (e) => {
this.onProgress(this.startByte + e.loaded, this.startByte + e.total);
};
console.log("faylni yuborish, boshlash nuqtasi:", this.startByte);
xhr.send(this.file.slice(this.startByte));
// qaytarish:
// yuklash muvaffaqiyatli bo'lsa true,
// to'xtatilgan bo'lsa false
// xatolik holatida exception tashlash
return await new Promise((resolve, reject) => {
xhr.onload = xhr.onerror = () => {
console.log(
"yuklash tugashi holati:" + xhr.status + " matn:" + xhr.statusText
);
if (xhr.status == 200) {
resolve(true);
} else {
reject(new Error("Yuklash muvaffaqiyatsiz: " + xhr.statusText));
}
};
// onabort faqat xhr.abort() chaqirilganda ishga tushadi
xhr.onabort = () => resolve(false);
});
}
stop() {
if (this.xhr) {
this.xhr.abort();
}
}
}
<!DOCTYPE html>
<script src="uploader.js"></script>
<form
name="upload"
method="POST"
enctype="multipart/form-data"
action="/upload"
>
<input type="file" name="myfile" />
<input
type="submit"
name="submit"
value="Yuklash (Qayta avtomatik ishga tushadi)"
/>
</form>
<button onclick="uploader.stop()">Yuklashni to'xtatish</button>
<div id="log">Progress indication</div>
<script>
function log(html) {
document.getElementById("log").innerHTML = html;
console.log(html);
}
function onProgress(loaded, total) {
log("progress " + loaded + " / " + total);
}
let uploader;
document.forms.upload.onsubmit = async function (e) {
e.preventDefault();
let file = this.elements.myfile.files[0];
if (!file) return;
uploader = new Uploader({ file, onProgress });
try {
let uploaded = await uploader.upload();
if (uploaded) {
log("muvaffaqiyatl");
} else {
log("to'xtatildi");
}
} catch (err) {
console.error(err);
log("error");
}
};
</script>
Ko’rib turganingizdek, zamonaviy tarmoq metodlari o’z imkoniyatlari bo’yicha fayl menejerlariga yaqin – header’lar ustidan nazorat, progress ko’rsatkichi, fayl qismlarini yuborish va hokazo.
Biz qayta boshlanuvchi yuklash va yana ko’p narsalarni amalga oshirishimiz mumkin.
Izohlar
<code>
yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>
yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)