सामग्री पर जाएँ
HexaTransfer
ब्लॉग पर वापस
तकनीकी गहन विश्लेषण

JavaScript में चंक्ड फ़ाइल अपलोड लागू करें

JavaScript में रिज़्यूमेबल चंक्ड फ़ाइल अपलोड लागू करें। बड़ी फ़ाइलों को संभालें, प्रगति ट्रैक करें और नेटवर्क रुकावटों से रिकवर करें।

JavaScript में चंक्ड फाइल अपलोड एक बड़ी फाइल को निश्चित आकार के टुकड़ों में बाँटता है (आमतौर पर 5-10 MB), प्रत्येक को एक अलग HTTP अनुरोध के रूप में अपलोड करता है, और उन्हें सर्वर पर फिर से जोड़ता है। यह पैटर्न तीन वास्तविक समस्याओं को हल करता है: ब्राउज़र और प्रॉक्सी 2 GB से अधिक के अनुरोधों को बंद कर देते हैं, मोबाइल नेटवर्क अपलोड के बीच में कनेक्शन छोड़ देते हैं, और उपयोगकर्ता प्रगति फ़ीडबैक चाहते हैं। एक काम करने वाला कार्यान्वयन चंक काटने के लिए File.slice(), प्रति चंक AbortSignal के साथ fetch, S3 मल्टीपार्ट या कस्टम मर्जर के माध्यम से सर्वर-साइड असेंबली, और एक स्थानीय इंडेक्स IndexedDB में उपयोग करता है ताकि टैब रीलोड के बाद भी रिज़्यूम काम करे।

चंक्स सिंगल-शॉट अपलोड से बेहतर क्यों हैं

एक अनुरोध के रूप में अपलोड किया गया 4 GB फाइल पूर्वानुमानित कारणों से विफल होता है: Nginx का डिफ़ॉल्ट client_max_body_size 1 MB है, Cloudflare फ्री-टियर अपलोड को प्रति अनुरोध 100 MB तक सीमित करता है, AWS API Gateway 10 MB पर हार्ड-स्टॉप करता है, और मोबाइल Safari 4 GB ArrayBuffer मेमोरी में रखने वाले टैब बंद कर देता है। चंक्ड अपलोड हर एक सीमा से बचते हैं। आपको प्रगति बार भी मिलते हैं जो वास्तव में आगे बढ़ते हैं, रिट्राई जो शून्य से शुरू नहीं होते, और पॉज़ और रिज़्यूम की क्षमता। ट्रेडऑफ़ अधिक सर्वर-साइड स्टेट और अधिक राउंड ट्रिप है — लगभग प्रति 5 MB एक HTTP अनुरोध, जो 10 GB फाइल पर 2,000 अनुरोध होते हैं।

चंक साइज़ चुनना

चंक साइज़ थ्रूपुट बनाम लचीलेपन का ट्रेडऑफ़ है। बहुत छोटा (1 MB से कम) और आप TLS हैंडशेक पर डेटा से अधिक समय बिताते हैं। बहुत बड़ा (100 MB से अधिक) और एक गिरा हुआ कनेक्शन मिनटों का अपलोड बर्बाद करता है। अधिकांश नेटवर्क के लिए 5-10 MB का स्वीट स्पॉट है, जो S3 के 5 MB मल्टीपार्ट न्यूनतम से मेल खाता है और सामान्य TCP विंडो साइज़ के साथ अच्छी तरह से संरेखित होता है।

पहले उपयोगकर्ता का नेटवर्क मापें:

const downlink = navigator.connection?.downlink ?? 10;
const chunkSize = downlink > 20 ? 10 * 1024 * 1024 : 5 * 1024 * 1024;

100 Mbit कनेक्शन पर, 10 MB चंक लगभग एक सेकंड में पूरे होते हैं। 4G पर, 5 MB चंक सुरंग में बेहतर रिकवरी देते हैं।

फाइल स्लाइसिंग और हैशिंग

File.slice() एक Blob लौटाता है जो बिना कॉपी किए अंतर्निहित डिस्क बाइट्स को संदर्भित करता है, इसलिए 20 GB फाइल स्लाइस करना कुछ भी खर्च नहीं करता:

function* sliceFile(file, chunkSize) {
  for (let offset = 0; offset < file.size; offset += chunkSize) {
    yield {
      index: Math.floor(offset / chunkSize),
      blob: file.slice(offset, offset + chunkSize),
      start: offset,
      end: Math.min(offset + chunkSize, file.size)
    };
  }
}

सर्वर को अखंडता सत्यापित करने देने के लिए अपलोड करने से पहले प्रत्येक चंक का SHA-256 हैश कम्प्यूट करें:

const buffer = await chunk.blob.arrayBuffer();
const digest = await crypto.subtle.digest('SHA-256', buffer);
const hash = Array.from(new Uint8Array(digest))
  .map(b => b.toString(16).padStart(2, '0')).join('');

10 GB डेटा के लिए, हैशिंग आधुनिक लैपटॉप पर लगभग 20 सेकंड जोड़ता है — अस्थिर सेल्युलर अपलिंक पर साइलेंट करप्शन पकड़ने के लिए इसका मूल्य है।

नियंत्रित समवर्तिता के साथ अपलोड करना

अनुक्रमिक अपलोड बैंडविड्थ बर्बाद करते हैं; असीमित समानांतरता ब्राउज़र क्रैश करती है। 3-4 इन-फ्लाइट चंक की समवर्तिता सीमा दोनों को संतुलित करती है:

async function uploadAll(file, sessionId) {
  const queue = [...sliceFile(file, 5 * 1024 * 1024)];
  const workers = Array.from({ length: 4 }, async () => {
    while (queue.length) {
      const chunk = queue.shift();
      await uploadChunk(chunk, sessionId);
      emitProgress(chunk.index);
    }
  });
  await Promise.all(workers);
}

प्रत्येक uploadChunk कॉल एक PUT /upload/:sessionId/:index है जिसमें blob बॉडी के रूप में और हैश एक हेडर में है। प्रति चंक AbortController का उपयोग करें ताकि आप पूरे बैच को मारे बिना व्यक्तिगत अनुरोध रद्द कर सकें।

सर्वर को नुकसान पहुँचाए बिना रिट्राई करना

नेटवर्क त्रुटियों को टाइट रिट्राई लूप नहीं, एक्सपोनेंशियल बैकऑफ़ चाहिए। एक उचित नीति: 3 प्रयास, बेस देरी 500 ms, 50% तक जिटर:

async function uploadChunk(chunk, sessionId, attempt = 0) {
  try {
    const res = await fetch(`/upload/${sessionId}/${chunk.index}`, {
      method: 'PUT', body: chunk.blob, headers: { 'X-Hash': chunk.hash }
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
  } catch (e) {
    if (attempt >= 3) throw e;
    const delay = 500 * 2 ** attempt + Math.random() * 250;
    await new Promise(r => setTimeout(r, delay));
    return uploadChunk(chunk, sessionId, attempt + 1);
  }
}

5xx प्रतिक्रियाओं को रिट्राईयोग्य मानें, 4xx को घातक (408 और 429 को छोड़कर)। 429 पर, अपने स्थानीय बैकऑफ़ के बजाय Retry-After हेडर का सम्मान करें।

टैब रीलोड के बाद रिज़्यूम करना

हर सफल चंक के बाद अपलोड स्टेट को IndexedDB में सहेजें:

await db.put('uploads', {
  sessionId, fileName: file.name, fileSize: file.size,
  completedChunks: [...completedSet], updatedAt: Date.now()
}, sessionId);

जब उपयोगकर्ता उसी फाइल पिकर के साथ पेज फिर से खोलता है, तो फाइल के size, lastModified, और नाम की तुलना संग्रहीत सत्रों से करें। मिलान होने पर, सर्वर से पूछें कि उसे कौन से चंक पहले ही मिले (GET /upload/:sessionId/status एक बिटमैप लौटाना काम करता है), फिर केवल गायब चंक अपलोड करें। tus प्रोटोकॉल Upload-Offset हेडर के साथ इस पैटर्न को औपचारिक बनाता है, और यदि आप अपना खुद का नहीं बनाना चाहते तो tus-js-client लाइब्रेरी एक ठोस कार्यान्वयन प्रदान करती है।

सर्वर पर चंक असेंबल करना

दो गंभीर विकल्प: S3 मल्टीपार्ट अपलोड, जहाँ प्रत्येक चंक एक PartNumber बनता है और एक अंतिम CompleteMultipartUpload उन्हें जोड़ता है, या एक कस्टम असेंबलर जो प्रत्येक चंक को टेम्प फाइल में लिखता है और अंत में उन्हें जोड़ता है। S3 मल्टीपार्ट स्केल पर सस्ता है क्योंकि आप असेंबली के दौरान कभी एग्रेस नहीं देते और R2 ज़ीरो-एग्रेस रीड देता है। कस्टम दृष्टिकोण डीबग करना आसान है और असेंबली के दौरान स्ट्रीम-एन्क्रिप्ट करने देता है।

S3-स्टाइल के लिए:

const upload = await s3.createMultipartUpload({ Bucket, Key });
// per chunk: s3.uploadPart({ UploadId, PartNumber, Body })
await s3.completeMultipartUpload({ UploadId, MultipartUpload: { Parts } });

10,000-पार्ट सीमा से सावधान रहें — 50 GB से अधिक फाइलों के लिए इसके अंतर्गत रहने के लिए 5 MB+ चंक चाहिए।

उपयोगकर्ता वास्तव में विश्वास करें ऐसी प्रगति ट्रैक करना

प्रगति बार जो इधर-उधर कूदते हैं टूटे हुए लगते हैं। प्रगति को पूरे बाइट्स में कम्प्यूट करें, न कि पूर्ण चंक में, और जिटर छुपाने के लिए 2 सेकंड के मूविंग एवरेज से स्मूद करें। बाइट्स गिनने के लिए ReadableStream और Transform के साथ fetch का उपयोग करें, क्योंकि XMLHttpRequest.upload.onprogress HTTP/3 पर हमेशा विश्वसनीय रूप से फायर नहीं होता। बचे हुए बाइट्स को ट्रेलिंग थ्रूपुट से विभाजित करके ETA दिखाएं, लेकिन कम से कम 5 सेकंड तक क्लैंप करें ताकि कुख्यात "2 सेकंड शेष... 10 मिनट के लिए" अनुभव से बचा जा सके।

सामान्य गलतियों से बचना

तीन गलतियाँ प्रोडक्शन में चंक्ड अपलोड को खत्म करती हैं: प्रति चंक Content-Length सेट करना भूल जाना (कुछ एज प्रॉक्सी तोड़ता है), अलग-अलग फाइलों में एक ही सत्र ID का पुन: उपयोग (असेंबली भ्रष्ट करता है), और सत्र को वर्जन किए बिना उपयोगकर्ता को अपलोड के बीच फाइल बदलने देना। सत्र फिंगरप्रिंट करने के लिए हमेशा फाइल के पहले 1 MB साथ उसके आकार और lastModified को हैश करें। और केवल lastModified पर भरोसा न करें — macOS Finder इसे मेटाडेटा परिवर्तनों पर अपडेट करता है।

HexaTransfer अपने 10 GB अपलोड के लिए हुड के नीचे इस तरह की चंक्ड + रिज़्यूमेबल पाइपलाइन का उपयोग करता है, जिसमें PUT से पहले प्रत्येक चंक में क्लाइंट-साइड AES-256-GCM जोड़ा जाता है। hexatransfer.com पर मुफ्त में आज़माएं — कोई खाता नहीं, 10 GB अधिकतम।

सब कुछ एक साथ

एक प्रोडक्शन-ग्रेड चंक्ड अपलोडर लगभग 300 लाइन JavaScript है: File.slice से स्लाइस, SubtleCrypto से हैश, एक्सपोनेंशियल बैकऑफ़ के साथ 3-4 चंक समानांतर में अपलोड, IndexedDB में सत्र स्टेट सहेजना, और सर्वर को S3 मल्टीपार्ट या कस्टम मर्जर के माध्यम से पार्ट जोड़ने देना। इससे पहले कि आप इस पर भरोसा करें, इसे एयरप्लेन मोड टॉगल, टैब रीलोड, और 4G पर 15 GB फाइल के विरुद्ध परखें। एक बार जब यह काम करे, एन्क्रिप्शन, प्रगति, और रिज़्यूमेबिलिटी जोड़ना उसी ढाँचे पर अतिरिक्त परतें मात्र हैं।

एंड-टू-एंड एन्क्रिप्शन के साथ बड़ी फ़ाइलें सुरक्षित रूप से भेजें

एंड-टू-एंड एन्क्रिप्शन के साथ 10 GB तक की फ़ाइलें मुफ़्त में ट्रांसफ़र करें। अकाउंट की आवश्यकता नहीं। अपलोड से पहले आपकी फ़ाइलें ब्राउज़र में एन्क्रिप्ट की जाती हैं — कोई और उन्हें पढ़ नहीं सकता।

फ़ाइल भेजें