Chunked Transfer هي آلية من بروتوكول HTTP يقوم الخادم من خلالها بنقل جسم الاستجابة في أجزاء منفصلة (chunks) دون تحديد الحجم الإجمالي للبيانات مسبقاً. يحتوي كل جزء على حجمه بتنسيق سداسي عشري وبيانات بالطول المحدد، وينتهي بجزء فارغ بحجم صفر. وفقاً MDN Web Docs, 2025، يتم تفعيل Transfer-Encoding: chunked تلقائياً بواسطة الخادم عندما يكون حجم الاستجابة غير معروف مسبقاً — على سبيل المثال، أثناء إنشاء المحتوى بشكل فوري أو نقل البيانات المتدفق.
الخلاصة
Chunked Transfer هي آلية HTTP محددة في مواصفة HTTP/1.1 (RFC 7230، القسم 4.1) تسمح للخادم بإرسال جسم الاستجابة في أجزاء دون تحديد Content-Length الإجمالي. بدلاً من حساب حجم الاستجابة قبل الإرسال، يبدأ الخادم النقل فوراً، مرسلاً أجزاء البيانات بمجرد أن تصبح جاهزة. كل جزء مصحوب برأس الحجم الخاص به، مما يسمح للعميل بتجميع الاستجابة من القطع.
يتم تفعيل الآلية بواسطة الرأس Transfer-Encoding: chunked. عندما يرى العميل هذا الرأس في الاستجابة، يعلم أن الجسم سينقل مجزأً ويجب أن يقرأ الاستجابة في حلقة: قراءة حجم الجزء، ثم قراءة البيانات بالحجم المحدد، ثم التكرار. تنتهي العملية عند مواجهة جزء بحجم صفر. Chunked Transfer هو جزء إلزامي من HTTP/1.1، مدعوم من جميع خوادم الويب الحديثة وعملاء HTTP.
السبب الرئيسي لاستخدام النقل المجزأ هو إنشاء المحتوى الديناميكي. عندما يولد الخادم استجابة بناءً على استعلام قاعدة بيانات أو API خارجي أو حساب طويل، لا يمكنه معرفة حجم النتيجة مسبقاً. بدلاً من تخزين الاستجابة كاملة في الذاكرة مؤقتاً (وهو أمر محفوف بالمخاطر للأحجام الكبيرة)، يقوم الخادم بتفعيل Transfer-Encoding: chunked ويرسل البيانات بمجرد أن تصبح متاحة. هذا مهم بشكل خاص للخوادم ذات الذاكرة المحدودة وللاستجابات التي قد يكون حجمها كبيراً جداً — من 100 MB فما فوق.
في HTTP/2، آلية النقل المجزأ كآلية مستقلة غير موجودة، لأن البروتوكول يستخدم تعدد إرسال التدفقات على مستوى الإطارات. في HTTP/2، تنقل البيانات من أي حجم في إطارات DATA، ولا حاجة للإعلان عن حجم جسم الاستجابة مسبقاً — يمكن إغلاق التدفق في أي لحظة. الخوادم الحديثة تحول تلقائياً الاستجابات المجزأة HTTP/1.1 إلى نقل متدفق مكافئ عند التوكيل إلى upstream HTTP/2. يبقى Chunked Transfer مهماً لاتصالات HTTP/1.1.
عندما يقرر الخادم استخدام Chunked Transfer، لا يحسب Content-Length بل يرسل الرأس Transfer-Encoding: chunked. ثم يتشكل جسم الاستجابة كسلسلة من الأجزاء. كل جزء يبدأ بسطر يحتوي على حجم الجزء بالتنسيق السداسي عشري (بدون البادئة 0x)، متبوعاً بـ CRLF ( ). ثم تأتي بيانات الجزء بالحجم المحدد، وتنتهي بـ CRLF. الجزء الأخير له حجم 0، وبعده قد تأتي رؤوس trailer.
الحجم السداسي عشري يسمح بنقل أجزاء من أي حجم من 1 بايت إلى حجم غير محدود نظرياً. عملياً، يتم اختيار حجم الجزء بواسطة الخادم: القيم النموذجية هي 4 KB أو 8 KB أو 16 KB. الحجم الأمثل للجزء يجب أن يكون مضاعفاً لحجم مقطع TCP (عادة 1460 بايت لـ Ethernet) لتقليل التجزئة على طبقة النقل. يستخدم Nginx أجزاء بحجم 4 KB افتراضياً، و Apache يستخدم أجزاء بحجم 8 KB.
العميل الذي يتلقى Transfer-Encoding: chunked يجب أن يقرأ الاستجابة جزءاً جزءاً حتى الجزء الصفري الختامي. إذا كان العميل لا يدعم النقل المجزأ، لا يمكن للخادم استخدام هذا الوضع. عملياً، جميع عملاء HTTP الحديثين — المتصفحات، OkHttp، URLSession، curl — يدعمون بالكامل الاستجابات المجزأة. القراءة المتدفقة تسمح للعميل ببدء معالجة البيانات قبل تلقي الاستجابة كاملة، وهو أمر بالغ الأهمية للأداء.
| عنصر الجزء | التنسيق | مثال |
|---|---|---|
| حجم الجزء | HEX + CRLF | 1000 |
| بيانات الجزء | [الحجم بايت] + CRLF | [4096 بايت من البيانات] |
| الجزء الختامي | 0 | 0 |
| Trailer (اختياري) | رؤوس + CRLF | Expires: Wed, 21 Oct 2025 |
يدعم Chunked Transfer رؤوس trailer — رؤوس HTTP إضافية تنقل بعد آخر جزء. هذا مفيد للبيانات الوصفية التي تصبح معروفة فقط بعد اكتمال توليد الاستجابة: على سبيل المثال، Content-MD5 أو X-Compression-Ratio. يجب الإعلان عن رؤوس trailer في الرأس Trailer: Trailer: Content-MD5, X-Compression-Ratio. عملياً، نادراً ما تستخدم trailers — معظم الخوادم لا تضمّنها في الاستجابات.
الاستجابة المجزأة لها بنية محددة بدقة يجب على العميل تحليلها بشكل صحيح. لنأخذ مثالاً كاملاً لاستجابة HTTP مع Transfer-Encoding: chunked. بعد الرؤوس والسطر الفارغ، يبدأ جسم الاستجابة. بنية الجسم هي تسلسل: حجم_الجزء بيانات حجم_الجزء بيانات ... حتى 0 . كل حجم ينقل بترميز سداسي عشري باستخدام أحرف ASCII.
مثال لاستجابة خادم مع Chunked Transfer:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7
Hello
6
World!
0
في هذا المثال، ينقل الخادم السلسلة “Hello World!” في جزئين. الجزء الأول بحجم 7 بايت يحتوي على “Hello ”، والثاني بحجم 6 بايت يحتوي على “World!”. يجمع العميل البيانات من كلا الجزئين ويحصل على السلسلة الكاملة. مهم: حجم الجزء يشمل البيانات فقط، وليس فواصل CRLF للأجزاء نفسها. الجزء الفارغ الختامي (0 ) يخطر العميل بانتهاء النقل.
import java.net.HttpURLConnection
import java.io.BufferedReader
import java.io.InputStreamReader
fun readChunkedResponse() {
val url = java.net.URL("https://stream.example.com/data")
val connection = url.openConnection() as HttpURLConnection
val reader = BufferedReader(
InputStreamReader(connection.inputStream)
)
var line: String?
while (reader.readLine().also { line = it } != null) {
println("الجزء: $line")
}
reader.close()
}
يُجرد OkHttp المطور تماماً من تفاصيل Chunked Transfer. عند تلقي استجابة مع Transfer-Encoding: chunked، يجمع OkHttp الأجزاء تلقائياً ويوفر للمطور جسم الاستجابة الكامل عبر response.body?.string(). للمعالجة المتدفقة، يُستخدم response.body?.source() الذي يعيد BufferedSource ويسمح بقراءة البيانات فور وصولها. لا يحتاج المطور إلى تحليل الأحجام السداسية عشرية و CRLF يدوياً — المكتبة تقوم بذلك تلقائياً.
Content-Length و Transfer-Encoding: chunked هما طريقتان متنافيتان لتحديد حجم جسم رسالة HTTP. Content-Length هو رأس يحتوي على الحجم الدقيق للجسم بالبايت. وهو إلزامي للاستجابات التي يعرف حجمها مسبقاً وللطلبات ذات الجسم (POST, PUT). يتيح Content-Length للعميل تخصيص مخزن مؤقت بالحجم المطلوب مسبقاً والتحقق من استلام جميع البيانات.
يستخدم Chunked Transfer عندما لا يكون حجم الجسم معروفاً مسبقاً. يحدث هذا في ثلاثة سيناريوهات رئيسية: إنشاء المحتوى الديناميكي (مثلاً، استعلام قاعدة بيانات نتيجته غير متاحة بعد)، النقل المتدفق للملفات الكبيرة (لتجنب تخزين الملف كاملاً في الذاكرة مؤقتاً)، و Server-Sent Events (SSE) لنقل الأحداث في الوقت الفعلي. الاختيار بين Content-Length والمجزأ هو مسؤولية الخادم. إذا كان الخادم يعرف الحجم قبل بدء النقل، يجب عليه استخدام Content-Length كآلية أبسط وأكثر قابلية للتنبؤ.
nمواصفة HTTP/1.1 تمنع الاستخدام المتزامن لـ Content-Length و Transfer-Encoding: chunked. إذا أرسل الخادم كلا الرأسين، يجب على العميل تجاهل Content-Length ومعالجة الاستجابة كمجزأة. أولوية Transfer-Encoding على Content-Length محددة في RFC 7230 للحالات التي يعدل فيها خادم وكيل جسم الاستجابة ولا يمكنه الحفاظ على Content-Length الأصلي. بعض عملاء HTTP القدامى يتعاملون مع هذه الحالة بشكل غير صحيح، لكن التطبيقات الحديثة تتبع المواصفة.
هناك سيناريوهات لا يمكن فيها حساب Content-Length مبدئياً مسبقاً. التقارير الديناميكية التي يتم إنشاؤها عند الطلب مع التصفية والتجميع — الخادم لا يعرف حجم البيانات حتى يكتمل استعلام قاعدة البيانات. فيديو متدفق من كاميرا في الوقت الفعلي — الحجم لا نهائي. SSE و long polling للإشعارات — قد تستمر الاستجابة إلى أجل غير مسمى. في كل هذه الحالات، Chunked Transfer هو الآلية الصحيحة الوحيدة.
يشكل Chunked Transfer أساس العديد من تقنيات البث على الويب. الأكثر شهرة هو Server-Sent Events (SSE)، حيث يرسل الخادم أحداثاً للعميل عبر اتصال HTTP واحد مع Transfer-Encoding: chunked. يستخدم SSE تنسيقاً نصياً خاصاً (data: رسالة )، لكن طبقة النقل هي نقل مجزأ عادي. يستقبل المتصفح الأحداث فور إرسال الخادم لها، دون انتظار اكتمال الاستجابة.
بث الصوت والفيديو يعتمد أيضاً على Chunked Transfer. خوادم الوسائط مثل Nginx RTMP و Wowza Streaming Engine ترسل بيانات الوسائط مجزأة عبر HTTP. يبدأ المشغل من جانب العميل التشغيل بمجرد استلام أول جزء، دون انتظار تحميل الملف كاملاً. هذا يقلص الوقت حتى أول إطار من عشرات الثواني إلى 1-2 ثانية. YouTube و Netflix يستخدمان هذا النهج تماماً لتدفقات HTTP الخاصة بهما.
في تطوير التطبيقات المحمولة، يُستخدم Chunked Transfer لنقل كميات كبيرة من البيانات دون تحميل الاستجابة كاملة في الذاكرة. عند تحميل الصور عبر Coil أو Glide على Android، تقرأ المكتبات بيانات البث مجزأة وتفك تشفير الصورة تدريجياً. هذا يسمح بعرض الصور الكبيرة (10+ MB) دون OutOfMemoryError. يدعم OkHttp القراءة المتدفقة عبر response.body?.byteStream() الذي يعيد InputStream يقرأ البيانات جزءاً جزءاً.
يستخدم gRPC HTTP/2، حيث البث المباشر مدمج على مستوى البروتوكول ولا يتطلب آلية مجزأة منفصلة. خوادم GraphQL التي تعمل عبر HTTP/1.1 يمكنها استخدام Chunked Transfer لنقل نتائج الاشتراكات بشكل متدفق. Apollo Server و Hasura يرسلان استجابات مجزأة لاشتراكات GraphQL، ناقلين الأحداث فور حدوثها. يتلقى العميل التحديثات في الوقت الفعلي دون حاجة إلى الاستقصاء.
يوفر Chunked Transfer مزايا مهمة لتطبيقات الويب. إرسال فوري للبيانات — لا يخزن الخادم الاستجابة مؤقتاً قبل الإرسال، مما يقلل زمن الوصول حتى أول بايت. معالجة متدفقة — يمكن للعميل بدء معالجة البيانات فور وصولها دون انتظار التحميل الكامل. لا توجد قيود على الذاكرة — لا يخزن الخادم الاستجابة كاملة في الذاكرة، وهو أمر بالغ الأهمية للأحجام الكبيرة من البيانات. القدرة على نقل التدفقات اللانهائية — SSE، فيديو مباشر، مراقبة.
مع ذلك، لدى Chunked Transfer قيود. عبء إضافي لكل جزء يتراوح بين 6-12 بايت للحجم + CRLF، مما قد يزيد حجم الاستجابة بنسبة 10-15% للعديد من الأجزاء الصغيرة (مثلاً، 100 بايت لكل منها). عدم القدرة على تحديد الحجم الدقيق — لا يمكن للعميل تخصيص مخزن مؤقت مسبقاً أو عرض شريط تقدم. مشاكل مع خوادم الوكيل — بعض البروكسيات القديمة لا تدعم النقل المجزأ ولا يمكنها تخزين هذه الاستجابات في ذاكرة التخزين المؤقت. عدم دعم استئناف التحميل — لا يمكن إجراء طلبات Range للاستجابات المجزأة المستلمة جزئياً.
وفقاً لـ HTTP Archive, 2025، حوالي 35% من جميع استجابات HTTP تستخدم Transfer-Encoding: chunked. من بينها، تسود الصفحات الديناميكية (60%)، ثم استجابات API (25%) وتدفقات الوسائط (15%). الملفات الثابتة تستخدم دائماً Content-Length تقريباً لأن حجمها معروف مسبقاً. حصة الاستجابات المجزأة تتناقص تدريجياً مع انتشار HTTP/2، حيث البث المباشر منفذ على مستوى الإطارات دون حاجة لرأس Transfer-Encoding إضافي.
في تطوير التطبيقات المحمولة، استخدم Chunked Transfer لتحميل الملفات الكبيرة (صور، فيديو) ولطلبات API التي تعيد مصفوفات بيانات كبيرة. يدعم OkHttp النقل المجزأ بالكامل دون تكوين إضافي. لـ الرفع إلى الخادم، لا يستخدم Chunked Transfer — HTTP/1.1 ليس لديه Transfer-Encoding للرفع. على iOS، يدعم URLSession كلاً من إرسال واستقبال البيانات المجزأة دون تكوين خاص. تحليل JSON المتدفق (عبر Jackson Streaming API أو Moshi) يسمح بمعالجة مصفوفات JSON الكبيرة فور وصولها في تدفق مجزأ.
الأسئلة الشائعة
يفعل الخادم Chunked Transfer تلقائياً عندما يكون حجم الاستجابة غير معروف. يضيف Nginx Transfer-Encoding: chunked إذا لم يتم تعيين Content-Length. في Spring Boot، يستخدم StreamingResponseBody و SseEmitter النقل المجزأ تلقائياً. في Node.js Express، تصبح الاستجابة مجزأة عند استدعاء res.write() و res.end() بدون Content-Length.
لا، مواصفة HTTP/1.1 تمنع الاستخدام المتزامن لـ Content-Length و Transfer-Encoding: chunked. إذا أرسل الخادم كلا الرأسين، يجب على العميل تجاهل Content-Length ومعالجة الاستجابة كمجزأة. هذه القاعدة محددة في RFC 7230 للتوافق مع خوادم الوكيل التي قد تعدل جسم الاستجابة.
الحجم الأمثل للجزء يعتمد على السيناريو. لصفحات الويب العادية — 4-8 KB. لبث الفيديو — 16-64 KB. لـ SSE — أجزاء صغيرة بحجم 1-2 KB لتقليل زمن الوصول. حجم الجزء يجب أن يكون مضاعفاً لحجم مقطع TCP (1460 بايت لـ Ethernet) لتقليل التجزئة على طبقة النقل.
خوادم الوكيل الحديثة (Nginx, HAProxy, Envoy) تدعم Chunked Transfer. يمكن للوكيل تمرير الأجزاء دون تخزين مؤقت (بث مباشر) أو تخزين الاستجابة كاملة مؤقتاً وإعادة إرسالها مع Content-Length. البروكسيات القديمة قد تخزن الاستجابة المجزأة مؤقتاً حتى الاكتمال، مما يزيد زمن الوصول. HTTP/2 يحل هذه المشكلة على مستوى البروتوكول.
هما نفس الشيء. Chunked Transfer هو الاسم الكامل للآلية من مواصفة HTTP/1.1. HTTP chunked encoding هو نفس الشيء، يُستخدم أحياناً في توثيق المكتبات. Transfer-Encoding: chunked هو الرأس الذي يفعل هذا الوضع. المصطلحات الثلاثة تصف نفس آلية نقل البيانات في أجزاء.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا