go websocket 问题(Hijacker)

在写websocket包的时候发现一个比较有趣问题!go 使用 TLS验证的时候发现 websocket 使用不了。深入了解发现其中奥秘:go 在执行 TLS 验证时候默认是使用 http2 协议进行的!但是 websocket 是无法支持 http2 协议(暂时),导致这个问题所在的原因!

解决方法:

  1. 不开启 http/2 协议
  2. 使用 Hijacker
第一种:不开始 http/2 协议

使用空 map 来使用 http1.x协议

server := http.Server {
Addr : ":8080",
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), // 这
}
log.Fatal(server.ListenAndServeTLS("./coms.crt", "./coms.key"))

因为默认支持h2,所有我们把降到http1.x。

第二种:使用 Hijack
// w 是 http.ResponseWriter
hj,ok := w.(http.Hijacker)
  if !ok {
    return
}
conn , _ , err := hj.Hijack()
if err != nil {
    return
}

使用 http.Hijacker 对其进行劫持 net.Conn , 让程序员自己控制使用!其实这个时候已经脱离 http 协议规范!

//hijacker请求
http.HandleFunc("/hijacker", func(w http.ResponseWriter, r *http.Request) {
      hj, _ := w.(http.Hijacker)
      conn, buf, err := hj.Hijack()
      if err != nil {
          return
       }
       defer conn.Close()
       buf.WriteString("coms is good boyrn")
       buf.Flush()
})

//正常的http请求
http.HandleFunc("/http", func(writer http.ResponseWriter, request *http.Request) {
    io.WriteString(w,"coms is good boy")
})
$ curl "http://127.0.0.1:9001/hijacker" -i
coms is good boy

$ curl "http://127.0.0.1:9001/http" -i
HTTP/1.1 200 OK
Date: Mon, 30 September 2020 01:11:52 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

coms is good boy

发现使用 Hijacker 会脱离 http 协议范畴,可以解决h2与websocket的相关问题!

使用Hijacker实现 websocket 实现连接测试

协议: 状态码:101 , Upgrade , Connection, Sec-WebSocket-Accept

package main

import (
    "crypto/sha1"
    "encoding/base64"
    "fmt"
    "net/http"
)

var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")

func computeAcceptKey(challengeKey string) string {
    h := sha1.New()
    h.Write([]byte(challengeKey))
    h.Write(keyGUID)
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func echo(w http.ResponseWriter,r *http.Request) {
    hj,ok := w.(http.Hijacker)
    if !ok {
        return
    }

    conn , _ , err := hj.Hijack()
    if err != nil {
        return
    }


    challengeKey := r.Header.Get("Sec-Websocket-Key")
    var p []byte
    p = append(p, "HTTP/1.1 101 Switching ProtocolsrnUpgrade: websocketrnConnection: UpgradernSec-WebSocket-Accept: "...)
    p = append(p, computeAcceptKey(challengeKey)...)
    p = append(p, "rn"...)
    p = append(p, "rn"...)
    if _, err = conn.Write(p); err != nil {
        fmt.Println(err)
        conn.Close()
    }
}

func main() {
    http.HandleFunc("/",echo)
    http.ListenAndServe(":8082",nil)
}

实现最简单 websocket 连接!

QQ截图20201001220741.png

websocket连接已经建立,有些 sec 是默认添加上去!

对 Hijack 源码分析
  1. 如何脱离协议范畴
  2. 如何劫持连接

根据 http.Listen 进行向下追踪
在 func (srv *Server) Serve(l net.Listener) error 发现原由

func (srv *Server) Serve(l net.Listener) error {
  ...
  c.setState(c.rwc, StateNew) 
  go c.serve(connCtx)
}

对 c.setState 进入分析

func (c *conn) setState(nc net.Conn, state ConnState) {
    srv := c.server
    switch state {
    case StateNew:
        srv.trackConn(c, true)
    case StateHijacked, StateClosed:
        srv.trackConn(c, false)
    }
    if state > 0xff || state 

发现当 case 是 Hijack【StateHijacked, StateClosed】状态时候执行 trackConn

func (s *Server) trackConn(c *conn, add bool) {
    s.mu.Lock()
    defer s.mu.Unlock()
    if s.activeConn == nil {
        s.activeConn = make(map[*conn]struct{})
    }
    if add {
        s.activeConn[c] = struct{}{}
    } else {
        delete(s.activeConn, c)
    }
}

add 为 false ,对其 delete(s.activeConn,c) 。Hijacker是满足相关条件
所以在 go c.serve(connCtx) 里面不在给 Hijacker 进行操作!导致 http header 无法设置!最后交给程序员自己操作!

使用场景

RPC使用

go中自带的rpc可以直接复用http server处理请求的那一套流程去创建连接,连接创建完毕后再使用Hijack方法拿到连接。

func (server *server) serveset(w http.responsewriter, req *http.request) {
    if req.method != "connect" {
        w.header().set("content-type", "text/plain; charset=utf-8")
        w.writeheader(http.statusmethodnotallowed)
        io.writestring(w, "405 must connectn")
        return
    }
    conn, _, err := w.(http.hijacker).hijack()
    if err != nil {
        log.print("rpc hijacking ", req.remoteaddr, ": ", err.error())
        return
    }
    io.writestring(conn, "http/1.0 "+connected+"rn")
    server.serveconn(conn)
}
还有在上述的 websocket 实用

注: github.com/gorilla/websocket
这包就接入 HIjacker ,直接使用现成包就香~~~~~

文章来源于互联网:go websocket 问题(Hijacker)

发表评论