أوامر تحكم مخزن HTTP ما يعنيه لا تخزين، لا تُخزن، وعمر التخزين الأقصى
تحليل عملي للتعليمات الخاصة بـ Cache-Control — ما يفعله المتصفحات والخدمات الموزعة (CDNs) مع إعدادات no-cache، no-store، max-age، s-maxage، وETags. يشمل الأخطاء التي تؤثر على معظم المطورين في البيئة الإنتاجية.
لقد كتبت بالفعل Cache-Control: no-cache وأفترضت أن المتصفح سيتجاهل المخزن تمامًا. هذا ليس صحيحًا. no-cache تعني "إعادة التحقق قبل تقديم المحتوى من المخزن". إذا كنت ترغب في أن تكون الاستجابة غير مخزنة أبدًا، فإن ذلك هو no-store. هذه الارتباك تؤدي إلى أخطاء في البيانات القديمة في البيئة الإنتاجية، وهي صعبة التحقيق لأن كل شيء يبدو طبيعي في علامة الشبكة عند التحميل الأول.
دعنا نمرر كل توجيه بوضوح — ماذا يفعل المتصفح معه، وماذا يفعل المزودون (CDNs) معه، وما هي الأخطاء الناتجة عن مزجها.
مراجع التوجيه الكامل لـ Cache-Control
| الموجّهة | سلوك المتصفح | سلوك المزود / الواجهة الوسيطة | استخدام معتاد |
|---|---|---|---|
max-age=N | تخزين لمدة N ثانية | تخزين لمدة N ثانية (إلا إذا s-maxage يُلغى) | العناصر الثابتة، استجابات الـ API |
s-maxage=N | مُهملة | تخزين لمدة N ثانية | مدة الاحتفاظ في المزود منفصلة عن المتصفح |
no-cache | تخزين ولكن إعادة التحقق مع كل طلب | تخزين ولكن إعادة التحقق مع كل طلب | محتوى يتغير بانتظام مع ETags |
no-store | لا يتم تخزينه في أي مكان | لا يتم تخزينه في أي مكان | استجابات التحقق من الهوية، بيانات المستخدم الحساسة |
must-revalidate | لا تُقدم بيانات قديمة — إعادة التحقق أو الفشل | لا تُقدم بيانات قديمة — إعادة التحقق أو الفشل | استجابات الـ API حيث البيانات القديمة تؤدي إلى تلف |
proxy-revalidate | مُهملة | لا تُقدم بيانات قديمة في المخازن المشتركة | مُطلوب إعادة التحقق من المزود فقط (مخصص للمزود) |
private | يمكن للمتصفح تخزينه | لا يُسمح بالتخزين | صفحات خاصة بالمستخدم |
public | يمكن لأي مخزن تخزينه | يمكن التخزين | موارد ثابتة مشتركة |
immutable | لا يتم إعادة التحقق داخل مدة max-age | تختلف حسب المزود | مُتَّسِع / مُصدَّر بنسخة |
stale-while-revalidate=N | تُقدَّم بيانات قديمة لمدة N ثانية أثناء استلام البيانات الجديدة | تختلف حسب المزود | سرعة دون تدهور قديم |
مدة الاحتفاظ (max-age) و s-maxage: المدة في المتصفح مقابل المزود
max-age=N تُخبر المتصفح والـ CDNs بكمية الثواني التي تكون فيها الاستجابة مُحدثة. بعد N ثانية، تصبح الاستجابة المخزنة قديمة وتحتاج إلى إعادة التحقق قبل الاستخدام.
s-maxage=N هي مخصصة للمزود فقط. يتجاهل المتصفحها تمامًا. إذا كنت ترغب في أن يخزن المزود المحتوى لمدة ساعة ولكن المتصفح لمدة 5 دقائق:
Cache-Control: max-age=300, s-maxage=3600
يُخزن المتصفح لمدة 5 دقائق. CloudFront، Fastly، Nginx — سيستخدمون 3600 ثانية. فخ شائع: تعيين max-age=0 بما أن ذلك لا يُوقف التخزين. لا يفعل ذلك. max-age=0 تعني أن الاستجابة تصبح فورًا قديمة — يُخزن المتصفحها ويُعيد التحقق، من المرجح أن يحصل على 304. إذا لم ترغب في تخزينها أبدًا، استخدم no-store.
لا تُخزن: ليس ما تظن
Cache-Control: no-cache لا تعني "لا تستخدم المخزن". تعني "لا تُقدَّم من المخزن دون إعادة التحقق مع الخادم أولاً".
السلسلة عندما يكون لدى المتصفح استجابة مخزنة مع no-cache:
- طلب جديد يصل إلى الموارد المخزنة
- يُرسل المتصفح رقم ETag (من خلال
If-None-Match) أو وقت التحديث الأخير إلى الخادم - إذا لم يتغير المحتوى → يُرجع الخادم
304 Not Modifiedبدون جسم - إذا تغير المحتوى → يُرجع الخادم
200 OKمع الاستجابة الجديدة
المنفعة مقارنة مع no-store: تُحقق توفير موارد الاتصال من خلال الاستجابات 304. إذا تغير المحتوى بشكل متكرر ولكن يجب أن يكون محدثًا عند التغيير، فإن no-cache مُدمجة مع ETag هي المزيج الصحيح.
لا تُخزن: "لا تُخزن هذا"
Cache-Control: no-store تعني أن الاستجابة لا يجب أن تُخزن في أي مكان — سواء في مخزن المتصفح، أو في مزود، أو في واجهة وسطية. لا نسخ، تمامًا.
استخدم هذا للإدراة:
- استجابات التحقق من الهوية (مفاتيح تسجيل الدخول، بيانات الجلسة)
- بيانات شخصية حساسة
- محتوى محدود (تأكيد الدفع، صفحات التحقق من الهوية)
ملاحظة صغيرة: no-store لا تمنع صفحة من الظهور في مخزن المتصفح (bfcache). يحتفظ المتصفح بنسخة ذهنية لتحسين الأداء أثناء التنقل، وهي منفصلة عن مخزن HTTP. إذا كنت بحاجة إلى التعامل مع مشكلة الاتجاه العكسي بعد تسجيل الخروج، فقم بربطها مع pageshow الحدث وتحقق من event.persisted.
مُطلوب إعادة التحقق: لا توجد مرونة للبيانات القديمة
تُسمح في مواصفات تخزين HTTP بعرض استجابات قديمة عندما يكون الخادم غير متاح — ميزة مرونة تعرفها معظم المطورين. must-revalidate تُزيل هذه المرونة: بمجرد أن تصبح الاستجابة المخزنة قديمة، يجب أن يُعيد التحقق أو يُرجع 504. لا تُعرض أي بيانات قديمة بأي حال.
# Without must-revalidate: CDN may serve stale if origin is slow or down
Cache-Control: max-age=3600
# With must-revalidate: stale = error, not a fallback
Cache-Control: max-age=3600, must-revalidate
استخدم هذا لاستجابات الـ API حيث تؤدي البيانات القديمة إلى تلف الوظائف — مثل أرقام المخزون، الأسعار، حالة التحقق من الهوية — بدلًا من أن تبدو فقط خاطئة.
خاص ضد عامي: خطأ المزود الذي يُفقد بيانات المستخدم
private تعني أن الاستجابة مخصصة لمستخدم معين. يمكن للمتصفح تخزينها؛ يجب أن لا يخزنها المزود أو الواجهات الوسيطة المشتركة.
public تُسمح بشكل صريح لجميع المخازن — بما في ذلك المخازن المشتركة — بتخزين الاستجابة. بعض المخازن تُخزن فقط استجابات الطلبات المُصادقة إذا تم تسمية ذلك بشكل صريح public.
الخطأ في الواقع: ينسخ المطور Cache-Control: public, max-age=3600 من موارد ثابتة إلى صفحة تحتوي على بيانات مخصصة للمستخدم. يخزن المزود الاستجابة. يحصل المستخدم B على صفحة المستخدم A من المخزن. هذا ليس نظريًا — كان لدى GitHub نسخة من هذا في 2018. تُسمّي الاستجابات المُصادقة أو المخصصة بشكل صريح private بشكل صريح، حتى لو كنت تعتقد أن مزودك "يعرف" أنه لا يجب تخزينها.
ETags وطلبات الظروف
ETags هي الطريقة التي يُعبر بها الخادم عن "هناك بصمة لهذا الاستجابة". يُخزن المتصفح ETag مع الاستجابة المخزنة ويُرسله مرة أخرى في الطلب التالي من خلال If-None-Match. إذا لم يتغير المحتوى، يُرجع الخادم 304 Not Modified بدون جسم — نفس تأكيد التحديث كما في no-cache، أقل استهلاك للبيانات.
ال no-cache + تدفق ETag:
→ GET /api/config HTTP/1.1
← HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
[full response body]
→ GET /api/config HTTP/1.1
If-None-Match: "abc123"
← HTTP/1.1 304 Not Modified
[no body — browser uses its cached copy]
نوعان من ETags:
- ETags القوية (
"abc123") — متطابقة تمامًا على المستوى البايت. ضرورية لدعم طلبات النطاق في المزود. - ETags الضعيفة (
W/"abc123") — متماثلة من حيث المعنى ولكن ليس بالضرورة متطابقة على المستوى البايت. مناسبة لإعادة التحقق في المتصفح، لكنها غير مناسبة لطلبات النطاق.
يُولّد Nginx ETags تلقائيًا من وقت التحديث وحجم الملف. لا يضيف Expressها بشكل افتراضي — استخدم app.set('etag', 'strong') أو ال etag مُعالجة الوسائط بشكل صريح.
الوقت الأخير والـ If-Modified-Since
نفس المفهوم كـ ETags ولكن أبسط — مبني على توقيت بدلاً من مفتاح المحتوى. يحتوي الخادم على Last-Modified؛ يُرسل المتصفح If-Modified-Since في الطلبات التالية.
الإشكالية: إذا أعدت التثبيت، وتحديثات توقيت الملف تحدث حتى لو لم يتغير المحتوى، فإن المخازن تُلغى بشكل غير ضروري. لا يُعاني ETag المبني على مفتاح المحتوى من هذا السؤال. استخدم ETags حيثما يمكن، واعتبر Last-Modified كمُستند بديل للخوادم التي لا تدعم ETags.
Vary: الرأس الذي يُضاعف مخزونك بصمت
ال Vary الرأس يخبر المخازن أن الاستجابة قد تختلف بناءً على عناصر أخرى في طلب المتصفح. كل تشكيل فريد من هذه العناصر يحصل على مدخل مخزن منفصل.
Vary: Accept-Encoding
يُخبر المخازن بتحديث الاستجابة لضغط gzip، brotli، ووضع الوضع القياسي. صحيح وشائع. المُضلل هو: Vary: Cookie. كل مستخدم لديه مجموعة من الكوكيز الفريدة، لذا يحصل كل مستخدم على مدخل مخزن منفصل — مما يُوقف تبادل المخازن المشتركة. العديد من الأطر تضيف Vary: Cookie بشكل سري. إذا كان معدل تصادم المخزن منخفضًا بشكل غير منطقي على الرغم من القيم المُضاعفة max-age ، فتحقق من رؤوس الاستجابة من Vary: Cookie التي تظهر من وسائط الجلسة.
Vary: * تعني "لا تُخزن هذا على الإطلاق" في الممارسة — كل طلب يُعامل كمُختلف. يعادل no-store للمزود.
إجبار التحديث من خلال معلمات الاستعلام
عندما تحتاج إلى تحميل موارد مُحددة بنسخة جديدة، فإن إضافة معلمة استعلام هي الطريقة القياسية — يُعتبر الجزء من الـ URL مُضافًا، لذا يُعامل كمصدر جديد من قبل المتصفح والـ CDNs:
/app.js?v=2.1.4
/styles.css?hash=a1b2c3d4e5f6
إذا كنت تُنشئ معلمات تُستخدم لفرض التحديث من خلال مفاتيح المحتوى أو سلاسل النسخ التي قد تحتوي على أحرف خاصة، فتأكد من تشفيرها قبل إضافتها. يُعالج مشفر/فك تشفير URL هذا بسرعة إذا كنت تختبر أو تُنشئ روابط يدويًا.
الثلاثة أخطاء التي تُصيب معظم المطورين
1. استخدام no-cache عندما يعني no-store. إذا كنت تتعامل مع استجابات التحقق من الهوية، أو نقاط الخروج، أو أي شيء يحتوي على بيانات شخصية، فإنك ترغب في no-store. no-cache تُترك البيانات في مخزن المتصفح (مُعلّمة كقديمة); no-store تُزيل التأثير تمامًا. الفرق مهم عندما يشارك المستخدمون الأجهزة.
2. عدم تعيين s-maxage للتحكم في المزود. تنزيل كـ .yml s-maxage، يستخدم المزود max-age. إذا كان max-age قصيرًا لجودة المتصفح (مثلاً 60 ثانية)، فإن المزود يخزن لمدة 60 ثانية أيضًا — وهذا غالبًا ليس ما تريده. افصل بين المدةين بشكل صريح.
3. استخدام public على مسارات تُرجع بيانات المستخدم. هذا هو الحادث الأمني، وليس مجرد مشكلة أداء. أي استجابة مخصصة أو مصادقة يجب أن تكون private. ابدأ بـ private وأدخل فقط public للموارد المشتركة حقًا.
باستخدام لون واحد، يمكنك الآن إنشاء مقياس كامل، مُعرّف بمعنى، وقابل للوضع المظلم، في أقل من 50 سطرًا من JavaScript. الخطوات الأساسية:
النموذج الذهني: no-cache هو عن تأكيد التحديث — يعيش المحتوى في المخزن، لكنه يحتاج إلى تأكيد من الخادم قبل الاستخدام. no-store هو عن عدم ترك أي أثر. max-age هو مدة المتصفح. s-maxage هو مدة المزود منفصلة. ETags هي ما يجعل إعادة التحقق سهلة.
أحصل على التمييز الصحيح على أي مسار يتعامل مع بيانات المستخدم. هذا الخطأ الوحيد — نسخ رأس التخزين من موارد ثابتة إلى مسار مصادق — هو الذي يتحول إلى تسرب بيانات متعددة المستخدمين عندما يبدأ المزود في تخزين الاستجابات المخصصة. private/public أوامر تحكم مخزن HTTP: ما تعنيه no-cache، no-store، و max-age بالفعل 2
تثبيت ملحقاتنا
أضف أدوات IO إلى متصفحك المفضل للوصول الفوري والبحث بشكل أسرع
恵 وصلت لوحة النتائج!
لوحة النتائج هي طريقة ممتعة لتتبع ألعابك، يتم تخزين جميع البيانات في متصفحك. المزيد من الميزات قريبا!
