رفع فيديو بحجم اثنين جيجابايت عبر شبكة WiFi في مقهى هو بمثابة تمرين في التفاؤل. تراقب شريط التقدم وهو يزحف نحو السبعين بالمئة، ثم يضطرب الاتصال. تفشل عملية الرفع. وعندما تحاول مرة أخرى، يتوقع الخادم ملفاً جديداً تماماً، فتبدأ من الصفر. بالنسبة لأي شخص يبني تطبيقاً يتعامل مع الفيديوهات التي ينشئها المستخدمون، فإن هذه ليست حالة استثنائية، بل هي القاعدة. تم بناء بروتوكول tus خصيصاً للقضاء على هذه المشكلة؛ فهو معيار مفتوح يعمل عبر HTTP العادي ويمنح عمليات الرفع "ذاكرة". فعندما يستعيد الاتصال، تستأنف عملية نقل الملف من النقطة التي توقفت عندها تماماً.
لماذا تفشل عمليات الرفع القياسية
تعامل عمليات رفع الملفات القياسية من نوع multipart الحمولة بأكملها كمعاملة واحدة. يفتح المتصفح أو التطبيق اتصال TCP، ويبث البايتات، ويتوقع استجابة 200 OK في النهاية. إذا انقطع الاتصال لأن المستخدم انتقل من LTE إلى WiFi، أو لأن الكمبيوتر المحمول دخل في وضع السكون، فلن يجد الخادم طريقة لمعرفة أي البايتات وصلت بأمان. تقوم معظم أطر العمل ببساطة بالتخلص من البيانات الجزئية، ويتبقى للمستخدم نموذج فاشل ومزاج سيئ. وتضاعف ملفات الفيديو من هذه المعاناة لأنها كبيرة الحجم، وغالباً ما تُرفع من أجهزة محمولة عبر شبكات غير مستقرة، وكثيراً ما تكون مشفرة بتنسيقات لا يمكن معالجتها إلا بعد اكتمال الملف بالكامل.
كيف يغير tus قواعد اللعبة
يعيد tus التفكير في عملية الرفع باعتبارها محادثة ذات حالة (stateful conversation) بدلاً من عملية تسليم لمرة واحدة. بدلاً من إرسال ملف في دفق مستمر من البايتات، يقوم العميل بتقسيم الحمولة إلى أجزاء (chunks). والأهم من ذلك، يتذكر الخادم الإزاحة (offset)، وهي العدد الدقيق للبايتات التي استلمها حتى الآن. هذا الاستمرار في الحالة هو ما يجعل الاستئناف ممكناً. البروتوكول بسيط عن قصد؛ فهو لا يتطلب WebSockets أو gRPC أو SDKs مملوكة، بل يستخدم طرق HTTP التي تعرفها بالفعل.
الطلبات الثلاثة التي تدعم عملية الاستئناف
تتبع كل عملية رفع عبر tus "رقصة" متوقعة.
أولاً، يرسل العميل طلب POST إلى نقطة نهاية (endpoint) معروفة لـ tus. تصف الرؤوس (headers) الملف؛ حيث يحدد رأس Upload-Length الحجم النهائي بالبايتات، وتنتقل البيانات الوصفية الاختيارية مثل اسم الملف أو نوع المحتوى في رأس Upload-Metadata كقائمة مفصولة بفاصلة من أزواج المفتاح والقيمة المشفرة بتنسيق base64. ينشئ الخادم مورداً فارغاً للرفع ويستجيب بحالة 201 Created بالإضافة إلى رأس Location يشير إلى URL فريد. هذا الـ URL هو عنوان عملية الرفع لبقية حياتها.
بعد ذلك يأتي طلب PATCH. يرسل العميل أجزاءً من البيانات الثنائية الخام إلى ذلك الـ URL الفريد. يرافق الحمولة رأسان حاسمان: يجب أن يكون Content-Type هو application/offset+octet-stream ، ويجب أن يتطابق Upload-Offset مع موضع البايت الذي يبدأ منه هذا الجزء. يتحقق الخادم من تطابق الإزاحة مع سجلاته الخاصة، وإذا لم يتطابق، يعيد الخادم حالة 409 Conflict حمايةً من الحالة الفاسدة. إذا كان كل شيء متوافقاً، يقوم الخادم بإلحاق البايتات، وتحديث الإزاحة الداخلية، ويعيد استجابة 204 No Content جنباً إلى جنب مع Upload-Offset الجديد. حجم الجزء قابل للتهيئة، ولكن الممارسة الشائعة تقع في مكان ما بين بضع مئات من الكيلوبايتات وميجابايتَيْن. الأجزاء الأصغر تزيد من عبء HTTP ولكنها تتعافى بشكل أسرع من الأخطاء، بينما تقلل الأجزاء الأكبر من رحلات الذهاب والإياب (round trips) ولكنها تهدر المزيد من عرض النطاق الترددي (bandwidth) إذا فشلت أثناء الإرسال.
أخيراً، طلب HEAD. هذا هو شبكة الأمان. إذا فشل طلب PATCH في منتصف جزء ما، لنقل بعد ثلاثة ميجابايت من شريحة حجمها خمسة ميجابايت، ينقطع الاتصال. عندما يستعيد العميل الوصول إلى الشبكة، يرسل طلب HEAD إلى URL الرفع. يرد الخادم بـ Upload-Offset الحالي. يقارن العميل
