HTTP/1.1 协议提供了一种使用 Upgrade 标头字段的特殊机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议。这个机制是可选的;它并不能强制协议的更改(通常来说这一机制总是由客户端发起的)。如果它们支持新协议,实现甚至可以不利用upgrade,在实践中,这种机制主要用于引导 WebSocket 连接。
注意:HTTP/2 明确禁止使用此机制;这个机制只属于HTTP/1.1。
由于 HTTP/1.1 升级机制最常用的场景是 WebSocket。由于 WebSocket 是一块专业的内容,我们还是站在巨人的肩膀上实现我们的示例 demo。服务端使用 node + ws
实现 WebSocket 服务。客户端使用 浏览器 API WebSocket。
示例代码
- 客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>websocket</title>
</head>
<body>
<button>发送消息</button>
<script>
const ws = new WebSocket('ws://127.0.0.1:8081');
ws.addEventListener('open', function (event) {
console.log('连接成功', event);
});
ws.addEventListener('message', function (event) {
console.log('收到消息', event.data);
});
ws.addEventListener('close', function (event) {
console.log('连接关闭', event);
});
ws.addEventListener('error', function (event) {
console.log('连接错误', event);
})
document.querySelector('button').addEventListener('click', function () {
ws.send('hello');
})
</script>
</body>
</html>
- 服务端
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8081 });
server.on('open', function open() {
console.log('connected');
});
server.on('close', function close() {
console.log('disconnected');
});
server.on('connection', function connection(ws, req) {
const ip = req.connection.remoteAddress;
const port = req.connection.remotePort;
const clientName = ip + port;
console.log('%s is connected', clientName)
// 发送欢迎信息给客户端
ws.send("Welcome " + clientName);
ws.on('message', function incoming(message) {
console.log('received: %s from %s', message, clientName);
// 广播消息给所有客户端
server.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send( clientName + " -> " + message);
}
});
});
});
当浏览器和服务端进行连接时,向服务端发送了一个 HTTP/1.1 的 GET 请求。
- 请求头需要说明具体的协议,可以是一个,也可以是多个。例如我们的请求报文
Upgrade: websocket
表明是 websocket 协议。 - Sec-WebSocket-Extensions指定一个或多个请求服务器使用的协议级 WebSocket 扩展,例如:
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
。 - Sec-WebSocket-Key向服务器提供确认客户端有权请求升级到 WebSocket 的所需信息,密钥的值是使用 WebSocket 规范中定义的算法计算的,不提供安全性。Sec-WebSocket-Key用于防止非 WebSocket 客户端无意中或滥用请求 WebSocket 连接。
- Sec-WebSocket-Version指定客户端希望使用的 WebSocket 协议版本
这些信息我们都不用手动处理, 浏览器的 WebSocket API 已经帮我们处理好了。我们只管简单的使用就好了。
GET ws://127.0.0.1:8081/ HTTP/1.1
Host: 127.0.0.1:8081
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Sec-WebSocket-Key: bdXy5LYyPmxVi9ISux//gQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
当服务端收到客户端的 WebSocket 连接时,起始行会告诉浏览器是否支持这个协议。如果正常返回状态码: 例如 200,说明服务器忽略本次协议升级。如果服务端支持协议升级,响应报文起始行会返回 HTTP/1.1 101 Switching Protocols
,告诉浏览器支持协议升级。
- Upgrade: 升级协议
- Sec-WebSocket-Accept: 说明服务器愿意发起 WebSocket 连接。其包含在打开握手过程中来自服务器的响应消息中。它只会在响应标头中出现一次
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 7nSh4Fi5hvy0cRMURV29MpkaZbQ=
总结
协议升级不仅仅用于 WebSocket,还可以将协议升级为 HTTP 2.0。这个机制是可选的。它并不能强制协议的更改(通常来说这一机制总是由客户端发起的)。如果它们支持新协议,实现甚至可以不利用 upgrade,在实践中,这种机制主要用于引导 WebSocket 连接。
WebSocket协议是一个基于HTTP的协议,它在单个TCP连接上进行全双工通信。WebSocket 主要功能是用来和服务端进行实时通信。如果想使用从0开始来实现WebSocket,你需要自己实现WebSocket协议,这需要深入理解WebSocket协议的工作原理,包括消息帧的解析、心跳机制、分片消息的传输等等
相关参考
What is Sec-WebSocket-Key for?
10.3. Attacks On Infrastructure (Masking)
How does websocket frame masking protect against cache poisoning?