go语言webSocket框架——gorilla

Posted 尚墨1111

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go语言webSocket框架——gorilla相关的知识,希望对你有一定的参考价值。

文章目录

HTTP


参考文章:

1 HTTP协议

1.1 是什么?

HTTP是一个基于TCP/IP通信协议来传递数据(html 文件, 图片文件, 查询结果等)

  • HTTP是基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。
  • 一个HTTP"客户端"是一个应用程序(Web浏览器或其他任何客户端),连接到服务器达到向服务器发送一个或多个HTTP的请求。
  • 一个HTTP"服务器"同样也是一个应用程序(通常是一个Web服务,如Apache Web服务器或IIS服务器等),通过接收客户端的请求并向客户端发送HTTP响应数据。
  • HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

1.1.1 客户端请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1X0mAnbT-1661397084862)(https://atts.w3cschool.cn/attachments/image/21161225/1456372149731272.png)]

GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/1.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi

1.1.2 服务器响应消息

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-462SsJTz-1661397084863)(https://atts.w3cschool.cn/attachments/image/21161225/1456372149775233.jpg)]

# 状态行
HTTP/1.1 211 OK			
# 响应头
Date: Mon, 27 Jul 2119 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2119 19:15:56 GMT
ETag: "34aa387-d-1568eb11"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain

1.2 HTTP 请求方法

HTTP1.1 定义了三种请求方法: GET, POST 和 HEAD 方法。

HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

序号方法描述
1GET请求指定的页面信息,并返回实体主体。
2HEAD类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
3POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
4PUT从客户端向服务器传送的数据取代指定的文档的内容。
5DELETE请求服务器删除指定的页面。
6CONNECTHTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
7OPTIONS允许客户端查看服务器的性能。
8TRACE回显服务器收到的请求,主要用于测试或诊断。
9PATCH是对 PUT 方法的补充,用来对已知资源进行局部更新 。

1.3 HTTP 状态码

HTTP 状态码的英文为 HTTP Status Code

分类分类描述
1**信息,服务器收到请求,需要请求者继续执行操作
2**成功,操作被成功接收并处理
3**重定向,需要进一步的操作以完成请求
4**客户端错误,请求包含语法错误或无法完成请求
5**服务器错误,服务器在处理请求的过程中发生了错误

常用HTTP状态码列表:

状态码状态码英文名称中文描述
211OK请求成功。一般用于GET与POST请求
311Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。
315Use Proxy使用代理。所请求的资源必须通过代理访问
411Bad Request客户端请求的语法错误,服务器无法理解
411Unauthorized请求要求用户的身份认证
413Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
414Not Found服务器无法根据客户端的请求找到资源(网页)。
511Internal Server Error服务器内部错误,无法完成请求
512Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
514Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求

1.4 content-type 内容类型

参考:https://www.runoob.com/http/http-content-type.html

Content-Type 标头告诉客户端实际返回的内容的内容类型。用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。

语法格式:

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

实例:

常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型

  • application/xhtml+xml :XHTML格式
  • application/xml: XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json: JSON数据格式
  • application/pdf:pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

2 http.HandleFunc

参考:https://www.cnblogs.com/f-ck-need-u/p/11121951.html

2.1 是什么

Multiplexer根据URL将请求路由给指定的Handler。Handler用于处理请求并给予响应。更严格地说,用来读取请求体、并将请求对应的响应字段(respones header)写入ResponseWriter中,然后返回

什么是Handler。它是一个接口,定义在net/http/server.go中:

type Handler interface 
	ServeHTTP(ResponseWriter, *Request)

也就是说,实现了ServerHTTP方法的都是Handler。注意ServerHTTP方法的参数:http.ResponesWriter接口和Request指针。

在Handler的注释中,给出了几点主要的说明:

  • Handler用于响应一个HTTP request
  • 接口方法ServerHTTP应该用来将response header和需要响应的数据写入到ResponseWriter中,然后返回。

2.1.1 ResponseWriter接口

ResponseWriter接口的作用是用于构造HTTP response,并将响应header和响应数据通过网络链接发送给客户端

// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
//
// A ResponseWriter may not be used after the Handler.ServeHTTP method
// has returned.
type ResponseWriter interface 
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)

这个接口有3个方法:

  • Header()方法,用来构造响应Header,它返回一个Header对象,这个Header对象稍后将被WriterHeader()响应出去。Header类型是一个map类型的结构,字段名为key、字段值为value:

    type Header map[string][]string
    
  • Write()方法用于向网络连接中写响应数据。

  • WriteHeader()方法将给定的响应状态码和响应Header一起发送出去。

Go有一个函数HandleFunc(),它表示使用第二个参数的函数作为handler,处理匹配到的url路径请求。HandleFunc 的第一个参数指的是请求路径,第二个参数是一个函数类型,表示这个请求需要处理的事情。没有处理复杂的逻辑,而是直接给DefaultServeMux处理,如源码:

参考:https://blog.51cto.com/u_8184160/2052853

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) 
	DefaultServeMux.HandleFunc(pattern, handler)

golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.requesthttp.ResponseWriter 两个对象交互。

源码,相当于一个适配器

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) 
    f(w, r)

2.2 简单实现

package main

import (
   "io"
   "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) 
   io.WriteString(w, "hello world!\\n")


func main() 
    //  回调函数
   http.HandleFunc("/", helloHandler)
   http.ListenAndServe(":8181", nil)

2.2.1 net/http 提供的handler

大部分的服务器逻辑都需要使用者编写对应的 Handler,不过有些 Handler 使用频繁,因此 net/http 提供了它们的实现。

  • 比如负责文件 hosting 的 FileServer
  • 负责 414 的NotFoundHandler
  • 负责重定向的RedirectHandler

2.2.2 ListenAndServer()

在启动go http自带的web服务时,调用了函数ListenAndServe()。这个函数的定义如下:

func ListenAndServe(addr string, handler Handler) error

该函数有两个参数,第一个参数是自带的web监听地址和端口,第二个参数是Handler,用来处理每个接进来的http request,但一般第二个参数设置为nil,表示调用默认的Multiplexer:DefaultServeMux。这个默认的ServeMux唯一的作用,是将请求根据URL路由给对应的handler进行处理。

var DefaultServeMux = &defaultServeMux
// addr:监听的地址
// handler:回调函数
func ListenAndServe(addr string, handler Handler) error 
    server := &ServerAddr: addr, Handler: handler
    return server.ListenAndServe()


    http.ListenAndServe("127.1.1.1:8111", nil)

2.2.3 Request

Request 就是封装好的客户端请求,包括 URL,method,header 等等所有信息,以及一些方便使用的方法:

Handler 需要知道关于请求的任何信息,都要从这个对象中获取,一般不会直接修改这个对象

type Request struct 
    // Method specifies the HTTP method (GET, POST, PUT, etc.).For client requests an empty string means GET.
    Method string

    // URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
    URL *url.URL

    // The protocol version for incoming requests.
    // Client requests always use HTTP/1.1.
    Proto      string // "HTTP/1.1"
    ProtoMajor int    // 1
    ProtoMinor int    // 1

    // A header maps request lines to their values.
    // If the header says
    //
    //    accept-encoding: gzip, deflate
    //    Accept-Language: en-us
    //    Connection: keep-alive
    //
    // then
    //
    //    Header = map[string][]string
    //        "Accept-Encoding": "gzip, deflate",
    //        "Accept-Language": "en-us",
    //        "Connection": "keep-alive",
    //    
    Header Header

    // Body is the request's body.
    Body io.ReadCloser
    ContentLength int64
    TransferEncoding []string
    Close bool
    Host string
    Form url.Values
    PostForm url.Values
    MultipartForm *multipart.Form
    ...
    RemoteAddr string
    ...

2.2.4 ResponseWriter

ResponseWriter 是一个接口,定义了三个方法:

  • Header():返回一个 Header 对象,可以通过它的 Set() 方法设置头部,注意最终返回的头部信息可能和你写进去的不完全相同,因为后续处理还可能修改头部的值(比如设置 Content-LengthContent-type 等操作)
  • Write(): 写 response 的主体部分,比如 html 或者 json 的内容就是放到这里的
  • WriteHeader():设置 status code,如果没有调用这个函数,默认设置为 http.StatusOK, 就是 211 状态码
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface 
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)

2.3 test

2.3.1 HTTP服务端

package main

import (
    "fmt"
    "net/http"
)

func main() 
    http.HandleFunc("/go", myHandler)
    http.ListenAndServe("127.1.1.1:8111", nil)


func myHandler(w http.ResponseWriter, r *http.Request) 
    fmt.Println(r.RemoteAddr, "连接成功")
    // 请求方式:GET POST DELETE PUT UPDATE
    fmt.Println("method:", r.Method)
    fmt.Println("url:", r.URL.Path)
    fmt.Println("header:", r.Header)
    fmt.Println("body:", r.Body)
    // 回复
    w.Write([]byte("test成功"))

2.3.2 HTTP客户端

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() 
    //resp, _ := http.Get("http://www.baidu.com")
    //fmt.Println(resp)
    resp, _ := http.Get("http://127.1.1.1:8111/go")
    defer resp.Body.Close()
    // 211 OK
    fmt.Println(resp.Status)
    fmt.Println(resp.Header)

    buf := make([]byte, 1124)
    for 
        // 接收服务端信息
        n, err := resp.Body.Read(buf)
        if err != nil && err != io.EOF 
            fmt.Println(err)
            return
         else 
            fmt.Println("读取完毕")
            res := string(buf[:n])
            fmt.Println(res)
            break
        
    


3 webSocket

3.1 是什么

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议,长连接,双向传输
  • 需要安装第三方包:go get -u -v github.com/gorilla/websocket
  • WebSocket 协议实现起来相对简单。 HTTP 协议初始握手建立连接,WebSocket 实质上使用原始 TCP 读取 / 写入数据
  • http有良好的兼容性,ws和http的默认端口都是81,wss和https的默认端口都是443

3.2 webSocket握手协议

3.2.1 客户端请求 Request Header

GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket   	// 指明使用WebSocket协议
    Connection: Upgrade		// 指明使用WebSocket协议
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==   // Bse64 encode的值,是浏览器随机生成的
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13  //指定Websocket协议版本
    Origin: http://example.com

服务端收到Sec-WebSocket-Key后拼接上一个固定的GUID,进行一次SHA-1摘要,再转成Base64编码,得到Sec-WebSocket-Accept返回给客户端。客户端对本地的Sec-WebSocket-Key执行同样的操作跟服务端返回的结果进行对比,如果不一致会返回错误关闭连接。如此操作是为了把websocket header 跟http header区分开

3.2.2 服务器响应 Response Header

HTTP/1.1 111 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc1sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

3.2.3 websocket发送的消息类型

5种TextMessag、BinaryMessage、CloseMessag、PingMessage、PongMessage

  • TextMessagBinaryMessage分别表示发送文本消息和二进制消息

  • CloseMessage关闭帧,接收方收到这个消息就关闭连接

  • PingMessagePongMessage是保持心跳的帧,服务器发ping给浏览器,浏览器返回pong消息

3.2.4 控制类消息

Websocket协议定义了三种控制消息:Close、Ping和Pong。通过调用ConnWriteControlWriteMessageNextWriter方法向对端发送控制消息。

Conn收到了Close消息之后,调用由SetCloseHandler方法设置的handler函数,然后从NextReaderReadMessage或消息的Read 方法返回一个*CloseError。缺省的close handler会发送一个Close消息到对端。

Conn收到了Ping消息之后,调用由SetPingHandler 方法设置的handler函数。缺省的ping handler会发送一个Pong消息到对象。

Conn收到了Pong消息之后,调用由SetPongHandler 设置的handler函数。缺省的pong handler什么也不做。

控制消息的handler函数是从NextReaderReadMessage和消息的Read方法中调用的。缺省的close handler和ping handler向对端写数据时可能会短暂阻塞这些方法。

应用程序必须读取Conn,使得对端发送的close、ping、和pong消息能够得到处理。即使应用程序不关心对端发送的消息,也应该启动一个goroutine来读取对端的消息并丢弃。例如:

4 gorilla/websocket

websocket由http升级而来,首先发送附带Upgrade请求头的Http请求,所以我们需要在处理Http请求时拦截请求并判断其是否为websocket升级请求,如果是则调用gorilla/websocket库相应函数处理升级请求

4.1 Upgrader

Upgrader发送附带Upgrade请求头的Http请求,把 http 请求升级为长连接的 WebSocket,结构如下:

type Upgrader struct 
    // 升级 websocket 握手完成的超时时间
    HandshakeTimeout time.Duration

    // io 操作的缓存大小,如果不指定就会自动分配。
    ReadBufferSize, WriteBufferSize int

    // 写数据操作的缓存池,如果没有设置值,write buffers 将会分配到链接生命周期里。
    WriteBufferPool BufferPool

    //按顺序指定服务支持的协议,如值存在,则服务会从第一个开始匹配客户端的协议。
    Subprotocols []string

    // http 的错误响应函数,如果没有设置 Error 则,会生成 http.Error 的错误响应。
    Error func(w http.ResponseWriter, r *http.Request, status int, reason error)

    // 如果请求Origin标头可以接受,CheckOrigin将返回true。 如果CheckOrigin为nil,则使用安全默认值:如果Origin请求头存在且原始主机不等于请求主机头,则返回false。
    // 请求检查函数,用于统一的链接检查,以防止跨站点请求伪造。如果不检查,就设置一个返回值为true的函数
    CheckOrigin func(r *http.Request) bool

    // EnableCompression 指定服务器是否应尝试协商每个邮件压缩(RFC 7692)。 将此值设置为true并不能保证将支持压缩。 目前仅支持“无上下文接管”模式
    EnableCompression bool

4.1.1 创建Upgrader实例

该实例用于升级请求

var upgrader = websocket.Upgrader
    ReadBufferSize:  1124, //指定读缓存大小
    WriteBufferSize: 1124, //指定写缓存大小
    CheckOrigin:     checkOrigin,

// 检测请求来源
func checkOrigin(r *http.Request) bool 
    if r.Method != "GET" 
        fmt.Println("method is not GET")
        return false
    
    if r.URL.Path != "/ws" 
        fmt.Println("path error")
        return false
    
    return true

其中CheckOringin是一个函数,该函数用于拦截或放行跨域请求。函数返回值为bool类型,即true放行,false拦截。如果请求不是跨域请求可以不赋值

4.1.2 升级协议

func (*Upgrader) Upgrade 函数将 http 升级到 WebSocket 协议。

// responseHeader包含在对客户端升级请求的响应中。 
// 使用responseHeader指定cookie(Set-Cookie)和应用程序协商的子协议(Sec-WebSocket-Protocol)。
// 如果升级失败,则升级将使用HTTP错误响应回复客户端
// 返回一个 Conn 指针,使用 Conn 读写数据与客户端通信。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)

升级为websocket连接并获得一个conn实例,之后的发送接收操作皆有conn,其类型为websocket.Conn。

//Http入口
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) 
    //判断请求是否为websocket升级请求。
    if websocket.IsWebSocketUpgrade(r) 
        // 收到 http 请求后升级协议
        conn, err := upgrader.Upgrade(w, r, w.Header())
        // 向客户端发送消息使用 WriteMessage(messageType int, data []byte),参数1为消息类型,参数2消息内容
        conn.WriteMessage(websocket.TextMessage, []byte("升级成功"))
        // 接受客户端消息使用 ReadMessage(),该操作阻塞线程所以建议运行在其他协程上。
        //返回值(接收消息类型、接收消息内容、发生的错误)当然正常执行时错误为 nil。一旦连接关闭返回值类型为-1可用来终止读操作。
        go func() 
            for 
                t, c, _ := conn.ReadMessage()
                fmt.Println(t, string(c))
                if t == -1 
                    return
                
            
        ()
     else 
        //处理普通请求
        c := newContext(w, r)
        e.router.handle(c)
    

4.1.3 设置关闭连接监听

函数为SetCloseHandler(h func(code int, text string) error)函数接收一个函数为参数,参数为nil时有一个默认实现,其源码为:

func (c *Conn) SetCloseHandler(h func(code int, text string) error) 
    if h == nil 
        h = func(code int, text string) error 
            message := FormatCloseMessage(code, "")
            c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
            return nil
        
    
    c.handleClose = h

可以看到作为参数的函数的参数为int和string类型正好和前端的close(long string)对应即前端调用close(long string)关闭连接后两个参数会被发送给后端并最终被func(code int, text string) error所使用。

// 设置关闭连接监听
conn.SetCloseHandler(func(code int, text string) error 
    fmt.Println(code, text) // 断开连接时将打印code和text
    return nil
)

4.1.4 总览

type WsServer struct 
    ......
    // 定义一个 upgrade 类型用于升级 http 为 websocket
    upgrade  *websocket.Upgrader


func NewWsServer() *WsServer 
    ws.upgrade = &websocket.Upgrader
        ReadBufferSize:  4196,//指定读缓存区大小
        WriteBufferSize: 1124,// 指定写缓存区大小
        // 检测请求来源
        CheckOrigin: func(r *http.Request) bool 
            if r.Method != "GET" 
                fmt.Println("method is not GET")
                return false
            
            if r.URL.Path != "/ws" 
                fmt.Println("path error")
                return false
            
            return true
        ,upgrade
    
    return ws


func (self *WsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) 
    ......
    // 收到 http 请求后 升级 协议
    conn, err := self.upgrade.Upgrade(w, r, nil)
    if err != nil 
        fmt.Println("websocket error:", err)
        return
    
    fmt.Println("client connect :", conn.RemoteAddr())
    go self.connHandle(conn)



5 Demo

5.1

5.1.1 服务端

Go实现WebSocket案例

go-webSocket——gorilla

使用 Go 语言创建 WebSocket 服务

[Go WebSocket] 单房间的聊天室

golang gorilla websocket例子

Go语言实现建立websocket连接并定时发送心跳