前言#
jsDelivr —— 一個免費、快速、可靠的為 JS 和開源項目服務的 CDN。但是由於國內訪問速度慢,加之 jsDelivr 備案被吊銷和 DNS 污染常出現,國內使用了 jsDelivr 的站點常常出現訪問速度慢的問題。
你可能會問,為什麼不去使用別人的鏡像站呢?俗話說 "自己動手,豐衣足食",自建鏡像站可以讓你更好地掌握自己的數據和服務。使用第三方鏡像站存在一些潛在的風險和不確定性。首先,你無法確定第三方鏡像站的可靠性和穩定性,它們可能在任何時候關閉或遭遇故障。這將對你的站點造成嚴重影響,可能導致服務中斷。其次,第三方鏡像站可能無法及時更新和同步你的站點內容,導致訪問者無法獲取最新的信息。最後,自建鏡像站可以讓你更加靈活地定制和管理自己的站點,滿足特定需求和提供更好的用戶體驗。因此,建議在可行的情況下,考慮自建鏡像站來確保數據的安全和可靠性。
方法一:使用又拍雲搭建#
優點:#
- 又拍雲是國內知名企業級雲服務商,其服務極速、穩定、簡單易用,用來搭建鏡像站是十分合適的。又拍雲有非常多的優惠:加入又拍雲聯盟 (網站需備案,要在網站 / 應用底部添加又拍雲 LOGO 並指向官網) 後,可以獲得每月 10G 儲存空間和 15G 的 CDN 流量 (按年發放,以 67 元的代金卷發放到账戶);新用戶註冊 (點我註冊) 還可以直接獲得 61 元的代金卷 (有效期一年)。在這些優惠的加成下,你可以零成本搭建一個鏡像站。
2. 加速效果明顯 (如圖)
加速前:
加速後:
缺點:#
需要備案!!!
推薦指數:⭐⭐⭐⭐⭐#
搭建方法:#
第一步:#
創建又拍雲賬戶 (點我註冊)
第二步:#
進入控制台的 CDN 管理界面 (https://console.upyun.com/services/cdn/), 並創建服務,然後配置服務 (具體參數如下)。
- 服務名稱:唯一標識服務,例如:image-upyun-com,一個服務下面可以綁定多個自有域名。
注意事項
服務名稱僅限 5~20 位; 必須以小寫英文字符開頭,僅支持小寫英文字符、數字、中劃線組合。
- 加速域名:填寫此次需要配置的加速域名。
注意事項
加速域名必須已在工信部備案; 待加速域名尚未在又拍雲 CDN 平台配置。
加速域名需要進行域名所有權驗證,驗證通過後方能添加成功。
- 應用場景:這裡選擇全站加速
- 回源協議:選協議跟隨。
- 源站證書校驗:切記!!不要打開 (實測開了以後不能用)!!!
- 线路配置:源站地址填
cdn.jsdelivr.net
, 端口號不要動。
第三步#
- 服務創建成功後,操作界面會提示 CDN 加速服務創建成功,並會自動跳轉到該服務的【功能配置】界面
- 在功能配置界面,有域名管理、回源管理、緩存配置、性能優化、HTTPS、訪問控制、圖片處理等功能配置模塊,在【域名管理】模塊下,可以針對該服務綁定多個自有域名,請耐心等待域名配置(約 10 分鐘),查看域名對應的狀態是否為[正常]
- 可查看 CDN 平台為您分配的 CNAME 地址,此時需要去域名 DNS 解析商處,為該域名添加一條 CNAME 記錄,待 CNAME 配置生效之後,方可使用 CDN 服務。
第四步 (非必要,但是可以提升體驗)#
-
配置 HTTPS: 點擊 [HTTPS], 點擊 "HTTPS 配置" 右邊的管理 (如圖)然後你可以選擇添加自有證書或直接使用又拍雲註冊證書 (點我前往證書管理)。添加完後,打開 [HTTPS 訪問],不建議勾選 [強制 HTTPS 訪問]。
-
建議打開智能壓縮和頁面壓縮 (如圖)
-
建議配置緩存、瀏覽器緩存和分段緩存 (如圖)
-
在 [訪問控制] 中,打開 Referer 防盜鏈、WAF 保護、HTTP 請求體大小限制、IP 訪問限制 (如圖)
此配置僅作參考,請根據實際情況進行配置!
-
你還可以配置 [邊緣規則],來實現一些特殊效果。(這裡不做演示)
方法二:使用 Cloudflare workers 搭建#
優點:#
- 簡單,步驟少,加速範圍廣
- 無需備案,加速效果較好 (如圖)
加速前:
加速後: - 無費用,每天有免費額度 (每天免費 100000 個請求)
缺點:#
- 部分地區訪問較慢
- 免費額度較少,對於訪問量大的站點可能不夠
- 需要有一個掛在 cloudflare 的域名
推薦指數:⭐⭐⭐⭐#
搭建方法#
第一步#
- 在 Cloudflare 管理面板的 [Workers 和 Pages] 欄 ->[概述]->[創建應用程序]->[創建 Worker](如圖)
- 隨便填一個名稱,點擊 [部署](如圖)
第二步#
- 在創建完成的頁面點擊 [編輯代碼](如圖)
- 將以下代碼直接粘貼進代碼框:
'use strict'
/**
* static files (404.html, sw.js, conf.js)
*/
const ASSET_URL = 'https://cdn.jsdelivr.net'
const JS_VER = 10
const MAX_RETRY = 1
/** @type {RequestInit} */
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
}
/**
* @param {any} body
* @param {number} status
* @param {Object<string, string>} headers
*/
function makeRes(body, status = 200, headers = {}) {
headers['--ver'] = JS_VER
headers['access-control-allow-origin'] = '*'
return new Response(body, {status, headers})
}
/**
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}
addEventListener('fetch', e => {
const ret = fetchHandler(e)
.catch(err => makeRes('cfworker error:\n' + err.stack, 502))
e.respondWith(ret)
})
/**
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const req = e.request
const urlStr = req.url
const urlObj = new URL(urlStr)
const path = urlObj.href.substr(urlObj.origin.length)
if (urlObj.protocol === 'http:') {
urlObj.protocol = 'https:'
return makeRes('', 301, {
'strict-transport-security': 'max-age=99999999; includeSubDomains; preload',
'location': urlObj.href,
})
}
if (path.startsWith('/http/')) {
return httpHandler(req, path.substr(6))
}
switch (path) {
case '/http':
return makeRes('請更新 cfworker 到最新版本!')
case '/ws':
return makeRes('not support', 400)
case '/works':
return makeRes('it works')
default:
// static files
return fetch(ASSET_URL + path)
}
}
/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers
if (reqHdrRaw.has('x-jsproxy')) {
return Response.error()
}
// preflight
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT)
}
let acehOld = false
let rawSvr = ''
let rawLen = ''
let rawEtag = ''
const reqHdrNew = new Headers(reqHdrRaw)
reqHdrNew.set('x-jsproxy', '1')
// 此處邏輯和 http-dec-req-hdr.lua 大致相同
// https://github.com/EtherDream/jsproxy/blob/master/lua/http-dec-req-hdr.lua
const refer = reqHdrNew.get('referer')
const query = refer.substr(refer.indexOf('?') + 1)
if (!query) {
return makeRes('missing params', 403)
}
const param = new URLSearchParams(query)
for (const [k, v] of Object.entries(param)) {
if (k.substr(0, 2) === '--') {
// 系統信息
switch (k.substr(2)) {
case 'aceh':
acehOld = true
break
case 'raw-info':
[rawSvr, rawLen, rawEtag] = v.split('|')
break
}
} else {
// 還原 HTTP 請求頭
if (v) {
reqHdrNew.set(k, v)
} else {
reqHdrNew.delete(k)
}
}
}
if (!param.has('referer')) {
reqHdrNew.delete('referer')
}
// cfworker 會把路徑中的 `//` 合併成 `/`
const urlStr = pathname.replace(/^(https?):\/+/, '$1://')
const urlObj = newUrl(urlStr)
if (!urlObj) {
return makeRes('invalid proxy url: ' + urlStr, 403)
}
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'manual',
}
if (req.method === 'POST') {
reqInit.body = req.body
}
return proxy(urlObj, reqInit, acehOld, rawLen, 0)
}
/**
*
* @param {URL} urlObj
* @param {RequestInit} reqInit
* @param {number} retryTimes
*/
async function proxy(urlObj, reqInit, acehOld, rawLen, retryTimes) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)
let expose = '*'
for (const [k, v] of resHdrOld.entries()) {
if (k === 'access-control-allow-origin' ||
k === 'access-control-expose-headers' ||
k === 'location' ||
k === 'set-cookie'
) {
const x = '--' + k
resHdrNew.set(x, v)
if (acehOld) {
expose = expose + ',' + x
}
resHdrNew.delete(k)
}
else if (acehOld &&
k !== 'cache-control' &&
k !== 'content-language' &&
k !== 'content-type' &&
k !== 'expires' &&
k !== 'last-modified' &&
k !== 'pragma'
) {
expose = expose + ',' + k
}
}
if (acehOld) {
expose = expose + ',--s'
resHdrNew.set('--t', '1')
}
// verify
if (rawLen) {
const newLen = resHdrOld.get('content-length') || ''
const badLen = (rawLen !== newLen)
if (badLen) {
if (retryTimes < MAX_RETRY) {
urlObj = await parseYtVideoRedir(urlObj, newLen, res)
if (urlObj) {
return proxy(urlObj, reqInit, acehOld, rawLen, retryTimes + 1)
}
}
return makeRes(res.body, 400, {
'--error': `bad len: ${newLen}, except: ${rawLen}`,
'access-control-expose-headers': '--error',
})
}
if (retryTimes > 1) {
resHdrNew.set('--retry', retryTimes)
}
}
let status = res.status
resHdrNew.set('access-control-expose-headers', expose)
resHdrNew.set('access-control-allow-origin', '*')
resHdrNew.set('--s', status)
resHdrNew.set('--ver', JS_VER)
resHdrNew.delete('content-security-policy')
resHdrNew.delete('content-security-policy-report-only')
resHdrNew.delete('clear-site-data')
if (status === 301 ||
status === 302 ||
status === 303 ||
status === 307 ||
status === 308
) {
status = status + 10
}
return new Response(res.body, {
status,
headers: resHdrNew,
})
}
/**
* @param {URL} urlObj
*/
function isYtUrl(urlObj) {
return (
urlObj.host.endsWith('.googlevideo.com') &&
urlObj.pathname.startsWith('/videoplayback')
)
}
/**
* @param {URL} urlObj
* @param {number} newLen
* @param {Response} res
*/
async function parseYtVideoRedir(urlObj, newLen, res) {
if (newLen > 2000) {
return null
}
if (!isYtUrl(urlObj)) {
return null
}
try {
const data = await res.text()
urlObj = new URL(data)
} catch (err) {
return null
}
if (!isYtUrl(urlObj)) {
return null
}
return urlObj
}
3. 點擊 [保存並部署]。
第三步#
回到 [概述],點擊你剛才創建的項目,點擊 [觸發器],點擊 [添加自定義域],填入一個你賬戶中的域名或其子域名,點擊 [添加自定義域],完成搭建。
方法三:使用 nginx#
優點:簡單#
缺點:#
加速範圍不廣,速度受到伺服器性能和速度限制。
推薦指數:⭐⭐⭐#
新建一個網站,然後把下面內容覆蓋到 nginx 配置文件
server {
listen 80;
listen 443 ssl http2;
# 請更改為你的證書路徑
ssl_certificate fullchain.pem;
ssl_certificate_key privkey.pem;
# 請更改為你的鏡像域名
server_name jsd.ucbk.cn;
location / {
proxy_pass https://cdn.jsdelivr.net;
proxy_set_header Host $proxy_host;
proxy_set_header Accept-Encoding '';
proxy_ssl_server_name on;
proxy_ssl_name $proxy_host;
proxy_redirect / /;
# Proxy Cache
proxy_cache jsdelivr;
proxy_cache_lock on;
proxy_cache_lock_timeout 15s;
proxy_cache_use_stale updating;
proxy_cache_background_update on;
proxy_cache_key $host$request_uri;
proxy_cache_valid 200 301 302 30d;
proxy_cache_valid 500 501 502 503 15s;
proxy_cache_valid any 5m;
# Replace Domain
sub_filter_once off;
sub_filter_types application/javascript application/json text/xml text/css;
sub_filter '$proxy_host' '$host';
}
}
# 緩存路徑請根據需要更改
proxy_cache_path /var/tmp/nginx/jsdelivr levels=1:2 use_temp_path=off keys_zone=jsdelivr:300m inactive=30d max_size=30g;
之後直接把網站涉及到 cdn.jsdelivr.net
都改成你自己的就可以了,如果不想改的話直接在你網站 nginx 配置中加以下內容就可以了
sub_filter_once off;
sub_filter_types application/javascript application/json text/xml text/css;
sub_filter 'cdn.jsdelivr.net' 'jsd.ucbk.cn';
結尾#
非常感謝你能看到這裡,如有什麼問題歡迎在評論區中提出!