Skip to content

Instantly share code, notes, and snippets.

@phlinhng
Last active July 16, 2021 02:35
Show Gist options
  • Select an option

  • Save phlinhng/53855b8d39c0291dfda8b6f0722ad1d8 to your computer and use it in GitHub Desktop.

Select an option

Save phlinhng/53855b8d39c0291dfda8b6f0722ad1d8 to your computer and use it in GitHub Desktop.

Revisions

  1. phlinhng revised this gist Jul 16, 2021. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions cfworkers_reverse_proxy.js
    Original file line number Diff line number Diff line change
    @@ -23,6 +23,7 @@ const contentTypes = [
    // 反代网站中其他需要被替换的地址
    const replaceDict = {
    '$upstream': '$workerDomain',
    'github.com': 'hub.fastgit.org'
    }

    addEventListener('fetch', event => {
  2. phlinhng created this gist Jul 16, 2021.
    152 changes: 152 additions & 0 deletions cfworkers_reverse_proxy.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    // 需要反代的地址
    const upstream = 'api.github.com'
    // 反代地址的子路径
    const upstreamPath = '/'
    // 反代网站的移动端域名
    const upstreamMobile = 'api.github.com'

    // 是否使用 https
    const useHttps = true

    // 禁止使用该 worker 的国家代码
    const blockedRegion = ['CN']

    // 禁止使用该 worker 的 ip 地址
    const blockedIp = ['0.0.0.0', '127.0.0.1']

    // 是否关闭缓存
    const disableCache = false
    // 替换条件
    const contentTypes = [
    'application/json'
    ]
    // 反代网站中其他需要被替换的地址
    const replaceDict = {
    '$upstream': '$workerDomain',
    }

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

    /**
    * Respond to the request
    * @param {Request} request
    */
    async function handleRequest(request) {
    const region = request.headers.get('cf-ipcountry') || '';
    const ip = request.headers.get('cf-connecting-ip');

    if (blockedRegion.includes(region.toUpperCase())) {
    return new Response('Access denied: WorkersProxy is not available in your region yet.', {
    status: 403
    });
    }

    if (blockedIp.includes(ip)) {
    return new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
    status: 403
    });
    }

    const upstreamDomain = isMobile(request.headers.get('user-agent')) ? upstreamMobile : upstream;

    // 构建上游请求地址
    let url = new URL(request.url);
    const workerDomain = url.host;

    url.protocol = useHttps ? 'https:' : 'http';
    url.pathname = url.pathname === '/' ? upstreamPath : upstreamPath + url.pathname;
    url.host = upstreamDomain;

    // 构建上游请求头
    const newRequestHeaders = new Headers(request.headers);
    newRequestHeaders.set('Host', upstreamDomain);
    newRequestHeaders.set('Referer', url.protocol + '//' + workerDomain);

    // 获取上游响应
    const originalResponse = await fetch(url.href, {
    method: request.method,
    headers: newRequestHeaders
    })

    const connectionUpgrade = newRequestHeaders.get("Upgrade");
    if (connectionUpgrade && connectionUpgrade.toLowerCase() === "websocket") {
    return originalResponse;
    }

    let originalResponseClone = originalResponse.clone();

    // 构建响应头
    let responseHeaders = originalResponseClone.headers;
    let newResponseHeaders = buildResponseHeaders(responseHeaders);
    if (newResponseHeaders.get("x-pjax-url")) {
    newResponseHeaders.set("x-pjax-url", responseHeaders.get("x-pjax-url").replace("//" + upstreamDomain, "//" + workerDomain));
    }

    // 构建响应体
    let originalText;
    const contentType = newResponseHeaders.get('content-type');
    if (contentType != null) {
    const types = contentType.replace(' ','').split(';')
    if (types.includes('charset=utf-8')){
    for (let i of contentTypes) {
    if (types.includes(i)){
    originalText = await replaceResponseText(originalResponseClone, upstreamDomain, workerDomain);
    break
    }
    }
    }
    } else {
    originalText = originalResponseClone.body
    }

    return new Response(originalText, {
    status: originalResponseClone.status,
    headers: newResponseHeaders
    })
    }

    function isMobile(userAgent) {
    userAgent = userAgent || ''
    let agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
    for (let v = 0; v < agents.length; v++) {
    if (userAgent.indexOf(agents[v]) > 0) {
    return true;
    }
    }
    }

    function buildResponseHeaders(originalHeaders) {
    const result = new Headers(originalHeaders);
    if (disableCache) {
    result.set('Cache-Control', 'no-store');
    }
    result.set('access-control-allow-origin', '*');
    result.set('access-control-allow-credentials', true);
    result.delete('content-security-policy');
    result.delete('content-security-policy-report-only');
    result.delete('clear-site-data');

    return result
    }

    async function replaceResponseText(response, upstreamDomain, workerDomain) {
    let text = await response.text()
    const placeholders = {
    "$upstream": upstreamDomain,
    "$workerDomain": workerDomain
    }

    for (let origin in replaceDict) {
    let target = replaceDict[origin]

    origin = placeholders[origin] || origin
    target = placeholders[target] || target

    const re = new RegExp(origin, 'g')
    text = text.replace(re, target);
    }

    return text;
    }