search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

Cloudflare Workers™ - 在 Cloudflare Edge 中加入 Script 規則,實現流量過濾與提升網站快取加速能力 - TechMoon 科技月球

SiteGround 3 折主機優惠 + WordPress 一鍵安裝完整教學SiteGround 3 折主機優惠 + WordPress 一鍵安裝完整教學
Hostinger 1.2 折主機優惠 + WordPress 一鍵安裝完整教學Hostinger 1.2 折主機優惠 + WordPress 一鍵安裝完整教學
3 分鐘閱讀

我之前介紹過,大部分網站都會使用 Cloudflare 的服務來加速網站,而使用 CloudFlare 擁有許多好處,除了免費的 DNS 服務之外,最重要的還有 CloudFlare 提供免費的 CDN 服務,能夠幫助我們的網站獲得更佳的載入速度,並降低主機的負載。

CloudFlare 的 CDN 服務原理是將網站的檔案內容快取在 Cloudflare 的全球節點當中來實現網站加速,而近期 Cloudflare 提供了另外一項新的功能 —「 Cloudflare Workers」。

今天就要來教大家如何在 CloudFlare 當中實作「Workers」的功能,來加快 WordPress 網站的速度

在進行教學之前,首先我來要先簡單介紹一下 Cloudflare Workers 的功能以及運作原理。

Cloudflare Workers™ 原理

Cloudflare Workers™Cloudflare Workers™

究竟 Cloudflare Workers 是什麼?為什麼它可以讓我們的 WordPress 能夠比以往更快的自動更新快取、同時也能強化降低主機的負載?

依據 Cloudflare 官方的說法,Cloudflare Workers 的功能為「在邊緣執行程式碼,提供强大的網路延展能力」。

透過 Cloudflare 的 Workers 功能,能夠讓我們在 Cloudflare 端自定義規則與邏輯,來偵測魁儡程式、爬蟲機器人…等等程式,用以防止它們過度消耗主機資源,進而拖垮整個網站速度,以及維護提升整體網站主機的安全性。

Cloudflare Workers 的工作原理是,透過在每個網頁標頭當中加入 x-HTML-Edge-Cache,接著使用 JavaScript 辨識每個頁面內容,以及你進行你所自定義的過濾邏輯,針對每個頁面進行個別的執行內容。

使用 Cloudflare Workers 之前

在 Cloudflare Workers 推出之前,開發人員主要可在兩個地方部署程式碼:

  • 在使用者裝置上執行的前端程式碼
  • 在中央資料中心執行的後端程式碼

而上述這兩種方式都有著一些致命的缺點。因此,Cloudflare Workers 為開發人員提供了接近顧客的第三個地方來部署他們的程式碼:Cloudflare 不斷擴大的全球網路的邊緣。

如此就納入了雲端資料中心的能力與彈性,以及大量傳輸系統的備援能力,而且僅在毫秒之間就能傳給幾乎每一位網際網路使用者。

開發人員現在可以打造日趨複雜且充滿動態的應用程式,以滿足消費者需求:更具個人化與彈性的豐富環境。現在客戶可以將現有投資最大化,聚焦於:

  • 減少對來源基礎結構的依賴
  • 增進快取命中率
  • 簡化應用程式與數量日增的 API 通訊的方式
  • 為任何裝置或網路上的使用者提供更良好的使用者體驗
  • 減少惡意傀儡程式對其基礎結構的衝擊

那麼,應用 Cloudflare Workers 在 WordPress 有什麼好處

根據上述我們對 Workers 工作原理的理解,我們可以讓以往需要手動清除 Cloudflare 過期快取的操作,改由透過 Workers 自動即時更新最新的內容、更新快取,加快這一過程與減少人為的介入。

此外,我們也能透過 Cloudflare Workers 在 Cloudflare 的邊緣當中加入 JavaScript 程式碼,讓我們得以實現各種爬蟲、惡意程式的阻擋。更進一步,我們還可以自定義使用者的 Cookies 邏輯,讓以往我們僅能夠過快取外掛來判斷使用者是否是登入狀態來進行顯示快取與否,改由 Workers 來自動判別現在的使用者登入狀態。

如此除了能夠確實的辨別已知與未知使用者所需進行的相對應的處理之外,也能夠有效的使我們的 WordPress 網站更加的安全。

Cloudflare Workers 教學

在應用 Cloudflare Workers 功能之前,請先確保你的網站目前使使用 Cloudflare 的服務,若你尚未使用 Cloudflare 進行 DNS 的代管,可以先參考我們先前的 Cloudflare 介紹

步驟一

安裝 Cloudflare Page Cache 外掛

首先,在我們的 WordPress 網站當中,需要先安裝一個外掛「Cloudflare Page Cache」:

步驟二

啟用 Cloudflare Workers 功能

接著我們前往 Cloudflare 控制台 > Workers 頁面,點選「Launch Editor」打開編輯界面:

在 Cloudflare 後台當中選取 Workers 頁面啟用在 Cloudflare 後台當中選取 Workers 頁面啟用
步驟三

新增 Script

接著我們點選「Add Sciprt」來新增腳本:

點擊「Add Script」新增腳本點擊「Add Script」新增腳本

接著給予這個 Script 一個名稱,這個名稱隨便取都可以,我們在這裡取「techmoon-edge-cache」:

設定 Workers 當中 Script 的名稱設定 Workers 當中 Script 的名稱

新增完一個 Script 之後,就能在左側列表當中看當我們剛剛新增名為「techmoon-edge-cache」的 Script,點選「Edit」就能進行編輯:

點選「Edit」編輯剛剛新增的 Script點選「Edit」編輯剛剛新增的 Script

進入後,Script 裡面會有預設的程式碼,我們先清空裡面的內容:

先將 Script 當中預設的程式碼刪除先將 Script 當中預設的程式碼刪除

接著我們借鑑在 Github 上,Workers 的 Script 程式碼,將所有內容貼至我們所建立的 Script 當中:

JS
// IMPORTANT: Either A Key/Value Namespace must be bound to this worker script // using the variable name EDGE_CACHE. or the API parameters below should be // configured. KV is recommended if possible since it can purge just the HTML // instead of the full cache. // API settings if KV isn't being used const CLOUDFLARE_API = { email: "", // From https://dash.cloudflare.com/profile key: "", // Global API Key from https://dash.cloudflare.com/profile zone: "" // "Zone ID" from the API section of the dashboard overview page https://dash.cloudflare.com/ }; // Default cookie prefixes for bypass const DEFAULT_BYPASS_COOKIES = [ "wp-", "wordpress", "comment_", "woocommerce_" ]; /** * Main worker entry point. */ addEventListener("fetch", event => { const request = event.request; let upstreamCache = request.headers.get('x-HTML-Edge-Cache'); // Only process requests if KV store is set up and there is no // HTML edge cache in front of this worker (only the outermost cache // should handle HTML caching in case there are varying levels of support). let configured = false; if (typeof EDGE_CACHE !== 'undefined') { configured = true; } else if (CLOUDFLARE_API.email.length && CLOUDFLARE_API.key.length && CLOUDFLARE_API.zone.length) { configured = true; } // Bypass processing of image requests (for everything except Firefox which doesn't use image/*) const accept = request.headers.get('Accept'); let isImage = false; if (accept && (accept.indexOf('image/*') !== -1)) { isImage = true; } if (configured && !isImage && upstreamCache === null) { event.passThroughOnException(); event.respondWith(processRequest(request, event)); } }); /** * Process every request coming through to add the edge-cache header, * watch for purge responses and possibly cache HTML GET requests. * * @param {Request} originalRequest – Original request * @param {Event} event – Original event (for additional async waiting) */ async function processRequest(originalRequest, event) { let cfCacheStatus = null; const accept = originalRequest.headers.get('Accept'); const isHTML = (accept && accept.indexOf('text/html') >= 0); let {response, cacheVer, status, bypassCache} = await getCachedResponse(originalRequest); if (response === null) { // Clone the request, add the edge-cache header and send it through. let request = new Request(originalRequest); request.headers.set('x-HTML-Edge-Cache', 'supports=cache|purgeall|bypass-cookies'); response = await fetch(request); if (response) { const options = getResponseOptions(response); if (options && options.purge) { await purgeCache(cacheVer, event); status += ', Purged'; } bypassCache = bypassCache || shouldBypassEdgeCache(request, response); if ((!options || options.cache) && isHTML && originalRequest.method === 'GET' && response.status === 200 && !bypassCache) { status += await cacheResponse(cacheVer, originalRequest, response, event); } } } else { // If the origin didn't send the control header we will send the cached response but update // the cached copy asynchronously (stale-while-revalidate). This commonly happens with // a server-side disk cache that serves the HTML directly from disk. cfCacheStatus = 'HIT'; if (originalRequest.method === 'GET' && response.status === 200 && isHTML) { bypassCache = bypassCache || shouldBypassEdgeCache(originalRequest, response); if (!bypassCache) { const options = getResponseOptions(response); if (!options) { status += ', Refreshed'; event.waitUntil(updateCache(originalRequest, cacheVer, event)); } } } } if (response && status !== null && originalRequest.method === 'GET' && response.status === 200 && isHTML) { response = new Response(response.body, response); response.headers.set('x-HTML-Edge-Cache-Status', status); if (cacheVer !== null) { response.headers.set('x-HTML-Edge-Cache-Version', cacheVer.toString()); } if (cfCacheStatus) { response.headers.set('CF-Cache-Status', cfCacheStatus); } } return response; } /** * Determine if the cache should be bypassed for the given request/response pair. * Specifically, if the request includes a cookie that the response flags for bypass. * Can be used on cache lookups to determine if the request needs to go to the origin and * origin responses to determine if they should be written to cache. * @param {Request} request – Request * @param {Response} response – Response * @returns {bool} true if the cache should be bypassed */ function shouldBypassEdgeCache(request, response) { let bypassCache = false; if (request && response) { const options = getResponseOptions(response); const cookieHeader = request.headers.get('cookie'); let bypassCookies = DEFAULT_BYPASS_COOKIES; if (options) { bypassCookies = options.bypassCookies; } if (cookieHeader && cookieHeader.length && bypassCookies.length) { const cookies = cookieHeader.split(';'); for (let cookie of cookies) { // See if the cookie starts with any of the logged-in user prefixes for (let prefix of bypassCookies) { if (cookie.trim().startsWith(prefix)) { bypassCache = true; break; } } if (bypassCache) { break; } } } } return bypassCache; } const CACHE_HEADERS = ['Cache-Control', 'Expires', 'Pragma']; /** * Check for cached HTML GET requests. * * @param {Request} request – Original request */ async function getCachedResponse(request) { let response = null; let cacheVer = null; let bypassCache = false; let status = 'Miss'; // Only check for HTML GET requests (saves on reading from KV unnecessarily) // and not when there are cache-control headers on the request (refresh) const accept = request.headers.get('Accept'); const cacheControl = request.headers.get('Cache-Control'); let noCache = false; if (cacheControl && cacheControl.indexOf('no-cache') !== -1) { noCache = true; status = 'Bypass for Reload'; } if (!noCache && request.method === 'GET' && accept && accept.indexOf('text/html') >= 0) { // Build the versioned URL for checking the cache cacheVer = await GetCurrentCacheVersion(cacheVer); const cacheKeyRequest = GenerateCacheRequest(request, cacheVer); // See if there is a request match in the cache try { let cache = caches.default; let cachedResponse = await cache.match(cacheKeyRequest); if (cachedResponse) { // Copy Response object so that we can edit headers. cachedResponse = new Response(cachedResponse.body, cachedResponse); // Check to see if the response needs to be bypassed because of a cookie bypassCache = shouldBypassEdgeCache(request, cachedResponse); // Copy the original cache headers back and clean up any control headers if (bypassCache) { status = 'Bypass Cookie'; } else { status = 'Hit'; cachedResponse.headers.delete('Cache-Control'); cachedResponse.headers.delete('x-HTML-Edge-Cache-Status'); for (header of CACHE_HEADERS) { let value = cachedResponse.headers.get('x-HTML-Edge-Cache-Header-' + header); if (value) { cachedResponse.headers.delete('x-HTML-Edge-Cache-Header-' + header); cachedResponse.headers.set(header, value); } } response = cachedResponse; } } else { status = 'Miss'; } } catch (err) { // Send the exception back in the response header for debugging status = "Cache Read Exception: " + err.message; } } return {response, cacheVer, status, bypassCache}; } /** * Asynchronously purge the HTML cache. * @param {Int} cacheVer – Current cache version (if retrieved) * @param {Event} event – Original event */ async function purgeCache(cacheVer, event) { if (typeof EDGE_CACHE !== 'undefined') { // Purge the KV cache by bumping the version number cacheVer = await GetCurrentCacheVersion(cacheVer); cacheVer++; event.waitUntil(EDGE_CACHE.put('html_cache_version', cacheVer.toString())); } else { // Purge everything using the API const url = "https://api.cloudflare.com/client/v4/zones/" + CLOUDFLARE_API.zone + "/purge_cache"; event.waitUntil(fetch(url,{ method: 'POST', headers: {'X-Auth-Email': CLOUDFLARE_API.email, 'X-Auth-Key': CLOUDFLARE_API.key, 'Content-Type': 'application/json'}, body: JSON.stringify({purge_everything: true}) })); } } /** * Update the cached copy of the given page * @param {Request} originalRequest – Original Request * @param {String} cacheVer – Cache Version * @param {EVent} event – Original event */ async function updateCache(originalRequest, cacheVer, event) { // Clone the request, add the edge-cache header and send it through. let request = new Request(originalRequest); request.headers.set('x-HTML-Edge-Cache', 'supports=cache|purgeall|bypass-cookies'); response = await fetch(request); if (response) { status = ': Fetched'; const options = getResponseOptions(response); if (options && options.purge) { await purgeCache(cacheVer, event); } let bypassCache = shouldBypassEdgeCache(request, response); if ((!options || options.cache) && !bypassCache) { await cacheResponse(cacheVer, originalRequest, response, event); } } } /** * Cache the returned content (but only if it was a successful GET request) * * @param {Int} cacheVer – Current cache version (if already retrieved) * @param {Request} request – Original Request * @param {Response} originalResponse – Response to (maybe) cache * @param {Event} event – Original event * @returns {bool} true if the response was cached */ async function cacheResponse(cacheVer, request, originalResponse, event) { let status = ""; const accept = request.headers.get('Accept'); if (request.method === 'GET' && originalResponse.status === 200 && accept && accept.indexOf('text/html') >= 0) { cacheVer = await GetCurrentCacheVersion(cacheVer); const cacheKeyRequest = GenerateCacheRequest(request, cacheVer); try { // Move the cache headers out of the way so the response can actually be cached. // First clone the response so there is a parallel body stream and then // create a new response object based on the clone that we can edit. let cache = caches.default; let clonedResponse = originalResponse.clone(); let response = new Response(clonedResponse.body, clonedResponse); for (header of CACHE_HEADERS) { let value = response.headers.get(header); if (value) { response.headers.delete(header); response.headers.set('x-HTML-Edge-Cache-Header-' + header, value); } } response.headers.delete('Set-Cookie'); response.headers.set('Cache-Control', 'public; max-age=315360000'); event.waitUntil(cache.put(cacheKeyRequest, response)); status = ", Cached"; } catch (err) { // status = ", Cache Write Exception: " + err.message; } } return status; } /****************************************************************************** * Utility Functions *****************************************************************************/ /** * Parse the commands from the x-HTML-Edge-Cache response header. * @param {Response} response – HTTP response from the origin. * @returns {*} Parsed commands */ function getResponseOptions(response) { let options = null; let header = response.headers.get('x-HTML-Edge-Cache'); if (header) { options = { purge: false, cache: false, bypassCookies: [] }; let commands = header.split(','); for (let command of commands) { if (command.trim() === 'purgeall') { options.purge = true; } else if (command.trim() === 'cache') { options.cache = true; } else if (command.trim().startsWith('bypass-cookies')) { let separator = command.indexOf('='); if (separator >= 0) { let cookies = command.substr(separator + 1).split('|'); for (let cookie of cookies) { cookie = cookie.trim(); if (cookie.length) { options.bypassCookies.push(cookie); } } } } } } return options; } /** * Retrieve the current cache version from KV * @param {Int} cacheVer – Current cache version value if set. * @returns {Int} The current cache version. */ async function GetCurrentCacheVersion(cacheVer) { if (cacheVer === null) { if (typeof EDGE_CACHE !== 'undefined') { cacheVer = await EDGE_CACHE.get('html_cache_version'); if (cacheVer === null) { // Uninitialized – first time through, initialize KV with a value // Blocking but should only happen immediately after worker activation. cacheVer = 0; await EDGE_CACHE.put('html_cache_version', cacheVer.toString()); } else { cacheVer = parseInt(cacheVer); } } else { cacheVer = -1; } } return cacheVer; } /** * Generate the versioned Request object to use for cache operations. * @param {Request} request – Base request * @param {Int} cacheVer – Current Cache version (must be set) * @returns {Request} Versioned request object */ function GenerateCacheRequest(request, cacheVer) { let cacheUrl = request.url; if (cacheUrl.indexOf('?') >= 0) { cacheUrl += '&'; } else { cacheUrl += '?'; } cacheUrl += 'cf_edge_cache_ver=' + cacheVer; return new Request(cacheUrl); }

貼過來之後,記得要在其中的三個變數當中,填入你的 Cloudflare Credentials。這三個變數分別為:

  • email:這裡填入你的 Cloudflare 帳號(註冊的 Email 信箱)
  • key:這裡要填入 Cloudflare 的 Global API Key。你可以在 Cloudflare 帳號的「My Profile」頁面底下找到「Global API Key」,一個帳號僅有一個 Global API Key。
  • zone:這裡填入你所要啟用的網站的 Zone ID,每一個由 Cloudflare 託管的網站都會有一個獨立的 Zone ID。你可以在「Overview」頁面當中,的右下方找到該網址的 Zone ID。
貼上程式碼之後,需要分別輸入你的 Email、Global API Key 與 Zone ID貼上程式碼之後,需要分別輸入你的 Email、Global API Key 與 Zone ID

當我們按下「Save」儲存後,會跳出提示「This Worker won’t be deployed yet until you assign it to a route.」,這是因為我們還需要指派 Route 才能真正的啟用這個 Script 的功能。

儲存 Script 後,需要指定 Route 才能真正的啟用剛剛設定的腳本規則儲存 Script 後,需要指定 Route 才能真正的啟用剛剛設定的腳本規則

因此,我們在剛剛「Scripts」右邊點選「Routes」選項,接著點擊「Add Route」新增一個 Route:

到「Routes」點擊「Add Route」新增一個 Route到「Routes」點擊「Add Route」新增一個 Route

我們在 Route 當中右邊 Script 的部分,選擇剛剛的「techmoon-edge-cache」,左側則是填入你要應用的網址。

由於我們要應用在整個網站,因此左側這裡我們填入「techmoon.xyz/*」表示我們應用在整個網域當中。

左側填入要應用的網域,右側則是指定剛剛先增的 Script左側填入要應用的網域,右側則是指定剛剛先增的 Script

儲存之後就大功告成了!

Workers 優化成果展示

最後,我們要來檢測一下剛剛所設定的 Workers 是否有確實的啟用快取。因此,我們可以透過 Chrome 瀏覽器,在頁面上點選滑鼠右鍵「檢查」,開啟開發人員工具,或是直接按下 F12 也能啟用。接著選擇「Network」分頁之後,在重整一次網頁,就可以獲取網站的載入資訊:

Cloudflare 快取狀態Cloudflare 快取狀態
html edge 快取狀態html edge 快取狀態

從上面兩張圖可以看出,我們的 Cloudflare 快取以及 html edge 快取狀態都顯示為「HIT」,即表示快取功能已經正式的部屬成功。

如果你喜歡今天的 Cloudflare Workers 教學,歡迎幫我們分享出去,若有任何想和我們說的,也歡迎在下方留下評論。

猜你喜歡



熱門推薦

本文由 techmoonxyz 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦