HTML over WebSockets:用几乎零 JavaScript 构建实时 SPA
构建单页应用(SPA,Single-page Application)是个复杂的难题:需要用JavaScript框架渲染视图,用API提供JSON数据,还要让两个独立的代码库通过契约实现互通。这是业内公认的标准化方案,但标准不代表唯一。今天我要介绍另一种并非全新、却逐渐流行的思路:基于WebSocket的HTML传输。
核心逻辑很简单:服务器不再发送JSON让浏览器拼接HTML,而是直接发送已渲染完成的HTML,客户端只需将其插入对应位置即可。所有渲染逻辑都留在后端,用同一种语言实现,无需契约或API。这种模式被称为超媒体或HTML直传(HTML over the wire)。HTML的传输方式决定了通信的延迟与双向性,主要分为三类:
- 基于HTTP的请求响应模式,如htmx或Unicorn。
- 基于**SSE(Server-Sent Events,服务器推送事件)**的单向持续通道模式,如Datastar。
- 基于WebSocket的永久双向通道模式,如Phoenix LiveView或Django LiveView。
传输通道的选择至关重要,它直接决定了应用的架构与通信模式。本文将聚焦基于WebSocket的HTML传输——这是该家族中支持实时双向通信的分支。借助它,你几乎无需编写JavaScript,只用一种语言、一套渲染引擎,就能构建SPA,还能省去契约与API的繁琐。我们将深入探讨它的定义、工作原理,以及相较于HTTP或SSE方案的适用场景。
起源
Elixir生态最受欢迎的框架Phoenix的创始人克里斯·麦科德(Chris McCord),在2019年ElixirConf大会上展示了一项名为LiveView的技术。他仅用15分钟就构建了一个实时运行的Twitter克隆版,全程未添加任何负责渲染的JavaScript代码,也没有使用React、Angular、Vue等主流前端框架管理视图。这证明了开发者可以专注于后端开发,同时兼顾开发效率与良好性能。此后,这一方案逐渐流行,启发了其他开发者在多种编程语言中实现基于WebSocket的HTML传输方案。如今,你无需放弃前端的优势,就能回归后端开发的简洁。
工作原理
初看之下似乎与JavaScript无关,但客户端确实会用到JavaScript——它的作用并非渲染,而是通过WebSocket建立通信通道,并将收到的HTML插入正确位置,同时处理动画、事件响应等次要任务。
麦科德的方案核心是不向前端发送JSON,而是直接发送无需预处理的HTML,从而将渲染负载及所有相关逻辑转移到后端。那如何让服务器无需等待请求就能主动推送新内容?答案很简单:借助WebSocket。
我们先回顾开篇提到的传统SPA系统:用户在网页发起HTTP请求,浏览器触发操作,服务器返回包含原始数据的JSON;随后浏览器解析JSON,再用自身渲染引擎生成对应的HTML。
sequenceDiagram
participant C as Browser
participant S as Server
C->>S: 1. HTTP request (GET /api/article/2/) and maybe auth
S->>S: 2. Query the DB
S->>S: 3. Build a JSON with the article data
S-->>C: 4. Return JSON
C->>C: 5. Parse the JSON
C->>C: 6. Build the HTML with its rendering engine
而在基于WebSocket的HTML传输模式下,请求通过永久通道发送,响应则是已组装完成的HTML,全程无需JSON中转。由于通道始终保持开放,服务器甚至可以主动推送内容,无需等待客户端请求。
以下是WebSocket模式的流程(忽略仅在通道建立时执行一次的初始连接与认证步骤):
sequenceDiagram
participant C as Browser
participant S as Server (Back-End)
C->>S: 1. Sends a text: "I want /article/2/"
S->>S: 2. Query the DB
S->>S: 3. Render HTML with its template engine
S-->>C: 4. Return the assembled HTML/CSS/JS
"
... " C->>C: 5. Place the HTML where it belongs
流程简洁、优雅且高效:客户端只需负责HTML的插入与事件监听,其余工作全由服务器处理。你无需操心客户端状态或渲染逻辑,因为所有逻辑都在后端。
包含连接建立与认证的完整复杂流程如下:
sequenceDiagram
participant C as Browser
participant S as Server (Back-End)
C->>S: 1. Opens WebSocket connection and authenticates
Note over C,S: A single persistent channel
C->>S: 2. Sends a text: "I want /article/2/"
S->>S: 3. Query the DB
S->>S: 4. Render HTML with its template engine
S-->>C: 5. Return the assembled HTML/CSS/JS
"
changes without the client asking (broadcast)
... " C->>C: 6. Place the HTML where it belongs Note over S,C: The server can also push
这种架构天生具备一些其他方案没有的优势。
优势
- 单一渲染引擎:大幅降低复杂度。
- 无需构建API:服务器直接生成并发送HTML,无需中间层。
- 状态托管于服务器:不同于无状态的请求响应模式,每个连接的客户端对应一个进程,可保留用户状态。这与刻意设计为无状态的htmx恰好相反。
- 直接连接数据库:无需JSON或GraphQL作为中间层。
- 真正的实时性:客户端无需轮询服务器,就能最快速度接收更新。
- 广播功能:服务器可同时向所有连接的客户端推送更新,轻松实现聊天、仪表盘或多人游戏等功能。
- 更低的交互流量与延迟:单一持久连接避免了每次交互都重复执行TCP握手与HTTP头信息传输。并非“WebSocket协议天生更快”(HTTP/2与HTTP/3已大幅缩小了请求响应模式下的差距),而是因为它省去了往返环节,直接发送已组装好的HTML。
- 几乎无需JavaScript即可构建SPA:无需依赖React、Angular或Vue等重型框架。
- 良好的SEO表现:由于HTML在服务器端渲染,首次加载的内容可被搜索引擎抓取。但需注意,爬虫无法获取后续通过WebSocket推送的更新,因此重要内容需包含在首次响应中。
- 更安全的防注入机制:服务器在发送前会渲染并转义HTML,即便是试图混入的
<script>标签,也会以惰性文本形式传输,最终在用户屏幕上显示为普通文字而非可执行代码。这种让聊天功能实现变得简单的架构,天然具备XSS免疫能力。
劣势
- 服务器资源占用更高:需维持WebSocket连接,通常还需在内存中存储每个客户端的状态。水平扩展时需实现状态共享(如Django中需搭配Channels + ASGI服务器 + Redis作为通道层)。不过,只有在同时在线客户端数量极大时才会出现实际问题,通过精心设计可缓解这一压力。我的网站曾在类似树莓派3的硬件上,同时运行其他服务的情况下,平稳应对600人同时在线的峰值。
- 物理延迟影响体验:若网络物理延迟较高,“即时响应”的体验会大打折扣。
- 无法离线工作:连接中断后网站将无法使用,需设计重连机制与容错方案。
- 初始学习曲线更陡:部署WebSocket服务器并非易事,还需掌握LiveView模式的使用方法,比直接引入一段
<script>代码复杂得多。
当前生态:主流框架有哪些?
超媒体技术几乎已覆盖所有编程语言。查看下表的传输方式列:基于WebSocket的实时双向方案(即LiveView模式),与HTTP、SSE方案共存,可根据是否需要双向通道灵活选择。以下是部分主流框架:
| 语言 | 框架 | 传输方式 | 支持服务器推送? | 状态 |
|---|---|---|---|---|
| Elixir | Phoenix LiveView | WebSocket | 是 | 成熟(1.x版本,LiveView 1.0于2024年12月发布) |
| Ruby | Hotwire (Turbo + Stimulus) | HTTP + WebSocket/SSE(Streams) | 是 | Turbo 8已支持页面形态更新(morphing) |
| Ruby | Live / Lively(socketry) | WebSocket | 是 | 维护中(v0.18,预计2026年更新),小众:基于morphdom的纯WebSocket方案,独立于Rails,侧重演示 |
| Python / Django | Django LiveView | WebSocket | 是 | 活跃开发中(本文作者维护) |
| Python / Django | Reactor | WebSocket | 是 | 活跃开发中 |
| Python / Django | djust | WebSocket | 是 | 新项目,搭配Rust虚拟DOM(VDOM) |
| Python / Django | django-unicorn | HTTP / AJAX | 否 | 活跃开发中 |
| Python / Django | Tetra | AJAX + WebSocket | 是 | 新项目,基于Alpine.js |
| C# / .NET | Blazor(交互式服务器模式) | WebSocket(SignalR) | 是 | .NET 9已支持多种渲染模式 |
| PHP / Laravel | Livewire 3 + Reverb | WebSocket | 是 | Reverb:Laravel官方WebSocket服务器(2024年推出) |
| 通用(JS) | htmx | HTTP + WS/SSE扩展 | 是(需扩展) | 2.0版本 |
| 通用(JS) | Datastar | SSE | 是 | 1.0版本 |
SSE:低成本替代方案
WebSocket功能强大,但为每个客户端维持双向通道需要成本。而很多场景下并不需要双向通信:如果主要是服务器向客户端推送内容(如通知、实时信息流、仪表盘、AI响应内容),**SSE(服务器推送事件)**就足够了。核心思路同样是直传已渲染的HTML,但通过单向HTTP通道实现。
这是一种低成本方案:基础设施最简单,无需为每个客户端维持有状态进程,更容易实现负载均衡与扩展。
但它也存在局限性:
- 单向通信:仅服务器可推送内容。若客户端需发送请求,需单独发起HTTP请求,没有现成通道可用。
- 仅支持文本:仅能传输UTF-8文本,无法传输二进制数据(WebSocket支持)。
- 双向频繁交互场景表现不佳:在聊天、协作编辑或游戏等场景中,通过HTTP反复请求的开销,远高于维持一个永久开放的WebSocket连接。
htmx的SSE扩展在理念上几乎一致:只需通过属性声明通道,每个事件中传输的HTML会自动插入对应位置:
<div hx-ext="sse" sse-connect="/updates" sse-swap="message">
Real-time content appears here
</div>
其底层使用浏览器原生的EventSource接口,自带重连机制,服务器通过text/event-stream发送HTML片段。核心思路与本文介绍的方案一致,仅传输方式不同。类似的还有Datastar,它将Alpine风格的响应式能力与SSE结合。
简单判断规则:若需要双向低延迟通信(如聊天、协作、游戏),选择WebSocket;若仅需服务器单向推送,SSE更简单、运维成本更低。
总结
基于WebSocket的HTML传输并非万能,所有技术都有其适用边界。传输方式的选择应由你的业务需求决定:需要实时双向交互(如聊天、实时面板、协作工具),选WebSocket;仅需服务器推送内容,选SSE;普通请求响应模式足够,选基于HTTP的htmx。
每个项目都有其独特性与限制条件。若要从中提炼一个核心思想,那就是:发送HTML而非JSON,专注于一种编程语言,省去API、契约和半数前端工作。
信赖优秀的架构,而非跟风潮流框架或模式。
参考资料
Phoenix LiveView官方文档:该模式的典范,在服务器端维护客户端状态,通过WebSocket传输差异内容。
Phoenix LiveView 1.0 发布,Phoenix官方博客:首个提交六年后,于2024年12月迎来1.0里程碑。
Hotwire官方网站:“HTML Over The Wire”概念的起源,以及Turbo为何主要基于HTTP运行。
htmx文档:基于HTTP的超媒体方案,刻意设计为无状态,仅通过扩展支持WebSocket与SSE。
Turbo手册:页面刷新:介绍Turbo 8的**形态更新(morphing)**功能,仅修改变化内容并保留滚动位置与焦点。
idiomorph:多个此类方案使用的DOM**形态更新(morphing)**库。
Datastar:主打SSE而非WebSocket的超媒体框架。
使用服务器推送事件,MDN文档:SSE的工作原理(
EventSource接口、自动重连、Last-Event-ID、text/event-stream格式)。Laravel Reverb:Laravel官方WebSocket服务器(2024年推出),证明PHP无需第三方扩展也能实现实时通信。
ASP.NET Core Blazor渲染模式,Microsoft Learn:交互式服务器模式基于SignalR(WebSocket)运行。
起源
工作原理
优势
劣势
当前生态:主流框架有哪些?
SSE:低成本替代方案
总结
支持我持续创作
你的一杯咖啡,就是我写下一篇文章的动力。