מטמון עמודים מלא

איך WIZZO CMS שומר ומגיש עמוד HTML שלם מהדיסק: מחזור החיים מהבדיקה בראש הבקשה ועד השמירה, נעילת הבנייה, הגשת עמוד ישן בזמן בנייה, פרמטרי מעקב, ואיך מבטלים עמוד.

⏱ 5 דק' קריאה 921 מילים ערוך דף זה ב-GitHub

מטמון העמודים המלא מגיש עמוד ציבורי ישירות מקובץ, לפני שהליבה טוענת נתב, תבניות או מסד נתונים. הוא מה שמאפשר לאתר לשרת הרבה תנועה בעלות נמוכה. בדף הזה תראו את מחזור החיים של עמוד במטמון, את ההגנות מפני עומס, ואיך מבטלים ובונים מחדש עמוד.

בעמוד הזה system/ הוא תיקיית הליבה הפרוסה (= api/core בריפו) ו-{admin} הוא CONFIG::$admin_url של האתר.

תנאי להפעלה#

עמוד נשמר רק כששלושת התנאים מתקיימים (core.php):

  1. CONFIG::$system_type == "client" (עמודי אדמין לא נשמרים לעולם).
  2. CONFIG::$allow_cache == true (הערך נקבע בקונפיגורציית האתר, ולא בליבה).
  3. PAGE::$cache_this_page === true, כלומר הבקר של האתר קרא ל-cache_engine::cache_all_page().

ברירת המחדל של PAGE::$cache_this_page היא false: עמוד נכנס למטמון רק אם הבקר ביקש זאת במפורש.

// בתוך בקר של האתר (application/controllers/news.php)
class news extends bgl_controller
{
    function index()
    {
        cache_engine::cache_all_page(24);    // העמוד הזה יישמר ל-24 שעות
        return $this->view("news_index.tpl");
    }
}

הארגומנט השני, $set_page, שומר את העמוד תחת כתובת אחרת מה-REQUEST_URI (דרך set_page_url).

מחזור החיים#

שלב 1: בדיקה בתחילת הבקשה#

core.php קורא ל-cache_engine::cache_php_check() בראשית הבקשה:

  1. כש-allow_cache כבוי, חוזרים מיד.
  2. מסירים פרמטרי מעקב מה-REQUEST_URI (ראו למטה). $_GET נשאר כמו שהוא.
  3. בקשת POST עם reload_cache מדלגת על הגשה ומכריחה בנייה מחדש (כך עובד ping_cache).
  4. הכתובת מקודדת לשם קובץ (encode_url) ומחפשים pages/<url>.html.gz, ובטלפון pages_mobile/. ההפרדה היא לפי PAGE::is_mobile().
  5. קובץ שתוקפו עתידי מוגש, והבקשה מסתיימת ב-exit.
  6. אין קובץ תקף אבל יש קובץ שפג ומישהו אחר בונה אותו כרגע: מוגש הקובץ הישן.
  7. אם אין עמוד, נבדק גם scss/<url>.css (קומפילציית SCSS) ומוגש כ-text/css.

ב-HIT נשלח Content-Type לפי הכתובת (.json, /rss, /feed, .xml) ולפי תחילת התוכן.

שלב 2: הבקר מבקש מטמון#

cache_all_page($hours = 24) מסמנת PAGE::$cache_this_page = true ואת משך התוקף, ותופסת נעילת בנייה. אם הנעילה תפוסה, הבקשה ממתינה קצרות לתוצאה של הבונה (ראו הנעילה).

שלב 3: שמירה#

אחרי שהעמוד רונדר ועבר MASK::out (מצב הסוואה), cache_all_page_save דוחסת אותו (gzip רמה 6), כותבת אטומית, ומציבה תפוגה של now + hours. בקשה ש"הפסידה" במרוץ הבנייה ופג זמנה לא כותבת: הבונה האמיתי יכתוב.

הגשה ישירה של קובץ gzip#

כדי לא לפתוח ולדחוס מחדש בכל HIT, try_serve_gz_direct שולחת את הבייטים הדחוסים כמו שהם עם Content-Encoding: gzip, Vary: Accept-Encoding ו-Content-Length. היא פועלת רק כשכל התנאים מתקיימים:

  • לא הוגדרה PAGE::$parse_page_func.
  • zlib.output_compression כבוי.
  • הלקוח שולח Accept-Encoding: gzip.
  • כל ה-output buffers הפתוחים הם ob_gzhandler או ברירת המחדל.
  • סוג התוכן זוהה, והקובץ באורך 18 בתים לפחות.

אחרת הקובץ נפתח, מועבר דרך PAGE::parse_page() אם הוגדרה, ומוגש רגיל. אתר שמשנה את הפלט דרך buffer משלו נופל אוטומטית למסלול הזה ($serve_gz_direct = false מכבה את האופטימיזציה כולה).

נעילת בנייה והגשת עמוד ישן#

כשעמוד פופולרי פג, בלי הגנה כל מי שנמצא באתר היה בונה אותו בו-זמנית. שתי הגנות מונעות זאת:

מנגנוןמה הוא עושה
נעילת בנייה ($use_build_lock)רק בקשה אחת תופסת flock על locks/<sha256(url)>.lock ובונה. האחרות ממתינות עד $build_lock_wait_ms (750 מ"ש), בדיקה כל 50 מ"ש. הקובץ הופיע: מגישות אותו. הבונה נכשל: אחת משתלטת. פג הזמן: בונות לעצמן בלי לכתוב
הגשת עמוד ישן ($serve_stale_while_building)קובץ שפג לא נמחק ונשאר זמין. כל עוד מישהו בונה, שאר המבקרים מקבלים אותו

הנעילה "נכשלת פתוח": אם flock לא זמין או שקובץ הנעילה לא נפתח, הבקשה בונה כרגיל.

פרמטרי מעקב#

פרמטרים שמזהים מקור תנועה בלבד לא משנים את העמוד, ולכן הם מוסרים מהכתובת לפני חישוב המפתח. כך קליק עם ?utm_source=... משתמש באותו קובץ של הכתובת הנקייה, ולא יוצר קובץ חדש לכל קמפיין. הרשימה ב-cache_engine::$tracking_params כוללת בין השאר utm_*, fbclid, gclid, gbraid, wbraid, msclkid, ttclid, twclid, mc_cid, mc_eid, _ga, _gl.

cache_engine::strip_tracking_params("/shop?utm_source=fb&color=red");
// "/shop?color=red"
פרמטר אחר = קובץ חדש

פרמטר GET שאינו ברשימה הוא חלק ממפתח המטמון, ולכן כל ערך שונה יוצר קובץ חדש בלי תקרה. encode_url מסיר את תו ה-?, ולכן page?a=1 ו-pagea=1 חולקים אותו קובץ. אם הבקר שלכם מתעלם מפרמטר מסוים, מוטב להוסיף אותו ל-$tracking_params.

ביטול עמוד ובנייה מחדש#

cache_engine::cache_all_page_remove("news/123");        // מסמן כפג תוקף
cache_engine::cache_all_page_remove("news/123", true);  // מחיקה פיזית
cache_engine::cache_all_page_update("news/123");        // בנייה מחדש בפועל
  • cache_all_page_remove($url, $hard_delete = false) מטפלת ב-pages וב-pages_mobile, בקובצי .html ו-.html.gz, ובווריאציות קידוד של הכתובת. כשהמפתח אינו דף הבית היא מבטלת גם כל עמוד תחת אותו נתיב: remove("news") מבטל גם news/123.
  • ברירת המחדל היא ביטול, לא מחיקה. drop_page_file מזיזה את זמן השינוי של הקובץ לעבר, כך שהקובץ ממשיך לשמש כעמוד ישן בזמן הבנייה. אם ה-touch נכשל, היא מוחקת. true מוחק מיד, ומתאים להסרות שחייבות להיעלם מיד (הסרה משפטית).
  • cache_all_page_update($url) קוראת ל-ping_cache, ששולח POST עם reload_cache=true לכתובת המלאה (CONFIG::$env_url . $url), פעם בדסקטופ ופעם כטלפון, וכך הקובץ נבנה מחדש. כשקיים exec, זה נעשה ברקע עם curl, אחרת עם curl_* ו-timeout.

הקריאה היחידה ל-cache_all_page_remove בליבה היא בשמירת עמוד בפאנל Pages (admin/Pages.php). שמירה בפאנל אחר, למשל חדשות או מוצרים של האתר, לא מנקה את המטמון אוטומטית, וזו אחריות הפאנל שלכם.

reload_cache ללא אימות

בקשת POST עם reload_cache מכריחה בנייה מחדש ושומרת את התוצאה במטמון המשותף, ללא כל אימות. כל מי שמכיר את הכתובת יכול לגרום לבנייה מחדש. ו-ping_cache מעביר את ה-cookies של בקשת המקור, ולכן אם הבקר מרנדר אחרת למנהל מחובר, זה מה שיישמר לכל המבקרים. הקפידו שהבקרים של עמודים ציבוריים לא מציגים תוכן שונה למנהל.

ראו גם#

מצאתם טעות או חוסר? תקנו את הדף או פתחו Issue בריפו. התיעוד נכתב מתוך הקוד של ליבה 5.0.115.