标签 CDN 下的文章 - 🥝 E 家 分 享 🥝
首页
📋 留言板
🔗 友情链接
🛠️ E家百宝箱
❤️ 关于
推荐
🔍 VPS监控
🐉 青龙面板
💽 E家网盘
----------
🔗 CloudFlare
🔗 甲骨文云
🔗 RackNerd
搜 索
1
自动提取 ChromeGo 一键翻墙包内的免费节点
145 阅读
2
【汇总:免费节点 - 每周更新】
114 阅读
3
GigaFile - 日本免费大文件加密分享服务,最长保留文档100天
71 阅读
4
【E家分享月刊系列】2024-12
56 阅读
5
CloudFlare WARP 免费 VPN 搭建教程
55 阅读
精选网站
网站搭建
科学上网搭建
有感而发
软件技巧
Excel技巧
WordPress技巧
登录
搜 索
标签搜索
WordPress
脚本
GitHub
科学上网
哈佛管理导师
E家分享月刊系列
V2ray
Mac软件
AI
Cloudflare
Docker
免费节点
建站在线工具
Excel技巧
Notion
Nginx
ChatGPT
图像编辑
免费图床
网盘资源
E家之长
累计撰写
195
篇文章
累计收到
245
条评论
首页
栏目
精选网站
网站搭建
科学上网搭建
有感而发
软件技巧
Excel技巧
WordPress技巧
页面
📋 留言板
🔗 友情链接
🛠️ E家百宝箱
❤️ 关于
推荐
🔍 VPS监控
🐉 青龙面板
💽 E家网盘
----------
🔗 CloudFlare
🔗 甲骨文云
🔗 RackNerd
用户登录
登录
找到
4
篇与
CDN
相关的结果
2024-06-10
【E家原创】免费图床解决方案
前言自从建站以来,最开始文章的附图都是以附件形式上传到博客文件夹下,好处是方便备份和迁移。后来发现这样每次加载都会增加服务器的负荷,影响页面的打开速度。因此希望图文分离,图床的需求由此产生。图床的基础要求就两点:速度+稳定,本着白嫖的想法,开始尝试过网上一些免费图床,但是体验不是很好,不是隔三岔五打不开了,就是有各种限制。所以最后尽量选择一些大厂提供的免费空间作为图床。接下来我为大家分享我使用的几种免费图床解决方案:Github + CDNBackblaze + CloudflareCloudflare R2Github + CDNGithub 的图床稳定性是不容置疑的,所以图床一旦出问题,就快速切换到这个方案应急。需要注意的是 Github 图片仓库如果过大的时候,记得换仓库,否则有封号的风险。配置方案网上有很多教程,这里我想分享一直使用的方案:通过 PicX 压缩成 webp 格式并上传至 Github 上,同时复制 Markdown 格式的带 CDN 的图片链接,添加博客文章中。图片制作见另外一个帖子:fabritor:快速构建属于自己的图片设计编辑器PicX 是一款安全且免费的图床工具,提供图片上传托管、生成图片链接和图片工具箱服务。PicX 建议使用自定义的仓库和分支,即 PicX v2.0:https://v2.picx.xpoet.cn/这样你可以自由的选择上传的文件夹。使用手册见:https://picx-docs.xpoet.cn/usage-guide/get-start.html在我的设置 -> 图片链接规则配置,选择适合的 CDN,从而实现 CDN 加速。推荐使用 jsDelivr,相对稳定。类型图片链接规则GitHub Pageshttps://}.github.io/}/}GitHubhttps://github.com/}/}/raw/}/}jsDelivrhttps://cdn.jsdelivr.net/gh/}/}@}/}Staticallyhttps://cdn.statically.io/gh/}/}@}/}ChinaJsDelivrhttps://jsd.cdn.zzko.cn/gh/}/}@}/}上传时建议勾选压缩图片和转换 Markdown,这样上传后直接就得到压缩后的 Markdown 图片地址,复制到博客文章中即可。压缩图片格式推荐是 webp 格式。Backblaze + CloudflareBackblaze 的优势是每月前 10G 流量免费,用于做图床是非常够用的。同时使用 Cloudflare 自定义域名做 CDN 加速。准备条件:Backblaze 免费账户,申请地址:https://www.backblaze.com/sign-up/cloud-storage?referrer=getstartedCloudflare 免费账户,这个基本上大家都知道怎么注册了。创建存储桶登录后,点击左侧的 “桶”,点击“创作一个桶”桶独特名字:Bucket 名称必须是全局唯一的,并且必须至少有6个字符。一旦创建,后期无法修改。桶里面的档案是:设置为“公众”。桶创建后,点击“桶设定”,在“桶信息”中添加 来配置桶的缓存时间。这样可以适当的控制下流量。接下来就可以点击“上载/下载”按钮进行图片上传了。图片链接地址格式如下:https://f004.backblazeb2.com/file/自定义桶名称/图片.webp在 CloudFlare 中配置域名如上面提到的图片链接格式,链接很长又不友好。如果想替代成自定义域名的方式,就需要在 CloudFlare 中进行设置了。在 CloudFlare 域名 DNS 里面创建一条 CNAME 记录,并把上面图片链接地址中域名部分填到目标里面,并且启用 CloudFlare 的代理,这样我们才能享受到带宽联盟的优惠。注意:这里只能是二级域名,如 img.5iehome.cc,而不能是多级的 blog.img.5iehome.cc,否则 CloudFlare 会无法申请证书,也就无法正常启用 HTTPS。这时候我们就可以用 https://img.5iehome.cc/file/自定义桶名称/图片.webp 访问这个图片了。但是我们仍然想只看到 https://img.5iehome.cc/图片.webp 形式,那么还需要进行进一步的配置。为请求配置 CloudFlare 规则前往 CloudFlare 的“规则页面”,选择“转换规则”,然后在“重写 URL”这个 tab 中新增一个规则。规则名称:随意填写选择“自定义筛选表达式”:依次设置“主机名",“等于”,“img.5iehome.cc”点击“And”按钮,增加一条规则,依次设置“URL 路径”,“开头不是”,“/file/wp-img”或者直接“编辑表达式”,输入:(http.host eq "img.5iehome.cc" and not starts_with(http.request.uri.path, "/file/wp-img"))接下来设置重写参数,选择“重写到...”,表达式类型选择“Dynamic”,表达式填写:concat("/file/wp-img", http.request.uri.path)这样 CloudFlare 就会自动补全完整的路径,即可以通过 https://img.5iehome.cc/图片.webp 形式来访问图片了。对象存储管理使用 Blackblaze 自己的网页形式进行图片的上传和下载固然可以,但是操作起来感觉还是不算友好。这里推荐使用对象存储形式来管理图片。可参考 Alist 官方文档对象存储部分:https://alist.nn.ci/zh/guide/drivers/s3.html按照下图的步骤进行配置,即可在电脑端或网页端进行快速的管理,要比 Blackblaze 官方自己的管理方式要友好方便的多。Cloudflare R2CloudFlare R2 可以提供免费 CDN,绑定域名不需要备案,每月有免费 10GB 流量额度,足矣支撑个人博客使用,即使超出之后,费用也是相当便宜。详细定价: https://developers.cloudflare.com/r2/pricing/准备条件:Cloudflare 免费账户,域名已经托管到 Cloudflare 上,最好是直接在 Cloudflare 上购买的域名。在 Cloudflare 上已经绑定银行卡,不会产生扣费。创建 R2 存储桶点击页面左侧的 "R2",选择“将 R2 订阅添加到我的帐户”。接下来选择“创建存储桶”。存储桶名称:任意。位置:如果是面向大陆用户,建议指定“亚太地区(APAC)”。接下来就可以通过拖放或从计算机中选择,添加文件到 R2 存储桶中了。注意:超过 300 MB 的文件只能使用 S3 兼容性 API 或 Workers 上载。给 R2 存储桶绑定域名选择“设置”页面,在“自定义域”选择“连接域”。接下来添加自定义域名,这里就体现出直接在 Cloudflare 上购买和托管域名的好处了,直接输入想要的二级域名,点击“继续”。这样 Cloudflare 会自动为你添加二级域名了。点击“连接域”,稍等几分钟,域名就连接好了。设置允许公开访问这一步很重要,如果不设置,上传图片后,是不能直接在公网访问的。在“R2.dev 子域”中,选择“允许访问”。键入“allow”并确认,允许公开访问。设置之后可以看到,已经允许公共访问,到这一步,桶的配置已经完成。对象存储管理同样使用 Cloudflare 自己的网页形式进行图片的上传和下载操作起来感觉还是不算友好。这里推荐使用对象存储形式来管理图片。可参考 Alist 官方文档对象存储部分:https://alist.nn.ci/zh/guide/drivers/s3.html按照下图的步骤进行配置,即可在电脑端或网页端进行快速的管理。参考资料:https://www.howie6879.com/post/2022/05_pic-url-solution/#-github--jsdelivrhttps://www.boris1993.com/migrating-blog-images-to-backblaze.htmlhttps://testerhome.com/topics/36077https://alist.nn.ci/zh/guide/drivers/s3.html#list-object-version【END】
2024年06月10日
42 阅读
0 评论
0 点赞
2023-08-06
CloudFlare Workers VLESS 永久免费节点搭建
前言近期很火的利用 CloudFlare Workers 搭建 VLESS 节点,支持自定义伪装网页,可以 CDN 自选域名,13个端口随便换。注册 CloudFlare相信很多人都有 CloudFlare 帐户,这里额外介绍一个临时邮箱地址,用于注册 CloudFlare。 临时邮箱:https://www.linshiyouxiang.net/CloudFlare Workers vless 代码部署登录 CloudFlare:https://dash.cloudflare.com/点击 Workers 和 Pages,点击 创建 Workers。 创建一个脚本名称,比如 vless,点击 部署。 点击 配置 Worker,点击 快速编辑。清除默认代码。将下面章节提到的代码,粘贴到代码框中。找到 let userID = 和 let proxyIP,更改 UUID 和推荐的 CDN 加速 IP。点击 保存并部署。以下介绍两组代码供选择使用,强烈建议更换默认的 userID,如果不更换,容易被扫描盗用。生成 UUID在线生成地址:https://1024tools.com/uuid打开 V2ray 客户端,选择 添加 VLESS 服务器,在弹出的对话框中,点击 生成 按钮,即可生成 UUID。CDN 加速 IPcdn-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(阿里香港优选)vless 部署代码1代码引用地址:https://github.com/zizifn/edgetunnel/blob/main/src/worker-vless.js 注意:需要更改 UUID, 添加 proxyIP 地址。// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->. // @ts-ignore import 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'; let proxyIP = ''; if (!isValidUUID(userID)) { throw new Error('uuid is not valid'); } export default { /** * @param request * @param } env * @param ctx * @returns */ 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), ); case `/$`: { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response(`$`, { status: 200, headers: { "Content-Type": "text/plain;charset=utf-8", } }); } default: return new Response('Not found', ); } } else { return await vlessOverWSHandler(request); } } catch (err) { /** @type */ let e = err; return new Response(e.toString()); } }, }; /** * * @param request */ async function vlessOverWSHandler(request) { /** @type */ // @ts-ignore const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); let address = ''; let portWithRandomLog = ''; const log = (/** @type */ info, /** @type */ event) => { console.log(`[$:$] $`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); /** @type }*/ 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 = `$--$ ${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 = 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 remoteSocket * @param addressRemote The remote address to connect to. * @param portRemote The remote port to connect to. * @param rawClientData The raw client data to write. * @param webSocket The WebSocket to pass the remote socket to. * @param vlessResponseHeader The VLESS response header. * @param log The logging function. * @returns The remote socket. */ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { async function connectAndWrite(address, port) { /** @type */ const tcpSocket = connect({ hostname: address, port: port, }); remoteSocket.value = tcpSocket; log(`connected to $:$`); 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 webSocketServer * @param earlyDataHeader for ws 0rtt * @param 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 = 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 $`) 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 vlessBuffer * @param 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 $ 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 $`, }; } if (!addressValue) { return { hasError: true, message: `addressValue is empty, addressType is $`, }; } return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, }; } /** * * @param remoteSocket * @param webSocket * @param vlessResponseHeader * @param retry * @param log */ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // check if remoteSocket has incoming data await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param 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 $`); // 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 base64Str * @returns */ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return ; } 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 ; } catch (error) { return ; } } /** * This is not real UUID validation * @param uuid */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]-[0-9a-f]-[4][0-9a-f]-[89ab][0-9a-f]-[0-9a-f]$/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 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 webSocket * @param vlessResponseHeader * @param 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 $`); 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 chunk */ write(chunk) { writer.write(chunk); } }; } /** * * @param userID * @param hostName * @returns */ function getVLESSConfig(userID, hostName) { const vlessMain = `vless://$@$:443?encryption=none&security=tls&sni=$&fp=randomized&type=ws&host=$&path=%2F%3Fed%3D2048#$` return ` ################################################################ v2ray --------------------------------------------------------------- $ --------------------------------------------------------------- ################################################################ clash-meta --------------------------------------------------------------- - type: vless name: $ server: $ port: 443 uuid: $ network: ws tls: true udp: false sni: $ client-fingerprint: chrome ws-opts: path: "/?ed=2048" headers: host: $ --------------------------------------------------------------- ################################################################ `; }vless 部署代码2代码引用地址:https://github.com/yonggekkk/CF-workers-pages-vless/blob/main/\_worker.js需要更改 UUID自带 CloudFlare 证书的 proxyIP 地址代码104行,支持自定义伪装网页,修改对应的网页地址即可。代码中地址为 CCTV 官网// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->. // @ts-ignore import 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'; const proxyIPs = ['cdn-all.xn--b6gac.eu.org', 'cdn-all.xijingping.link', 'cdn.xn--b6gac.eu.org', 'cdn-b100.xn--b6gac.eu.org', 'edgetunnel.anycast.eu.org', 'cdn.anycast.eu.org']; let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query // v2board api environment variables let nodeId = ''; // 1 let apiToken = ''; //abcdefghijklmnopqrstuvwxyz123456 let apiHost = ''; // api.v2board.com if (!isValidUUID(userID)) { throw new Error('uuid is not valid'); } export default { /** * @param request * @param } env * @param ctx * @returns */ async fetch(request, env, ctx) { try { userID = env.UUID || userID; proxyIP = env.PROXYIP || proxyIP; dohURL = env.DNS_RESOLVER_URL || dohURL; nodeId = env.NODE_ID || nodeId; apiToken = env.API_TOKEN || apiToken; apiHost = env.API_HOST || apiHost; 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 '/connect': // for test connect to cf socket const [hostname, port] = ['cloudflare.com', '80']; console.log(`Connecting to $:$...`); try { const socket = await connect({ hostname: hostname, port: parseInt(port, 10), }); const writer = socket.writable.getWriter(); try { await writer.write(new TextEncoder().encode('GET / HTTP/1.1\r\nHost: ' + hostname + '\r\n\r\n')); } catch (writeError) { writer.releaseLock(); await socket.close(); return new Response(writeError.message, ); } writer.releaseLock(); const reader = socket.readable.getReader(); let value; try { const result = await reader.read(); value = result.value; } catch (readError) { await reader.releaseLock(); await socket.close(); return new Response(readError.message, ); } await reader.releaseLock(); await socket.close(); return new Response(new TextDecoder().decode(value), ); } catch (connectError) { return new Response(connectError.message, ); } case `/$`: { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response(`$`, { status: 200, headers: { "Content-Type": "text/plain;charset=utf-8", } }); } default: // return new Response('Not found', ); // For any other path, reverse proxy to 'www.fmprc.gov.cn' and return the original response url.hostname = 'tv.cctv.com'; url.protocol = 'https:'; request = new Request(url, request); return await fetch(request); } } else { return await vlessOverWSHandler(request); } } catch (err) { /** @type */ let e = err; return new Response(e.toString()); } }, }; /** * * @param request */ async function vlessOverWSHandler(request) { /** @type */ // @ts-ignore const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); let address = ''; let portWithRandomLog = ''; const log = (/** @type */ info, /** @type */ event) => { console.log(`[$:$] $`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); /** @type }*/ 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, } = await processVlessHeader(chunk, userID); address = addressRemote; portWithRandomLog = `$--$ ${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 = 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, }); } let apiResponseCache = null; let cacheTimeout = null; /** * Fetches the API response from the server and caches it for future use. * @returns A Promise that resolves to the API response object or null if there was an error. */ async function fetchApiResponse() { const requestOptions = { method: 'GET', redirect: 'follow' }; try { const response = await fetch(`https://$/api/v1/server/UniProxy/user?node_id=$&node_type=v2ray&token=$`, requestOptions); if (!response.ok) { console.error('Error: Network response was not ok'); return null; } const apiResponse = await response.json(); apiResponseCache = apiResponse; // Refresh the cache every 5 minutes (300000 milliseconds) if (cacheTimeout) { clearTimeout(cacheTimeout); } cacheTimeout = setTimeout(() => fetchApiResponse(), 300000); return apiResponse; } catch (error) { console.error('Error:', error); return null; } } /** * Returns the cached API response if it exists, otherwise fetches the API response from the server and caches it for future use. * @returns A Promise that resolves to the cached API response object or the fetched API response object, or null if there was an error. */ async function getApiResponse() { if (!apiResponseCache) { return await fetchApiResponse(); } return apiResponseCache; } /** * Checks if a given UUID is present in the API response. * @param targetUuid The UUID to search for. * @returns A Promise that resolves to true if the UUID is present in the API response, false otherwise. */ async function checkUuidInApiResponse(targetUuid) { // Check if any of the environment variables are empty if (!nodeId || !apiToken || !apiHost) { return false; } try { const apiResponse = await getApiResponse(); if (!apiResponse) { return false; } const isUuidInResponse = apiResponse.users.some(user => user.uuid === targetUuid); return isUuidInResponse; } catch (error) { console.error('Error:', error); return false; } } // Usage example: // const targetUuid = "65590e04-a94c-4c59-a1f2-571bce925aad"; // checkUuidInApiResponse(targetUuid).then(result => console.log(result)); /** * Handles outbound TCP connections. * * @param remoteSocket * @param addressRemote The remote address to connect to. * @param portRemote The remote port to connect to. * @param rawClientData The raw client data to write. * @param webSocket The WebSocket to pass the remote socket to. * @param vlessResponseHeader The VLESS response header. * @param log The logging function. * @returns The remote socket. */ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { async function connectAndWrite(address, port) { /** @type */ const tcpSocket = connect({ hostname: address, port: port, }); remoteSocket.value = tcpSocket; log(`connected to $:$`); 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 webSocketServer * @param earlyDataHeader for ws 0rtt * @param 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 = 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 $`) 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 vlessBuffer * @param userID * @returns */ async 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; const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17)); const slicedBufferString = stringify(slicedBuffer); const uuids = userID.includes(',') ? userID.split(",") : [userID]; const checkUuidInApi = await checkUuidInApiResponse(slicedBufferString); isValidUser = uuids.some(userUuid => checkUuidInApi || slicedBufferString === userUuid.trim()); console.log(`checkUuidInApi: $, userID: $`); 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 $ 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 $`, }; } if (!addressValue) { return { hasError: true, message: `addressValue is empty, addressType is $`, }; } return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, }; } /** * * @param remoteSocket * @param webSocket * @param vlessResponseHeader * @param retry * @param log */ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // check if remoteSocket has incoming data await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param 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 $`); // 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 base64Str * @returns */ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return ; } 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 ; } catch (error) { return ; } } /** * This is not real UUID validation * @param uuid */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]-[0-9a-f]-[4][0-9a-f]-[89ab][0-9a-f]-[0-9a-f]$/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 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 webSocket * @param vlessResponseHeader * @param 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(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 $`); 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 chunk */ write(chunk) { writer.write(chunk); } }; } /** * * @param userID * @param hostName * @returns */ function getVLESSConfig(userID, hostName) { const vlessws = `vless://$@time.cloudflare.com:8880?encryption=none&type=ws&host=$&path=%2F%3Fed%3D2048#$` const vlesswstls = `vless://$@time.cloudflare.com:8443?encryption=none&security=tls&type=ws&host=兄弟,你的自定义域名呢?&path=%2F%3Fed%3D2048#$` return ` ==========================配置详解============================== ################################################################ 一、CF-workers-vless+ws节点,分享链接如下: $ --------------------------------------------------------------- 注意:当前节点无需域名,TLS选项关闭 --------------------------------------------------------------- 客户端必要文明参数如下: 客户端地址(address):自选域名 或者 自选IP 端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095) 用户ID(uuid):$ 传输协议(network):ws/websocket 伪装域名(host):$ 路径(path):/?ed=2048 ################################################################ ################################################################ 二、CF-workers-vless+ws+tls节点,分享链接如下: $ --------------------------------------------------------------- 注意:客户端ws选项后的伪装域名host必须改为你自定义的域名 --------------------------------------------------------------- 客户端必要文明参数如下: 客户端地址(address):自选域名 或者 自选IP 端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096) 用户ID(uuid):$ 传输协议(network):ws/websocket 伪装域名(host):兄弟,你的自定义域名呢? 路径(path):/?ed=2048 传输安全(TLS):开启 跳过证书验证(allowlnsecure):false ################################################################ ################################################################ clash-meta --------------------------------------------------------------- - type: vless name: $ server: $ port: 443 uuid: $ network: ws tls: true udp: false sni: $ client-fingerprint: chrome ws-opts: path: "/?ed=2048" headers: host: $ --------------------------------------------------------------- ################################################################ `; }V2ray 使用 CloudFlare Workers vless 节点(无域名)复制刚刚创建的代码地址,格式为:https://vless.xxx.workers.dev/,其中 vless 为创建的脚本名称,xxx 为 CloudFlare 用户名。在浏览器地址栏中粘贴代码地址后,如果出现一段字符,即表明创建成功。接下来在该地址后输入 /UUID字符串,回车即可看到 vless 地址。 将页面中的 V2ray 地址复制,打开 V2ray 软件,点击 服务器 选项,选择 从剪贴板导入批量URL,即可看到创建的节点。双击刚刚创建的节点,将端口(port)改为 2052,传输层安全(TLS) 设置为空,传输协议(network) 为 ws,地址(address) 改为优选 IP。 注:7个 http 端口可任意选择:80、8080、8880、2052、2082、2086、2095 Workers 专用 IP 优选工具CloudFlare 优质 IP 自动切换:https://stock.hostmonit.com/CloudFlareYes172.64.32.1/24 (推荐移动,走香港) 104.28.14.0/24 (推荐移动,走新加坡) 104.23.240.0 ~ 104.23.243.254 (推荐联通、移动,线路未知) 108.162.236.1/24 (推荐联通,走美国) 104.20.157.0/24 (推荐联通,走日本) 104.16.160.1/24 (推荐电信,走洛杉矶) 172.64.0.0/24 (推荐电信,走旧金山) 172.64.32.* (走欧洲)优选工具 Windows 版本下载地址,断开所有 VPN 和科学上网方式,选择运行任意批处理文件,在运行后的结果中,选择 PING 值低的 IP 地址,复制到 V2ray 的 地址(address) 中。 CloudFlare 自选域名自选域名下载地址,断开所有 VPN 和科学上网方式,选择自选域名程序单文件,运行后在相同文件夹下中打开 txt 文件,选择 PING 值低的域名,复制到 V2ray 的 地址(address) 中。V2ray 使用 CloudFlare Workers vless 节点(有域名)在 CloudFlare Workers 中,打开刚才创建的脚本,点击 查看,点击 添加自定义域。 在自定义域对话框中输入已经在 CloudFlare 注册域名的二级域名,点击 添加自定义域。比如:vless.5iehome.cf。接下来只需在浏览器地址栏中粘贴输入 https://vless.5iehome.cf/UUID字符串,回车即可看到 vless 地址。将页面中的 V2ray 地址复制,打开 V2ray 软件,点击 服务器 选项,选择 从剪贴板导入批量URL,即可看到创建的节点。双击刚刚创建的节点,将端口(port)改为 443,传输层安全(TLS) 设置为 tls,传输协议(network) 为 ws,地址(address) 改为优选 IP。 注:6个 http 端口可任意选择:443、8443、2053、2083、2087、2096 节点测速V2ray 下测试节点速度,要选择 测试服务器真连接延迟(多选)(Ctrl+R),延迟数值为大于 0 即为导通,数值为 -1 即为不导通。IP 地址查看https://whatismyipaddress.com/https://ip.gs文中程序下载CloudFlare Workers VLESS 永久免费节点搭建https://www.aliyundrive.com/s/mXFeMtdBF2T 提取码: c1m7https://www.123pan.com/s/Oy5RVv-swXB.html 提取码: V3iX参考文章https://jdssl.top/index.php/2023/07/21/2023vpn/https://www.youtube.com/watch?v=5fvhws6ZXrMhttps://ygkkk.blogspot.com/2023/07/cfworkers-vless.htmlhttps://www.youtube.com/watch?v=9V9CQxmfwoA【END】
2023年08月06日
27 阅读
3 评论
0 点赞
2023-08-06
CloudFlare WARP 免费 VPN 搭建教程
前言最近比较火的科学上网的方法,是使用 CloudFlare WARP 免费 VPN,并且可以优选 IP,提高上网速度。CloudFlare WARP 免费 VPNWARP 介绍1.1.1.1 的 WARP 应用以一个经过优化的现代协议来代替您的设备和 Internet 之间的连接。具有快速、免费、私密的特点,最关键的通过优选 IP 后,可以大幅度提高上网速度。下载安装WARP 支持各个平台,Windows 的下载地址为:Microsoft应用中心1.1.1.1下载后双击安装即可。直接使用 WARP在状态栏打开软件,点击连接按钮即可使用。默认情况下,软件会自动分配一个普通账号,只有 1GB 的使用流量。获取 WARP+ 永久免费流量打开 Telegram,搜索 @generatewarpplusbot按照提示,关联相关订阅后,获取 WARP 账户许可证密钥 (26个字符),格式为:xxxxxxxx-xxxxxxxx-xxxxxxxx使用 WARP+ 免费流量方法一:更改 WARP 帐户直接在 WARP 中选择 帐户,点击 使用其他密钥 按钮,在弹出的对话框中输入在 Telegram 上得到的许可证密钥。方法二:使用 WireGuard下载 WireGuard,下载对应客户端。经测试,下载及安装都需要代理才能完成。在线生成 WARP 的 WireGuard 配置文件,打开生成配置文件网址,点击 Run,在运行代码框中输入 2,即 WARP+。然后输入在 Telegram 上得到的26位许可证密钥。设备名设置可有可无。 将生成的配置文件输入到 WireGuard 中,就可以连接使用了。 WireGuard 可以脱离 CloudFlare WARP 独立使用。CloudFlare 优化 IPWARP+ IP 优选工具下载:https://gitlab.com/Misaka-blog/warp-script/-/blob/main/files/warp-yxip/warp-yxip-win.7z断开所有 VPN 和科学上网方式,双击 warp-yxip.bat,选择默认 1 IPv4 优选地址即可。打开 result.csv,选择延迟小,丢包率为 0 的 IP 地址即可。 复制延迟小,丢包率为 0 的 IP 地址及端口号,将 WireGuard 配置文件 Endpoint 地址替换,保存后再连接,就会发现速度有了明显改善。 文中软件下载地址「CloudFlare WARP.exe」https://www.123pan.com/s/Oy5RVv-wwXB.html 提取码: 7iAahttps://www.aliyundrive.com/s/Rn7dPV86Nqn 提取码: 6ep3【END】
2023年08月06日
55 阅读
0 评论
0 点赞
2023-04-01
自动切换 cdn.jsdelivr.net 域名的脚本
前言cdn.jsdelivr.net 有时候会出现国内无法访问的情况,以至于造成网站 js, css, image,字体等文件无法正常显示。 因此 BestTools 大神开发出自动检查 cdn.jsdelivr.net 是否可用的脚本, 如果不可用时,会自动把所有资源地址切换到其他可用的域名。比如:gcore.jsdelivr.net,fastly.jsdelivr.net等其他 CDN。项目地址Github 地址:https://github.com/PipecraftNet/jsdelivr-auto-fallback使用方法直接复制 index.js 或 index.min.js 里的内容,加到网站里。强烈建议添加到 head 标签最上面。所有 script 标签加上 defer 属性。如果原来有 async 属性,可以跳过。这个可以避免 pending 状态带来的等待时间,大大提升性能。如果是 hexo 生成的网站,可以安装 hexo-filter-jsdelivr-auto-fallback 插件,自动添加。示例:# 可以把 js 文件放到其他目录下进行引用 <script defer src="index.js"></script> <script defer src="index.min.js"></script>index.js 代码:((document) => { 'use strict'; let fastNode; let failed; let isRunning; const DEST_LIST = [ 'cdn.jsdelivr.net', 'fastly.jsdelivr.net', 'gcore.jsdelivr.net', 'cdn.zenless.top', 'testingcf.jsdelivr.net', 'test1.jsdelivr.net' ]; const PREFIX = '//'; const SOURCE = DEST_LIST[0]; const starTime = Date.now(); const TIMEOUT = 2000; const STORE_KEY = 'jsdelivr-auto-fallback'; const TEST_PATH = '/gh/PipecraftNet/jsdelivr-auto-fallback@main/empty.css?'; const shouldReplace = (text) => text && text.includes(PREFIX + SOURCE); const replace = (text) => text.replace(PREFIX + SOURCE, PREFIX + fastNode); const setTimeout = window.setTimeout; const $ = document.querySelectorAll.bind(document); const replaceElementSrc = () => { let element; let value; for (element of $('link[rel="stylesheet"]')) { value = element.href; if (shouldReplace(value) && !value.includes(TEST_PATH)) { element.href = replace(value); } } for (element of $('script')) { value = element.src; if (shouldReplace(value)) { const newNode = document.createElement('script'); newNode.src = replace(value); element.defer = true; element.src = ''; element.before(newNode); element.remove(); } } for (element of $('img')) { value = element.src; if (shouldReplace(value)) { // Used to cancel loading. Without this line it will remain pending status. element.src = ''; element.src = replace(value); } } // All elements that have a style attribute for (element of $('*[style]')) { value = element.getAttribute('style'); if (shouldReplace(value)) { element.setAttribute('style', replace(value)); } } for (element of $('style')) { value = element.innerHTML; if (shouldReplace(value)) { element.innerHTML = replace(value); } } }; const tryReplace = () => { if (!isRunning && failed && fastNode) { console.warn(SOURCE + ' is not available. Use ' + fastNode); isRunning = true; setTimeout(replaceElementSrc, 0); // Some need to wait for a while setTimeout(replaceElementSrc, 20); // Replace dynamically added elements setInterval(replaceElementSrc, 500); } }; const checkAvailable = (url, callback) => { let timeoutId; const newNode = document.createElement('link'); const handleResult = (isSuccess) => { if (!timeoutId) { return; } clearTimeout(timeoutId); timeoutId = 0; // Used to cancel loading. Without this line it will remain pending status. if (!isSuccess) newNode.href = 'data:text/plain;base64,'; newNode.remove(); callback(isSuccess); }; timeoutId = setTimeout(handleResult, TIMEOUT); newNode.addEventListener('error', () => handleResult(false)); newNode.addEventListener('load', () => handleResult(true)); newNode.rel = 'stylesheet'; newNode.text = 'text/css'; newNode.href = url + TEST_PATH + starTime; document.head.insertAdjacentElement('afterbegin', newNode); }; const cached = (() => { try { return Object.assign( , JSON.parse(localStorage.getItem(STORE_KEY) || '') ); } catch { return ; } })(); const main = () => { cached.time = starTime; cached.failed = false; cached.fastNode = null; for (const url of DEST_LIST) { checkAvailable('https://' + url, (isAvailable) => { // console.log(url, Date.now() - starTime, Boolean(isAvailable)); if (!isAvailable && url === SOURCE) { failed = true; cached.failed = true; } if (isAvailable && !fastNode) { fastNode = url; } if (isAvailable && !cached.fastNode) { cached.fastNode = url; } tryReplace(); }); } setTimeout(() => { // If all domains are timeout if (failed && !fastNode) { fastNode = DEST_LIST[1]; tryReplace(); } localStorage.setItem(STORE_KEY, JSON.stringify(cached)); }, TIMEOUT + 100); }; if ( cached.time && starTime - cached.time < 60 * 60 * 1000 && cached.failed && cached.fastNode ) { failed = true; fastNode = cached.fastNode; tryReplace(); setTimeout(main, 1000); } else { main(); } })(document);index.min.js 代码:(n=>for(e of v("img"))t=e.src,d(t)&&(e.src="",e.src=m(t));for(e of v("*[style]"))t=e.getAttribute("style"),d(t)&&e.setAttribute("style",m(t));for(e of v("style"))t=e.innerHTML,d(t)&&(e.innerHTML=m(t))},y=()=>,b=(()=>,JSON.parse(localStorage.getItem(c)||""))}catch}})();var h=()=>;r=u(l,o),s.addEventListener("error",()=>l(!1)),s.addEventListener("load",()=>l(!0)),s.rel="stylesheet",s.text="text/css",s.href=e+f+i,n.head.insertAdjacentElement("afterbegin",s)})("https://"+t,e=>);u(()=>,o+100)};b.time&&i-b.time<36e5&&b.failed&&b.fastNode?(s=!0,r=b.fastNode,y(),u(h,1e3)):h()})(document);用户脚本作为用户,你也可以使用油猴脚本将网站中的 cdn.jsdelivr.net 替换为可以访问的域名。浏览器安装 Tampermonkey。安装脚本: https://greasyfork.org/zh-CN/scripts/445701-jsdelivr-auto-fallbackjsdelivr 可用节点比较gcore.jsdelivr.netGcore 节点可用性高testingcf.jsdelivr.netCloudflare 节点可用性高quantil.jsdelivr.netQuantil 节点可用性尚可fastly.jsdelivr.netFastly 节点可用性尚可originfastly.jsdelivr.netFastly 节点可用性低cdn.jsdelivr.net通用节点可用性低参考文章https://www.neosey.com/archives/55.htmlhttps://iui.su/167/
2023年04月01日
5 阅读
0 评论
0 点赞