【微服务网关——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,一经查实,立即删除!

相关文章

题目:只通过+1和×2两种操作,最少几次能把0变成20240701?

偶然在网上看到了一个题目&#xff0c;看了看视频和评论&#xff0c;感觉挺有意思&#xff0c;记录一下。 只通过1和2两种操作&#xff0c;最少几次能把0变成20240701&#xff1f;_哔哩哔哩_bilibili 题目&#xff1a;只通过1和2两种操作&#xff0c;最少几次能把0变成202407…

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文件模拟结果注…

HTML5 SVG: 探索矢量图形的新纪元

HTML5 SVG: 探索矢量图形的新纪元 引言 HTML5,作为现代网页开发的核心技术之一,极大地推动了网页内容的丰富性和交互性。在HTML5的众多特性中,SVG(可缩放矢量图形)发挥着重要作用,它允许开发者创建复杂、响应式的矢量图形,这些图形在任何分辨率下都能保持清晰。本文将…

服务器推送有几种方式,分别有什么优缺点

服务器推送主要有以下几种方式&#xff1a; 长轮询&#xff08;Long Polling&#xff09;&#xff1a; 优点&#xff1a; 相对简单易实现。能够在一定程度上减少无效的请求&#xff0c;降低服务器资源消耗。 缺点&#xff1a; 仍然存在一定的延迟。如果连接意外中断&#xff…

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

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

Mamba项目实战-Ubuntu

注&#xff1a;演示环境需要一个可用的cuda环境&#xff0c;可执行两个命令进行验证 1.nvidia-smi 2.nvcc -V 若出现正确输出&#xff0c;可继续博客以下的操作步骤&#xff0c;否则请确认是否已经安装或已配置环境变量&#xff0c;若未安装则转到博客&#xff1a;深度学习项…

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…

Java使用分布式实现数据库读写分离

配置主从复制&#xff1a;在MySQL中&#xff0c;可以通过配置主从复制来实现数据同步。将一个节点作为主节点&#xff08;Master&#xff09;&#xff0c;其他节点作为从节点&#xff08;Slave&#xff09;。当主节点上的数据发生变化时&#xff0c;自动将变更内容同步到所有从…

JS面试题6——深拷贝和浅拷贝

它们都是用来复制的 1. 浅拷贝&#xff08;只复制引用&#xff0c;而未复制真正的值&#xff09; /* 简单赋值 */ var arr1 [a, b, c, d]; var arr2 arr1; /* Object.assign实现的也是浅拷贝 */ var obj1 {a:1, b:2} var obj2 Object.assign(obj1); 2. 深拷贝&#xff08;是…

C++ 预处理器定义

目录 一、什么是预处理器定义 二、常见用法 1. 条件编译 2. 宏定义 3. 防止重复包含 4. 动态链接库的导入导出 5、如何设置预处理器定义 一、什么是预处理器定义 预处理器定义&#xff08;Preprocessor Definitions&#xff09;在C和C编程中用于在编译过程中进行文本替换…

华为OCR识别技术 [C#]

了解华为OCR识别技术 光学字符识别&#xff08;OCR&#xff0c;Optical Character Recognition&#xff09;是一种将印刷或手写的文本字符转换为机器可读文本的技术。华为的OCR技术在业界以其高精度和高效率而著称&#xff0c;广泛应用于金融、教育、政府等多个领域。 OCR技术…

hive explain分析Hsql详解

Hive explain HIVE提供了EXPLAIN命令来展示一个查询的执行计划&#xff0c;可以帮助我们查看stage的依赖情况、排查数据倾斜、hive 调优等 使用语法如下&#xff1a; EXPLAIN [EXTENDED|CBO|AST|DEPENDENCY|AUTHORIZATION|LOCKS|VECTORIZATION|ANALYZE] query explain 后面可以…

【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 /…

Symfony框架实战入门:打造简易博客应用全过程解析

Symfony框架详解 一、引言 随着Web应用的复杂度不断提升&#xff0c;开发者们越来越需要高效、可扩展和易于维护的框架来支持他们的工作。Symfony就是这样一款优秀的PHP框架&#xff0c;它基于MVC&#xff08;模型-视图-控制器&#xff09;设计模式&#xff0c;提供了许多强大…