【網路應用】透過 Cloudflare Worker 反向代理網站

Cloudflare Worker 基於  Google Chrome V8 JavaScript 運行,旨在邊緣執行程式碼,提供强大的網路延展能力。主要原理是透過 fetch 攔截通往該網域(子網域)的流量,並且在修改後給出指定的 respond 回應。

這次來玩的酷酷東西就是透過修改 respond 來達成利用 Cloudflare Worker 反向代理網站的應用,還可以使用自己的網域、同時針對指定國家或 IP 進行封鎖。

實作步驟

前往 Cloudflare Workers 管理頁面,點選「建立服務」
服務名稱可以依照自己喜好而定,啟動器選擇「HTTP 處理常式」即可,完成後就可以按下「建立服務」
進去後,點選「快速編輯」,將下方程式碼貼上到編輯區
(參數請依照自身用途調整,示範代理網站為 github.com
 // 你要反代的網址
const upstream = 'github.com'

// 你要反代的網址目錄(如果是需要反代 github.com/about 就輸入 /about)
const upstream_path = '/'

// 手機版使用者的反代網站(透過 device_status 函式判斷 UA)
const upstream_mobile = 'docs.github.com'

// 封鎖國家國碼
const blocked_region = ['CN', 'JP']

// 封鎖 IP
const blocked_ip_address = ['1.1.1.1']

// 反代對象網站是否使用 HTTPS
const https = true

// 是否開啟快取
const disable_cache = false

// 反代設定字串取代,不知道可以不用寫
const replace_dict = {
    '$upstream': '$custom_domain',
}

addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event.request));
})

async function fetchAndApply(request) {
    const region = request.headers.get('cf-ipcountry').toUpperCase();
    const ip_address = request.headers.get('cf-connecting-ip');
    const user_agent = request.headers.get('user-agent');

    let response = null;
    let url = new URL(request.url);
    let url_hostname = url.hostname;

    if (https == true) {
        url.protocol = 'https:';
    } else {
        url.protocol = 'http:';
    }

    if (await device_status(user_agent)) {
        var upstream_domain = upstream;
    } else {
        var upstream_domain = upstream_mobile;
    }

    url.host = upstream_domain;
    if (url.pathname == '/') {
        url.pathname = upstream_path;
    } else {
        url.pathname = upstream_path + url.pathname;
    }

    if (blocked_region.includes(region)) {
        response = new Response('您所在的國家已被封鎖', {
            status: 403
        });
    } else if (blocked_ip_address.includes(ip_address)) {
        response = new Response('您使用的 IP 已被封鎖', {
            status: 403
        });
    } else {
        let method = request.method;
        let request_headers = request.headers;
        let new_request_headers = new Headers(request_headers);

        new_request_headers.set('Host', upstream_domain);
        new_request_headers.set('Referer', url.protocol + '//' + url_hostname);

        let original_response = await fetch(url.href, {
            method: method,
            headers: new_request_headers
        })

        connection_upgrade = new_request_headers.get("Upgrade");
        if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") {
            return original_response;
        }

        let original_response_clone = original_response.clone();
        let original_text = null;
        let response_headers = original_response.headers;
        let new_response_headers = new Headers(response_headers);
        let status = original_response.status;

		if (disable_cache) {
			new_response_headers.set('Cache-Control', 'no-store');
	    }

        new_response_headers.set('access-control-allow-origin', '*');
        new_response_headers.set('access-control-allow-credentials', true);
        new_response_headers.delete('content-security-policy');
        new_response_headers.delete('content-security-policy-report-only');
        new_response_headers.delete('clear-site-data');

		if (new_response_headers.get("x-pjax-url")) {
            new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname));
        }

        const content_type = new_response_headers.get('content-type');
        if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) {
            original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
        } else {
            original_text = original_response_clone.body
        }

        response = new Response(original_text, {
            status,
            headers: new_response_headers
        })
    }
    return response;
}

async function replace_response_text(response, upstream_domain, host_name) {
    let text = await response.text()

    var i, j;
    for (i in replace_dict) {
        j = replace_dict[i]
        if (i == '$upstream') {
            i = upstream_domain
        } else if (i == '$custom_domain') {
            i = host_name
        }

        if (j == '$upstream') {
            j = upstream_domain
        } else if (j == '$custom_domain') {
            j = host_name
        }

        let re = new RegExp(i, 'g')
        text = text.replace(re, j);
    }
    return text;
}


async function device_status(user_agent_info) {
    var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
    var flag = true;
    for (var v = 0; v < agents.length; v++) {
        if (user_agent_info.indexOf(agents[v]) > 0) {
            flag = false;
            break;
        }
    }
    return flag;
}
確定更改完成後點擊下方「儲存與部署」後確認,你就可以透過他提供的網址先看看結果咯!
目前我們確定電腦版的 https://github.cloudflare8101.workers.dev 已經可以成功反代 github.com
手機版瀏覽 https://github.cloudflare8101.workers.dev 就會到 docs.github.com ^W^
這邊用的 UA 是 Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; SCH-I535 Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
想要用自訂網域的話,可以到「觸發程序」新增自己的網域唷!前提是網域要先在 Cloudflare DNS 託管下!這邊以 rgithub.kangjw.me 為例,新增後需要至多 24 小時來設定及簽發憑證。
這樣就完成了,就可以到 rgithub.kangjw.me 看咯
有趣吧~趕緊來試試看吧~

2 則留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *