glider/proxy/ws/client.go

133 lines
3.0 KiB
Go
Raw Normal View History

2018-07-21 22:56:37 +08:00
package ws
import (
"bufio"
2018-07-24 00:45:41 +08:00
"crypto/rand"
"crypto/sha1"
"encoding/base64"
2018-07-21 22:56:37 +08:00
"errors"
"io"
"net"
"net/textproto"
"strings"
2020-04-19 23:20:15 +08:00
"github.com/nadoo/glider/common/pool"
2018-07-21 22:56:37 +08:00
)
2018-07-24 00:45:41 +08:00
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
2019-09-07 17:17:38 +08:00
// Client is ws client struct.
2018-07-21 22:56:37 +08:00
type Client struct {
host string
2018-07-21 22:56:37 +08:00
path string
}
2019-09-07 17:17:38 +08:00
// Conn is a connection to ws server.
2018-07-21 22:56:37 +08:00
type Conn struct {
net.Conn
reader io.Reader
writer io.Writer
}
2019-09-07 17:17:38 +08:00
// NewClient creates a new ws client.
func NewClient(host, path string) (*Client, error) {
if path == "" {
path = "/"
}
c := &Client{host: host, path: path}
2018-07-21 22:56:37 +08:00
return c, nil
}
2019-09-07 17:17:38 +08:00
// NewConn creates a new ws client connection.
2018-07-21 22:56:37 +08:00
func (c *Client) NewConn(rc net.Conn, target string) (*Conn, error) {
conn := &Conn{Conn: rc}
return conn, conn.Handshake(c.host, c.path)
2018-07-21 22:56:37 +08:00
}
2019-09-07 17:17:38 +08:00
// Handshake handshakes with the server using HTTP to request a protocol upgrade.
func (c *Conn) Handshake(host, path string) error {
2018-07-24 00:45:41 +08:00
clientKey := generateClientKey()
2020-04-19 23:20:15 +08:00
buf := pool.GetWriteBuffer()
defer pool.PutWriteBuffer(buf)
2019-09-07 17:17:38 +08:00
buf.WriteString("GET " + path + " HTTP/1.1\r\n")
buf.WriteString("Host: " + host + "\r\n")
buf.WriteString("Upgrade: websocket\r\n")
buf.WriteString("Connection: Upgrade\r\n")
buf.WriteString("Origin: http://" + host + "\r\n")
buf.WriteString("Sec-WebSocket-Key: " + clientKey + "\r\n")
buf.WriteString("Sec-WebSocket-Protocol: binary\r\n")
buf.WriteString("Sec-WebSocket-Version: 13\r\n")
buf.WriteString(("\r\n"))
2018-07-24 00:45:41 +08:00
if _, err := c.Conn.Write(buf.Bytes()); err != nil {
return err
}
2018-07-21 22:56:37 +08:00
tpr := textproto.NewReader(bufio.NewReader(c.Conn))
2019-09-07 17:17:38 +08:00
line, err := tpr.ReadLine()
if err != nil {
return err
}
_, code, _, ok := parseFirstLine(line)
2018-07-21 22:56:37 +08:00
if !ok || code != "101" {
2018-07-24 00:45:41 +08:00
return errors.New("[ws] error in ws handshake parseFirstLine")
2018-07-21 22:56:37 +08:00
}
2018-07-24 00:45:41 +08:00
respHeader, err := tpr.ReadMIMEHeader()
if err != nil {
return err
}
2018-07-21 22:56:37 +08:00
2018-07-24 00:45:41 +08:00
serverKey := respHeader.Get("Sec-WebSocket-Accept")
if serverKey != computeServerKey(clientKey) {
return errors.New("[ws] error in ws handshake, got wrong Sec-Websocket-Key")
}
2018-07-21 22:56:37 +08:00
return nil
}
2018-07-21 22:56:37 +08:00
func (c *Conn) Write(b []byte) (n int, err error) {
if c.writer == nil {
c.writer = FrameWriter(c.Conn)
}
return c.writer.Write(b)
}
2018-07-21 22:56:37 +08:00
func (c *Conn) Read(b []byte) (n int, err error) {
if c.reader == nil {
c.reader = FrameReader(c.Conn)
}
return c.reader.Read(b)
}
// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts.
2018-07-25 08:23:58 +08:00
// TODO: move to separate http lib package for reuse(also for http proxy module)
2019-09-07 17:17:38 +08:00
func parseFirstLine(line string) (r1, r2, r3 string, ok bool) {
2018-07-21 22:56:37 +08:00
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
2018-07-24 00:45:41 +08:00
func generateClientKey() string {
p := pool.GetBuffer(16)
defer pool.PutBuffer(p)
2018-07-24 00:45:41 +08:00
rand.Read(p)
return base64.StdEncoding.EncodeToString(p)
}
func computeServerKey(clientKey string) string {
h := sha1.New()
h.Write([]byte(clientKey))
h.Write(keyGUID)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}