PeerWeb 是一个基于 WebTorrent 协议的去中心化网站托管方案,其核心设计理念是将传统的客户端 — 服务器架构转换为点对点的分布式网络。在传统 Web 托管模式中,网站数据存储在中心化服务器上,用户通过 DNS 解析和 HTTP 请求获取内容;而 PeerWeb 则将网站文件打包成 torrent 种子,通过 WebRTC 在浏览器之间直接传输数据。这种架构从根本上消除了对中心化托管服务的依赖,使得网站能够在全球分布的 Peer 节点上持久存活。
P2P 分发机制的核心实现
PeerWeb 的 P2P 分发机制建立在 WebTorrent 协议之上,整个流程始于用户拖拽上传网站文件夹的那一刻。当用户将包含 index.html 的文件夹拖入上传区域后,系统首先会遍历所有文件并检查必要的文件是否存在。如果未检测到 index.html,系统会提示用户确认是否继续,因为缺少入口文件将导致网站无法正常加载。这个设计体现了工程实践中对边界条件的严格把控。
在文件验证通过后,系统调用 client.seed() 方法创建 torrent 种子。值得注意的是,种子创建时采用动态分块大小策略:根据文件总体积自动选择 16KB、32KB、256KB 或 1MB 的分块大小。这种自适应的分块策略能够平衡下载效率和传输开销 —— 对于小型网站使用较小的分块可以提高并行度,而大型网站使用较大的分块则能减少元数据开销。
种子创建完成后,系统会自动连接 7 个 WebRTC 追踪器进行节点发现。这些追踪器包括 wss://tracker.btorrent.xyz、wss://tracker.openwebtorrent.com 等,分布在不同的域名上以提高去中心化程度。追踪器负责在请求下载的客户端和正在做种的节点之间牵线搭桥,建立 WebRTC 连接。一旦连接建立,网站数据便开始在节点之间流动,无需经过任何中心化服务器的中转。
在做种期间,系统持续跟踪已上传的数据量和当前连接的节点数。当新用户通过 ?orc={hash} 参数访问网站时,WebTorrent 客户端会向追踪器请求拥有该种子的节点列表,然后逐一下载文件的各个分块。下载过程中,系统采用选择性下载策略:优先获取 index.html 和 CSS、JS 等关键资源,仅当这些核心文件达到 90% 以上的下载进度时才触发页面渲染。这种渐进式加载机制确保用户能够尽快看到页面内容,同时后台继续下载次要资源。
内容寻址系统的设计哲学
PeerWeb 的内容寻址系统是其去中心化架构的技术基石。与传统网站使用易变的域名和 IP 地址不同,PeerWeb 完全基于内容本身的身份标识来定位资源。这个标识就是 torrent 种子的 infoHash—— 一个由 WebTorrent 根据种子内部文件结构计算出的 40 字符十六进制字符串。由于 SHA-1 算法的特性,即使种子中仅有一个字节发生改变,生成的 hash 值也会完全不同,这确保了内容标识的不可篡改性。
系统在处理用户输入的 hash 时,会首先调用 sanitizeHash() 方法进行严格过滤。该方法仅保留十六进制字符(a-fA-F0-9),并自动转换为小写,任何非法的字符都会被剔除。这一步骤不仅是安全防护,也是对用户输入的规范化处理,避免因大小写不一致或额外字符导致的解析错误。hash 长度必须恰好为 40 个字符,系统会拒绝不符合格式要求的输入,并给出清晰的错误提示。
网站链接的生成遵循简洁原则:https://peerweb.lol/?orc={hash}。用户只需分享这个链接,访问者便能通过 hash 值从网络中的任意节点下载完整的网站内容。在内部资源路由层面,系统将 URL 转换为 /peerweb-site/{hash}/{file-path} 的虚拟路径格式。例如,当网页中的 <script src="js/app.js"> 需要加载时,实际请求的 URL 会变为 /peerweb-site/d4f5e6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3/js/app.js。这种路径转换在 HTML 处理阶段完成,系统会解析 DOM 中的各类资源引用,将相对路径转换为虚拟路径,同时保留外部链接不做处理。
路径转换逻辑支持多种相对路径场景:./ 前缀会被自动剥离,../ 父目录引用会通过路径解析算法正确处理,最终映射到虚拟路径下的正确位置。CSS 文件中的 url() 引用和 @import 语句同样会被递归处理,确保样式表中引用的字体、图片等资源也能正确加载。这种全方位的路径转换机制使得开发者无需修改现有的网站代码,即可将其部署到 PeerWeb 平台上运行。
客户端资源加载的技术细节
客户端资源加载是 PeerWeb 系统中最具工程挑战性的环节,它需要在浏览器环境中模拟传统 Web 服务器的行为,同时处理 P2P 网络特有的异步性和不确定性。PeerWeb 采用 Service Worker 作为资源拦截层,配合主线程的 WebTorrent 客户端,实现了一套透明的资源获取机制。
当用户访问包含 ?orc={hash} 参数的 URL 时,系统会启动一系列初始化流程:首先加载 WebTorrent 和 DOMPurify 两个核心库,然后注册 Service Worker,最后验证 hash 格式并开始下载 torrent。在所有组件准备就绪之前,系统会持续轮询检查状态,避免因时序问题导致的资源加载失败。这个等待过程有动态超时机制保护 —— 超时时间基于种子大小计算,确保大型网站有足够的初始化时间。
Service Worker 在整个资源加载流程中扮演着核心角色。它拦截所有指向 /peerweb-site/ 路径的请求,解析出 hash 和文件路径后,首先检查本地媒体缓存。如果请求的是已缓存的媒体文件(大于 100KB 的视频、音频或 GIF),Service Worker 会直接返回缓存内容,无需与主线程通信。对于未缓存的文件,Service Worker 会生成一个唯一请求 ID,通过 postMessage 将请求发送给主线程。
主线程收到资源请求后,会在当前站点的文件映射表中查找对应文件。文件映射表是在 torrent 下载过程中构建的内存数据结构,键为文件路径,值为文件内容和 MIME 类型。如果找到文件,主线程将二进制数据转换为 Uint8Array,附加请求 ID 后发回 Service Worker。整个通信过程是异步的,Service Worker 使用 Promise 包装请求,在收到响应或超时(默认 5 秒,媒体文件 10 秒)之前保持请求挂起。
媒体文件的 Range 请求支持是另一个技术亮点。当网页中的 <video> 或 <audio> 元素请求流式播放时,会发送带有 Range 请求头的 HTTP 请求。Service Worker 解析 bytes={start}-{end} 格式的范围标识,从完整文件中切取对应片段,返回 206 Partial Content 状态码和部分数据。Service Worker 还会将大于 100KB 的媒体文件缓存在内存中,后续的 Range 请求可以直接从缓存读取,避免重复访问主线程。
为了保证资源的持久可用性,PeerWeb 在客户端实现了 IndexedDB 缓存层。下载完成的网站数据会被序列化存储在浏览器的 IndexedDB 中,有效期为 7 天。当用户再次访问相同 hash 的网站时,系统会首先查询缓存,如果缓存命中则直接加载,大幅缩短访问延迟。缓存条目包含时间戳信息,过期条目会被自动清理,避免占用过多存储空间。
工程实践中的关键设计决策
在工程实现层面,PeerWeb 展现出对浏览器环境特性的深刻理解和多项针对性优化。动态超时机制是一个典型例子:系统根据种子大小和文件数量计算处理超时时间,基准为 15 秒,每增加 1MB 数据额外增加 2 秒,每增加一个文件额外增加 500 毫秒,但整体限制在 15 秒到 5 分钟之间。这种自适应策略避免了小网站等待过久或大网站超时失败的问题。
安全防护贯穿整个系统设计。所有 HTML 内容在展示前都会经过 DOMPurify 过滤,移除潜在的 XSS payload,同时保留 <script>、<style> 等标签以确保网站功能正常。嵌入网站的 <iframe> 被施加了严格的 sandbox 属性限制:allow-scripts allow-same-origin allow-forms,禁止自动跳转和弹窗,防止恶意网站利用嵌入环境实施攻击。Service Worker 还会阻止嵌入页面注册自己的 Service Worker,避免命名空间冲突。
资源清理机制确保了长期运行的稳定性。系统追踪所有通过 URL.createObjectURL() 创建的对象 URL,在页面隐藏或离开时统一释放,防止内存泄漏。setTimeout 创建的定时器同样被追踪管理,在组件销毁时清理。这种对资源的精细管理对于单页应用尤为重要,因为浏览器标签页可能长时间保持打开状态,任何微小的内存泄漏累积起来都会影响用户体验。
整体而言,PeerWeb 的工程实现展示了一种在浏览器环境中构建去中心化系统的可行路径。它充分利用了 WebTorrent 的 P2P 传输能力,结合 Service Worker 的中间人拦截机制和 IndexedDB 的持久化存储,在没有中心化服务器的情况下实现了网站的分发和访问。虽然 WebRTC 连接数和浏览器环境限制仍然存在,但 PeerWeb 为去中心化 Web 应用提供了一个完整的技术参考实现。