• HTML5客户端
  • WebSocket服务端
  • 基于HTTPS的WebSocket
  • 示例结果展示
  • Websocket安全校验
  • WebSocket聊天室案例

    使用gf框架进行websocket开发相当简单。我们以下通过实现一个简单的echo服务器来演示gf框架的websocket的使用(客户端使用HTML5实现)。示例代码:https://github.com/gogf/gf/tree/master/geg/net/ghttp/server/websocket

    HTML5客户端

    先上H5客户端的代码

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title>gf websocket echo server</title>
    5. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
    6. <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
    7. </head>
    8. <body>
    9. <div class="container">
    10. <div class="list-group" id="divShow"></div>
    11. <div>
    12. <div><input class="form-control" id="txtContent" autofocus rows="6" placeholder="请输入发送内容"></div>
    13. <div><button class="btn btn-default" id="btnSend" style="margin-top:15px">发 送</button></div>
    14. </div>
    15. </div>
    16. </body>
    17. </html>
    18. <script type="application/javascript">
    19. // 显示提示信息
    20. function showInfo(content) {
    21. $("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow")
    22. }
    23. // 显示警告信息
    24. function showWaring(content) {
    25. $("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow")
    26. }
    27. // 显示成功信息
    28. function showSuccess(content) {
    29. $("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow")
    30. }
    31. // 显示错误信息
    32. function showError(content) {
    33. $("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow")
    34. }
    35. $(function () {
    36. var url = "ws://127.0.0.1:8199/ws";
    37. var ws = new WebSocket(url);
    38. try {
    39. // ws连接成功
    40. ws.onopen = function () {
    41. showInfo("WebSocket Server [" + url +"] 连接成功!");
    42. };
    43. // ws连接关闭
    44. ws.onclose = function () {
    45. if (ws) {
    46. ws.close();
    47. ws = null;
    48. }
    49. showError("WebSocket Server [" + url +"] 连接关闭!");
    50. };
    51. // ws连接错误
    52. ws.onerror = function () {
    53. if (ws) {
    54. ws.close();
    55. ws = null;
    56. }
    57. showError("WebSocket Server [" + url +"] 连接关闭!");
    58. };
    59. // ws数据返回处理
    60. ws.onmessage = function (result) {
    61. showWaring(" > " + result.data);
    62. };
    63. } catch (e) {
    64. alert(e.message);
    65. }
    66. // 按钮点击发送数据
    67. $("#btnSend").on("click", function () {
    68. if (ws == null) {
    69. showError("WebSocket Server [" + url +"] 连接失败,请F5刷新页面!");
    70. return;
    71. }
    72. var content = $.trim($("#txtContent").val()).replace("/[\n]/g", "");
    73. if (content.length <= 0) {
    74. alert("请输入发送内容!");
    75. return;
    76. }
    77. $("#txtContent").val("")
    78. showSuccess(content);
    79. ws.send(content);
    80. });
    81. // 回车按钮触发发送点击事件
    82. $("#txtContent").on("keydown", function (event) {
    83. if (event.keyCode == 13) {
    84. $("#btnSend").trigger("click");
    85. }
    86. });
    87. })
    88. </script>

    注意我们这里的服务端连接地址为:ws://127.0.0.1:8199/ws

    客户端的功能很简单,主要实现了这几个功能:

    • 与服务端websocket连接状态保持及信息展示;
    • 界面输入内容并发送信息到websocket服务端;
    • 接收到websocket的返回信息后回显在界面上;

    WebSocket服务端

    package main
    
    import (
        "github.com/gogf/gf/g"
        "github.com/gogf/gf/g/net/ghttp"
        "github.com/gogf/gf/g/os/gfile"
        "github.com/gogf/gf/g/os/glog"
    )
    
    func main() {
        s := g.Server()
        s.BindHandler("/ws", func(r *ghttp.Request) {
            ws, err := r.WebSocket()
            if err != nil {
                glog.Error(err)
                r.Exit()
            }
            for {
                msgType, msg, err := ws.ReadMessage()
                if err != nil {
                    return
                }
                if err = ws.WriteMessage(msgType, msg); err != nil {
                    return
                }
            }
        })
        s.SetServerRoot(gfile.MainPkgPath())
        s.SetPort(8199)
        s.Run()
    }
    

    可以看到,服务端的代码相当简单,这里需要着重说明的是3个地方:

    1. WebSocket方法 websocket服务端的路由注册方式和普通的http回调函数注册方式一样,但是在接口处理中我们需要通过ghttp.Request.WebSocket方法(这里直接使用指针对象r.WebSocket())将请求转换为websocket操作,并返回一个WebSocket对象,该对象用于后续的websocket通信操作。当然,如果客户端请求并非为websocket操作时,转换将会失败,该方法会返回错误信息,使用时请注意判断方法的error返回值。
    2. ReadMessage & WriteMessage 读取消息以及写入消息对应的是websocket的数据读取以及写入操作(ReadMessage & WriteMessage),需要注意的是这两个方法都有一个msgType的变量,表示请求读取及写入数据的类型,常见的两种数据类型为:字符串数据或者二进制数据。在使用过程中,由于接口双方都会约定统一的数据格式,因此读取和写入的msgType几乎都是一致的,所以在本示例中的返回消息时,数据类型参数直接使用的是读取到的msgType

    基于HTTPS的WebSocket

    如果需要支持HTTPS的WebSocket服务,只需要依赖的WebServer支持HTTPS即可,访问的WebSocket地址需要使用 wss:// 协议访问。以上客户端HTML5页面中的WebSocket访问地址需要修改为:wss://127.0.0.1:8199/wss。服务端示例代码:

    package main
    
    import (
        "github.com/gogf/gf/g"
        "github.com/gogf/gf/g/net/ghttp"
        "github.com/gogf/gf/g/os/gfile"
        "github.com/gogf/gf/g/os/glog"
    )
    
    func main() {
        s := g.Server()
        s.BindHandler("/wss", func(r *ghttp.Request) {
            ws, err := r.WebSocket()
            if err != nil {
                glog.Error(err)
                r.Exit()
            }
            for {
                msgType, msg, err := ws.ReadMessage()
                if err != nil {
                    return
                }
                if err = ws.WriteMessage(msgType, msg); err != nil {
                    return
                }
            }
        })
        s.SetServerRoot(gfile.MainPkgPath())
        s.EnableHTTPS("../../https/server.crt", "../../https/server.key")
        s.SetPort(8199)
        s.Run()
    }
    

    示例结果展示

    我们首先执行示例代码main.go,随后访问页面 http://127.0.0.1:8199/,随意输入请求内容并提交,随后在服务端关闭程序。可以看到,页面会回显提交的内容信息,并且即时展示websocket的连接状态的改变,当服务端关闭时,客户端也会即时地打印出关闭信息。

    WebSocket服务 - 图1

    Websocket安全校验

    gf框架的websocket模块并不会做同源检查(origin),也就是说,这种条件下的websocket允许完全跨域。

    安全的校验需要由业务层来处理,安全校验主要包含以下几个方面:

    1. origin的校验: 业务层在执行r.WebSocket()之前需要进行origin同源请求的校验;或者按照自定义的处理对请求进行校验(如果请求提交参数);如果未通过校验,那么调用r.Exit()终止请求。
    2. websocket通信数据校验: 数据通信往往都有一些自定义的数据结构,在这些通信数据中加上鉴权处理逻辑;

    WebSocket聊天室案例

    gf框架示例项目中提供了基于WebSocket的聊天室案例,具体请查看:https://github.com/gogf/gf-demos