نفّذ GET مشروطًا بإرسال مُعرّفات تحقق ثابتة مع كل مستند، والتحقق من مُعرّفات التحقق المحفوظة لدى العميل، ثم إعادة المستند الحالي أو استجابة 304 Not Modified بلا محتوى. يشرح هذا البرنامج التعليمي على Mac العملية باستخدام صفحة نصية اصطناعية.
المطلوب هو جعل طلبات المستند العام المتكررة تنقل محتواه عند الحاجة فقط. ستشغّل خادم Python محليًا صغيرًا، وتختبره من Terminal، وتحاكي تحديثًا للمستند. يظل المثال محليًا؛ أما نشره فمهمة منفصلة.
كيف يختار الخادم بين 200 و304؟
يُرجع الطلب الأول 200 OK ومتن المستند ومُعرّفات التحقق الخاصة به. ويرسل الطلب الشرطي اللاحق مُعرّفات التحقق المحفوظة. إذا لم يتغير المستند، يُرجع الخادم 304 Not Modified، ما يتيح للعميل إعادة استخدام المتن المخزّن لديه. أما المحتوى الذي تغيّر فيتلقى استجابة 200 جديدة. طلبات HTTP الشرطية.
بالنسبة إلى مستند موجود كان سيرجع لولاه استجابة 200، طبّق تسلسل القرارات التالي. تكون أولوية If-None-Match على If-Modified-Since؛ فهما ليسا اختبارين مستقلين. RFC 9110، حقول الطلبات الشرطية.
| شرط الطلب | الاستجابة |
|---|---|
| لا توجد ترويسات شرطية | 200 مع المستند |
تطابق If-None-Match الوسم الحالي |
304، بلا متن |
وجود If-None-Match مع عدم تطابقه |
200، حتى إن أوحى التاريخ المرسل بعدم حدوث تغيير |
إرسال If-Modified-Since صالح فقط، والمستند ليس أحدث من ذلك التاريخ |
304، بلا متن |
إرسال If-Modified-Since صالح فقط، والمستند أحدث من ذلك التاريخ |
200 مع المستند |
قيّم هذه الشروط بعد اختيار المستند وتحديد استجابته المعتادة. يجب أن يؤدي المستند غير الموجود إلى استجابة المورد المفقود المعتادة. ولا يجوز أن تحتوي استجابة 304 على محتوى أو ذيول. ويجب أن تحمل بيانات التخزين المؤقت الوصفية المنطبقة، بما فيها الحقول Date وETag وVary وCache-Control وExpires وContent-Location التي كانت سترافق استجابة 200. RFC 9110، تقييم الشروط المسبقة ومتطلبات 304.
اختر مُعرّفات تحقق تصف المستند المنشور
ETag هو مُعرّف إصدار موضوع بين علامتي اقتباس، مثل "doc-v1". طريقة إنشائه متروكة للتنفيذ: يمكن استخدام مُعرّف مراجعة أو تجزئة للمحتوى. حافظ على ثباته للتمثيل نفسه وغيّره عند تغيّر ذلك التمثيل. يستخدم المثال مراجعتين صريحتين محتواهما ثابت. ترويسة ETag.
يحدّد الوسم القوي تمثيلًا بعينه؛ أما الوسم الضعيف فيبدأ بالبادئة W/ ويمكنه وصف محتوى متكافئ دلاليًا تختلف بايتاته. تستخدم مقارنات GET الشرطية مع If-None-Match مقارنة ضعيفة، ولذلك يطابق W/"doc-v1" الوسم "doc-v1". ويمكن أن تتضمن الطلبات قائمة وسوم أو محرف البدل المستقل *. RFC 9110، If-None-Match.
تصف Last-Modified وقت تغيّر المستند. استخدم وقت تعديل محددًا على نحو متسق، مع احتساب مكونات المستند ذات الصلة، بدلًا من استخدام وقت كل طلب. ويجب ألا يكون هذا الوقت لاحقًا لتاريخ الاستجابة Date. RFC 9110، إنشاء Last-Modified.
يُسنِد المثال أوقات نشر توضيحية عمدًا إلى مراجعتيه الاصطناعيتين: ظهرًا بتوقيت غرينتش في 1 و2 يناير 2024. هذه التواريخ بيانات تجريبية. في سير عمل نشر حقيقي، استخدم بيانات نشر تتم صيانتها. دقة Last-Modified أقل من دقة ETag في اكتشاف اختلافات المحتوى، لكنها توفر خيارًا احتياطيًا مفيدًا. ترويسة Last-Modified.
اختر سياسة التخزين المؤقت على نحو مستقل
تحدد مُعرّفات التحقق ما إذا كانت النسخة المخزنة لا تزال مطابقة. وتحدد سياسة الحداثة الوقت الذي يحتاج فيه التخزين المؤقت إلى الاستعلام. يرسل هذا البرنامج التعليمي Cache-Control: no-cache، ما يسمح بالتخزين لكنه يشترط نجاح التحقق قبل إعادة الاستخدام. وهذا يجعل ملاحظة سلوك الطلبات المتكررة سهلة. تحظر no-store التخزين، بينما تسمح max-age بإعادة الاستخدام ما دامت الاستجابة حديثة. RFC 9111، توجيهات التخزين المؤقت للاستجابات.
بالنسبة إلى مستند عام يمكنه تحمّل تأخير قصير قبل ظهور التحديث، قد تكون سياسة مثل max-age=60 مناسبة. وهذا خيار تحريري توضيحي؛ اختر المدة وفق متطلبات تحديث المستند. يمكن إعادة استخدام استجابة مخزنة حديثة من دون الاتصال بالخادم، لذا لا يلزم أن تؤدي كل زيارة متكررة إلى استجابة 304. RFC 9111، الحداثة.
شغّل مثالًا محليًا كاملًا على جهاز Mac
افتح Terminal وتحقق من المتطلبات المسبقة باستخدام الأوامر التالية. استخدم Python 3.9 أو إصدارًا أحدث لأن البرنامج يستخدم str.removeprefix. إذا لم يكن python3 متاحًا، فثبّت Python 3 قبل المتابعة. لا حاجة إلى حزم Python من جهات خارجية.
python3 --version
curl --version
في مجلد عمل جديد، احفظ البرنامج الكامل التالي باسم server.py باستخدام محرر نصوص عادي. لا يخدم إلا /page.txt، وينفّذ GET وHEAD، ويختار مراجعة المستند من وسيط سطر الأوامر. توجّه BaseHTTPRequestHandler في Python الطلبات إلى الأساليب المقابلة وتوفر دوالًا مساعدة لكتابة الاستجابات. وثائق خادم HTTP في Python.
import argparse
import re
from datetime import datetime, timezone
from email.utils import format_datetime, parsedate_to_datetime
from http.server import BaseHTTPRequestHandler, HTTPServer
parser = argparse.ArgumentParser()
parser.add_argument("version", type=int, choices=(1, 2))
args = parser.parse_args()
BODY = {
1: b"Paper Shapes\nA circle and a square.\n",
2: b"Paper Shapes\nA circle, a square, and a triangle.\n",
}[args.version]
ETAG = '"doc-v%d"' % args.version
MODIFIED = datetime(2024, 1, args.version, 12, tzinfo=timezone.utc)
LAST_MODIFIED = format_datetime(MODIFIED, usegmt=True)
TOKEN = r'(?:W/)?"[\x21\x23-\x7e\x80-\xff]*"'
TAG_LIST = re.compile(rf'[ \t]*(?:{TOKEN})?(?:[ \t]*,[ \t]*(?:{TOKEN})?)*[ \t]*')
def tag_matches(value):
if value.strip() == "*":
return True
if not TAG_LIST.fullmatch(value):
return False
return any(tag.removeprefix("W/") == ETAG
for tag in re.findall(TOKEN, value))
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.serve_document()
def do_HEAD(self):
self.serve_document()
def serve_document(self):
if self.path != "/page.txt":
self.send_error(404)
return
not_modified = False
tags = self.headers.get_all("If-None-Match")
if tags is not None:
not_modified = tag_matches(",".join(tags))
else:
dates = self.headers.get_all("If-Modified-Since", [])
if len(dates) == 1:
try:
since = parsedate_to_datetime(dates[0])
if since.tzinfo is None:
since = since.replace(tzinfo=timezone.utc)
not_modified = MODIFIED <= since
except (TypeError, ValueError, OverflowError):
pass
self.send_response(304 if not_modified else 200)
self.send_header("ETag", ETAG)
self.send_header("Cache-Control", "no-cache")
if not not_modified:
self.send_header("Last-Modified", LAST_MODIFIED)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(BODY)))
self.end_headers()
if not not_modified and self.command == "GET":
self.wfile.write(BODY)
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
هذا عرض توضيحي محلي لمُعرّفي التحقق المذكورين، وليس مجموعة كاملة لخدمة HTTP. يستخدم تحويل التاريخ محلل تواريخ البريد الإلكتروني المتساهل في Python؛ أما التنفيذ المنشور فينبغي أن يستخدم التحقق من التواريخ الخاص بـHTTP ومعالجة الطلبات الشرطية. توثّق Python المدخلات التي يقبلها ذلك المحلل والاستثناءات التي يرفعها. أدوات التواريخ في Python email.
في Terminal، انتقل إلى المجلد الذي يحتوي على server.py وابدأ المراجعة 1. اترك هذه النافذة قيد التشغيل. إذا كان المنفذ 8000 مستخدمًا بالفعل، فاختر منفذًا آخر متاحًا في البرنامج واستخدم المنفذ نفسه في كل عناوين URL للاختبار.
python3 server.py 1
اختبر المحتوى غير المتغير والمتغير وأولوية الشروط
افتح نافذة Terminal أخرى. تعرض الطلبات التالية ترويسات الاستجابة وأي متن مُعاد. يضمّن الخيار -i الترويسات، ويُمرّر -H ترويسة طلب، ويحدد --max-time حدًا زمنيًا لكل عملية نقل. دليل curl.
احصل على الاستجابة الأولية
curl --max-time 5 -sS -i http://127.0.0.1:8000/page.txt
توقع 200 OK ونص الصفحة الاصطناعية وETag: "doc-v1" وLast-Modified: Mon, 01 Jan 2024 12:00:00 GMT. دوّن القيم التي أُعيدت فعليًا عند تطبيق هذا الإجراء على مستند آخر؛ إذ ترتبط مُعرّفات التحقق بالاستجابة المعنية.
أعد التحقق باستخدام كل مُعرّف
curl --max-time 5 -sS -i \
-H 'If-None-Match: "doc-v1"' \
http://127.0.0.1:8000/page.txt
curl --max-time 5 -sS -i \
-H 'If-Modified-Since: Mon, 01 Jan 2024 12:00:00 GMT' \
http://127.0.0.1:8000/page.txt
ينبغي أن يعيد كلا الطلبين 304 Not Modified بلا نص الصفحة. تحتفظ الاستجابة بترويستي ETag وCache-Control، وتضيف دالة المساعدة الخاصة بالاستجابة في Python ترويسة Date. ومن المفترض أن تؤدي إعادة الطلب الأول مع W/"doc-v1" أيضًا إلى 304. يحلل المثال الوسوم الموضوعة بين علامتي اقتباس بدلًا من تقسيمها عشوائيًا عند الفواصل، لأن الفاصلة قد ترد داخل الوسم.
أثبت أولوية ETag
curl --max-time 5 -sS -i \
-H 'If-None-Match: "older-revision"' \
-H 'If-Modified-Since: Wed, 03 Jan 2024 12:00:00 GMT' \
http://127.0.0.1:8000/page.txt
توقع 200 OK مع المستند. لا يتطابق الوسم، لذا يجب ألا يؤدي التاريخ اللاحق إلى 304. يكشف هذا الطلب المتعارض عمدًا عن أي تنفيذ يدمج الاختبارين على نحو خاطئ.
حاكِ نشر المراجعة التالية
في نافذة الخادم، اضغط Control-C ثم ابدأ المراجعة 2. اترك عنوان URL للطلب كما هو.
python3 server.py 2
أعِد طلبي التحقق السابقين اللذين يحتويان على مُعرّفات التحقق الخاصة بالمراجعة 1. ينبغي أن يتلقى كلاهما الآن 200، مع الجملة المحدّثة التي تذكر مثلثًا، وETag: "doc-v2"، وتاريخ التعديل في 2 يناير. ومن المفترض أن يؤدي تكرار طلب يتضمن If-None-Match: "doc-v2" إلى 304.
تعطيك هذه النتائج تسلسلًا للتحقق من القبول: التنزيل الأولي، والتحقق مع عدم حدوث تغيير، وتعارض مُعرّفات التحقق، وتغيّر المستند، ثم التحقق مع عدم حدوث تغيير للمراجعة الجديدة. اختُبر المثال من خلال 24 فحصًا محليًا عبر المراجعتين، شملت الوسوم الضعيفة وقوائم الوسوم ومطابقة محرف البدل وHEAD والمسارات غير الموجودة.
لماذا قد تبدو طلبات المتصفح وGoogle مختلفة؟
تدير المتصفحات عادةً مُعرّفات تحقق HTTP عبر ذاكرة التخزين المؤقت لديها. تضيف خوارزمية WHATWG Fetch مُعرّفات التحقق المحفوظة عند إعادة التحقق، وبعد نجاح 304 تحدّث ترويسات الاستجابة المخزنة وتُعيد الاستجابة المخزنة. ونتيجة لذلك، قد تتلقى JavaScript الخاصة بالتطبيق استجابة 200 قابلة للاستخدام ومتن المستند، حتى إن كانت إعادة التحقق عبر الشبكة قد تلقت 304. معيار Fetch، جلب HTTP عبر الشبكة أو ذاكرة التخزين المؤقت.
عند تشخيص سلوك الخادم، ابدأ بطلبات curl الصريحة أعلاه. إن إدخال ترويسات شرطية يدويًا في JavaScript بالمتصفح يغيّر الاختبار: إذ يحوّل Fetch طلبًا بوضع التخزين المؤقت الافتراضي يتضمن تلك الترويسات إلى no-store. اترك المتصفح يدير ذاكرة التخزين المؤقت عند اختبار إعادة التحقق المعتادة فيه. معيار Fetch، وضع التخزين المؤقت للطلب.
تدعم بنية Google الخاصة بالزحف كلا زوجي مُعرّفات التحقق، وتوصي بـETag لأنه يتجنب مشكلات تنسيق التاريخ. وتذكر وثائقها أيضًا أن هذه البنية لا تدعم توجيهات التخزين المؤقت الأخرى في HTTP. اختر سياسة التخزين المؤقت للمتصفح على نحو مستقل عن توقعاتك بشأن سلوك Google في إعادة الزحف. نظرة عامة على برامج زحف Google وأدوات الجلب.
لا ترسل Google ترويسات شرطية في كل محاولة زحف. توضح وثائق البحث لديها أن 304 يسمح بإعادة استخدام المحتوى الذي سبق زحفه، بينما قد يتلقى المحتوى الأحدث 200 مع المتن المحدّث. أبقِ الطلبات العادية غير المشروطة قيد العمل؛ فطلب زاحف بلا مُعرّفات تحقق لا يدل على فشل تنفيذك. استكشاف أخطاء الزحف في بحث Google وإصلاحها.
قبل تطبيق هذا النمط على مستند منشور، أعِد تسلسل التحقق من القبول على عنوان URL الفعلي الذي يقدّم المستند. تحقق من أن المراجعات التي لم تتغير تحتفظ بمُعرّفات التحقق، وأن التعديلات تحدّثها، وأن استجابة 304 لا تحمل متن المستند. والنتيجة المفيدة هي إعادة استخدام المستند على نحو صحيح مع بقاء المحتوى المتغيّر متاحًا للاسترجاع.
المصادر
- RFC 9110: دلالات HTTP — IETF / محرر RFC.
- RFC 9111: التخزين المؤقت لـHTTP — IETF / محرر RFC.
- معيار Fetch — WHATWG.
- طلبات HTTP الشرطية — MDN Web Docs.
- ترويسة ETag — MDN Web Docs.
- ترويسة Last-Modified — MDN Web Docs.
- http.server — خوادم HTTP — مؤسسة Python Software Foundation.
- email.utils: أدوات مساعدة متنوعة — مؤسسة Python Software Foundation.
- curl — كيفية الاستخدام — مشروع curl.
- نظرة عامة على برامج زحف Google وأدوات الجلب (وكلاء المستخدم) — Google.
- استكشاف أخطاء الزحف في بحث Google وإصلاحها — مركز بحث Google.