Cloud flare反向代理流量实验

news2024/11/20 18:25:05

    前言

       本实验将会为大家解析cloud flare的反向解析代理服务如何搭建,works如何创建等等。本文中教学创建的实例已在文章编写结束后释放,该项技术不可用于违法用途!违者自行承担后果!!

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

原理拓扑图

一、知识链条

1、Cloud flare简介

      Cloudflare 是一家全球性的云服务提供商,成立于2009年。它提供了一系列网络基础设施和安全性服务,帮助网站加速、保护、以及优化其在线内容。

Cloudflare 提供的主要服务包括:

1. CDN(内容分发网络):通过将内容分发到全球性的数据中心,加速网站加载速度,提高用户体验。
2. DDoS 攻击防护:保护网站免受分布式拒绝服务(DDoS)攻击,确保服务的可用性和稳定性。
3. WAF(网络应用防火墙):监控和过滤网站流量,阻止恶意攻击、SQL 注入和跨站脚本等网络攻击。
4. DNS(域名解析服务):提供快速、可靠的域名解析服务,帮助网站管理和优化其网络流量。
5. SSL/TLS 加密:提供免费的 SSL/TLS 加密证书,加密网站与访问者之间的通信,保护数据安全和隐私。
6. Workers:允许开发者在 Cloudflare 的边缘网络上运行自定义的代码,以实现更高级的功能和定制化的处理。

Cloudflare 的服务被广泛应用于各种规模的网站和在线应用程序中,包括企业网站、电子商务平台、博客、游戏、移动应用等。其目标是通过提供简单易用、高性能和高安全性的服务,帮助客户在互联网上取得成功。同时,Cloud flare持有全球顶级DNS1.1.1.1,可见其实力不凡。

=========================================================================

2、VLess协议

       vless 协议是一个相对于 V2Ray 的一种新的协议,它是 V2Ray 的一个轻量级替代品。vless 的设计目标是减少传输数据的开销,提高性能,并且简化配置。

vless 协议的原理基本上与 V2Ray 中的 vmess 协议类似,都是在传输层上实现的一个加密隧道,用于在客户端和服务器之间传输数据。但是 vless 相对于 vmess 有一些不同之处:

1. **减少传输数据的开销**:vless 协议在传输数据时采用了更加精简的数据格式,以减少传输数据的开销,提高传输效率。
   
2. **简化配置**:相对于 vmess,vless 的配置更加简单,主要是因为它省略了一些在某些情况下不必要的选项和字段。

3. **增强安全性**:vless 在安全性方面与 vmess 相当,都是采用了 TLS 加密和传输,保障了数据的安全性和隐私。

4. **性能提升**:由于 vless 减少了数据传输的开销,并且在设计上更加精简,因此在一些场景下可能会提供更好的性能表现。

总的来说,vless 协议是为了简化配置、提高性能、减少传输开销而设计的一种协议,它保留了 vmess 协议的安全性和灵活性,同时简化了配置和提高了性能。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

3、Cloud flare-works实例

        Cloudflare的"Works"是Cloudflare推出的一系列整合服务,旨在为企业提供更全面的网络安全和性能优化解决方案。Works整合了Cloudflare的不同产品,包括:

1. **Access:** 提供零信任访问控制,确保用户可以安全地访问企业网络和应用程序,无论他们身在何处。

2. **Gateway:** 提供安全的互联网访问,包括网络内容过滤、恶意软件防护、广告拦截和隐私保护。

3. **Spectrum:** 扩展Cloudflare的边缘网络保护到任何TCP/UDP流量,使得所有的网络流量都能受到Cloudflare的保护。

4. **Workers:** 允许开发者在Cloudflare的边缘网络上运行轻量级的JavaScript代码,从而实现定制化的网络功能和逻辑。

5. **Orbit:** 提供实时的API和事件监控,使得开发者可以实时地查看和分析其应用程序的性能和行为。

通过将这些服务整合在一起,Cloudflare Works可以为企业提供更强大、更全面的网络安全和性能优化解决方案,帮助他们保护应用程序、数据和用户,并提高网络性能和可用性。

4、UUID

      UUID(Universally Unique Identifier)是一种标识符,用于在计算系统中唯一地标识信息或实体。它是由一个128位的数字组成,通常以32个十六进制数字的形式表示,中间用连字符分隔。UUID的生成算法保证了在理论上具有非常低的重复概率。

UUID广泛应用于各种场景,例如:

1. 数据库系统:UUID可以用作数据库表格的主键,确保每个记录在整个数据库中具有唯一标识。
2. 分布式系统:在分布式系统中,UUID可以用于唯一标识各个节点、任务或事件,以确保全局唯一性。
3. 软件开发:开发人员可以使用UUID作为临时文件名、会话标识符或唯一的标识符来跟踪和管理数据。
4. 网络通信:UUID可以用于标识网络协议中的消息、数据包或会话,以确保唯一性和识别性。

总之,UUID是一种在计算系统中用于唯一标识信息或实体的标识符,具有广泛的应用领域。

接下来我们开始创建workers实例

二、创建workers

1、访问cloud flare

       先进入官网>>>Cloud flare<<<

       请记住,访问cloud flare时,会有人机验证,照常过就行,如果无法通过,请开启霍格沃兹力量或者切换IP即可。

这里已经进入首页,可以在右上角调整语言设置:

首先,你要有非CN的账号,(推荐使用Google)我这里使用Google/US账户进行注册登录:

进入cloud flare首页,在左侧找到workers和pages,进入。

2、创建workers

进入创建应用程序,开始创建一个workers实例。

给你的workers创建一个名称,我这里就使用ceshi,然后点击部署。

部署完成后,点击编辑代码。

将默认原来的代码全部清除

3、实例代码部署

edgetunnel/src/worker-vless.js at main · leilei223/edgetunnel · GitHub

实例代码1、(本章节使用)

清除后导入workers代码

// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->.
// @ts-ignore
import { connect } from 'cloudflare:sockets';
 
// How to generate your own UUID:
// [Windows] Press "Win + R", input cmd and run:  Powershell -NoExit -Command "[guid]::NewGuid()"
let userID = 'UUID';
 
let proxyIP = 'CDN-IP';
 
if (!isValidUUID(userID)) {
    throw new Error('uuid is not valid');
}
 
export default {
    /**
     * @param {import("@cloudflare/workers-types").Request} request
     * @param {{UUID: string, PROXYIP: string}} env
     * @param {import("@cloudflare/workers-types").ExecutionContext} ctx
     * @returns {Promise<Response>}
     */
    async fetch(request, env, ctx) {
        try {
            userID = env.UUID || userID;
            proxyIP = env.PROXYIP || proxyIP;
            const upgradeHeader = request.headers.get('Upgrade');
            if (!upgradeHeader || upgradeHeader !== 'websocket') {
                const url = new URL(request.url);
                switch (url.pathname) {
                    case '/':
                        return new Response(JSON.stringify(request.cf), { status: 200 });
                    case `/${userID}`: {
                        const vlessConfig = getVLESSConfig(userID, request.headers.get('Host'));
                        return new Response(`${vlessConfig}`, {
                            status: 200,
                            headers: {
                                "Content-Type": "text/plain;charset=utf-8",
                            }
                        });
                    }
                    default:
                        return new Response('Not found', { status: 404 });
                }
            } else {
                return await vlessOverWSHandler(request);
            }
        } catch (err) {
            /** @type {Error} */ let e = err;
            return new Response(e.toString());
        }
    },
};
 
/**
 * 
 * @param {import("@cloudflare/workers-types").Request} request
 */
async function vlessOverWSHandler(request) {
 
    /** @type {import("@cloudflare/workers-types").WebSocket[]} */
    // @ts-ignore
    const webSocketPair = new WebSocketPair();
    const [client, webSocket] = Object.values(webSocketPair);
 
    webSocket.accept();
 
    let address = '';
    let portWithRandomLog = '';
    const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
        console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
    };
    const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';
 
    const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
 
    /** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/
    let remoteSocketWapper = {
        value: null,
    };
    let udpStreamWrite = null;
    let isDns = false;
 
    // ws --> remote
    readableWebSocketStream.pipeTo(new WritableStream({
        async write(chunk, controller) {
            if (isDns && udpStreamWrite) {
                return udpStreamWrite(chunk);
            }
            if (remoteSocketWapper.value) {
                const writer = remoteSocketWapper.value.writable.getWriter()
                await writer.write(chunk);
                writer.releaseLock();
                return;
            }
 
            const {
                hasError,
                message,
                portRemote = 443,
                addressRemote = '',
                rawDataIndex,
                vlessVersion = new Uint8Array([0, 0]),
                isUDP,
            } = processVlessHeader(chunk, userID);
            address = addressRemote;
            portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
                } `;
            if (hasError) {
                // controller.error(message);
                throw new Error(message); // cf seems has bug, controller.error will not end stream
                // webSocket.close(1000, message);
                return;
            }
            // if UDP but port not DNS port, close it
            if (isUDP) {
                if (portRemote === 53) {
                    isDns = true;
                } else {
                    // controller.error('UDP proxy only enable for DNS which is port 53');
                    throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream
                    return;
                }
            }
            // ["version", "附加信息长度 N"]
            const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
            const rawClientData = chunk.slice(rawDataIndex);
 
            // TODO: support udp here when cf runtime has udp support
            if (isDns) {
                const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
                udpStreamWrite = write;
                udpStreamWrite(rawClientData);
                return;
            }
            handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
        },
        close() {
            log(`readableWebSocketStream is close`);
        },
        abort(reason) {
            log(`readableWebSocketStream is abort`, JSON.stringify(reason));
        },
    })).catch((err) => {
        log('readableWebSocketStream pipeTo error', err);
    });
 
    return new Response(null, {
        status: 101,
        // @ts-ignore
        webSocket: client,
    });
}
 
/**
 * Handles outbound TCP connections.
 *
 * @param {any} remoteSocket 
 * @param {string} addressRemote The remote address to connect to.
 * @param {number} portRemote The remote port to connect to.
 * @param {Uint8Array} rawClientData The raw client data to write.
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to.
 * @param {Uint8Array} vlessResponseHeader The VLESS response header.
 * @param {function} log The logging function.
 * @returns {Promise<void>} The remote socket.
 */
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
    async function connectAndWrite(address, port) {
        /** @type {import("@cloudflare/workers-types").Socket} */
        const tcpSocket = connect({
            hostname: address,
            port: port,
        });
        remoteSocket.value = tcpSocket;
        log(`connected to ${address}:${port}`);
        const writer = tcpSocket.writable.getWriter();
        await writer.write(rawClientData); // first write, nomal is tls client hello
        writer.releaseLock();
        return tcpSocket;
    }
 
    // if the cf connect tcp socket have no incoming data, we retry to redirect ip
    async function retry() {
        const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote)
        // no matter retry success or not, close websocket
        tcpSocket.closed.catch(error => {
            console.log('retry tcpSocket closed error', error);
        }).finally(() => {
            safeCloseWebSocket(webSocket);
        })
        remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
    }
 
    const tcpSocket = await connectAndWrite(addressRemote, portRemote);
 
    // when remoteSocket is ready, pass to websocket
    // remote--> ws
    remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}
 
/**
 * 
 * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer
 * @param {string} earlyDataHeader for ws 0rtt
 * @param {(info: string)=> void} log for ws 0rtt
 */
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
    let readableStreamCancel = false;
    const stream = new ReadableStream({
        start(controller) {
            webSocketServer.addEventListener('message', (event) => {
                if (readableStreamCancel) {
                    return;
                }
                const message = event.data;
                controller.enqueue(message);
            });
 
            // The event means that the client closed the client -> server stream.
            // However, the server -> client stream is still open until you call close() on the server side.
            // The WebSocket protocol says that a separate close message must be sent in each direction to fully close the socket.
            webSocketServer.addEventListener('close', () => {
                // client send close, need close server
                // if stream is cancel, skip controller.close
                safeCloseWebSocket(webSocketServer);
                if (readableStreamCancel) {
                    return;
                }
                controller.close();
            }
            );
            webSocketServer.addEventListener('error', (err) => {
                log('webSocketServer has error');
                controller.error(err);
            }
            );
            // for ws 0rtt
            const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
            if (error) {
                controller.error(error);
            } else if (earlyData) {
                controller.enqueue(earlyData);
            }
        },
 
        pull(controller) {
            // if ws can stop read if stream is full, we can implement backpressure
            // https://streams.spec.whatwg.org/#example-rs-push-backpressure
        },
        cancel(reason) {
            // 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here
            // 2. if readableStream is cancel, all controller.close/enqueue need skip,
            // 3. but from testing controller.error still work even if readableStream is cancel
            if (readableStreamCancel) {
                return;
            }
            log(`ReadableStream was canceled, due to ${reason}`)
            readableStreamCancel = true;
            safeCloseWebSocket(webSocketServer);
        }
    });
 
    return stream;
 
}
 
// https://xtls.github.io/development/protocols/vless.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
 
/**
 * 
 * @param { ArrayBuffer} vlessBuffer 
 * @param {string} userID 
 * @returns 
 */
function processVlessHeader( 
    vlessBuffer,
    userID
 ) {
    if (vlessBuffer.byteLength < 24) {
        return {
            hasError: true,
            message: 'invalid data',
        };
    }
    const version = new Uint8Array(vlessBuffer.slice(0, 1));
    let isValidUser = false;
    let isUDP = false;
    if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
        isValidUser = true;
    }
    if (!isValidUser) {
        return {
            hasError: true,
            message: 'invalid user',
        };
    }
 
    const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
    //skip opt for now
 
    const command = new Uint8Array(
        vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
    )[0];
 
    // 0x01 TCP
    // 0x02 UDP
    // 0x03 MUX
    if (command === 1) {
    } else if (command === 2) {
        isUDP = true;
    } else {
        return {
            hasError: true,
            message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
        };
    }
    const portIndex = 18 + optLength + 1;
    const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
    // port is big-Endian in raw data etc 80 == 0x005d
    const portRemote = new DataView(portBuffer).getUint16(0);
 
    let addressIndex = portIndex + 2;
    const addressBuffer = new Uint8Array(
        vlessBuffer.slice(addressIndex, addressIndex + 1)
    );
 
    // 1--> ipv4  addressLength =4
    // 2--> domain name addressLength=addressBuffer[1]
    // 3--> ipv6  addressLength =16
    const addressType = addressBuffer[0];
    let addressLength = 0;
    let addressValueIndex = addressIndex + 1;
    let addressValue = '';
    switch (addressType) {
        case 1:
            addressLength = 4;
            addressValue = new Uint8Array(
                vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
            ).join('.');
            break;
        case 2:
            addressLength = new Uint8Array(
                vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
            )[0];
            addressValueIndex += 1;
            addressValue = new TextDecoder().decode(
                vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
            );
            break;
        case 3:
            addressLength = 16;
            const dataView = new DataView(
                vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
            );
            // 2001:0db8:85a3:0000:0000:8a2e:0370:7334
            const ipv6 = [];
            for (let i = 0; i < 8; i++) {
                ipv6.push(dataView.getUint16(i * 2).toString(16));
            }
            addressValue = ipv6.join(':');
            // seems no need add [] for ipv6
            break;
        default:
            return {
                hasError: true,
                message: `invild  addressType is ${addressType}`,
            };
    }
    if (!addressValue) {
        return {
            hasError: true,
            message: `addressValue is empty, addressType is ${addressType}`,
        };
    }
 
    return {
        hasError: false,
        addressRemote: addressValue,
        addressType,
        portRemote,
        rawDataIndex: addressValueIndex + addressLength,
        vlessVersion: version,
        isUDP,
    };
}
 
/**
 * 
 * @param {import("@cloudflare/workers-types").Socket} remoteSocket 
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket 
 * @param {ArrayBuffer} vlessResponseHeader 
 * @param {(() => Promise<void>) | null} retry
 * @param {*} log 
 */
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
    // remote--> ws
    let remoteChunkCount = 0;
    let chunks = [];
    /** @type {ArrayBuffer | null} */
    let vlessHeader = vlessResponseHeader;
    let hasIncomingData = false; // check if remoteSocket has incoming data
    await remoteSocket.readable
        .pipeTo(
            new WritableStream({
                start() {
                },
                /**
                 * 
                 * @param {Uint8Array} chunk 
                 * @param {*} controller 
                 */
                async write(chunk, controller) {
                    hasIncomingData = true;
                    // remoteChunkCount++;
                    if (webSocket.readyState !== WS_READY_STATE_OPEN) {
                        controller.error(
                            'webSocket.readyState is not open, maybe close'
                        );
                    }
                    if (vlessHeader) {
                        webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
                        vlessHeader = null;
                    } else {
                        // seems no need rate limit this, CF seems fix this??..
                        // if (remoteChunkCount > 20000) {
                        //  // cf one package is 4096 byte(4kb),  4096 * 20000 = 80M
                        //  await delay(1);
                        // }
                        webSocket.send(chunk);
                    }
                },
                close() {
                    log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);
                    // safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.
                },
                abort(reason) {
                    console.error(`remoteConnection!.readable abort`, reason);
                },
            })
        )
        .catch((error) => {
            console.error(
                `remoteSocketToWS has exception `,
                error.stack || error
            );
            safeCloseWebSocket(webSocket);
        });
 
    // seems is cf connect socket have error,
    // 1. Socket.closed will have error
    // 2. Socket.readable will be close without any data coming
    if (hasIncomingData === false && retry) {
        log(`retry`)
        retry();
    }
}
 
/**
 * 
 * @param {string} base64Str 
 * @returns 
 */
function base64ToArrayBuffer(base64Str) {
    if (!base64Str) {
        return { error: null };
    }
    try {
        // go use modified Base64 for URL rfc4648 which js atob not support
        base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
        const decode = atob(base64Str);
        const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
        return { earlyData: arryBuffer.buffer, error: null };
    } catch (error) {
        return { error };
    }
}
 
/**
 * This is not real UUID validation
 * @param {string} uuid 
 */
function isValidUUID(uuid) {
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return uuidRegex.test(uuid);
}
 
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
/**
 * Normally, WebSocket will not has exceptions when close.
 * @param {import("@cloudflare/workers-types").WebSocket} socket
 */
function safeCloseWebSocket(socket) {
    try {
        if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
            socket.close();
        }
    } catch (error) {
        console.error('safeCloseWebSocket error', error);
    }
}
 
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
    byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
    return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
function stringify(arr, offset = 0) {
    const uuid = unsafeStringify(arr, offset);
    if (!isValidUUID(uuid)) {
        throw TypeError("Stringified UUID is invalid");
    }
    return uuid;
}
 
/**
 * 
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket 
 * @param {ArrayBuffer} vlessResponseHeader 
 * @param {(string)=> void} log 
 */
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
 
    let isVlessHeaderSent = false;
    const transformStream = new TransformStream({
        start(controller) {
 
        },
        transform(chunk, controller) {
            // udp message 2 byte is the the length of udp data
            // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
            for (let index = 0; index < chunk.byteLength;) {
                const lengthBuffer = chunk.slice(index, index + 2);
                const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
                const udpData = new Uint8Array(
                    chunk.slice(index + 2, index + 2 + udpPakcetLength)
                );
                index = index + 2 + udpPakcetLength;
                controller.enqueue(udpData);
            }
        },
        flush(controller) {
        }
    });
 
    // only handle dns udp for now
    transformStream.readable.pipeTo(new WritableStream({
        async write(chunk) {
            const resp = await fetch('https://1.1.1.1/dns-query',
                {
                    method: 'POST',
                    headers: {
                        'content-type': 'application/dns-message',
                    },
                    body: chunk,
                })
            const dnsQueryResult = await resp.arrayBuffer();
            const udpSize = dnsQueryResult.byteLength;
            // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));
            const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);
            if (webSocket.readyState === WS_READY_STATE_OPEN) {
                log(`doh success and dns message length is ${udpSize}`);
                if (isVlessHeaderSent) {
                    webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
                } else {
                    webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
                    isVlessHeaderSent = true;
                }
            }
        }
    })).catch((error) => {
        log('dns udp has error' + error)
    });
 
    const writer = transformStream.writable.getWriter();
 
    return {
        /**
         * 
         * @param {Uint8Array} chunk 
         */
        write(chunk) {
            writer.write(chunk);
        }
    };
}
 
/**
 * 
 * @param {string} userID 
 * @param {string | null} hostName
 * @returns {string}
 */
function getVLESSConfig(userID, hostName) {
    const vlessMain = `vless://${userID}@${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`
    return `
################################################################
v2ray
---------------------------------------------------------------
${vlessMain}
---------------------------------------------------------------
################################################################
clash-meta
---------------------------------------------------------------
- type: vless
  name: ${hostName}
  server: ${hostName}
  port: 443
  uuid: ${userID}
  network: ws
  tls: true
  udp: false
  sni: ${hostName}
  client-fingerprint: chrome
  ws-opts:
    path: "/?ed=2048"
    headers:
      host: ${hostName}
---------------------------------------------------------------
################################################################
`;
}

代码文件workers.js同时存于GitHub

实例代码2、(进阶玩家使用)

       提供给有技术能力的极客玩家和开发者们自行开发,支持伪装域名等等,借鉴:E家之长,零度解说。

// @ts-ignore
import { connect } from 'cloudflare:sockets';
@ts-ignore import { connect } from 'cloudflare:sockets';
// How to generate your own UUID:
// [Windows] Press "Win + R", input cmd and run:  Powershell -NoExit -Command "[guid]::NewGuid()"
let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
如何生成自己的 UUID: // [Windows] 按“Win + R”,输入 cmd 并运行: Powershell -NoExit -Command “[guid]::NewGuid()” let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];
const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];
// if you want to use ipv6 or single proxyIP, please add comment at this line and remove comment at the next line
let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
// use single proxyIP instead of random
// let proxyIP = 'cdn.xn--b6gac.eu.org';
// ipv6 proxyIP example remove comment to use
// let proxyIP = "[2a01:4f8:c2c:123f:64:5:6810:c55a]"
如果要使用 ipv6 或单个 proxyIP,请在此行添加注释,并在下一行删除注释 let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];use single proxyIP instead of random // let proxyIP = 'cdn.xn--b6gac.eu.org';ipv6 proxyIP 示例删除要使用的注释 // let proxyIP = “[2a01:4f8:c2c:123f:64:5:6810:c55a]”
let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query
让 dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg=';https://cloudflare-dns.com/dns-query 或 https://dns.google/dns-query
if (!isValidUUID(userID)) {
	throw new Error('uuid is invalid');
}
if (!isValidUUID(userID)) { throw new Error('uuid is invalid');
export default {
	/**
	 * @param {import("@cloudflare/workers-types").Request} request
	 * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env
	 * @param {import("@cloudflare/workers-types").ExecutionContext} ctx
	 * @returns {Promise<Response>}
	 */
	async fetch(request, env, ctx) {
		// uuid_validator(request);
		try {
			userID = env.UUID || userID;
			proxyIP = env.PROXYIP || proxyIP;
			dohURL = env.DNS_RESOLVER_URL || dohURL;
			let userID_Path = userID;
			if (userID.includes(',')) {
				userID_Path = userID.split(',')[0];
			}
			const upgradeHeader = request.headers.get('Upgrade');
			if (!upgradeHeader || upgradeHeader !== 'websocket') {
				const url = new URL(request.url);
				switch (url.pathname) {
					case `/cf`: {
						return new Response(JSON.stringify(request.cf, null, 4), {
							status: 200,
							headers: {
								"Content-Type": "application/json;charset=utf-8",
							},
						});
					}
					case `/${userID_Path}`: {
						const vlessConfig = getVLESSConfig(userID, request.headers.get('Host'));
						return new Response(`${vlessConfig}`, {
							status: 200,
							headers: {
								"Content-Type": "text/html; charset=utf-8",
							}
						});
					};
					case `/sub/${userID_Path}`: {
						const url = new URL(request.url);
						const searchParams = url.searchParams;
						const vlessSubConfig = createVLESSSub(userID, request.headers.get('Host'));
						// Construct and return response object
						return new Response(btoa(vlessSubConfig), {
							status: 200,
							headers: {
								"Content-Type": "text/plain;charset=utf-8",
							}
						});
					};
					case `/bestip/${userID_Path}`: {
						const headers = request.headers;
						const url = `https://sub.xf.free.hr/auto?host=${request.headers.get('Host')}&uuid=${userID}&path=/`;
						const bestSubConfig = await fetch(url, { headers: headers });
						return bestSubConfig;
					};
					default:
						// return new Response('Not found', { status: 404 });
						// For any other path, reverse proxy to 'ramdom website' and return the original response, caching it in the process
						const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)];
						const newHeaders = new Headers(request.headers);
						newHeaders.set('cf-connecting-ip', '1.2.3.4');
						newHeaders.set('x-forwarded-for', '1.2.3.4');
						newHeaders.set('x-real-ip', '1.2.3.4');
						newHeaders.set('referer', 'https://www.google.com/search?q=edtunnel');
						// Use fetch to proxy the request to 15 different domains
						const proxyUrl = 'https://' + randomHostname + url.pathname + url.search;
						let modifiedRequest = new Request(proxyUrl, {
							method: request.method,
							headers: newHeaders,
							body: request.body,
							redirect: 'manual',
						});
						const proxyResponse = await fetch(modifiedRequest, { redirect: 'manual' });
						// Check for 302 or 301 redirect status and return an error response
						if ([301, 302].includes(proxyResponse.status)) {
							return new Response(`Redirects to ${randomHostname} are not allowed.`, {
								status: 403,
								statusText: 'Forbidden',
							});
						}
						// Return the response from the proxy server
						return proxyResponse;
				}
			} else {
				return await vlessOverWSHandler(request);
			}
		} catch (err) {
			/** @type {Error} */ let e = err;
			return new Response(e.toString());
		}
	},
};
导出默认值 { /** * @param {import(“@cloudflare/workers-types”)。request} request * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env * @param {import(“@cloudflare/workers-types”)。ExecutionContext} ctx * @returns {Promise<Response>} */ async fetch(request, env, ctx) { // uuid_validator(request); try { userID = env.UUID ||用户 ID;proxyIP = 环境。代理IP ||代理IP;dohURL = 环境。DNS_RESOLVER_URL ||dohURL;let userID_Path = userID;if (userID.includes(',')) { userID_Path = userID.split(',')[0]; } const upgradeHeader = request.headers.get('升级');if (!upgradeHeader || upgradeHeader !== 'websocket') { const url = new URL(request.url); switch (url.pathname) { case '/cf': { return new Response(JSON.stringify(request.cf, null, 4), { status: 200, headers: { “Content-Type”: “application/json;charset=utf-8“, }, });} case '/${userID_Path}': { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response('${vlessConfig}', { status: 200, headers: { “Content-Type”: “text/html;charset=utf-8“, } });};case '/sub/${userID_Path}': { const url = new URL(request.url); const searchParams = url.searchParams; const vlessSubConfig = createVLESSSub(userID, request.headers.get('Host')); // 构造并返回响应对象 return new Response(btoa(vlessSubConfig), { status: 200, headers: { “Content-Type”: “text/plain;charset=utf-8“, } });};case '/bestip/${userID_Path}': { const headers = request.headers; const url = 'https://sub.xf.free.hr/auto?host=${request.headers.get('主机')}&uuid=${userID}&path=/';const bestSubConfig = await fetch(url, { headers: headers });返回 bestSubConfig;};default: // 返回 new Response('Not found', { status: 404 });对于任何其他路径,反向代理到“ramdom website”并返回原始响应,将其缓存在进程中 const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)];const newHeaders = 新标头(request.headers);newHeaders.set('cf-connecting-ip', '1.2.3.4');newHeaders.set('x-转发-for', '1.2.3.4');newHeaders.set('x-real-ip', '1.2.3.4');newHeaders.set('referer', 'https://www.google.com/search?q=edtunnel');使用 fetch 将请求代理到 15 个不同的域 const proxyUrl = 'https://' + randomHostname + url.pathname + url.search;let modifiedRequest = new Request(proxyUrl, { method: request.method, headers: newHeaders, body: request.body, redirect: 'manual', });const proxyResponse = await fetch(modifiedRequest, { redirect: 'manual' });检查 302 或 301 重定向状态并返回错误响应 if ([301, 302].includes(proxyResponse.status)) { return new Response('Redirects to ${randomHostname} are not allowed.', { status: 403, statusText: 'Forbidden', }); } // 从代理服务器返回响应 return proxyResponse;} } else { return await vlessOverWSHandler(request);catch (err) { /** @type {Error} */ let e = err; return new Response(e.toString());} }, };
export async function uuid_validator(request) {
	const hostname = request.headers.get('Host');
	const currentDate = new Date();
export async function uuid_validator(request) { const hostname = request.headers.get('Host'); const currentDate = new Date();
	const subdomain = hostname.split('.')[0];
	const year = currentDate.getFullYear();
	const month = String(currentDate.getMonth() + 1).padStart(2, '0');
	const day = String(currentDate.getDate()).padStart(2, '0');
常量子域 = hostname.split('.')[0];常量年份 = currentDate.getFullYear();常量月 = String(currentDate.getMonth() + 1).padStart(2, '0');const day = String(currentDate.getDate()).padStart(2, '0');
	const formattedDate = `${year}-${month}-${day}`;
const formattedDate = '${year}-${month}-${day}';
	// const daliy_sub = formattedDate + subdomain
	const hashHex = await hashHex_f(subdomain);
	// subdomain string contains timestamps utc and uuid string TODO.
	console.log(hashHex, subdomain, formattedDate);
}
const daliy_sub = formattedDate + 子域 const hashHex = await hashHex_f(子域);子域字符串包含时间戳 UTC 和 UUID 字符串 TODO。console.log(hashHex, subdomain, formattedDate);}
export async function hashHex_f(string) {
	const encoder = new TextEncoder();
	const data = encoder.encode(string);
	const hashBuffer = await crypto.subtle.digest('SHA-256', data);
	const hashArray = Array.from(new Uint8Array(hashBuffer));
	const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
	return hashHex;
}
export async function hashHex_f(string) { const encoder = new TextEncoder(); const data = encoder.encode(string); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); return hashHex; }
/**
 * Handles VLESS over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the VLESS header.
 * @param {import("@cloudflare/workers-types").Request} request The incoming request object.
 * @returns {Promise<Response>} A Promise that resolves to a WebSocket response object.
 */
async function vlessOverWSHandler(request) {
	const webSocketPair = new WebSocketPair();
	const [client, webSocket] = Object.values(webSocketPair);
	webSocket.accept();
/** * 通过创建 WebSocket 对、接受 WebSocket 连接并处理 VLESS 标头来处理 VLESS over WebSocket 请求。* @param {import(“@cloudflare/workers-types”)。Request} request 传入的请求对象。* @returns {Promise} 解<Response>析为 WebSocket 响应对象的 Promise。 */ 异步函数 vlessOverWSHandler(request) { const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept();
	let address = '';
	let portWithRandomLog = '';
	let currentDate = new Date();
	const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
		console.log(`[${currentDate} ${address}:${portWithRandomLog}] ${info}`, event || '');
	};
	const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';
let address = '';easy portWithRandomLog = '';let currentDate = 新日期();const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { console.log('[${currentDate} ${address}:${portWithRandomLog}] ${info}', event ||'');};const earlyDataHeader = request.headers.get('sec-websocket-protocol') ||'';
	const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
	/** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/
	let remoteSocketWapper = {
		value: null,
	};
	let udpStreamWrite = null;
	let isDns = false;
/** @type {{ value: import(“@cloudflare/workers-types”)。插座 |null}}*/ let remoteSocketWapper = { value: null, };让 udpStreamWrite = null;let isDns = false;
	// ws --> remote
	readableWebSocketStream.pipeTo(new WritableStream({
		async write(chunk, controller) {
			if (isDns && udpStreamWrite) {
				return udpStreamWrite(chunk);
			}
			if (remoteSocketWapper.value) {
				const writer = remoteSocketWapper.value.writable.getWriter()
				await writer.write(chunk);
				writer.releaseLock();
				return;
			}
ws --> remote readableWebSocketStream.pipeTo(new WritableStream({ async write(chunk, controller) { if (isDns &&; udpStreamWrite) { return udpStreamWrite(chunk); } if (remoteSocketWapper.value) { const writer = remoteSocketWapper.value.writable.getWriter() await writer.write(chunk); writer.releaseLock(); return; }
			const {
				hasError,
				message,
				portRemote = 443,
				addressRemote = '',
				rawDataIndex,
				vlessVersion = new Uint8Array([0, 0]),
				isUDP,
			} = processVlessHeader(chunk, userID);
			address = addressRemote;
			portWithRandomLog = `${portRemote} ${isUDP ? 'udp' : 'tcp'} `;
			if (hasError) {
				// controller.error(message);
				throw new Error(message); // cf seems has bug, controller.error will not end stream
			}
const { hasError, message, portRemote = 443, addressRemote = '', rawDataIndex, vlessVersion = new Uint8Array([0, 0]), isUDP, } = processVlessHeader(chunk, userID);地址 = addressRemote;portWithRandomLog = '${portRemote} ${isUDP ?'udp' : 'tcp'} ';if (hasError) { // controller.error(message); throw new Error(message); // cf 似乎有 bug,controller.error 不会结束流 }
			// If UDP and not DNS port, close it
			if (isUDP && portRemote !== 53) {
				throw new Error('UDP proxy only enabled for DNS which is port 53');
				// cf seems has bug, controller.error will not end stream
			}
如果 UDP 而不是 DNS 端口,请关闭它 if (isUDP && portRemote !== 53) { throw new Error('UDP proxy only enabled for DNS which is port 53'); // cf 似乎有 bug,controller.error 不会结束流 }
			if (isUDP && portRemote === 53) {
				isDns = true;
			}
if (isUDP && portRemote === 53) { isDns = true;
			// ["version", "附加信息长度 N"]
			const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
			const rawClientData = chunk.slice(rawDataIndex);
[“version”, “附加信息长度 N”] const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);const rawClientData = chunk.slice(rawDataIndex);
			// TODO: support udp here when cf runtime has udp support
			if (isDns) {
				const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
				udpStreamWrite = write;
				udpStreamWrite(rawClientData);
				return;
			}
			handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
		},
		close() {
			log(`readableWebSocketStream is close`);
		},
		abort(reason) {
			log(`readableWebSocketStream is abort`, JSON.stringify(reason));
		},
	})).catch((err) => {
		log('readableWebSocketStream pipeTo error', err);
	});
TODO:当 cf 运行时支持 udp 时,这里支持 udp if (isDns) { const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); udpStreamWrite = write; udpStreamWrite(rawClientData); return; } handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);}, close() { log('readableWebSocketStream is close'); }, abort(reason) { log('readableWebSocketStream is abort', JSON.stringify(reason)); }, })).catch((err) => { log('readableWebSocketStream pipeTo error', err);
	return new Response(null, {
		status: 101,
		webSocket: client,
	});
}
return new Response(null, { status: 101, webSocket: client, });}
/**
 * Handles outbound TCP connections.
 *
 * @param {any} remoteSocket 
 * @param {string} addressRemote The remote address to connect to.
 * @param {number} portRemote The remote port to connect to.
 * @param {Uint8Array} rawClientData The raw client data to write.
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to.
 * @param {Uint8Array} vlessResponseHeader The VLESS response header.
 * @param {function} log The logging function.
 * @returns {Promise<void>} The remote socket.
 */
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
/** * 处理出站 TCP 连接。* * @param {any} remoteSocket * @param {string} addressRemote 要连接到的远程地址。* @param {number} portRemote 要连接到的远程端口。* @param {Uint8Array} rawClientData 要写入的原始客户端数据。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 要将远程套接字传递到的 WebSocket。* @param {Uint8Array} vlessResponseHeader VLESS 响应标头。* @param {function} log 日志记录函数。* @returns {Promise<void>} 远程套接字。 */ 异步函数 handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
	/**
	 * Connects to a given address and port and writes data to the socket.
	 * @param {string} address The address to connect to.
	 * @param {number} port The port to connect to.
	 * @returns {Promise<import("@cloudflare/workers-types").Socket>} A Promise that resolves to the connected socket.
	 */
	async function connectAndWrite(address, port) {
		/** @type {import("@cloudflare/workers-types").Socket} */
		const tcpSocket = connect({
			hostname: address,
			port: port,
		});
		remoteSocket.value = tcpSocket;
		log(`connected to ${address}:${port}`);
		const writer = tcpSocket.writable.getWriter();
		await writer.write(rawClientData); // first write, nomal is tls client hello
		writer.releaseLock();
		return tcpSocket;
	}
/** * 连接到给定的地址和端口,并将数据写入套接字。* @param {string} address 要连接到的地址。* @param {number} port 要连接到的端口。* @returns {Promise<import(“@cloudflare/workers-types”)。Socket>} 解析为连接的套接字的 Promise。*/ 异步函数 connectAndWrite(address, port) { /** @type {import(“@cloudflare/workers-types”)。套接字} */ const tcpSocket = connect({ hostname: address, port: port, });remoteSocket.value = tcpSocket;log('连接到${address}:${port}');常量写入器 = tcpSocket.writable.getWriter();等待 writer.write(rawClientData);首先写入,nomal 是 tls 客户端 hello writer.releaseLock();返回 tcpSocket;}
	/**
	 * Retries connecting to the remote address and port if the Cloudflare socket has no incoming data.
	 * @returns {Promise<void>} A Promise that resolves when the retry is complete.
	 */
	async function retry() {
		const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote)
		tcpSocket.closed.catch(error => {
			console.log('retry tcpSocket closed error', error);
		}).finally(() => {
			safeCloseWebSocket(webSocket);
		})
		remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
	}
/** * 如果 Cloudflare 套接字没有传入数据,则重试连接到远程地址和端口。* @returns {Promise<void>} 重试完成后解析的 Promise。 */ 异步函数 retry() { const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) tcpSocket.closed.catch(error => { console.log('retry tcpSocket closed error', error);finally(() => { safeCloseWebSocket(webSocket); remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);}
	const tcpSocket = await connectAndWrite(addressRemote, portRemote);
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
	// when remoteSocket is ready, pass to websocket
	// remote--> ws
	remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}
当 remoteSocket 准备就绪时,传递到 websocket // remote--> ws remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);}
/**
 * Creates a readable stream from a WebSocket server, allowing for data to be read from the WebSocket.
 * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer The WebSocket server to create the readable stream from.
 * @param {string} earlyDataHeader The header containing early data for WebSocket 0-RTT.
 * @param {(info: string)=> void} log The logging function.
 * @returns {ReadableStream} A readable stream that can be used to read data from the WebSocket.
 */
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
	let readableStreamCancel = false;
	const stream = new ReadableStream({
		start(controller) {
			webSocketServer.addEventListener('message', (event) => {
				const message = event.data;
				controller.enqueue(message);
			});
/** * 从 WebSocket 服务器创建可读流,允许从 WebSocket 读取数据。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocketServer 要从中创建可读流的 WebSocket 服务器。* @param {string} earlyDataHeader 包含 WebSocket 0-RTT 早期数据的标头。* @param {(info: string)=> void} log 日志函数。* @returns {ReadableStream} 可用于从 WebSocket 读取数据的可读流。*/ function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { let readableStreamCancel = false; const stream = new ReadableStream({ start(controller) { webSocketServer.addEventListener('message', (event) => { const message = event.data; controller.enqueue(message);
			webSocketServer.addEventListener('close', () => {
				safeCloseWebSocket(webSocketServer);
				controller.close();
			});
webSocketServer.addEventListener('close', () => { safeCloseWebSocket(webSocketServer);
			webSocketServer.addEventListener('error', (err) => {
				log('webSocketServer has error');
				controller.error(err);
			});
			const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
			if (error) {
				controller.error(error);
			} else if (earlyData) {
				controller.enqueue(earlyData);
			}
		},
webSocketServer.addEventListener('error', (err) => { log('webSocketServer has error'); controller.error(err);const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);if (error) { controller.error(error); } else if (earlyData) { controller.enqueue(earlyData); } },
		pull(controller) {
			// if ws can stop read if stream is full, we can implement backpressure
			// https://streams.spec.whatwg.org/#example-rs-push-backpressure
		},
pull(controller) { // 如果 ws 可以在流已满时停止读取,我们可以实现背压 // https://streams.spec.whatwg.org/#example-rs-push-backpressure },
		cancel(reason) {
			log(`ReadableStream was canceled, due to ${reason}`)
			readableStreamCancel = true;
			safeCloseWebSocket(webSocketServer);
		}
	});
cancel(reason) { log('由于 ${reason}',ReadableStream 已取消 = true; safeCloseWebSocket(webSocketServer);
	return stream;
} 回流;}
// https://xtls.github.io/development/protocols/vless.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
https://xtls.github.io/development/protocols/vless.html // https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
/**
 * Processes the VLESS header buffer and returns an object with the relevant information.
 * @param {ArrayBuffer} vlessBuffer The VLESS header buffer to process.
 * @param {string} userID The user ID to validate against the UUID in the VLESS header.
 * @returns {{
 *  hasError: boolean,
 *  message?: string,
 *  addressRemote?: string,
 *  addressType?: number,
 *  portRemote?: number,
 *  rawDataIndex?: number,
 *  vlessVersion?: Uint8Array,
 *  isUDP?: boolean
 * }} An object with the relevant information extracted from the VLESS header buffer.
 */
function processVlessHeader(vlessBuffer, userID) {
	if (vlessBuffer.byteLength < 24) {
		return {
			hasError: true,
			message: 'invalid data',
		};
	}
/** * 处理 VLESS 标头缓冲区并返回包含相关信息的对象。* @param {ArrayBuffer} vlessBuffer 要处理的 VLESS 标头缓冲区。* @param {string} userID 要根据 VLESS 标头中的 UUID 进行验证的用户 ID。* @returns {{ * hasError: boolean, * message?: string, * addressRemote?: string, * addressType?: number, * portRemote?: number, * rawDataIndex?: number, * vlessVersion?: Uint8Array, * isUDP?: boolean * }} 包含从 VLESS 标头缓冲区提取的相关信息的对象。*/ 函数 processVlessHeader(vlessBuffer, userID) { if (vlessBuffer.byteLength < 24) { return { hasError: true, message: 'invalid data', }; }
	const version = new Uint8Array(vlessBuffer.slice(0, 1));
	let isValidUser = false;
	let isUDP = false;
	const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17));
	const slicedBufferString = stringify(slicedBuffer);
	// check if userID is valid uuid or uuids split by , and contains userID in it otherwise return error message to console
	const uuids = userID.includes(',') ? userID.split(",") : [userID];
	// uuid_validator(hostName, slicedBufferString);
const version = new Uint8Array(vlessBuffer.slice(0, 1));let isValidUser = false;let isUDP = false;const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17));const slicedBufferString = stringify(slicedBuffer);检查 userID 是否有效 uuid 或 uuids 拆分为 ,并且其中包含 userID,否则返回错误消息到控制台 const uuids = userID.includes(',') ?userID.split(“,”) : [用户ID];uuid_validator(主机名,slicedBufferString);

	// isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim());
	isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) || uuids.length === 1 && slicedBufferString === uuids[0].trim();
isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim());isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) ||uuids.length === 1 && slicedBufferString === uuids[0].trim();
	console.log(`userID: ${slicedBufferString}`);
console.log('用户 ID: ${slicedBufferString}');
	if (!isValidUser) {
		return {
			hasError: true,
			message: 'invalid user',
		};
	}
if (!isValidUser) { return { hasError: true, message: 'invalid user', };
	const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
	//skip opt for now
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];立即跳过选择
	const command = new Uint8Array(
		vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
	)[0];
const 命令 = new Uint8Array( vlessBuffer.slice(18 + optLength, 18 + optLength + 1) )[0];
	// 0x01 TCP
	// 0x02 UDP
	// 0x03 MUX
	if (command === 1) {
		isUDP = false;
	} else if (command === 2) {
		isUDP = true;
	} else {
		return {
			hasError: true,
			message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
		};
	}
	const portIndex = 18 + optLength + 1;
	const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
	// port is big-Endian in raw data etc 80 == 0x005d
	const portRemote = new DataView(portBuffer).getUint16(0);
0x01 TCP // 0x02 UDP // 0x03 MUX if (command === 1) { isUDP = false; } else if (command === 2) { isUDP = true; } else { return { hasError: true, message: 'command ${command} is not support, command 01-tcp,02-udp,03-mux', }; } const portIndex = 18 + optLength + 1;const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);port 在原始数据中是 big-Endian 等 80 == 0x005d const portRemote = new DataView(portBuffer).getUint16(0);
	let addressIndex = portIndex + 2;
	const addressBuffer = new Uint8Array(
		vlessBuffer.slice(addressIndex, addressIndex + 1)
	);
let addressIndex = 端口索引 + 2;const addressBuffer = new Uint8Array( vlessBuffer.slice(addressIndex, addressIndex + 1) );
	// 1--> ipv4  addressLength =4
	// 2--> domain name addressLength=addressBuffer[1]
	// 3--> ipv6  addressLength =16
	const addressType = addressBuffer[0];
	let addressLength = 0;
	let addressValueIndex = addressIndex + 1;
	let addressValue = '';
	switch (addressType) {
		case 1:
			addressLength = 4;
			addressValue = new Uint8Array(
				vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
			).join('.');
			break;
		case 2:
			addressLength = new Uint8Array(
				vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
			)[0];
			addressValueIndex += 1;
			addressValue = new TextDecoder().decode(
				vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
			);
			break;
		case 3:
			addressLength = 16;
			const dataView = new DataView(
				vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
			);
			// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
			const ipv6 = [];
			for (let i = 0; i < 8; i++) {
				ipv6.push(dataView.getUint16(i * 2).toString(16));
			}
			addressValue = ipv6.join(':');
			// seems no need add [] for ipv6
			break;
		default:
			return {
				hasError: true,
				message: `invild  addressType is ${addressType}`,
			};
	}
	if (!addressValue) {
		return {
			hasError: true,
			message: `addressValue is empty, addressType is ${addressType}`,
		};
	}
1--> ipv4 addressLength =4 // 2--> 域名 addressLength=addressBuffer[1] // 3--> ipv6 addressLength =16 const addressType = addressBuffer[0];let addressLength = 0;让 addressValueIndex = addressIndex + 1;let addressValue = '';switch (addressType) { case 1: addressLength = 4; addressValue = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ).join('.'); break; case 2: addressLength = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) )[0]; addressValueIndex += 1; addressValue = new TextDecoder().decode( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ); break; case 3: addressLength = 16; const dataView = new DataView( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) );2001:0db8:85a3:0000:0000:8a2e:0370:7334 const IPv6 = [];for (let i = 0; i < 8; i++) { ipv6.push(dataView.getUint16(i * 2).toString(16)); } addressValue = ipv6.join(':');似乎不需要为 IPv6 中断添加 [];默认值: return { hasError: true, message: 'invild addressType is ${addressType}', };} if (!addressValue) { return { hasError: true, message: 'addressValue is empty, addressType is ${addressType}', }; }
	return {
		hasError: false,
		addressRemote: addressValue,
		addressType,
		portRemote,
		rawDataIndex: addressValueIndex + addressLength,
		vlessVersion: version,
		isUDP,
	};
}
return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, };}

/**
 * Converts a remote socket to a WebSocket connection.
 * @param {import("@cloudflare/workers-types").Socket} remoteSocket The remote socket to convert.
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to connect to.
 * @param {ArrayBuffer | null} vlessResponseHeader The VLESS response header.
 * @param {(() => Promise<void>) | null} retry The function to retry the connection if it fails.
 * @param {(info: string) => void} log The logging function.
 * @returns {Promise<void>} A Promise that resolves when the conversion is complete.
 */
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
	// remote--> ws
	let remoteChunkCount = 0;
	let chunks = [];
	/** @type {ArrayBuffer | null} */
	let vlessHeader = vlessResponseHeader;
	let hasIncomingData = false; // check if remoteSocket has incoming data
	await remoteSocket.readable
		.pipeTo(
			new WritableStream({
				start() {
				},
				/**
				 * 
				 * @param {Uint8Array} chunk 
				 * @param {*} controller 
				 */
				async write(chunk, controller) {
					hasIncomingData = true;
					remoteChunkCount++;
					if (webSocket.readyState !== WS_READY_STATE_OPEN) {
						controller.error(
							'webSocket.readyState is not open, maybe close'
						);
					}
					if (vlessHeader) {
						webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
						vlessHeader = null;
					} else {
						// console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`);
						// seems no need rate limit this, CF seems fix this??..
						// if (remoteChunkCount > 20000) {
						// 	// cf one package is 4096 byte(4kb),  4096 * 20000 = 80M
						// 	await delay(1);
						// }
						webSocket.send(chunk);
					}
				},
				close() {
					log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);
					// safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.
				},
				abort(reason) {
					console.error(`remoteConnection!.readable abort`, reason);
				},
			})
		)
		.catch((error) => {
			console.error(
				`remoteSocketToWS has exception `,
				error.stack || error
			);
			safeCloseWebSocket(webSocket);
		});
 /** * 将远程套接字转换为 WebSocket 连接。* @param {import(“@cloudflare/workers-types”)。Socket} remoteSocket 要转换的远程套接字。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 要连接到的 WebSocket。* @param {ArrayBuffer | null} vlessResponseHeader VLESS 响应标头。* @param {(() => Promise<void>) | null} retry 如果连接失败,则重试连接的函数。 * @param {(info: string) => void} log 日志记录函数。 * @returns {Promise<void>} 转换完成后解析的 Promise。 */ 异步函数 remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type {ArrayBuffer | null} */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // 检查 remoteSocket 是否有传入数据 await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param {Uint8Array} chunk * @param {*} controller */ async write(chunk, controller) { hasIncomingData = true; remoteChunkCount++; if (webSocket.readyState !== WS_READY_STATE_OPEN) { controller.error( 'webSocket.readyState is not open, may close' ); } if (vlessHeader) { webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); vlessHeader = null; } else { // 控制台。log('remoteSocketToWS 发送块 ${chunk.byteLength}');似乎不需要速率限制这个,CF似乎解决了这个问题??..if (remoteChunkCount > 20000) { // // cf 一个包是 4096 byte(4kb), 4096 * 20000 = 80M // await delay(1); // } webSocket.send(chunk);} }, close() { log('remoteConnection!.readable 接近,hasIncomingData 为 ${hasIncomingData}');safeCloseWebSocket(webSocket);在某些情况下,不需要服务器关闭 websocket 首先会导致 HTTP ERR_CONTENT_LENGTH_MISMATCH问题,客户端无论如何都会发送关闭事件。}, abort(reason) { console.error('remoteConnection!.可读中止',原因);}, }) ) .catch((error) => { console.error( 'remoteSocketToWS has exception ', error.堆栈 ||错误);safeCloseWebSocket(webSocket);});
	// seems is cf connect socket have error,
	// 1. Socket.closed will have error
	// 2. Socket.readable will be close without any data coming
	if (hasIncomingData === false && retry) {
		log(`retry`)
		retry();
	}
}
似乎是 cf 连接套接字有错误, // 1.Socket.closed 将出现错误 // 2.Socket.readable 将关闭,没有任何数据传入,如果 (hasIncomingData === false &&; retry) { log('retry') retry();
/**
 * Decodes a base64 string into an ArrayBuffer.
 * @param {string} base64Str The base64 string to decode.
 * @returns {{earlyData: ArrayBuffer|null, error: Error|null}} An object containing the decoded ArrayBuffer or null if there was an error, and any error that occurred during decoding or null if there was no error.
 */
function base64ToArrayBuffer(base64Str) {
	if (!base64Str) {
		return { earlyData: null, error: null };
	}
	try {
		// go use modified Base64 for URL rfc4648 which js atob not support
		base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
		const decode = atob(base64Str);
		const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
		return { earlyData: arryBuffer.buffer, error: null };
	} catch (error) {
		return { earlyData: null, error };
	}
}
/** * 将 base64 字符串解码为 ArrayBuffer。* @param {string} base64Str 要解码的 base64 字符串。* @returns {{earlyData: ArrayBuffer|null, error: Error|null}} 包含解码的 ArrayBuffer 或 null(如果有错误)的对象,以及解码期间发生的任何错误,如果没有错误,则包含 null。*/ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return { earlyData: null, error: null }; } try { // go use modified Base64 for URL rfc4648 which js atob not support base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); const decode = atob(base64Str); const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); return { earlyData: arryBuffer.buffer, error: null }; } catch (error) { return { earlyData: null, error }; } }
/**
 * Checks if a given string is a valid UUID.
 * Note: This is not a real UUID validation.
 * @param {string} uuid The string to validate as a UUID.
 * @returns {boolean} True if the string is a valid UUID, false otherwise.
 */
function isValidUUID(uuid) {
	const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
	return uuidRegex.test(uuid);
}
/** * 检查给定字符串是否为有效的 UUID。* 注意:这不是真正的 UUID 验证。* @param {string} uuid 要验证为 UUID 的字符串。* @returns {boolean} 如果字符串是有效的 UUID,则为 true,否则为 false。*/ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); }
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
/**
 * Closes a WebSocket connection safely without throwing exceptions.
 * @param {import("@cloudflare/workers-types").WebSocket} socket The WebSocket connection to close.
 */
function safeCloseWebSocket(socket) {
	try {
		if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
			socket.close();
		}
	} catch (error) {
		console.error('safeCloseWebSocket error', error);
	}
}
常量 WS_READY_STATE_OPEN = 1;常量 WS_READY_STATE_CLOSING = 2;/** * 安全地关闭 WebSocket 连接,而不会引发异常。* @param {import(“@cloudflare/workers-types”)。WebSocket} socket 要关闭的 WebSocket 连接。*/ function safeCloseWebSocket(socket) { try { if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { socket.close(); } } catch (error) { console.error('safeCloseWebSocket error', error);
const byteToHex = []; 常量字节ToHex = [];
for (let i = 0; i < 256; ++i) {
	byteToHex.push((i + 256).toString(16).slice(1));
}
for (let i = 0; i < 256; ++i) { byteToHex.push((i + 256).toString(16).slice(1));
function unsafeStringify(arr, offset = 0) {
	return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
函数 unsafeStringify(arr, offset = 0) { return(byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + “-” + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + “-” + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + “-” + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + “-” + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] +byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();}
function stringify(arr, offset = 0) {
	const uuid = unsafeStringify(arr, offset);
	if (!isValidUUID(uuid)) {
		throw TypeError("Stringified UUID is invalid");
	}
	return uuid;
}
function stringify(arr, offset = 0) { const uuid = unsafeStringify(arr, offset); if (!isValidUUID(uuid)) { throw TypeError(“Stringified UUID is invalid”); } return uuid; }

/**
 * Handles outbound UDP traffic by transforming the data into DNS queries and sending them over a WebSocket connection.
 * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket connection to send the DNS queries over.
 * @param {ArrayBuffer} vlessResponseHeader The VLESS response header.
 * @param {(string) => void} log The logging function.
 * @returns {{write: (chunk: Uint8Array) => void}} An object with a write method that accepts a Uint8Array chunk to write to the transform stream.
 */
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
/** * 通过将数据转换为 DNS 查询并通过 WebSocket 连接发送来处理出站 UDP 流量。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 用于发送 DNS 查询的 WebSocket 连接。* @param {ArrayBuffer} vlessResponseHeader VLESS 响应标头。* @param {(string) => void} log 日志函数。* @returns {{write: (chunk: Uint8Array) => void}} 一个对象,其写入方法接受 Uint8Array 块以写入转换流。*/ 异步函数 handleUDPOutBound(webSocket, vlessResponseHeader, log) {
	let isVlessHeaderSent = false;
	const transformStream = new TransformStream({
		start(controller) {
let isVlessHeaderSent = false;const transformStream = new TransformStream({ start(controller) {
		},
		transform(chunk, controller) {
			// udp message 2 byte is the the length of udp data
			// TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
			for (let index = 0; index < chunk.byteLength;) {
				const lengthBuffer = chunk.slice(index, index + 2);
				const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
				const udpData = new Uint8Array(
					chunk.slice(index + 2, index + 2 + udpPakcetLength)
				);
				index = index + 2 + udpPakcetLength;
				controller.enqueue(udpData);
			}
		},
		flush(controller) {
		}
	});
}, transform(chunk, controller) { // udp message 2 byte 是 udp 数据的长度 // TODO: 这应该有 bug, beacsue 也许 udp 块可以在两个 websocket 消息中 for (let index = 0; index < chunk.byteLength;){ const lengthBuffer = chunk.slice(index, index + 2); const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); const udpData = new Uint8Array( chunk.slice(index + 2, index + 2 + udpPakcetLength) ); index = index + 2 + udpPakcetLength; controller.enqueue(udpData); }}, flush(controller) { } });
	// only handle dns udp for now
	transformStream.readable.pipeTo(new WritableStream({
		async write(chunk) {
			const resp = await fetch(dohURL, // dns server url
				{
					method: 'POST',
					headers: {
						'content-type': 'application/dns-message',
					},
					body: chunk,
				})
			const dnsQueryResult = await resp.arrayBuffer();
			const udpSize = dnsQueryResult.byteLength;
			// console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));
			const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);
			if (webSocket.readyState === WS_READY_STATE_OPEN) {
				log(`doh success and dns message length is ${udpSize}`);
				if (isVlessHeaderSent) {
					webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
				} else {
					webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
					isVlessHeaderSent = true;
				}
			}
		}
	})).catch((error) => {
		log('dns udp has error' + error)
	});
现在只处理 dns udp transformStream.readable.pipeTo(new WritableStream({ async write(chunk) { const resp = await fetch(dohURL, // dns server url { method: 'POST', headers: { 'content-type': 'application/dns-message', }, body: chunk, }) const dnsQueryResult = await resp.arrayBuffer(); const udpSize = dnsQueryResult.byteLength; // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);if (webSocket.readyState === WS_READY_STATE_OPEN) { log('doh 成功且 dns 消息长度为 ${udpSize}'); if (isVlessHeaderSent) { webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); } else { webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); isVlessHeaderSent = true;})).catch((error) => { log('dns udp has error' + error) });
	const writer = transformStream.writable.getWriter();
常量写入器 = transformStream.writable.getWriter();
	return {
		/**
		 * 
		 * @param {Uint8Array} chunk 
		 */
		write(chunk) {
			writer.write(chunk);
		}
	};
}
return { /** * * @param {Uint8Array} 块 */ write(chunk) { writer.write(chunk);}
/**
 *
 * @param {string} userID - single or comma separated userIDs
 * @param {string | null} hostName
 * @returns {string}
 */
function getVLESSConfig(userIDs, hostName) {
	const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`;
	const hashSeparator = "################################################################";
/** * * @param {string} userID - 单个或逗号分隔的 userID * @param {string | null} hostName * @returns {string} */ function getVLESSConfig(userIDs, hostName) { const commonUrlPart = ':443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}'; const hashSeparator = “################################################################”;
	// Split the userIDs into an array
	const userIDArray = userIDs.split(",");
将 userID 拆分为一个数组 const userIDArray = userIDs.split(“,”);
	// Prepare output string for each userID
	const output = userIDArray.map((userID) => {
		const vlessMain = `vless://${userID}@${hostName}${commonUrlPart}`;
		const vlessSec = `vless://${userID}@${proxyIP}${commonUrlPart}`;
		return `<h2>UUID: ${userID}</h2>${hashSeparator}\nv2ray default ip
---------------------------------------------------------------
${vlessMain}
<button onclick='copyToClipboard("${vlessMain}")'><i class="fa fa-clipboard"></i> Copy vlessMain</button>
---------------------------------------------------------------
v2ray with bestip
---------------------------------------------------------------
${vlessSec}
<button onclick='copyToClipboard("${vlessSec}")'><i class="fa fa-clipboard"></i> Copy vlessSec</button>
---------------------------------------------------------------`;
	}).join('\n');
	const sublink = `https://${hostName}/sub/${userIDArray[0]}?format=clash`
	const subbestip = `https://${hostName}/bestip/${userIDArray[0]}`;
	const clash_link = `https://api.v1.mk/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
	// Prepare header string
	const header = `
<p align='center'><img src='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' alt='图片描述' style='margin-bottom: -50px;'>
<b style='font-size: 15px;'>Welcome! This function generates configuration for VLESS protocol. If you found this useful, please check our GitHub project for more:</b>
<b style='font-size: 15px;'>欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个star:</b>
<a href='https://github.com/3Kmfi6HP/EDtunnel' target='_blank'>EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a>
<iframe src='https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large' frameborder='0' scrolling='0' width='170' height='30' title='GitHub'></iframe>
<a href='//${hostName}/sub/${userIDArray[0]}' target='_blank'>VLESS 节点订阅连接</a>
<a href='clash://install-config?url=${encodeURIComponent(`https://${hostName}/sub/${userIDArray[0]}?format=clash`)}}' target='_blank'>Clash for Windows 节点订阅连接</a>
<a href='${clash_link}' target='_blank'>Clash 节点订阅连接</a>
<a href='${subbestip}' target='_blank'>优选IP自动节点订阅</a>
<a href='clash://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>Clash优选IP自动</a>
<a href='sing-box://import-remote-profile?url=${encodeURIComponent(subbestip)}' target='_blank'>singbox优选IP自动</a>
<a href='sn://subscription?url=${encodeURIComponent(subbestip)}' target='_blank'>nekobox优选IP自动</a>
<a href='v2rayng://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>v2rayNG优选IP自动</a></p>`;
 为每个用户 ID 准备输出字符串 const output = userIDArray.map((userID) => { const vlessMain = 'vless://${userID}@${hostName}${commonUrlPart}'; const vlessSec = 'vless://${userID}@${proxyIP}${commonUrlPart}'; return '<h2>UUID: ${userID}</h2>${hashSeparator}\nv2ray default ip --------------------------------------------------------------- ${vlessMain} <button onclick='copyToClipboard(“${vlessMain}”)'><i class=“fa fa-clipboard”></i> 复制 vlessMain</button>--------------------------------------------------------------- v2ray with bestip --------------------------------------------------------------- ${vlessSec} <button onclick='copyToClipboard(“${vlessSec}”)'><i class=“fa fa-clipboard”></i> Copy vlessSec</button> ---------------------------------------------------------------'; })。联接('\n');const sublink = 'https://${hostName}/sub/${userIDArray[0]}?format=clash' const subbestip = 'https://${hostName}/bestip/${userIDArray[0]}';const clash_link = 'https://api.v1.mk/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true';准备头文件字符串 const header = ' <p align='center'><img src='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' alt='图片描述' style='margin-bottom: -50px;'> <b style='font-size: 15px;'>欢迎!此函数生成 VLESS 协议的配置。如果您觉得这很有用,请查看我们的 GitHub 项目了解更多信息:</b> <b style='font-size: 15px;'>欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个星:</b> <a href='https://github.com/3Kmfi6HP/EDtunnel' target='_blank'>EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a> <iframe src='https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large' frameborder='0' scrolling='0' width='170' height='30' title='GitHub'></iframe> <a href='//${hostName}/sub/${userIDArray[0]}' target='_blank'>VLESS 节点订阅连接</a> <a href='clash://install-config?url=${encodeURIComponent('https://${hostName}/sub/${userIDArray[0]}?format=clash')}}' target='_blank'>Clash for Windows 节点订阅连接</a> <a href='${clash_link}' target='_blank'>Clash 节点订阅连接</a><a href='${subbestip}' target='_blank'>优选IP自动节点订阅</a> <a href='clash://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>Clash优选IP自动</a> <a href='sing-box://import-remote-profile?url=${encodeURIComponent(subbestip)}' target='_blank'>singbox优选IP自动</a> <a href='sn://subscription?url=${encodeURIComponent(subbestip)}' target='_blank'>nekobox优选IP自动</a> <a href='v2rayng://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>v2rayNG优选IP自动</a></p>';
	// HTML Head with CSS and FontAwesome library
	const htmlHead = `
  <head>
	<title>EDtunnel: VLESS configuration</title>
	<meta name='description' content='This is a tool for generating VLESS protocol configurations. Give us a star on GitHub https://github.com/3Kmfi6HP/EDtunnel if you found it useful!'>
	<meta name='keywords' content='EDtunnel, cloudflare pages, cloudflare worker, severless'>
	<meta name='viewport' content='width=device-width, initial-scale=1'>
	<meta property='og:site_name' content='EDtunnel: VLESS configuration' />
	<meta property='og:type' content='website' />
	<meta property='og:title' content='EDtunnel - VLESS configuration and subscribe output' />
	<meta property='og:description' content='Use cloudflare pages and worker severless to implement vless protocol' />
	<meta property='og:url' content='https://${hostName}/' />
	<meta property='og:image' content='https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${encodeURIComponent(`vless://${userIDs.split(",")[0]}@${hostName}${commonUrlPart}`)}' />
	<meta name='twitter:card' content='summary_large_image' />
	<meta name='twitter:title' content='EDtunnel - VLESS configuration and subscribe output' />
	<meta name='twitter:description' content='Use cloudflare pages and worker severless to implement vless protocol' />
	<meta name='twitter:url' content='https://${hostName}/' />
	<meta name='twitter:image' content='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' />
	<meta property='og:image:width' content='1500' />
	<meta property='og:image:height' content='1500' />
带有 CSS 和 FontAwesome 库的 HTML Head const htmlHead = ' <head> <title>EDtunnel: VLESS 配置</title> <meta name='description' content='这是一个用于生成 VLESS 协议配置的工具。 如果您觉得有用,请在 GitHub 上给我们一颗星 https://github.com/3Kmfi6HP/EDtunnel!'> <meta name='keywords' content='EDtunnel, cloudflare pages, cloudflare worker, severless'> <meta name='viewport' content='width=device-width, initial-scale=1'><meta property='og:site_name' content='EDtunnel: VLESS 配置' /> <meta property='og:type' content='website' /> <meta property='og:title' content='EDtunnel - VLESS 配置和订阅输出' /> <meta property='og:description' content='使用 cloudflare pages 和 worker severless 实现 vless 协议' /> <meta property='og:url' content='https://${hostName}/' /><meta property='og:image' content='https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${encodeURIComponent('vless://${userIDs.split(“,”)[0]}@${hostName}${commonUrlPart}')}' /> <meta name='twitter:card' content='summary_large_image' /> <meta name='twitter:title' content='EDtunnel - VLESS 配置和订阅输出' /> <meta name='twitter:description' content='使用 cloudflare pages 和 worker severless 实现 vless 协议' /><meta name='twitter:url' content='https://${hostName}/' /> <meta name='twitter:image' content='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' /> <meta property='og:image:width' content='1500' /> <meta property='og:image:height' content='1500' />
	<style>
	body {
	  font-family: Arial, sans-serif;
	  background-color: #f0f0f0;
	  color: #333;
	  padding: 10px;
	}
<style> body { font-family: Arial, sans-serif; background-color: #f0f0f0; color: #333; padding: 10px; }
	a {
	  color: #1a0dab;
	  text-decoration: none;
	}
	img {
	  max-width: 100%;
	  height: auto;
	}
a { color: #1a0dab; text-decoration: none; } img { max-width: 100%; height: auto; }
	pre {
	  white-space: pre-wrap;
	  word-wrap: break-word;
	  background-color: #fff;
	  border: 1px solid #ddd;
	  padding: 15px;
	  margin: 10px 0;
	}
	/* Dark mode */
	@media (prefers-color-scheme: dark) {
	  body {
		background-color: #333;
		color: #f0f0f0;
	  }
pre { white-space: pre-wrap; word-wrap: break-word; background-color: #fff; border: 1px solid #ddd; padding: 15px; margin: 10px 0; } /* 深色模式 */ @media (prefers-color-scheme: dark) { body { background-color: #333; color: #f0f0f0; }
	  a {
		color: #9db4ff;
	  }
a { 颜色: #9db4ff;
	  pre {
		background-color: #282a36;
		border-color: #6272a4;
	  }
	}
	</style>
	<!-- Add FontAwesome library -->
	<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'>
  </head>
  `;
<!-- 添加 FontAwesome 库 --> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'> </head> ';
	// Join output with newlines, wrap inside <html> and <body>
	return `
  <html>
  ${htmlHead}
  <body>
  <pre style='background-color: transparent; border: none;'>${header}</pre>
  <pre>${output}</pre>
  </body>
  <script>
	function copyToClipboard(text) {
	  navigator.clipboard.writeText(text)
		.then(() => {
		  alert("Copied to clipboard");
		})
		.catch((err) => {
		  console.error("Failed to copy to clipboard:", err);
		});
	}
  </script>
  </html>`;
}
用换行符连接输出,在里面<html>换行并<body>返回 ' <html> ${htmlHead} <body> <pre style='background-color: transparent; border: none;'>${header}</pre> <pre>${output}</pre> </body> <script> function copyToClipboard(text) { navigator.clipboard.writeText(text) .then(() => { alert(“复制到剪贴板”); }) .catch((err) => { console.error(“无法复制到剪贴板:”, err); </script> </html> });';}
const portSet_http = new Set([80, 8080, 8880, 2052, 2086, 2095, 2082]);
const portSet_https = new Set([443, 8443, 2053, 2096, 2087, 2083]);
const portSet_http = 新集([80, 8080, 8880, 2052, 2086, 2095, 2082]);常量 portSet_https = 新集([443, 8443, 2053, 2096, 2087, 2083]);
function createVLESSSub(userID_Path, hostName) {
	const userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path];
	const commonUrlPart_http = `?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`;
	const commonUrlPart_https = `?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`;
函数 createVLESSSub(userID_Path, hostName) { const userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path]; const commonUrlPart_http = '?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#'; const commonUrlPart_https = '?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#';
	const output = userIDArray.flatMap((userID) => {
		const httpConfigurations = Array.from(portSet_http).flatMap((port) => {
			if (!hostName.includes('pages.dev')) {
				const urlPart = `${hostName}-HTTP-${port}`;
				const vlessMainHttp = `vless://${userID}@${hostName}:${port}${commonUrlPart_http}${urlPart}`;
				return proxyIPs.flatMap((proxyIP) => {
					const vlessSecHttp = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_http}${urlPart}-${proxyIP}-EDtunnel`;
					return [vlessMainHttp, vlessSecHttp];
				});
			}
			return [];
		});
const output = userIDArray.flatMap((userID) => { const httpConfigurations = Array.from(portSet_http).flatMap((port) => { if (!hostName.includes('pages.dev')) { const urlPart = '${hostName}-HTTP-${port}'; const vlessMainHttp = 'vless://${userID}@${hostName}:${port}${commonUrlPart_http}${urlPart}'; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttp = 'vless://${userID}@${proxyIP}:${port}${commonUrlPart_http}${urlPart}-${proxyIP}-EDtunnel'; 返回 [vlessMainHttp,vlessSecHttp];});} 返回 [];});
		const httpsConfigurations = Array.from(portSet_https).flatMap((port) => {
			const urlPart = `${hostName}-HTTPS-${port}`;
			const vlessMainHttps = `vless://${userID}@${hostName}:${port}${commonUrlPart_https}${urlPart}`;
			return proxyIPs.flatMap((proxyIP) => {
				const vlessSecHttps = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_https}${urlPart}-${proxyIP}-EDtunnel`;
				return [vlessMainHttps, vlessSecHttps];
			});
		});
const httpsConfigurations = Array.from(portSet_https).flatMap((port) => { const urlPart = '${hostName}-HTTPS-${port}'; const vlessMainHttps = 'vless://${userID}@${hostName}:${port}${commonUrlPart_https}${urlPart}'; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttps = 'vless://${userID}@${proxyIP}:${port}${commonUrlPart_https}${urlPart}-${proxyIP}-EDtunnel'; return [vlessMainHttps, vlessSecHttps]; });
		return [...httpConfigurations, ...httpsConfigurations];
	});
返回 [...httpConfigurations、...https配置];});
	return output.join('\n');
}
返回 output.join('\n');}
const cn_hostnames = [
	'weibo.com',                // Weibo - A popular social media platform
	'www.baidu.com',            // Baidu - The largest search engine in China
	'www.qq.com',               // QQ - A widely used instant messaging platform
	'www.taobao.com',           // Taobao - An e-commerce website owned by Alibaba Group
	'www.jd.com',               // JD.com - One of the largest online retailers in China
	'www.sina.com.cn',          // Sina - A Chinese online media company
	'www.sohu.com',             // Sohu - A Chinese internet service provider
	'www.tmall.com',            // Tmall - An online retail platform owned by Alibaba Group
	'www.163.com',              // NetEase Mail - One of the major email providers in China
	'www.zhihu.com',            // Zhihu - A popular question-and-answer website
	'www.youku.com',            // Youku - A Chinese video sharing platform
	'www.xinhuanet.com',        // Xinhua News Agency - Official news agency of China
	'www.douban.com',           // Douban - A Chinese social networking service
	'www.meituan.com',          // Meituan - A Chinese group buying website for local services
	'www.toutiao.com',          // Toutiao - A news and information content platform
	'www.ifeng.com',            // iFeng - A popular news website in China
	'www.autohome.com.cn',      // Autohome - A leading Chinese automobile online platform
	'www.360.cn',               // 360 - A Chinese internet security company
	'www.douyin.com',           // Douyin - A Chinese short video platform
	'www.kuaidi100.com',        // Kuaidi100 - A Chinese express delivery tracking service
	'www.wechat.com',           // WeChat - A popular messaging and social media app
	'www.csdn.net',             // CSDN - A Chinese technology community website
	'www.imgo.tv',              // ImgoTV - A Chinese live streaming platform
	'www.aliyun.com',           // Alibaba Cloud - A Chinese cloud computing company
	'www.eyny.com',             // Eyny - A Chinese multimedia resource-sharing website
	'www.mgtv.com',             // MGTV - A Chinese online video platform
	'www.xunlei.com',           // Xunlei - A Chinese download manager and torrent client
	'www.hao123.com',           // Hao123 - A Chinese web directory service
	'www.bilibili.com',         // Bilibili - A Chinese video sharing and streaming platform
	'www.youth.cn',             // Youth.cn - A China Youth Daily news portal
	'www.hupu.com',             // Hupu - A Chinese sports community and forum
	'www.youzu.com',            // Youzu Interactive - A Chinese game developer and publisher
	'www.panda.tv',             // Panda TV - A Chinese live streaming platform
	'www.tudou.com',            // Tudou - A Chinese video-sharing website
	'www.zol.com.cn',           // ZOL - A Chinese electronics and gadgets website
	'www.toutiao.io',           // Toutiao - A news and information app
	'www.tiktok.com',           // TikTok - A Chinese short-form video app
	'www.netease.com',          // NetEase - A Chinese internet technology company
	'www.cnki.net',             // CNKI - China National Knowledge Infrastructure, an information aggregator
	'www.zhibo8.cc',            // Zhibo8 - A website providing live sports streams
	'www.zhangzishi.cc',        // Zhangzishi - Personal website of Zhang Zishi, a public intellectual in China
	'www.xueqiu.com',           // Xueqiu - A Chinese online social platform for investors and traders
	'www.qqgongyi.com',         // QQ Gongyi - Tencent's charitable foundation platform
	'www.ximalaya.com',         // Ximalaya - A Chinese online audio platform
	'www.dianping.com',         // Dianping - A Chinese online platform for finding and reviewing local businesses
	'www.suning.com',           // Suning - A leading Chinese online retailer
	'www.zhaopin.com',          // Zhaopin - A Chinese job recruitment platform
	'www.jianshu.com',          // Jianshu - A Chinese online writing platform
	'www.mafengwo.cn',          // Mafengwo - A Chinese travel information sharing platform
	'www.51cto.com',            // 51CTO - A Chinese IT technical community website
	'www.qidian.com',           // Qidian - A Chinese web novel platform
	'www.ctrip.com',            // Ctrip - A Chinese travel services provider
	'www.pconline.com.cn',      // PConline - A Chinese technology news and review website
	'www.cnzz.com',             // CNZZ - A Chinese web analytics service provider
	'www.telegraph.co.uk',      // The Telegraph - A British newspaper website	
	'www.ynet.com',             // Ynet - A Chinese news portal
	'www.ted.com',              // TED - A platform for ideas worth spreading
	'www.renren.com',           // Renren - A Chinese social networking service
	'www.pptv.com',             // PPTV - A Chinese online video streaming platform
	'www.liepin.com',           // Liepin - A Chinese online recruitment website
	'www.881903.com',           // 881903 - A Hong Kong radio station website
	'www.aipai.com',            // Aipai - A Chinese online video sharing platform
	'www.ttpaihang.com',        // Ttpaihang - A Chinese celebrity popularity ranking website
	'www.quyaoya.com',          // Quyaoya - A Chinese online ticketing platform
	'www.91.com',               // 91.com - A Chinese software download website
	'www.dianyou.cn',           // Dianyou - A Chinese game information website
	'www.tmtpost.com',          // TMTPost - A Chinese technology media platform
	'www.douban.com',           // Douban - A Chinese social networking service
	'www.guancha.cn',           // Guancha - A Chinese news and commentary website
	'www.so.com',               // So.com - A Chinese search engine
	'www.58.com',               // 58.com - A Chinese classified advertising website
	'www.cnblogs.com',          // Cnblogs - A Chinese technology blog community
	'www.cntv.cn',              // CCTV - China Central Television official website
	'www.secoo.com',            // Secoo - A Chinese luxury e-commerce platform
];
const cn_hostnames = [ 'weibo.com', // 微博 - 流行的社交媒体平台 'www.baidu.com', // 百度 - 中国最大的搜索引擎 'www.qq.com', // QQ - 广泛使用的即时通讯平台 'www.taobao.com', // 淘宝 - 阿里巴巴集团旗下的电子商务网站 'www.jd.com', // JD.com - 中国最大的在线零售商之一 'www.sina.com.cn', // 新浪 - 中国在线媒体公司 'www.sohu.com', // 搜狐 - 中国互联网服务提供商 'www.tmall.com', 天猫 - 阿里巴巴集团旗下的在线零售平台 “www.163.com”, // 网易邮箱 - 中国主要的电子邮件提供商之一 “www.zhihu.com”, // 知乎 - 一个受欢迎的问答网站 “www.youku.com”, // 优酷 - 中国视频分享平台“www.xinhuanet.com”, // 新华社 - 中国官方通讯社“www.douban.com”, // 豆瓣 - 中国社交网络服务“www.meituan.com”, // 美团 - 一个中国的本地服务团购网站'www.toutiao.com', // 今日头条 - 新闻和信息内容平台 'www.ifeng.com', // 爱风 - 中国流行的新闻网站 'www.autohome.com.cn', // 汽车之家 - 中国领先的汽车在线平台'www.360.cn', // 360 - 中国互联网安全公司 'www.douyin.com', // 抖音 - 中国短视频平台'www.kuaidi100.com', // 快的100 - 中国快递跟踪服务'www.wechat.com', // 微信 - 流行的消息传递和社交媒体应用程序'www.csdn.net', // CSDN - 中国科技社区网站 'www.imgo.tv', // ImgoTV - 中国直播平台'www.aliyun.com', // 阿里云 - 中国云计算公司 'www.eyny.com', // Eyny - 中国多媒体资源共享网站'www.mgtv.com', // MGTV - 中国在线视频平台'www.xunlei.com', // 迅雷 - 中国下载管理器和种子客户端 'www.hao123.com', // Hao123 - 中国网络目录服务 'www.bilibili.com', // 哔哩哔哩 - 中国视频分享和流媒体平台'www.youth.cn', // Youth.cn - 中国青年报新闻门户网站'www.hupu.com', // 虎浦 - 中国体育社区和论坛 'www.youzu.com', // 游豆互动 - 中国游戏开发商和发行商 'www.panda.tv', // 熊猫电视 - 中国直播平台'www.tudou.com', 土豆 - 中国视频分享网站“www.zol.com.cn”, // ZOL - 中国电子和小工具网站“www.toutiao.io”, // 今日头条 - 新闻和信息应用程序“www.tiktok.com”, // TikTok - 中国短视频应用程序“www.netease.com”, // 网易 - 中国互联网科技公司“www.cnki.net”, // CNKI - 中国国家知识基础设施,信息聚合器“www.zhibo8.cc”, // Zhibo8 - 提供体育直播的网站 'www.zhangzishi.cc', // 张子石 - 中国公共知识分子张子石的个人网站 “www.xueqiu.com”, // 雪球 - 面向投资者和交易者的中国在线社交平台 “www.qqgongyi.com”, // QQ公益 - 腾讯慈善基金会平台“www.ximalaya.com”, // 喜马拉雅 - 中国在线音频平台“www.dianping.com”, // 大众点评 - 寻找和评论本地企业的中国在线平台 “www.suning.com”, // 苏宁 - 中国领先的在线零售商 “www.zhaopin.com”, // 智联招聘 - A中国招聘平台'www.jianshu.com', // 建书 - 中国在线写作平台'www.mafengwo.cn', // 马峰窝 - 中国旅游信息共享平台'www.51cto.com', // 51CTO - 中国IT技术社区网站'www.qidian.com', // 启电 - 中国网络小说平台'www.ctrip.com', // 携程 - 中国旅游服务提供商 'www.pconline.com.cn', // PConline - 中国科技新闻和评论网站 'www.cnzz.com', // CNZZ - 中国网络分析服务提供商 'www.telegraph.co.uk', // The Telegraph - 英国报纸网站 'www.ynet.com', // Ynet - 中国新闻门户网站 'www.ted.com', // TED - 一个值得传播思想的平台 “www.renren.com”, // 人人网 - 中国社交网络服务 'www.pptv.com', // PPTV - 一个中文在线视频流媒体平台“www.liepin.com”, // 列品 - 中国在线招聘网站“www.881903.com”, // 881903 - 香港广播电台网站“www.aipai.com”, // 爱拍 - 中国在线视频分享平台“www.ttpaihang.com”, // Ttpaihang - 中国名人人气排行榜网站“www.quyaoya.com”, // 曲耀雅 - 中国在线票务平台“www.91.com”, // 91.com - 中国软件下载网站“www.dianyou.cn”, // 点游 - 中国游戏信息网站'www.tmtpost.com', // TMTPost - 中国科技媒体平台'www.douban.com', // 豆瓣 - 中国社交网络服务'www.guancha.cn', // 关查 - 中国新闻评论网站'www.so.com', // So.com - 中国搜索引擎'www.58.com', // 58.com - 中国分类广告网站'www.cnblogs.com', // Cnblogs - 中国科技博客社区'www.cntv.cn', // 央视 - 中国中央电视台官方网站'www.secoo.com', // 寺库 - 中国奢侈品电商平台 ];

仓库:https://github.com/Li468446/workers.js/tree/main

---------------------------------------------------------------------------------------------------------------------------------

     导入完成后,我们需要修改两个地方,分别是UUID和CDN-IP。CDN-IP必须是反代IP,不能是官方IP!!!

4、UUID生成

    UUID的大概解释在本文开头的知识链条中有比较标准的解释,大家可以作为参考。UUID因其属性,随机生成且基本不会重合,我们现在去生成它,将它放入代码中即可。这里给大家推荐一个UUID在线随机生成的网站,希望能帮到各位开发者和极客玩家>>>open<<<,网址:

https://1024tools.com/uuid

进入后点击生成即可,然后随便选择一个就行,UUID非常重要,请把你使用的UUID保存一份副本,避免后续的操作无法进行!!!

这里我则第二个,复制出来后回到实例代码中,修改UUID为生成的这个。

5、cloud flare-proxy-IP 、proxy域的选择

优质域名推荐

cdn-all.xn--b6gac.eu.org
cdn.xn--b6gac.eu.org
cdn-b100.xn--b6gac.eu.org
edgetunnel.anycast.eu.org
cdn.anycast.eu.org(亚洲地区)
jp.cloudflarest.link
achk.cloudflarest.link(阿里香港优选)

这里给大家两种优选cloud flare优质IP的方案:

2、个人推荐

113.64.186.198 
103.200.112.108
199.15.76.35

edgetunnel.anycast.eu.org(美国的加速CDN)
cdn.anycast.eu.org(香港日本新加坡加速CDN)

香港优选IP:20.187.89.16
日本高速优选IP:146.56.149.205
美国优选IP:172.64.135.146)
韩国高速优选IP:129.154.199.251
日本高速优选IP:146.56.149.205

cdn-all.xn--b6gac.eu.org
cdn.xn--b6gac.eu.org 
cdn-b100.xn--b6gac.eu.org 
edgetunnel.anycast.eu.org 
cdn.anycast.eu.org 

    同时也可以选出运营商的最优反代IP,放入实例代码中的CDN-IP位置即可。这里我就直接使用这个IP。

113.64.186.198 

填入后点击部署即可。

我已经保存部署过了,所以这里这个按钮是灰色的

到这里,cloud flare的实例部署就已经完成了。

三、使用方法(无域名)

生成的实例流量转发的协议为vless,所以Vtwo(2)ray是个不错的选择.

1、V222222222222rayn

        复制刚刚创建的代码地址,格式为:https://vless.xxx.workers.dev/,其中 vless 为创建的脚本名称,xxx 为 CloudFlare 用户名。

复制出来后,后面加个/+UUID,如下

https://ceshi.xiaorantang68.workers.dev/37959939-2926-4e4c-a0d2-6468a1b9f7f7

这样我们就获取到了vless服务器的两个节点信息

     复制红框内的内容后粘贴进v222222222222rayn

    添加完成,但是现在这个服务器还不能使用,需要对节点进行进一步的修改。

因为不使用域名,所以加密方式不需要选择,将TLS改为空

如下图:

端口呢,也不能使用443端口,这里我切换为80端口(不使用TLS可以将端口改为80或2052)

2、修改地址

    现在就可以进行IP的优选了,脚本可以在我的仓库下载:https://github.com/Li468446/workers.js

   

填入v222222222rayn的vless服务器连接信息中即可。

lanzou:https://pan.lanpw.com/b0742hkxeicon-default.png?t=N7T8https://pan.lanpw.com/b0742hkxe

       下载下来后运行jp.bat批处理程序即可自动优选。优选前请关闭所有的系统代理,避免优选出现误差!!!

优选结果会写入rest.csv文件内,填入vless服务器IP中即可,这里就不多做解答

优选出来的IP直接填入vless服务器的地址栏即可,确定后切换为活动服务器,访问谷歌尝试:

到这里,v2222222222222rayn的配置就完成了,可以进行测试

四、实例测试

我这里优选出来的本地IP是104.19.170.**,填入后访问谷歌,测试成功。

查看cloud flare的实例,有正常的请求,正常工作。

实验成功

五、总结

       需要有一定的web基础和网络代理的基础,过程繁琐,在这个过程中,最容易出错的地方就在于实例中的workers的proxy IP和v222222222222rayn的客户端连接的CF优选IP容易搞混淆,Proxy-IP必须是反向代理IP,否则实例的反向代理会出现问题。

=========================================================================

        最后,建议各位开发者们仔细观看,认真学习,我在文中如有表达不够清晰,存在疑问的,可以在下方评论区提出,我会一一为大家解答,有错误的地方也请大佬指点一二,共同学习,一起进步。

本章节就到这里,感谢大家的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1566735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript(六)---【回调、异步、promise、Async】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 JavaScript(二)---【js数组、js对象、this指针】-CSDN博客 JavaScript(三)---【this指针&#xff0c;函数定义、Call、Apply、函数绑定、闭包】-CSDN博客 JavaScript(四)---【执…

STM32的I2C补充说明

1.前言 前面不是开发了F407的i2c嘛&#xff0c;最近做项目有三四个i2c器件&#xff0c;项目要求用f103&#xff0c;于是看了一下f103&#xff0c;发现并没有多大区别&#xff0c;下面我会说一下异同。还有关于接收的过程也有要补充的。 2.F103 VS F407 两者之间几乎没有区别&a…

Maven 项目之快速选择环境配置文件

Maven项目中&#xff0c;多环境之间如何进行配置文件的切换。在我们开发的过程中&#xff0c;经常会出现开发环境、测试环境、生产环境等之间的切换&#xff0c;如果我们每次都去替换配置文件&#xff0c;就会跟繁琐&#xff0c;这个时候就可以创建多个环境&#xff0c;同时在对…

下载页面上的视频

引言&#xff1a;有些页面上的视频可以直接右键另存为或者F12检索元素找到视频地址打开后保存&#xff0c;但有些视频页面是转码后的视频&#xff0c;不能直接另存为视频格式&#xff0c;可以参考下本方法 以该页面视频为例&#xff1a;加载中...点击查看详情https://wx.vzan.c…

zookeeper监听集群节点的实现zkclient组件实现方案(Java版)

ZooKeeper Watcher 机制 client 向zookeeper 注册监听client注册的同时会存储一个WatchManager对象向zookeeper发生改变则notification client 并发送一个WatchManager对象,然后client再更新该对象 package com.jacky.zk.demo;import org.I0Itec.zkclient.IZkChildListener;…

LeetCode575——分糖果

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 这道题比较简单&#xff0c;但我还是花费了将近四个小时的时间去解答&#xff0c;AC的那一刻&#xff0c;终于全身舒畅&#xff0c;这道题的思路就是先求出糖果的种数&#xff0c;然后我们从题中可以得出&#x…

如何配置vite的proxy

1.前言 vite项目&#xff0c;本地开发环境可以通过配置proxy代理实现跨域请求。但是生产环境&#xff0c;该配置不生效&#xff0c;一般使用 nginx 转发&#xff0c;或者后端配置cors 2.解释 server: {port: 9000,proxy: { // 本地开发环境通过代理实现跨域&#xff0c;生产…

如何处理Flutter应用在iOS平台上的兼容性问题

大家好&#xff0c;我是咕噜铁蛋&#xff01;今天我想和大家聊聊一个常见但令人头疼的问题——Flutter应用在iOS平台上的兼容性问题。在开发跨平台应用时&#xff0c;尤其是在Android和iOS两大平台上&#xff0c;兼容性问题总是难以避免。而Flutter&#xff0c;作为一个旨在解决…

使用OMP复原一维信号(MATLAB)

参考文献 https://github.com/aresmiki/CS-Recovery-Algorithms/tree/master MATLAB代码 %% 含有噪声 % minimize ||x||_1 % subject to: (||Ax-y||_2)^2<eps; % minimize : (||Ax-y||_2)^2lambda*||x||_1 % y传输中可能含噪 yyw % %% clc;clearvars; close all; %% 1.构…

IE浏览器兼容性问题——el-button点击失效

el-table 中经常有这种场景&#xff1a;最后一列是操作&#xff0c;只有一个图标 之前的实现是直接讲点击事件绑定到了 icon 图标上&#xff0c;这样在谷歌、edge、火狐等是没问题的&#xff0c;但是在ie浏览器下&#xff0c;就会出现点击事件无效的情况&#xff0c;点击后不会…

使用minikube安装使用单机版K8S(docker)

前置&#xff1a;作为一个开发&#xff0c;工作之余想玩一下k8s&#xff0c;但是搭建成本太高&#xff0c;所以就找到了minikube这个工具&#xff0c;快速搭建单机版k8s&#xff0c;下面是个人搭建流程&#xff0c;基于centos7&#xff0c;仅供参考。 1.下载kubectl&#xff0…

Idea2023创建Servlet项目

① Java EE 只是一个抽象的规范&#xff0c;具体实现称为应用服务器。 ② Java EE 只需要两个包 jsp-api.jar 和 servlet-api.jar&#xff0c;而这两个包是没有官方版本的。也就是说&#xff0c;Java 没有提供这两个包&#xff0c;只提供了一个规范。那么这两个包是谁提供的…

【Linux多线程】信号量以及线程池

【Linux多线程】信号量以及线程池 目录 【Linux多线程】信号量以及线程池POSIX信号量基于环形队列的生产者消费者模型 线程池 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2024.4.2 前言&#xff1a;本篇博客将会介绍Linux线程中的信号量以及线程池&#xff0c;完善生产者…

elementui 实现一个固定位置的Pagination(分页)组件

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、elementui 左侧导航菜单栏与main区域联动 三、elementui 中设置图片的高度并支持PC和手机自适应 四、 elementui 实现一个固定位置的Pagination&#xff08;分页&#xff09;组件 文章目录 系列文章目录…

Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview

​ 一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用&#xff0c;主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术&#xff0c;其中包括以下几个关键步骤&#xff1a; 图像预处理&#xff1a;首先&#xff0c;对输入图像进行预处理&am…

深入剖析主机安全中的零信任机制及其实施原理

引言 在数字化转型加速与云端服务普及的大背景下&#xff0c;传统依赖边界的网络安全模式逐渐显露出其局限性。面对愈发复杂多变的威胁环境&#xff0c;零信任安全架构作为新一代的安全范式应运而生&#xff0c;尤其是在主机层面的安全实践中&#xff0c;零信任机制正扮演着至…

RabbitMQ3.x之七_RabbitMQ消息队列模型

RabbitMQ3.x之七_RabbitMQ消息队列模型 文章目录 RabbitMQ3.x之七_RabbitMQ消息队列模型1. RabbitMQ消息队列模型1. 简单队列2. Work Queues(工作队列)3. Publish/Subscribe(发布/订阅)4. Routing(路由)5. Topics(主题)6. RPC(远程过程调用)7. Publisher Confirms(发布者确认) …

防止推特Twitter账号被冻结,应该选什么代理类型IP?

在处理多个 Twitter 帐号时&#xff0c;选择合适的代理IP对于避免大规模帐户暂停至关重要。现在&#xff0c;问题出现了&#xff1a;哪种类型的代理是满足您需求的最佳选择&#xff1f;下面文章将为你具体讲解推特账号冻结原因以及重点介绍如何选择代理IP。 一、推特账号被冻结…

C# WPF编程-命令

C# WPF编程-命令 概述WPF命令模型ICommand接口RoutedCommand类RoutedUICommand类命令库 概述 使用路由事件可以响应广泛的鼠标和键盘事件&#xff0c;这些事件是低级的元素。在实际应用程序中&#xff0c;功能被划分成一些高级的任务。这些任务可通过各种不同的动作和用户界面…

如何借助API实现自动化商品采集商品,自营商城选品上货搜索无货源模式

借助API实现自动化商品采集、自营商城选品上货以及搜索无货源模式&#xff0c;通常需要以下步骤&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 确定数据源&#xff1a; 首先&#xff0c;你需要确定你的商品数据来源。这可能是其他电商平台、供应商网站、数据提供商等…