0

0

Go语言WebSocket服务:解决403错误与Origin校验

花韻仙語

花韻仙語

发布时间:2025-11-06 23:04:01

|

376人浏览过

|

来源于php中文网

原创

go语言websocket服务:解决403错误与origin校验

本文旨在解决Go语言使用`go.net/websocket`包时常见的403 Forbidden错误,该错误通常源于默认的`Origin`头部校验机制。我们将深入探讨`websocket.Handler`与`websocket.Server`的区别,并提供使用`websocket.Server`来绕过或自定义`Origin`校验的解决方案,确保WebSocket连接的顺利建立,同时强调相关安全考量。

1. Go语言WebSocket基础服务构建与常见问题

在Go语言中,使用go.net/websocket包可以方便地构建WebSocket服务。一个常见的WebSocket Echo服务器示例如下,它旨在接收客户端消息并打印:

package main

import (
    "fmt"
    "golang.org/x/net/websocket" // 注意:原先的code.google.com/p/go.net/websocket已迁移至此
    "net/http"
)

// webHandler 处理WebSocket连接,接收消息并打印
func webHandler(ws *websocket.Conn) {
    var s string
    // 从WebSocket连接中读取字符串
    if _, err := fmt.Fscan(ws, &s); err != nil {
        fmt.Printf("Error reading from websocket: %v\n", err)
        return
    }
    fmt.Println("Received: ", s)
    // 可以选择将消息回显给客户端
    // if _, err := fmt.Fprint(ws, "Echo: "+s); err != nil {
    //  fmt.Printf("Error writing to websocket: %v\n", err)
    // }
}

func main() {
    fmt.Println("Starting websocket server on :8080/echo")
    // 将webHandler包装成websocket.Handler并注册到HTTP路由
    http.Handle("/echo", websocket.Handler(webHandler))

    // 启动HTTP服务器监听8080端口
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        panic("ListenAndServe: " + err.Error())
    }
}

对应的客户端JavaScript连接代码如下:

// 尝试连接到WebSocket服务
ws = new WebSocket("ws://localhost:8080/echo");

// 监听接收到的消息
ws.onmessage = function(e) {
    console.log("websock: " + e.data);
};

// 连接成功时发送消息
ws.onopen = function() {
    ws.send("Hello from client!");
};

// 连接关闭时
ws.onclose = function() {
    console.log("WebSocket connection closed.");
};

// 发生错误时
ws.onerror = function(err) {
    console.error("WebSocket error:", err);
};

然而,当尝试使用上述客户端代码连接到服务器时,浏览器控制台可能会报告WebSocket connection to 'ws://localhost:8080/echo' failed: Unexpected response code: 403错误。这表明服务器拒绝了连接请求。

立即学习go语言免费学习笔记(深入)”;

2. 深入理解403错误:Origin校验机制

这个403错误通常与go.net/websocket包中websocket.Handler的默认行为有关。websocket.Handler在处理WebSocket握手时,会默认检查HTTP请求头中的Origin字段。Origin头是浏览器在发起跨域请求(包括WebSocket连接)时自动添加的,用于指示请求的来源域。

websocket.Handler的默认行为是:

  • 它期望Origin头部是一个有效的URL。
  • 它会尝试验证Origin是否与服务器的地址匹配,或者至少是一个可接受的来源。
  • 如果Origin头部缺失、无效,或者不符合其内部的默认校验规则,websocket.Handler就会拒绝连接,并返回403 Forbidden状态码。

这种机制主要是为了增强基于浏览器的WebSocket连接的安全性,防止跨站请求伪造(CSRF)等攻击。然而,在某些场景下,例如:

论论App
论论App

AI文献搜索、学术讨论平台,涵盖了各类学术期刊、学位、会议论文,助力科研。

下载
  • 非浏览器客户端(如服务器到服务器的WebSocket通信)可能不发送Origin头部。
  • Origin头部与服务器地址不完全匹配,但连接是合法的。
  • 开发或测试环境中,需要临时禁用Origin校验。

在这些情况下,websocket.Handler的默认Origin校验就会成为障碍。

3. 解决方案:使用websocket.Server绕过或自定义Origin校验

为了解决Origin校验导致的403错误,我们可以使用websocket.Server结构体来替代直接使用websocket.Handler。websocket.Server提供了更灵活的配置选项,尤其是在处理握手阶段。

websocket.Server允许我们直接嵌入一个websocket.Handler,但更重要的是,它提供了一个Handshake字段,可以自定义握手逻辑。当Handshake字段为nil时,websocket.Server会采用一个默认的握手函数,这个默认函数不会强制执行Origin校验

下面是修改后的Go服务器代码,它使用websocket.Server来解决403问题:

package main

import (
    "fmt"
    "golang.org/x/net/websocket" // 注意:请确保导入路径正确
    "net/http"
)

// webHandler 处理WebSocket连接,接收消息并打印
func webHandler(ws *websocket.Conn) {
    var s string
    // 从WebSocket连接中读取字符串
    if _, err := fmt.Fscan(ws, &s); err != nil {
        // 忽略EOF错误,这通常表示客户端关闭了连接
        if err.Error() != "EOF" {
            fmt.Printf("Error reading from websocket: %v\n", err)
        }
        return
    }
    fmt.Println("Received: ", s)
    // 可以选择将消息回显给客户端
    if _, err := fmt.Fprint(ws, "Echo: "+s); err != nil {
        fmt.Printf("Error writing to websocket: %v\n", err)
    }
}

func main() {
    fmt.Println("Starting websocket server on :8080/echo")

    // 使用http.HandleFunc注册一个自定义处理函数
    http.HandleFunc("/echo",
        func(w http.ResponseWriter, req *http.Request) {
            // 创建websocket.Server实例
            // Handshake字段为nil时,默认的握手函数不会强制检查Origin
            s := websocket.Server{Handler: websocket.Handler(webHandler)}
            // 调用ServeHTTP方法来处理WebSocket连接
            s.ServeHTTP(w, req)
        })

    // 启动HTTP服务器监听8080端口
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        panic("ListenAndServe: " + err.Error())
    }
}

在这个修正后的代码中,我们不再直接将websocket.Handler(webHandler)传递给http.Handle。取而代之的是,我们创建了一个websocket.Server实例,并将websocket.Handler(webHandler)赋值给它的Handler字段。然后,通过s.ServeHTTP(w, req)方法来处理HTTP请求。由于websocket.Server的Handshake字段默认为nil,它将使用一个不执行Origin校验的默认握手函数,从而允许连接成功建立。

4. 示例代码与注意事项

完整的服务器代码

package main

import (
    "fmt"
    "golang.org/x/net/websocket"
    "net/http"
    "log" // 引入log包用于更专业的错误处理
)

// webHandler 处理WebSocket连接,接收消息并打印,并回显
func webHandler(ws *websocket.Conn) {
    defer ws.Close() // 确保连接关闭

    var msg string
    for {
        // 从WebSocket连接中读取字符串
        err := websocket.Message.Receive(ws, &msg) // 使用websocket.Message进行结构化读写
        if err != nil {
            if err.Error() != "EOF" { // 忽略EOF错误,通常表示客户端关闭了连接
                log.Printf("Error receiving message from websocket: %v\n", err)
            }
            break // 发生错误或客户端关闭时退出循环
        }
        log.Printf("Received from client %s: %s\n", ws.RemoteAddr().String(), msg)

        // 将消息回显给客户端
        err = websocket.Message.Send(ws, "Echo: "+msg)
        if err != nil {
            log.Printf("Error sending message to websocket: %v\n", err)
            break
        }
    }
    log.Printf("Client %s disconnected.\n", ws.RemoteAddr().String())
}

func main() {
    log.Println("Starting websocket server on :8080/echo")

    http.HandleFunc("/echo",
        func(w http.ResponseWriter, req *http.Request) {
            // 可以选择在此处手动检查Origin头部,以增加安全性
            // if req.Header.Get("Origin") != "http://localhost:8080" {
            //  http.Error(w, "Forbidden", http.StatusForbidden)
            //  return
            // }

            // 创建websocket.Server实例,Handler字段指向我们的业务逻辑
            // Handshake字段为nil时,默认的握手函数不会强制检查Origin
            s := websocket.Server{Handler: websocket.Handler(webHandler)}

            // 调用ServeHTTP方法来处理WebSocket连接
            s.ServeHTTP(w, req)
        })

    log.Fatal(http.ListenAndServe(":8080", nil)) // 使用log.Fatal来处理ListenAndServe错误
}

注意事项

  1. 安全性考虑: 禁用Origin校验会降低WebSocket连接的安全性,使其更容易受到CSRF攻击。在生产环境中,特别是面向浏览器的应用,强烈建议:
    • 启用Origin校验: 仅允许来自受信任域名的连接。如果使用websocket.Server,可以在Handshake字段中提供一个自定义函数来执行精确的Origin验证。
    • 使用TLS/SSL (HTTPS/WSS): 保护通信内容不被窃听和篡改。
  2. go.net/websocket的替代方案: go.net/websocket是一个相对较老的包,且其API设计在某些方面可能不如现代的WebSocket库灵活。在新的项目中,更推荐使用像gorilla/websocket这样的第三方库,它提供了更丰富的功能、更好的性能和更清晰的API。
  3. websocket.Conn.Config().Origin: 即使使用websocket.Server绕过了默认校验,你仍然可以在webHandler内部通过ws.Config().Origin获取到客户端发送的Origin头部,然后根据业务需求进行自定义验证。
  4. 错误处理: 在实际应用中,WebSocket连接的错误处理(如客户端意外断开、网络问题)至关重要。示例中增加了defer ws.Close()和更详细的错误日志。
  5. websocket.Message与fmt.Fscan: websocket.Message.Receive和websocket.Message.Send是go.net/websocket包中推荐的用于发送和接收结构化消息的方式,它们比fmt.Fscan更健壮,尤其是在处理二进制数据或更复杂的文本格式时。

总结

当在Go语言中使用go.net/websocket包构建WebSocket服务时,遇到403 Forbidden错误通常是由于websocket.Handler默认的Origin头部校验机制所致。为了解决这个问题,我们可以通过实例化websocket.Server并将其Handler字段设置为我们的业务逻辑,利用其默认的握手行为(不强制Origin校验)来成功建立连接。然而,在禁用Origin校验时,务必充分考虑安全风险,并在生产环境中采取适当的安全措施,如自定义Origin验证或使用更现代、功能更完善的WebSocket库。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

260

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

234

2023.09.06

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

450

2023.09.25

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

255

2023.10.13

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

703

2023.10.26

Go语言实现运算符重载有哪些方法
Go语言实现运算符重载有哪些方法

Go语言不支持运算符重载,但可以通过一些方法来模拟运算符重载的效果。使用函数重载来模拟运算符重载,可以为不同的类型定义不同的函数,以实现类似运算符重载的效果,通过函数重载,可以为不同的类型实现不同的操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

194

2024.02.23

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

233

2024.02.23

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.4万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.6万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号