【微服务网关——Websocket代理】

1.Websocket协议与原理

在这里插入图片描述

1.1 连接建立协议

1.1.1 客户端发起连接请求

客户端通过 HTTP 请求发起 WebSocket 连接。以下是一个 WebSocket 握手请求的例子:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
  • GET /chat HTTP/1.1:请求使用 GET 方法。
  • Host: server.example.com:请求目标主机。
  • Upgrade: websocket:表明希望升级到 WebSocket 协议。
  • Connection: Upgrade:指示当前的连接请求升级。
  • Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==:一个 Base64 编码的随机密钥,用于服务器响应中进行确认。
  • Sec-WebSocket-Version: 13:表明 WebSocket 的版本(13 是最新的版本)。

1.1.2 服务器响应

服务器接收到请求后,如果同意升级协议,将发送一个 HTTP 响应来确认连接升级:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
  • HTTP/1.1 101 Switching Protocols:表示协议切换成功。
  • Upgrade: websocket:确认升级到 WebSocket。
  • Connection: Upgrade:确认连接升级。
  • Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=:服务器根据客户端的 Sec-WebSocket-Key 生成的一个响应密钥。

1.1.3 连接建立

一旦服务器发回 101 状态码,WebSocket 连接便建立成功。之后,客户端和服务器可以在此连接上相互发送和接收数据帧,而不需要再使用 HTTP 协议的头部。

  • 握手过程:WebSocket 握手基于 HTTP,并包含 Upgrade 和 Connection 头部来请求和确认协议的升级。
  • Sec-WebSocket-Key:客户端生成的随机密钥,服务器用于验证连接的合法性。
  • Sec-WebSocket-Accept:服务器生成的响应密钥,基于客户端提供的 Sec-WebSocket-Key 和固定 GUID 的 SHA-1 和 Base64 编码。

1.2 数据传输协议

在这里插入图片描述

  • FIN (1 bit): 表示是否为消息的最后一个帧。1 表示是最后一个帧。
  • RSV1, RSV2, RSV3 (1 bit each): 保留位,通常设置为 0,除非在扩展中有定义。
  • Opcode (4 bits): 表示帧的类型:
    • 0x0: 延续帧(continuation frame)
    • 0x1: 文本帧(text frame)
    • 0x2: 二进制帧(binary frame)
    • 0x8: 连接关闭(connection close)
    • 0x9: Ping
    • 0xA: Pong
  • Mask (1 bit): 表示是否应用掩码。客户端发送的帧必须设置为 1,服务器发送的帧必须设置为 0。
  • Payload Length (7 bits or 7+16/64 bits): 数据载荷的长度:
    • 如果值在 0 到 125 之间,则为数据载荷的实际长度。
    • 126: 后接 16 位整数表示长度。
    • 127: 后接 64 位整数表示长度。
  • Masking-Key (0 or 4 bytes): 掩码键,存在于客户端发送的帧中,用于解码数据载荷。
  • Payload Data (x bytes): 实际的应用数据。

1.3 Websocket原理

掩码: 确保客户端发送的数据经过掩码处理,防止恶意数据影响。
控制帧: 必须尽快处理控制帧,避免它们与数据帧混合,导致错误。

Websocket补充-Connection Header头意义

  • 标记请求发起方与第一代理的状态
  • 决定当前事务完成后,是否会关闭代理
    • Connection:keep-alive 不关闭网络
    • Connection:close 关闭网络
    • Connection:Upgrade 协议升级

在这里插入图片描述

2.Websocket代理实战

2.1 基于go构建Websocket测试服务器和客户端

这是一个前后端一体代码:

package mainimport ("flag""html/template""log""net/http""github.com/gorilla/websocket"
)var addr = flag.String("addr", "localhost:2003", "http service address")var upgrader = websocket.Upgrader{} // use default optionsfunc echo(w http.ResponseWriter, r *http.Request) {c, err := upgrader.Upgrade(w, r, nil)if err != nil {log.Print("upgrade:", err)return}defer c.Close()for {mt, message, err := c.ReadMessage()if err != nil {log.Println("read:", err)break}log.Printf("recv: %s", message)err = c.WriteMessage(mt, message)if err != nil {log.Println("write:", err)break}}
}func home(w http.ResponseWriter, r *http.Request) {homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}func main() {flag.Parse()log.SetFlags(0)http.HandleFunc("/echo", echo)http.HandleFunc("/", home)log.Println("Starting websocket server at " + *addr)log.Fatal(http.ListenAndServe(*addr, nil))
}var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script>  
window.addEventListener("load", function(evt) {var output = document.getElementById("output");var input = document.getElementById("input");var ws;var print = function(message) {var d = document.createElement("div");d.innerHTML = message;output.appendChild(d);};document.getElementById("open").onclick = function(evt) {if (ws) {return false;}var web_url=document.getElementById("web_url").valuews = new WebSocket(web_url);ws.onopen = function(evt) {print("OPEN");}ws.onclose = function(evt) {print("CLOSE");ws = null;}ws.onmessage = function(evt) {print("RESPONSE: " + evt.data);}ws.onerror = function(evt) {print("ERROR: " + evt.data);}return false;};document.getElementById("send").onclick = function(evt) {if (!ws) {return false;}print("SEND: " + input.value);ws.send(input.value);return false;};document.getElementById("close").onclick = function(evt) {if (!ws) {return false;}ws.close();return false;};});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server, 
"Send" to send a message to the server and "Close" to close the connection. 
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="web_url" type="text" value="{{.}}">
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))

运行后访问:http://localhost:2003/
在这里插入图片描述
Open尝试与服务器建立连接
在这里插入图片描述
Send基于Websocket向服务器发送了链接
在这里插入图片描述

Close关闭连接

2.2 深入理解upgrader.Upgrade

  • 获取Sec-Websocket-Key
  • sha1生成Sec-WebSocket-Accept
  • 向客户端发送101status
// Upgrade 升级 HTTP 服务器连接到 WebSocket 协议。
// responseHeader 包含在响应中,以回应客户端的升级请求。
// 使用 responseHeader 指定 cookies (Set-Cookie) 和应用程序协商的子协议 (Sec-WebSocket-Protocol)。
// 如果升级失败,Upgrade 会用 HTTP 错误响应来回复客户端。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {const badHandshake = "websocket: the client is not using the websocket protocol: "// 检查 "Connection" 头部是否包含 "upgrade" 令牌if !tokenListContainsValue(r.Header, "Connection", "upgrade") {return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")}// 检查 "Upgrade" 头部是否包含 "websocket" 令牌if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")}// 检查请求方法是否为 GETif r.Method != "GET" {return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")}// 检查 "Sec-Websocket-Version" 头部是否包含版本号 "13"if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")}// 检查 responseHeader 中是否包含 "Sec-Websocket-Extensions",不支持应用程序特定的扩展头if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")}// 检查请求来源是否允许checkOrigin := u.CheckOriginif checkOrigin == nil {checkOrigin = checkSameOrigin}if !checkOrigin(r) {return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")}// 获取 "Sec-Websocket-Key" 头部的值,并检查是否为空challengeKey := r.Header.Get("Sec-Websocket-Key")if challengeKey == "" {return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")}// 选择协商的子协议subprotocol := u.selectSubprotocol(r, responseHeader)// PMCE (Per-Message Compression Extensions) 协商var compress boolif u.EnableCompression {for _, ext := range parseExtensions(r.Header) {if ext[""] != "permessage-deflate" {continue}compress = truebreak}}// 检查响应是否实现了 http.Hijacker 接口h, ok := w.(http.Hijacker)if !ok {return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")}var brw *bufio.ReadWriter// 通过劫持获得net/IO流netConn, brw, err := h.Hijack()if err != nil {return u.returnError(w, r, http.StatusInternalServerError, err.Error())}// 检查是否有未读数据if brw.Reader.Buffered() > 0 {netConn.Close()return nil, errors.New("websocket: client sent data before handshake is complete")}// 根据情况重用缓冲读取器var br *bufio.Readerif u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {br = brw.Reader}// 获取缓冲写入器buf := bufioWriterBuffer(netConn, brw.Writer)// 根据情况重用缓冲写入器作为连接缓冲var writeBuf []byteif u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {writeBuf = buf}// 创建新的 WebSocket 连接c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)c.subprotocol = subprotocol// 设置压缩/解压缩功能if compress {c.newCompressionWriter = compressNoContextTakeoverc.newDecompressionReader = decompressNoContextTakeover}// 使用较大的缓冲区写入响应头p := bufif len(c.writeBuf) > len(p) {p = c.writeBuf}p = p[:0]// 构建 WebSocket 握手响应p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)p = append(p, computeAcceptKey(challengeKey)...)p = append(p, "\r\n"...)if c.subprotocol != "" {p = append(p, "Sec-WebSocket-Protocol: "...)p = append(p, c.subprotocol...)p = append(p, "\r\n"...)}if compress {p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)}for k, vs := range responseHeader {if k == "Sec-Websocket-Protocol" {continue}for _, v := range vs {p = append(p, k...)p = append(p, ": "...)for i := 0; i < len(v); i++ {b := v[i]if b <= 31 {// 防止响应分割。b = ' '}p = append(p, b)}p = append(p, "\r\n"...)}}p = append(p, "\r\n"...)// 清除 HTTP 服务器设置的超时时间。netConn.SetDeadline(time.Time{})// 设置握手超时if u.HandshakeTimeout > 0 {netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))}if _, err = netConn.Write(p); err != nil {netConn.Close()return nil, err}if u.HandshakeTimeout > 0 {netConn.SetWriteDeadline(time.Time{})}return c, nil
}
  • 协议验证: 检查 HTTP 请求头部中的必要字段,确保请求是一个有效的 WebSocket 握手请求。
  • 请求合法性检查: 验证请求的方法、版本、来源和密钥等,以确保请求的合法性和安全性。
  • 协议协商: 根据客户端请求和服务器配置,选择合适的子协议和是否启用数据压缩。
  • 连接劫持: 使用 http.Hijacker 接口劫持 HTTP 连接,以便直接读取和写入网络数据。
  • 发送握手响应: 构建并发送 WebSocket 握手响应,确认协议升级成功。
  • 返回连接对象: 创建 WebSocket 连接对象,并返回给调用者,以便后续的数据传输。

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

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

相关文章

Python面试宝典第3题:石子游戏

题目 Alice 和 Bob 用几堆石子在做游戏&#xff1a;一共有偶数堆石子&#xff0c;排成一行&#xff1b;每堆都有正整数颗石子&#xff0c;数目为 piles[i] 。游戏以谁手中的石子最多来决出胜负&#xff0c;石子的总数是奇数 &#xff0c;所以没有平局。 Alice 和 Bob 轮流进行&…

CV01_相机成像原理与坐标系之间的转换

目录 0.引言&#xff1a;小孔成像->映射表达式 1. 相机自身的运动如何表征&#xff1f;->外参矩阵E 1.1 旋转 1.2 平移 2. 如何投影到“像平面”&#xff1f;->内参矩阵K 2.1 图像平面坐标转换为像素坐标系 3. 三维到二维的维度是如何丢失的&#xff1f;…

LVS-负载均衡

目录 一、概念 二、LVS工作原理 1. ipvs/ipvsadm 2.名词&#xff1a; 三、常用命令 四、工作模式 1.NAT地址转换模式 &#xff08;1&#xff09;工作流程 &#xff08;2&#xff09;特点 &#xff08;3&#xff09;实验过程 a.环境准备&#xff1a; b.修改测试机的…

UE5 动画蓝图

文章目录 一、State Machines二、Blend Spaces三、Aim Offset四、Montage 初步介绍 Unreal Engine 5 Tutorial - Animation Blueprint Part 1: State Machines (youtube.com) Unreal Engine 5 Tutorial - Animation Blueprint Part 2: Blend Spaces (youtube.com) Unreal Engi…

非静压模型SWASH学习(8)——三维孤立波在锥形岛屿上的爬坡过程(Runup of solitary waves on a conical island)

三维孤立波在锥形岛屿上的爬坡过程&#xff08;Runup of solitary waves on a conical island&#xff09; 算例简介模型配置网格及参数设置网格与地形初始条件与边界条件数值求解方法输出设置模拟时间 波浪&#xff08;孤立波&#xff09;入射边界的时间序列.bnd文件模拟结果注…

[吃瓜教程]南瓜书第4章决策树

1.决策树的算法原理 从逻辑角度&#xff0c;条件判断语句的组合&#xff1b;从几何角度&#xff0c;根据某种准则划分特征空间&#xff1b; 是一种分治的思想&#xff0c;其最终目的是将样本约分约纯&#xff0c;而划分的核心是在条件的选择或者说是**特征空间的划分标准 ** …

mac安装达梦数据库

参考&#xff1a;mac安装达梦数据库​​​​​​ 实践如下&#xff1a; 1、下载达梦Docker镜像文件 同参考链接 2、导入镜像 镜像可以随便放在某个目录&#xff0c;相当于安装包&#xff0c;导入后就没有作用了。 查找达梦镜像名称&#xff1a;dm8_20240613_rev229704_x86…

第1章 人工智能的基础概念与应用导论

亲爱的读者朋友们&#xff0c;你们好&#xff01;欢迎来到这个充满神奇与奥秘的人工智能世界。我知道&#xff0c;对于很多人来说&#xff0c;人工智能&#xff08;AI&#xff09;可能是个既神秘又高大上的词汇&#xff0c;仿佛遥不可及&#xff0c;只存在于科幻电影或者顶级科…

Figma-ui设计学习APP Store

Figma汉化&#xff1a;Figma 中文社区_插件组件库,软件汉化教程 - Figma.Cool 选择Chorme汉化版离线包 插件安装&#xff1a; 打开浏览器安装扩展&#xff0c;解压加载进去即可。 打开标尺&#xff0c;设置左右内边距参考线&#xff08;左21 右356&#xff09;&#xff0c;wi…

【postgresql】版本学习

PostgreSQL 17 Beta 2 发布于2024-06-27。 PostgreSQL 17 Beta 2功能和变更功能的完整列表&#xff1a;PostgreSQL: Documentation: 17: E.1. Release 17 ​ 支持的版本&#xff1a; 16 ( 当前版本) / 15 / 14 / 13 / 12 ​ 不支持的版本&#xff1a; 11 / 10 / 9.6 / 9.5 /…

探索工业AI智能摄像机的高端科技

在当今快速发展的工业智能化领域&#xff0c;工业AI智能摄像机系列以其卓越的性能和多功能性在国内外备受关注&#xff08;文末有国外工程师的评测链接&#xff09;。搭载Raspberry Pi CM4支持的旨在广泛应用&#xff0c;涵盖从简单的条形码扫描到基于人工智能的工业环境中的缺…

7.1.SQL注入-基于函数报错的方式来利用updatexml()

基于函数报错的方式来进行利用-字符型&#xff08;本页updatexml()&#xff09; 前提条件是后台数据库没有屏蔽数据库语法报错信息 updatexml()方法详解 注释&#xff1a; 第一个参数&#xff0c;意思就是xml文档的名称 第二个参数&#xff0c;意思就是定位到xml文档中指定…

OFDM关键技术——ICI消除技术

ICI消除算法可以分为以下几类&#xff1a; 1、OFDM符号长度和载波间隔的最优选择&#xff0c;较短的符号周期更有利于降低ICI 2、OFDM基信号的最佳选择&#xff0c;选择频域衰减更快的OFDM基带脉冲 3、自干扰消除技术&#xff0c;将信息调制到一组子载波上 4、频域均衡器&a…

认识100种电路之耦合电路

在电子电路的世界中&#xff0c;耦合电路宛如一座精巧的桥梁&#xff0c;连接着各个功能模块&#xff0c;发挥着至关重要的作用。 【为什么电路需要耦合】 在复杂的电子系统中&#xff0c;不同的电路模块往往需要协同工作&#xff0c;以实现特定的功能。然而&#xff0c;这些模…

网络爬虫(二) 哔哩哔哩热榜高频词按照图片形状排列

我们有时候需要爬取结果生成为自定义的词云图 生成自定义的词云图通常需要以下步骤&#xff1a; 1. 爬取数据&#xff1a;使用爬虫工具或库&#xff0c;如requests、BeautifulSoup等&#xff0c;可以爬取网页、论坛、社交媒体等平台上的文本数据。 2. 数据预处理&#xff1a…

uniapp微信小程序电子签名

先上效果图&#xff0c;不满意可以直接关闭这页签 新建成单独的组件&#xff0c;然后具体功能引入&#xff0c;具体功能点击签名按钮&#xff0c;把当前功能页面用样式隐藏掉&#xff0c;v-show和v-if也行&#xff0c;然后再把这个组件显示出来。 【签名-撤销】原理是之前绘画时…

AI影像测量:开启测量仪器的智能之眼

在基于机器视觉的影像测量中&#xff0c;一些复杂特征传统测量需要人工手动选点测量&#xff0c;不仅易受到人为因素的干扰&#xff0c;而且极大的降低测量效率&#xff0c;提高了人力成本和生产成本。AI影像测量技术运用先进的机器视觉和深度学习算法&#xff0c;可快速、准确…

【JVM】JVM 内存结构

程序计数器 Cpu 要不停的切换执行线程&#xff0c;所以在切换回同一个线程的时候要知道程序执行到哪了&#xff0c;程序计数器&#xff08;PC 计数器&#xff09;&#xff0c;用来存储指向下一条指令的地址&#xff0c;也就是将要执行的代码。 程序的分支、循环、跳转、异常处…

QuickBooks 2024 for Mac:财务智慧,触手可及

QuickBooks 2024 for Mac是一款专为Mac用户设计的专业财务管理软件&#xff0c;它集成了多种实用功能&#xff0c;助力企业和个人用户高效管理财务事务。 &#x1f4ca; 全面的财务管理工具&#xff1a;QuickBooks 2024 for Mac 提供了一套全面的财务管理功能&#xff0c;包括…

用免费的可视化工具制作3D智慧城市大屏,融合数字孪生,引领数据升级

在如今数据驱动的时代&#xff0c;越来越多的场景中都有可视化大屏的身影&#xff0c;许多企业和政府部门也从常规的二维看板渐渐地转向更加炫酷&#xff0c;立体的3D可视化大屏。3D可视化大屏成为了展示复杂数据、实时监控业务动态的重要工具。本文将详细介绍如何使用免费的数…