mirror of
https://github.com/fatedier/frp.git
synced 2025-01-31 10:31:55 +01:00
commit
8690075c0c
3
Makefile
3
Makefile
@ -44,6 +44,9 @@ ci:
|
|||||||
go test -v ./tests/...
|
go test -v ./tests/...
|
||||||
cd ./tests && ./clean_test.sh && cd -
|
cd ./tests && ./clean_test.sh && cd -
|
||||||
|
|
||||||
|
ciclean:
|
||||||
|
cd ./tests && ./clean_test.sh && cd -
|
||||||
|
|
||||||
alltest: gotest ci
|
alltest: gotest ci
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -412,10 +412,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
defer remote.Close()
|
|
||||||
if baseInfo.UseEncryption {
|
if baseInfo.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, encKey)
|
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
workConn.Close()
|
||||||
workConn.Error("create encryption stream error: %v", err)
|
workConn.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -433,6 +434,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
} else {
|
} else {
|
||||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
workConn.Close()
|
||||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
6
glide.lock
generated
6
glide.lock
generated
@ -1,5 +1,5 @@
|
|||||||
hash: 188e1149e415ff9cefab8db2cded030efae57558a0b9551795c5c7d0b0572a7b
|
hash: 4095d78a15bf0e7ffdd63331ce75d7199d663cc8710dcd08b9dcd09ba3183eac
|
||||||
updated: 2018-01-17T01:14:34.435613+08:00
|
updated: 2018-01-23T14:48:38.764359+08:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/armon/go-socks5
|
- name: github.com/armon/go-socks5
|
||||||
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||||
@ -17,6 +17,8 @@ imports:
|
|||||||
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||||
- name: github.com/golang/snappy
|
- name: github.com/golang/snappy
|
||||||
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||||
|
- name: github.com/gorilla/websocket
|
||||||
|
version: 292fd08b2560ad524ee37396253d71570339a821
|
||||||
- name: github.com/julienschmidt/httprouter
|
- name: github.com/julienschmidt/httprouter
|
||||||
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||||
- name: github.com/klauspost/cpuid
|
- name: github.com/klauspost/cpuid
|
||||||
|
@ -73,3 +73,4 @@ import:
|
|||||||
- ipv4
|
- ipv4
|
||||||
- package: github.com/rodaine/table
|
- package: github.com/rodaine/table
|
||||||
version: v1.0.0
|
version: v1.0.0
|
||||||
|
- package: github.com/gorilla/websocket
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MinPort = 1025
|
MinPort = 1
|
||||||
MaxPort = 65535
|
MaxPort = 65535
|
||||||
MaxPortReservedDuration = time.Duration(24) * time.Hour
|
MaxPortReservedDuration = time.Duration(24) * time.Hour
|
||||||
CleanReservedPortsInterval = time.Hour
|
CleanReservedPortsInterval = time.Hour
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[common]
|
[common]
|
||||||
server_addr = 0.0.0.0
|
server_addr = 127.0.0.1
|
||||||
server_port = 10700
|
server_port = 10700
|
||||||
log_file = ./frpc.log
|
log_file = ./frpc.log
|
||||||
# debug, info, warn, error
|
# debug, info, warn, error
|
||||||
@ -103,6 +103,18 @@ use_compression = true
|
|||||||
http_user = test
|
http_user = test
|
||||||
http_user = test
|
http_user = test
|
||||||
|
|
||||||
|
[subhost01]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10704
|
||||||
|
subdomain = test01
|
||||||
|
|
||||||
|
[subhost02]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10704
|
||||||
|
subdomain = test02
|
||||||
|
|
||||||
[tcp_port_not_allowed]
|
[tcp_port_not_allowed]
|
||||||
type = tcp
|
type = tcp
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
@ -144,3 +156,8 @@ type = udp
|
|||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 10702
|
local_port = 10702
|
||||||
remote_port = 0
|
remote_port = 0
|
||||||
|
|
||||||
|
[http_proxy]
|
||||||
|
type = tcp
|
||||||
|
plugin = http_proxy
|
||||||
|
remote_port = 0
|
||||||
|
@ -6,3 +6,4 @@ log_file = ./frps.log
|
|||||||
log_level = debug
|
log_level = debug
|
||||||
privilege_token = 123456
|
privilege_token = 123456
|
||||||
privilege_allow_ports = 10000-20000,20002,30000-40000
|
privilege_allow_ports = 10000-20000,20002,30000-40000
|
||||||
|
subdomain_host = sub.com
|
||||||
|
@ -2,14 +2,17 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/server"
|
"github.com/fatedier/frp/server"
|
||||||
|
"github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -50,6 +53,8 @@ var (
|
|||||||
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
|
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
|
||||||
ProxyUdpPortNormal string = "udp_port_normal"
|
ProxyUdpPortNormal string = "udp_port_normal"
|
||||||
ProxyUdpRandomPort string = "udp_random_port"
|
ProxyUdpRandomPort string = "udp_random_port"
|
||||||
|
|
||||||
|
ProxyHttpProxy string = "http_proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -120,57 +125,87 @@ func TestStcp(t *testing.T) {
|
|||||||
func TestHttp(t *testing.T) {
|
func TestHttp(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
// web01
|
// web01
|
||||||
code, body, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "", nil)
|
code, body, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// web02
|
// web02
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test2.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test2.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// error host header
|
// error host header
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(404, code)
|
assert.Equal(404, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// web03
|
// web03
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal(TEST_HTTP_FOO_STR, body)
|
assert.Equal(TEST_HTTP_FOO_STR, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// web04
|
// web04
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal(TEST_HTTP_BAR_STR, body)
|
assert.Equal(TEST_HTTP_BAR_STR, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// web05
|
// web05
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", nil)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(401, code)
|
assert.Equal(401, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
header := make(map[string]string)
|
header := make(map[string]string)
|
||||||
header["Authorization"] = basicAuth("test", "test")
|
header["Authorization"] = basicAuth("test", "test")
|
||||||
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", header)
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", header, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
assert.Equal(401, code)
|
assert.Equal(401, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subhost01
|
||||||
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal("test01.sub.com", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subhost02
|
||||||
|
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test02.sub.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal("test02.sub.com", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
u := url.URL{Scheme: "ws", Host: fmt.Sprintf("%s:%d", "127.0.0.1", TEST_HTTP_FRP_PORT), Path: "/ws"}
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
assert.NoError(err)
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
err = c.WriteMessage(websocket.TextMessage, []byte(TEST_HTTP_NORMAL_STR))
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, msg, err := c.ReadMessage()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(TEST_HTTP_NORMAL_STR, string(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivilegeAllowPorts(t *testing.T) {
|
func TestPrivilegeAllowPorts(t *testing.T) {
|
||||||
@ -226,3 +261,28 @@ func TestRandomPort(t *testing.T) {
|
|||||||
assert.Equal(TEST_UDP_ECHO_STR, res)
|
assert.Equal(TEST_UDP_ECHO_STR, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPluginHttpProxy(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
status, err := getProxyStatus(ProxyHttpProxy)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(client.ProxyStatusRunning, status.Status)
|
||||||
|
|
||||||
|
// http proxy
|
||||||
|
addr := status.RemoteAddr
|
||||||
|
code, body, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT),
|
||||||
|
"", nil, "http://"+addr)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect method
|
||||||
|
conn, err := net.ConnectTcpServerByHttpProxy("http://"+addr, fmt.Sprintf("127.0.0.1:%d", TEST_TCP_FRP_PORT))
|
||||||
|
if assert.NoError(err) {
|
||||||
|
res, err := sendTcpMsgByConn(conn, TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,16 +2,55 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
func StartHttpServer() {
|
func StartHttpServer() {
|
||||||
http.HandleFunc("/", request)
|
http.HandleFunc("/", handleHttp)
|
||||||
|
http.HandleFunc("/ws", handleWebSocket)
|
||||||
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", TEST_HTTP_PORT), nil)
|
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", TEST_HTTP_PORT), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func request(w http.ResponseWriter, r *http.Request) {
|
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
for {
|
||||||
|
mt, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = c.WriteMessage(mt, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHttp(w http.ResponseWriter, r *http.Request) {
|
||||||
|
match, err := regexp.Match(`.*\.sub\.com`, []byte(r.Host))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(r.Host))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
||||||
strings.Contains(r.Host, "test5.frp.com") {
|
strings.Contains(r.Host, "test5.frp.com") {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -81,7 +82,10 @@ func sendTcpMsg(addr string, msg string) (res string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
return sendTcpMsgByConn(c, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTcpMsgByConn(c net.Conn, msg string) (res string, err error) {
|
||||||
timer := time.Now().Add(5 * time.Second)
|
timer := time.Now().Add(5 * time.Second)
|
||||||
c.SetDeadline(timer)
|
c.SetDeadline(timer)
|
||||||
c.Write([]byte(msg))
|
c.Write([]byte(msg))
|
||||||
@ -122,8 +126,8 @@ func sendUdpMsg(addr string, msg string) (res string, err error) {
|
|||||||
return string(buf[:n]), nil
|
return string(buf[:n]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendHttpMsg(method, url string, host string, header map[string]string) (code int, body string, err error) {
|
func sendHttpMsg(method, urlStr string, host string, header map[string]string, proxy string) (code int, body string, err error) {
|
||||||
req, errRet := http.NewRequest(method, url, nil)
|
req, errRet := http.NewRequest(method, urlStr, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@ -135,7 +139,29 @@ func sendHttpMsg(method, url string, host string, header map[string]string) (cod
|
|||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
resp, errRet := http.DefaultClient.Do(req)
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(proxy) != 0 {
|
||||||
|
tr.Proxy = func(req *http.Request) (*url.URL, error) {
|
||||||
|
return url.Parse(proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errRet := client.Do(req)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.15.0"
|
var version string = "0.15.1"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -79,6 +79,11 @@ func NewHttpReverseProxy() *HttpReverseProxy {
|
|||||||
return rp.CreateConnection(host, url)
|
return rp.CreateConnection(host, url)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
url := ctx.Value("url").(string)
|
||||||
|
host := getHostFromAddr(ctx.Value("host").(string))
|
||||||
|
return rp.CreateConnection(host, url)
|
||||||
|
},
|
||||||
BufferPool: newWrapPool(),
|
BufferPool: newWrapPool(),
|
||||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// onExitFlushLoop is a callback set by tests to detect the state of the
|
// onExitFlushLoop is a callback set by tests to detect the state of the
|
||||||
@ -59,6 +61,8 @@ type ReverseProxy struct {
|
|||||||
// modifies the Response from the backend.
|
// modifies the Response from the backend.
|
||||||
// If it returns an error, the proxy returns a StatusBadGateway error.
|
// If it returns an error, the proxy returns a StatusBadGateway error.
|
||||||
ModifyResponse func(*http.Response) error
|
ModifyResponse func(*http.Response) error
|
||||||
|
|
||||||
|
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A BufferPool is an interface for getting and returning temporary
|
// A BufferPool is an interface for getting and returning temporary
|
||||||
@ -139,6 +143,48 @@ var hopHeaders = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if IsWebsocketRequest(req) {
|
||||||
|
p.serveWebSocket(rw, req)
|
||||||
|
} else {
|
||||||
|
p.serveHTTP(rw, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if p.WebSocketDialContext == nil {
|
||||||
|
rw.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
||||||
|
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
||||||
|
|
||||||
|
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(501)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer targetConn.Close()
|
||||||
|
|
||||||
|
p.Director(req)
|
||||||
|
|
||||||
|
hijacker, ok := rw.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
rw.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, _, errHijack := hijacker.Hijack()
|
||||||
|
if errHijack != nil {
|
||||||
|
rw.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
req.Write(targetConn)
|
||||||
|
frpIo.Join(conn, targetConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
transport := p.Transport
|
transport := p.Transport
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
@ -368,3 +414,16 @@ func (m *maxLatencyWriter) flushLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) stop() { m.done <- true }
|
func (m *maxLatencyWriter) stop() { m.done <- true }
|
||||||
|
|
||||||
|
func IsWebsocketRequest(req *http.Request) bool {
|
||||||
|
containsHeader := func(name, value string) bool {
|
||||||
|
items := strings.Split(req.Header.Get(name), ",")
|
||||||
|
for _, item := range items {
|
||||||
|
if value == strings.ToLower(strings.TrimSpace(item)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket")
|
||||||
|
}
|
||||||
|
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.iml
|
20
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
20
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.4
|
||||||
|
- go: 1.5
|
||||||
|
- go: 1.6
|
||||||
|
- go: 1.7
|
||||||
|
- go: 1.8
|
||||||
|
- go: 1.9
|
||||||
|
- go: tip
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go vet $(go list ./... | grep -v /vendor/)
|
||||||
|
- go test -v -race ./...
|
8
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# This is the official list of Gorilla WebSocket authors for copyright
|
||||||
|
# purposes.
|
||||||
|
#
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Gary Burd <gary@beagledreams.com>
|
||||||
|
Joachim Bauch <mail@joachim-bauch.de>
|
||||||
|
|
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
64
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
64
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Gorilla WebSocket
|
||||||
|
|
||||||
|
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||||
|
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||||
|
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||||
|
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||||
|
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||||
|
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
The Gorilla WebSocket package provides a complete and tested implementation of
|
||||||
|
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
|
||||||
|
package API is stable.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
go get github.com/gorilla/websocket
|
||||||
|
|
||||||
|
### Protocol Compliance
|
||||||
|
|
||||||
|
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||||
|
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
|
||||||
|
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||||
|
|
||||||
|
### Gorilla WebSocket compared with other packages
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
|
||||||
|
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||||
|
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||||
|
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||||
|
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||||
|
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||||
|
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
|
||||||
|
<tr><td colspan="3">Other Features</tr></td>
|
||||||
|
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
|
||||||
|
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
|
||||||
|
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
||||||
|
2. The application can get the type of a received data message by implementing
|
||||||
|
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
|
||||||
|
function.
|
||||||
|
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
|
||||||
|
Read returns when the input buffer is full or a frame boundary is
|
||||||
|
encountered. Each call to Write sends a single frame message. The Gorilla
|
||||||
|
io.Reader and io.WriteCloser operate on a single WebSocket message.
|
||||||
|
|
326
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
326
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||||
|
// invalid.
|
||||||
|
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||||
|
|
||||||
|
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||||
|
|
||||||
|
// NewClient creates a new client connection using the given net connection.
|
||||||
|
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||||
|
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||||
|
// (Cookie). Use the response.Header to get the selected subprotocol
|
||||||
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
|
//
|
||||||
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
|
// etc.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Dialer instead.
|
||||||
|
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||||
|
d := Dialer{
|
||||||
|
ReadBufferSize: readBufSize,
|
||||||
|
WriteBufferSize: writeBufSize,
|
||||||
|
NetDial: func(net, addr string) (net.Conn, error) {
|
||||||
|
return netConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return d.Dial(u.String(), requestHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer contains options for connecting to WebSocket server.
|
||||||
|
type Dialer struct {
|
||||||
|
// NetDial specifies the dial function for creating TCP connections. If
|
||||||
|
// NetDial is nil, net.Dial is used.
|
||||||
|
NetDial func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Proxy specifies a function to return a proxy for a given
|
||||||
|
// Request. If the function returns a non-nil error, the
|
||||||
|
// request is aborted with the provided error.
|
||||||
|
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||||
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
|
|
||||||
|
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||||
|
// If nil, the default configuration is used.
|
||||||
|
TLSClientConfig *tls.Config
|
||||||
|
|
||||||
|
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||||
|
HandshakeTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||||
|
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||||
|
// do not limit the size of the messages that can be sent or received.
|
||||||
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// Subprotocols specifies the client's requested subprotocols.
|
||||||
|
Subprotocols []string
|
||||||
|
|
||||||
|
// EnableCompression specifies if the client should attempt to negotiate
|
||||||
|
// per message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
|
|
||||||
|
// Jar specifies the cookie jar.
|
||||||
|
// If Jar is nil, cookies are not sent in requests and ignored
|
||||||
|
// in responses.
|
||||||
|
Jar http.CookieJar
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
|
|
||||||
|
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||||
|
hostPort = u.Host
|
||||||
|
hostNoPort = u.Host
|
||||||
|
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||||
|
hostNoPort = hostNoPort[:i]
|
||||||
|
} else {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "wss":
|
||||||
|
hostPort += ":443"
|
||||||
|
case "https":
|
||||||
|
hostPort += ":443"
|
||||||
|
default:
|
||||||
|
hostPort += ":80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostPort, hostNoPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDialer is a dialer with all fields set to the default values.
|
||||||
|
var DefaultDialer = &Dialer{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial creates a new client connection. Use requestHeader to specify the
|
||||||
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||||
|
// Use the response.Header to get the selected subprotocol
|
||||||
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
|
//
|
||||||
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
|
// etcetera. The response body may not contain the entire response and does not
|
||||||
|
// need to be closed by the application.
|
||||||
|
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
|
||||||
|
if d == nil {
|
||||||
|
d = &Dialer{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
challengeKey, err := generateChallengeKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "ws":
|
||||||
|
u.Scheme = "http"
|
||||||
|
case "wss":
|
||||||
|
u.Scheme = "https"
|
||||||
|
default:
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
// User name and password are not allowed in websocket URIs.
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Method: "GET",
|
||||||
|
URL: u,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Host: u.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cookies present in the cookie jar of the dialer
|
||||||
|
if d.Jar != nil {
|
||||||
|
for _, cookie := range d.Jar.Cookies(u) {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the request headers using the capitalization for names and values in
|
||||||
|
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||||
|
// servers that depend on it. The Header.Set method is not used because the
|
||||||
|
// method canonicalizes the header names.
|
||||||
|
req.Header["Upgrade"] = []string{"websocket"}
|
||||||
|
req.Header["Connection"] = []string{"Upgrade"}
|
||||||
|
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||||
|
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||||
|
if len(d.Subprotocols) > 0 {
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||||
|
}
|
||||||
|
for k, vs := range requestHeader {
|
||||||
|
switch {
|
||||||
|
case k == "Host":
|
||||||
|
if len(vs) > 0 {
|
||||||
|
req.Host = vs[0]
|
||||||
|
}
|
||||||
|
case k == "Upgrade" ||
|
||||||
|
k == "Connection" ||
|
||||||
|
k == "Sec-Websocket-Key" ||
|
||||||
|
k == "Sec-Websocket-Version" ||
|
||||||
|
k == "Sec-Websocket-Extensions" ||
|
||||||
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
|
default:
|
||||||
|
req.Header[k] = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.EnableCompression {
|
||||||
|
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if d.HandshakeTimeout != 0 {
|
||||||
|
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get network dial function.
|
||||||
|
netDial := d.NetDial
|
||||||
|
if netDial == nil {
|
||||||
|
netDialer := &net.Dialer{Deadline: deadline}
|
||||||
|
netDial = netDialer.Dial
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to set the connection deadline.
|
||||||
|
if !deadline.Equal(time.Time{}) {
|
||||||
|
forwardDial := netDial
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := forwardDial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = c.SetDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to connect through a proxy.
|
||||||
|
if d.Proxy != nil {
|
||||||
|
proxyURL, err := d.Proxy(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if proxyURL != nil {
|
||||||
|
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
netDial = dialer.Dial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
netConn, err := netDial("tcp", hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if netConn != nil {
|
||||||
|
netConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if u.Scheme == "https" {
|
||||||
|
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||||
|
if cfg.ServerName == "" {
|
||||||
|
cfg.ServerName = hostNoPort
|
||||||
|
}
|
||||||
|
tlsConn := tls.Client(netConn, cfg)
|
||||||
|
netConn = tlsConn
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !cfg.InsecureSkipVerify {
|
||||||
|
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||||
|
|
||||||
|
if err := req.Write(netConn); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.ReadResponse(conn.br, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Jar != nil {
|
||||||
|
if rc := resp.Cookies(); len(rc) > 0 {
|
||||||
|
d.Jar.SetCookies(u, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 101 ||
|
||||||
|
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||||
|
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||||
|
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||||
|
// Before closing the network connection on return from this
|
||||||
|
// function, slurp up some of the response to aid application
|
||||||
|
// debugging.
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
n, _ := io.ReadFull(resp.Body, buf)
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||||
|
return nil, resp, ErrBadHandshake
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range parseExtensions(resp.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, snct := ext["server_no_context_takeover"]
|
||||||
|
_, cnct := ext["client_no_context_takeover"]
|
||||||
|
if !snct || !cnct {
|
||||||
|
return nil, resp, errInvalidCompression
|
||||||
|
}
|
||||||
|
conn.newCompressionWriter = compressNoContextTakeover
|
||||||
|
conn.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
|
||||||
|
netConn.SetDeadline(time.Time{})
|
||||||
|
netConn = nil // to avoid close in defer.
|
||||||
|
return conn, resp, nil
|
||||||
|
}
|
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
if cfg == nil {
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
return cfg.Clone()
|
||||||
|
}
|
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
// cloneTLSConfig clones all public fields except the fields
|
||||||
|
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
|
||||||
|
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
|
||||||
|
// config in active use.
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
if cfg == nil {
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: cfg.Rand,
|
||||||
|
Time: cfg.Time,
|
||||||
|
Certificates: cfg.Certificates,
|
||||||
|
NameToCertificate: cfg.NameToCertificate,
|
||||||
|
GetCertificate: cfg.GetCertificate,
|
||||||
|
RootCAs: cfg.RootCAs,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
|
ServerName: cfg.ServerName,
|
||||||
|
ClientAuth: cfg.ClientAuth,
|
||||||
|
ClientCAs: cfg.ClientCAs,
|
||||||
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||||
|
CipherSuites: cfg.CipherSuites,
|
||||||
|
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||||
|
ClientSessionCache: cfg.ClientSessionCache,
|
||||||
|
MinVersion: cfg.MinVersion,
|
||||||
|
MaxVersion: cfg.MaxVersion,
|
||||||
|
CurvePreferences: cfg.CurvePreferences,
|
||||||
|
}
|
||||||
|
}
|
602
vendor/github.com/gorilla/websocket/client_server_test.go
generated
vendored
Normal file
602
vendor/github.com/gorilla/websocket/client_server_test.go
generated
vendored
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cstUpgrader = Upgrader{
|
||||||
|
Subprotocols: []string{"p0", "p1"},
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
EnableCompression: true,
|
||||||
|
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||||
|
http.Error(w, reason.Error(), status)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cstDialer = Dialer{
|
||||||
|
Subprotocols: []string{"p1", "p2"},
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
HandshakeTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
type cstHandler struct{ *testing.T }
|
||||||
|
|
||||||
|
type cstServer struct {
|
||||||
|
*httptest.Server
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cstPath = "/a/b"
|
||||||
|
cstRawQuery = "x=y"
|
||||||
|
cstRequestURI = cstPath + "?" + cstRawQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServer(t *testing.T) *cstServer {
|
||||||
|
var s cstServer
|
||||||
|
s.Server = httptest.NewServer(cstHandler{t})
|
||||||
|
s.Server.URL += cstRequestURI
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTLSServer(t *testing.T) *cstServer {
|
||||||
|
var s cstServer
|
||||||
|
s.Server = httptest.NewTLSServer(cstHandler{t})
|
||||||
|
s.Server.URL += cstRequestURI
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != cstPath {
|
||||||
|
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
|
||||||
|
http.Error(w, "bad path", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.RawQuery != cstRawQuery {
|
||||||
|
t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery)
|
||||||
|
http.Error(w, "bad path", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subprotos := Subprotocols(r)
|
||||||
|
if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) {
|
||||||
|
t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols)
|
||||||
|
http.Error(w, "bad protocol", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Upgrade: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
if ws.Subprotocol() != "p1" {
|
||||||
|
t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol())
|
||||||
|
ws.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
op, rd, err := ws.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("NextReader: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wr, err := ws.NextWriter(op)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("NextWriter: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(wr, rd); err != nil {
|
||||||
|
t.Logf("NextWriter: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := wr.Close(); err != nil {
|
||||||
|
t.Logf("Close: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeWsProto(s string) string {
|
||||||
|
return "ws" + strings.TrimPrefix(s, "http")
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendRecv(t *testing.T, ws *Conn) {
|
||||||
|
const message = "Hello World!"
|
||||||
|
if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
|
t.Fatalf("SetWriteDeadline: %v", err)
|
||||||
|
}
|
||||||
|
if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil {
|
||||||
|
t.Fatalf("WriteMessage: %v", err)
|
||||||
|
}
|
||||||
|
if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
|
t.Fatalf("SetReadDeadline: %v", err)
|
||||||
|
}
|
||||||
|
_, p, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadMessage: %v", err)
|
||||||
|
}
|
||||||
|
if string(p) != message {
|
||||||
|
t.Fatalf("message=%s, want %s", p, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyDial(t *testing.T) {
|
||||||
|
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
surl, _ := url.Parse(s.Server.URL)
|
||||||
|
|
||||||
|
cstDialer := cstDialer // make local copy for modification on next line.
|
||||||
|
cstDialer.Proxy = http.ProxyURL(surl)
|
||||||
|
|
||||||
|
connect := false
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "CONNECT" {
|
||||||
|
connect = true
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connect {
|
||||||
|
t.Log("connect not received")
|
||||||
|
http.Error(w, "connect not received", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyAuthorizationDial(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
surl, _ := url.Parse(s.Server.URL)
|
||||||
|
surl.User = url.UserPassword("username", "password")
|
||||||
|
|
||||||
|
cstDialer := cstDialer // make local copy for modification on next line.
|
||||||
|
cstDialer.Proxy = http.ProxyURL(surl)
|
||||||
|
|
||||||
|
connect := false
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxyAuth := r.Header.Get("Proxy-Authorization")
|
||||||
|
expectedProxyAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte("username:password"))
|
||||||
|
if r.Method == "CONNECT" && proxyAuth == expectedProxyAuth {
|
||||||
|
connect = true
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connect {
|
||||||
|
t.Log("connect with proxy authorization not received")
|
||||||
|
http.Error(w, "connect with proxy authorization not received", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDial(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialCookieJar(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
d := cstDialer
|
||||||
|
d.Jar = jar
|
||||||
|
|
||||||
|
u, _ := url.Parse(s.URL)
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "ws":
|
||||||
|
u.Scheme = "http"
|
||||||
|
case "wss":
|
||||||
|
u.Scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies := []*http.Cookie{{Name: "gorilla", Value: "ws", Path: "/"}}
|
||||||
|
d.Jar.SetCookies(u, cookies)
|
||||||
|
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
var gorilla string
|
||||||
|
var sessionID string
|
||||||
|
for _, c := range d.Jar.Cookies(u) {
|
||||||
|
if c.Name == "gorilla" {
|
||||||
|
gorilla = c.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Name == "sessionID" {
|
||||||
|
sessionID = c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gorilla != "ws" {
|
||||||
|
t.Error("Cookie not present in jar.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionID != "1234" {
|
||||||
|
t.Error("Set-Cookie not received from the server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTLS(t *testing.T) {
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
certs := x509.NewCertPool()
|
||||||
|
for _, c := range s.TLS.Certificates {
|
||||||
|
roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing server's root cert: %v", err)
|
||||||
|
}
|
||||||
|
for _, root := range roots {
|
||||||
|
certs.AddCert(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d := cstDialer
|
||||||
|
d.TLSClientConfig = &tls.Config{RootCAs: certs}
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func xTestDialTLSBadCert(t *testing.T) {
|
||||||
|
// This test is deactivated because of noisy logging from the net/http package.
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTLSNoVerify(t *testing.T) {
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d := cstDialer
|
||||||
|
d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTimeout(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d := cstDialer
|
||||||
|
d.HandshakeTimeout = -1
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialBadScheme(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.Server.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialBadOrigin(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("resp=nil, err=%v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusForbidden {
|
||||||
|
t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialBadHeader(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
for _, k := range []string{"Upgrade",
|
||||||
|
"Connection",
|
||||||
|
"Sec-Websocket-Key",
|
||||||
|
"Sec-Websocket-Version",
|
||||||
|
"Sec-Websocket-Protocol"} {
|
||||||
|
h := http.Header{}
|
||||||
|
h.Set(k, "bad")
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Errorf("Dial with header %s returned nil", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadMethod(t *testing.T) {
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := cstUpgrader.Upgrade(w, r, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("handshake succeeded, expect fail")
|
||||||
|
ws.Close()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", s.URL, strings.NewReader(""))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewRequest returned error %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Connection", "upgrade")
|
||||||
|
req.Header.Set("Upgrade", "websocket")
|
||||||
|
req.Header.Set("Sec-Websocket-Version", "13")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Do returned error %v", err)
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusMethodNotAllowed {
|
||||||
|
t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandshake(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
var sessionID string
|
||||||
|
for _, c := range resp.Cookies() {
|
||||||
|
if c.Name == "sessionID" {
|
||||||
|
sessionID = c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sessionID != "1234" {
|
||||||
|
t.Error("Set-Cookie not received from the server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws.Subprotocol() != "p1" {
|
||||||
|
t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol())
|
||||||
|
}
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRespOnBadHandshake(t *testing.T) {
|
||||||
|
const expectedStatus = http.StatusGone
|
||||||
|
const expectedBody = "This is the response body."
|
||||||
|
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(expectedStatus)
|
||||||
|
io.WriteString(w, expectedBody)
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("resp=nil, err=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != expectedStatus {
|
||||||
|
t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFull(resp.Body) returned error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(p) != expectedBody {
|
||||||
|
t.Errorf("resp.Body=%s, want %s", p, expectedBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHostHeader confirms that the host header provided in the call to Dial is
|
||||||
|
// sent to the server.
|
||||||
|
func TestHostHeader(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
specifiedHost := make(chan string, 1)
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
specifiedHost <- r.Host
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
if gotHost := <-specifiedHost; gotHost != "testhost" {
|
||||||
|
t.Fatalf("gotHost = %q, want \"testhost\"", gotHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialCompression(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
dialer := cstDialer
|
||||||
|
dialer.EnableCompression = true
|
||||||
|
ws, _, err := dialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocksProxyDial(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
proxyListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen failed: %v", err)
|
||||||
|
}
|
||||||
|
defer proxyListener.Close()
|
||||||
|
go func() {
|
||||||
|
c1, err := proxyListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("proxy accept failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
|
||||||
|
c1.SetDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
|
||||||
|
buf := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(c1, buf[:3]); err != nil {
|
||||||
|
t.Errorf("read failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if want := []byte{5, 1, 0}; !bytes.Equal(want, buf[:len(want)]) {
|
||||||
|
t.Errorf("read %x, want %x", buf[:len(want)], want)
|
||||||
|
}
|
||||||
|
if _, err := c1.Write([]byte{5, 0}); err != nil {
|
||||||
|
t.Errorf("write failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c1, buf[:10]); err != nil {
|
||||||
|
t.Errorf("read failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if want := []byte{5, 1, 0, 1}; !bytes.Equal(want, buf[:len(want)]) {
|
||||||
|
t.Errorf("read %x, want %x", buf[:len(want)], want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf[1] = 0
|
||||||
|
if _, err := c1.Write(buf[:10]); err != nil {
|
||||||
|
t.Errorf("write failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.IP(buf[4:8])
|
||||||
|
port := binary.BigEndian.Uint16(buf[8:10])
|
||||||
|
|
||||||
|
c2, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: int(port)})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("dial failed; %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c2.Close()
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
io.Copy(c1, c2)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
io.Copy(c2, c1)
|
||||||
|
<-done
|
||||||
|
}()
|
||||||
|
|
||||||
|
purl, err := url.Parse("socks5://" + proxyListener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cstDialer := cstDialer // make local copy for modification on next line.
|
||||||
|
cstDialer.Proxy = http.ProxyURL(purl)
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
32
vendor/github.com/gorilla/websocket/client_test.go
generated
vendored
Normal file
32
vendor/github.com/gorilla/websocket/client_test.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hostPortNoPortTests = []struct {
|
||||||
|
u *url.URL
|
||||||
|
hostPort, hostNoPort string
|
||||||
|
}{
|
||||||
|
{&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"},
|
||||||
|
{&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"},
|
||||||
|
{&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"},
|
||||||
|
{&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostPortNoPort(t *testing.T) {
|
||||||
|
for _, tt := range hostPortNoPortTests {
|
||||||
|
hostPort, hostNoPort := hostPortNoPort(tt.u)
|
||||||
|
if hostPort != tt.hostPort {
|
||||||
|
t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort)
|
||||||
|
}
|
||||||
|
if hostNoPort != tt.hostNoPort {
|
||||||
|
t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
|
||||||
|
maxCompressionLevel = flate.BestCompression
|
||||||
|
defaultCompressionLevel = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
|
||||||
|
flateReaderPool = sync.Pool{New: func() interface{} {
|
||||||
|
return flate.NewReader(nil)
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||||
|
const tail =
|
||||||
|
// Add four bytes as specified in RFC
|
||||||
|
"\x00\x00\xff\xff" +
|
||||||
|
// Add final block to squelch unexpected EOF error from flate reader.
|
||||||
|
"\x01\x00\x00\xff\xff"
|
||||||
|
|
||||||
|
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||||
|
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||||
|
return &flateReadWrapper{fr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidCompressionLevel(level int) bool {
|
||||||
|
return minCompressionLevel <= level && level <= maxCompressionLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
|
||||||
|
p := &flateWriterPools[level-minCompressionLevel]
|
||||||
|
tw := &truncWriter{w: w}
|
||||||
|
fw, _ := p.Get().(*flate.Writer)
|
||||||
|
if fw == nil {
|
||||||
|
fw, _ = flate.NewWriter(tw, level)
|
||||||
|
} else {
|
||||||
|
fw.Reset(tw)
|
||||||
|
}
|
||||||
|
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||||
|
// stream to another io.Writer.
|
||||||
|
type truncWriter struct {
|
||||||
|
w io.WriteCloser
|
||||||
|
n int
|
||||||
|
p [4]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||||
|
n := 0
|
||||||
|
|
||||||
|
// fill buffer first for simplicity.
|
||||||
|
if w.n < len(w.p) {
|
||||||
|
n = copy(w.p[w.n:], p)
|
||||||
|
p = p[n:]
|
||||||
|
w.n += n
|
||||||
|
if len(p) == 0 {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := len(p)
|
||||||
|
if m > len(w.p) {
|
||||||
|
m = len(w.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(w.p[:], w.p[m:])
|
||||||
|
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||||
|
nn, err := w.w.Write(p[:len(p)-m])
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type flateWriteWrapper struct {
|
||||||
|
fw *flate.Writer
|
||||||
|
tw *truncWriter
|
||||||
|
p *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
|
||||||
|
if w.fw == nil {
|
||||||
|
return 0, errWriteClosed
|
||||||
|
}
|
||||||
|
return w.fw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWriteWrapper) Close() error {
|
||||||
|
if w.fw == nil {
|
||||||
|
return errWriteClosed
|
||||||
|
}
|
||||||
|
err1 := w.fw.Flush()
|
||||||
|
w.p.Put(w.fw)
|
||||||
|
w.fw = nil
|
||||||
|
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
|
||||||
|
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
|
||||||
|
}
|
||||||
|
err2 := w.tw.w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
type flateReadWrapper struct {
|
||||||
|
fr io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||||
|
if r.fr == nil {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
n, err := r.fr.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
// Preemptively place the reader back in the pool. This helps with
|
||||||
|
// scenarios where the application does not call NextReader() soon after
|
||||||
|
// this final read.
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *flateReadWrapper) Close() error {
|
||||||
|
if r.fr == nil {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
err := r.fr.Close()
|
||||||
|
flateReaderPool.Put(r.fr)
|
||||||
|
r.fr = nil
|
||||||
|
return err
|
||||||
|
}
|
80
vendor/github.com/gorilla/websocket/compression_test.go
generated
vendored
Normal file
80
vendor/github.com/gorilla/websocket/compression_test.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nopCloser struct{ io.Writer }
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
func TestTruncWriter(t *testing.T) {
|
||||||
|
const data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz987654321"
|
||||||
|
for n := 1; n <= 10; n++ {
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := &truncWriter{w: nopCloser{&b}}
|
||||||
|
p := []byte(data)
|
||||||
|
for len(p) > 0 {
|
||||||
|
m := len(p)
|
||||||
|
if m > n {
|
||||||
|
m = n
|
||||||
|
}
|
||||||
|
w.Write(p[:m])
|
||||||
|
p = p[m:]
|
||||||
|
}
|
||||||
|
if b.String() != data[:len(data)-len(w.p)] {
|
||||||
|
t.Errorf("%d: %q", n, b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func textMessages(num int) [][]byte {
|
||||||
|
messages := make([][]byte, num)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i)
|
||||||
|
messages[i] = []byte(msg)
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWriteNoCompression(b *testing.B) {
|
||||||
|
w := ioutil.Discard
|
||||||
|
c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024)
|
||||||
|
messages := textMessages(100)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.WriteMessage(TextMessage, messages[i%len(messages)])
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWriteWithCompression(b *testing.B) {
|
||||||
|
w := ioutil.Discard
|
||||||
|
c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024)
|
||||||
|
messages := textMessages(100)
|
||||||
|
c.enableWriteCompression = true
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.WriteMessage(TextMessage, messages[i%len(messages)])
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidCompressionLevel(t *testing.T) {
|
||||||
|
c := newConn(fakeNetConn{}, false, 1024, 1024)
|
||||||
|
for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} {
|
||||||
|
if err := c.SetCompressionLevel(level); err == nil {
|
||||||
|
t.Errorf("no error for level %d", level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, level := range []int{minCompressionLevel, maxCompressionLevel} {
|
||||||
|
if err := c.SetCompressionLevel(level); err != nil {
|
||||||
|
t.Errorf("error for level %d", level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1155
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
1155
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
134
vendor/github.com/gorilla/websocket/conn_broadcast_test.go
generated
vendored
Normal file
134
vendor/github.com/gorilla/websocket/conn_broadcast_test.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// broadcastBench allows to run broadcast benchmarks.
|
||||||
|
// In every broadcast benchmark we create many connections, then send the same
|
||||||
|
// message into every connection and wait for all writes complete. This emulates
|
||||||
|
// an application where many connections listen to the same data - i.e. PUB/SUB
|
||||||
|
// scenarios with many subscribers in one channel.
|
||||||
|
type broadcastBench struct {
|
||||||
|
w io.Writer
|
||||||
|
message *broadcastMessage
|
||||||
|
closeCh chan struct{}
|
||||||
|
doneCh chan struct{}
|
||||||
|
count int32
|
||||||
|
conns []*broadcastConn
|
||||||
|
compression bool
|
||||||
|
usePrepared bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type broadcastMessage struct {
|
||||||
|
payload []byte
|
||||||
|
prepared *PreparedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type broadcastConn struct {
|
||||||
|
conn *Conn
|
||||||
|
msgCh chan *broadcastMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBroadcastConn(c *Conn) *broadcastConn {
|
||||||
|
return &broadcastConn{
|
||||||
|
conn: c,
|
||||||
|
msgCh: make(chan *broadcastMessage, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBroadcastBench(usePrepared, compression bool) *broadcastBench {
|
||||||
|
bench := &broadcastBench{
|
||||||
|
w: ioutil.Discard,
|
||||||
|
doneCh: make(chan struct{}),
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
usePrepared: usePrepared,
|
||||||
|
compression: compression,
|
||||||
|
}
|
||||||
|
msg := &broadcastMessage{
|
||||||
|
payload: textMessages(1)[0],
|
||||||
|
}
|
||||||
|
if usePrepared {
|
||||||
|
pm, _ := NewPreparedMessage(TextMessage, msg.payload)
|
||||||
|
msg.prepared = pm
|
||||||
|
}
|
||||||
|
bench.message = msg
|
||||||
|
bench.makeConns(10000)
|
||||||
|
return bench
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *broadcastBench) makeConns(numConns int) {
|
||||||
|
conns := make([]*broadcastConn, numConns)
|
||||||
|
|
||||||
|
for i := 0; i < numConns; i++ {
|
||||||
|
c := newConn(fakeNetConn{Reader: nil, Writer: b.w}, true, 1024, 1024)
|
||||||
|
if b.compression {
|
||||||
|
c.enableWriteCompression = true
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
}
|
||||||
|
conns[i] = newBroadcastConn(c)
|
||||||
|
go func(c *broadcastConn) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-c.msgCh:
|
||||||
|
if b.usePrepared {
|
||||||
|
c.conn.WritePreparedMessage(msg.prepared)
|
||||||
|
} else {
|
||||||
|
c.conn.WriteMessage(TextMessage, msg.payload)
|
||||||
|
}
|
||||||
|
val := atomic.AddInt32(&b.count, 1)
|
||||||
|
if val%int32(numConns) == 0 {
|
||||||
|
b.doneCh <- struct{}{}
|
||||||
|
}
|
||||||
|
case <-b.closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(conns[i])
|
||||||
|
}
|
||||||
|
b.conns = conns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *broadcastBench) close() {
|
||||||
|
close(b.closeCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *broadcastBench) runOnce() {
|
||||||
|
for _, c := range b.conns {
|
||||||
|
c.msgCh <- b.message
|
||||||
|
}
|
||||||
|
<-b.doneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBroadcast(b *testing.B) {
|
||||||
|
benchmarks := []struct {
|
||||||
|
name string
|
||||||
|
usePrepared bool
|
||||||
|
compression bool
|
||||||
|
}{
|
||||||
|
{"NoCompression", false, false},
|
||||||
|
{"WithCompression", false, true},
|
||||||
|
{"NoCompressionPrepared", true, false},
|
||||||
|
{"WithCompressionPrepared", true, true},
|
||||||
|
}
|
||||||
|
for _, bm := range benchmarks {
|
||||||
|
b.Run(bm.name, func(b *testing.B) {
|
||||||
|
bench := newBroadcastBench(bm.usePrepared, bm.compression)
|
||||||
|
defer bench.close()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bench.runOnce()
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.5
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
c.br.Discard(len(p))
|
||||||
|
return p, err
|
||||||
|
}
|
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.5
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
if len(p) > 0 {
|
||||||
|
// advance over the bytes just read
|
||||||
|
io.ReadFull(c.br, p)
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
496
vendor/github.com/gorilla/websocket/conn_test.go
generated
vendored
Normal file
496
vendor/github.com/gorilla/websocket/conn_test.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ net.Error = errWriteTimeout
|
||||||
|
|
||||||
|
type fakeNetConn struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c fakeNetConn) Close() error { return nil }
|
||||||
|
func (c fakeNetConn) LocalAddr() net.Addr { return localAddr }
|
||||||
|
func (c fakeNetConn) RemoteAddr() net.Addr { return remoteAddr }
|
||||||
|
func (c fakeNetConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
type fakeAddr int
|
||||||
|
|
||||||
|
var (
|
||||||
|
localAddr = fakeAddr(1)
|
||||||
|
remoteAddr = fakeAddr(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a fakeAddr) Network() string {
|
||||||
|
return "net"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a fakeAddr) String() string {
|
||||||
|
return "str"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFraming(t *testing.T) {
|
||||||
|
frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537}
|
||||||
|
var readChunkers = []struct {
|
||||||
|
name string
|
||||||
|
f func(io.Reader) io.Reader
|
||||||
|
}{
|
||||||
|
{"half", iotest.HalfReader},
|
||||||
|
{"one", iotest.OneByteReader},
|
||||||
|
{"asis", func(r io.Reader) io.Reader { return r }},
|
||||||
|
}
|
||||||
|
writeBuf := make([]byte, 65537)
|
||||||
|
for i := range writeBuf {
|
||||||
|
writeBuf[i] = byte(i)
|
||||||
|
}
|
||||||
|
var writers = []struct {
|
||||||
|
name string
|
||||||
|
f func(w io.Writer, n int) (int, error)
|
||||||
|
}{
|
||||||
|
{"iocopy", func(w io.Writer, n int) (int, error) {
|
||||||
|
nn, err := io.Copy(w, bytes.NewReader(writeBuf[:n]))
|
||||||
|
return int(nn), err
|
||||||
|
}},
|
||||||
|
{"write", func(w io.Writer, n int) (int, error) {
|
||||||
|
return w.Write(writeBuf[:n])
|
||||||
|
}},
|
||||||
|
{"string", func(w io.Writer, n int) (int, error) {
|
||||||
|
return io.WriteString(w, string(writeBuf[:n]))
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, compress := range []bool{false, true} {
|
||||||
|
for _, isServer := range []bool{true, false} {
|
||||||
|
for _, chunker := range readChunkers {
|
||||||
|
|
||||||
|
var connBuf bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
|
||||||
|
rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024)
|
||||||
|
if compress {
|
||||||
|
wc.newCompressionWriter = compressNoContextTakeover
|
||||||
|
rc.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
}
|
||||||
|
for _, n := range frameSizes {
|
||||||
|
for _, writer := range writers {
|
||||||
|
name := fmt.Sprintf("z:%v, s:%v, r:%s, n:%d w:%s", compress, isServer, chunker.name, n, writer.name)
|
||||||
|
|
||||||
|
w, err := wc.NextWriter(TextMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: wc.NextWriter() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nn, err := writer.f(w, n)
|
||||||
|
if err != nil || nn != n {
|
||||||
|
t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: w.Close() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opCode, r, err := rc.NextReader()
|
||||||
|
if err != nil || opCode != TextMessage {
|
||||||
|
t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rbuf, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: ReadFull() returned rbuf, %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rbuf) != n {
|
||||||
|
t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range rbuf {
|
||||||
|
if byte(i) != b {
|
||||||
|
t.Errorf("%s: bad byte at offset %d", name, i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControl(t *testing.T) {
|
||||||
|
const message = "this is a ping/pong messsage"
|
||||||
|
for _, isServer := range []bool{true, false} {
|
||||||
|
for _, isWriteControl := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl)
|
||||||
|
var connBuf bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024)
|
||||||
|
if isWriteControl {
|
||||||
|
wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second))
|
||||||
|
} else {
|
||||||
|
w, err := wc.NextWriter(PongMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: wc.NextWriter() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(message)); err != nil {
|
||||||
|
t.Errorf("%s: w.Write() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Errorf("%s: w.Close() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var actualMessage string
|
||||||
|
rc.SetPongHandler(func(s string) error { actualMessage = s; return nil })
|
||||||
|
rc.NextReader()
|
||||||
|
if actualMessage != message {
|
||||||
|
t.Errorf("%s: pong=%q, want %q", name, actualMessage, message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) {
|
||||||
|
const bufSize = 512
|
||||||
|
|
||||||
|
expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(make([]byte, bufSize+bufSize/2))
|
||||||
|
wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second))
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
|
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
|
||||||
|
}
|
||||||
|
_, _, err = rc.NextReader()
|
||||||
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
|
t.Fatalf("NextReader() returned %v, want %v", err, expectedErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFWithinFrame(t *testing.T) {
|
||||||
|
const bufSize = 64
|
||||||
|
|
||||||
|
for n := 0; ; n++ {
|
||||||
|
var b bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b}, false, 1024, 1024)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b, Writer: nil}, true, 1024, 1024)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(make([]byte, bufSize))
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
if n >= b.Len() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Truncate(n)
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if err == errUnexpectedEOF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("%d: NextReader() returned %d, %v", n, op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
_, _, err = rc.NextReader()
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("%d: NextReader() returned %v, want %v", n, err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFBeforeFinalFrame(t *testing.T) {
|
||||||
|
const bufSize = 512
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(make([]byte, bufSize+bufSize/2))
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
_, _, err = rc.NextReader()
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteAfterMessageWriterClose(t *testing.T) {
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &bytes.Buffer{}}, false, 1024, 1024)
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
io.WriteString(w, "hello")
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Fatalf("unxpected error closing message writer, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(w, "world"); err == nil {
|
||||||
|
t.Fatalf("no error writing after close")
|
||||||
|
}
|
||||||
|
|
||||||
|
w, _ = wc.NextWriter(BinaryMessage)
|
||||||
|
io.WriteString(w, "hello")
|
||||||
|
|
||||||
|
// close w by getting next writer
|
||||||
|
_, err := wc.NextWriter(BinaryMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting next writer, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(w, "world"); err == nil {
|
||||||
|
t.Fatalf("no error writing after close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadLimit(t *testing.T) {
|
||||||
|
|
||||||
|
const readLimit = 512
|
||||||
|
message := make([]byte, readLimit+1)
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
rc.SetReadLimit(readLimit)
|
||||||
|
|
||||||
|
// Send message at the limit with interleaved pong.
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(message[:readLimit-1])
|
||||||
|
wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second))
|
||||||
|
w.Write(message[:1])
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Send message larger than the limit.
|
||||||
|
wc.WriteMessage(BinaryMessage, message[:readLimit+1])
|
||||||
|
|
||||||
|
op, _, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("1: NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("2: NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != ErrReadLimit {
|
||||||
|
t.Fatalf("io.Copy() returned %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddrs(t *testing.T) {
|
||||||
|
c := newConn(&fakeNetConn{}, true, 1024, 1024)
|
||||||
|
if c.LocalAddr() != localAddr {
|
||||||
|
t.Errorf("LocalAddr = %v, want %v", c.LocalAddr(), localAddr)
|
||||||
|
}
|
||||||
|
if c.RemoteAddr() != remoteAddr {
|
||||||
|
t.Errorf("RemoteAddr = %v, want %v", c.RemoteAddr(), remoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnderlyingConn(t *testing.T) {
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
fc := fakeNetConn{Reader: &b1, Writer: &b2}
|
||||||
|
c := newConn(fc, true, 1024, 1024)
|
||||||
|
ul := c.UnderlyingConn()
|
||||||
|
if ul != fc {
|
||||||
|
t.Fatalf("Underlying conn is not what it should be.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufioReadBytes(t *testing.T) {
|
||||||
|
// Test calling bufio.ReadBytes for value longer than read buffer size.
|
||||||
|
|
||||||
|
m := make([]byte, 512)
|
||||||
|
m[len(m)-1] = '\n'
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, len(m)+64, len(m)+64)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(m)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bufio.NewReader(r)
|
||||||
|
p, err := br.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadBytes() returned %v", err)
|
||||||
|
}
|
||||||
|
if len(p) != len(m) {
|
||||||
|
t.Fatalf("read returned %d bytes, want %d bytes", len(p), len(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeErrorTests = []struct {
|
||||||
|
err error
|
||||||
|
codes []int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, true},
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, false},
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, true},
|
||||||
|
{errors.New("hello"), []int{CloseNormalClosure}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseError(t *testing.T) {
|
||||||
|
for _, tt := range closeErrorTests {
|
||||||
|
ok := IsCloseError(tt.err, tt.codes...)
|
||||||
|
if ok != tt.ok {
|
||||||
|
t.Errorf("IsCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unexpectedCloseErrorTests = []struct {
|
||||||
|
err error
|
||||||
|
codes []int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, false},
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, true},
|
||||||
|
{&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, false},
|
||||||
|
{errors.New("hello"), []int{CloseNormalClosure}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnexpectedCloseErrors(t *testing.T) {
|
||||||
|
for _, tt := range unexpectedCloseErrorTests {
|
||||||
|
ok := IsUnexpectedCloseError(tt.err, tt.codes...)
|
||||||
|
if ok != tt.ok {
|
||||||
|
t.Errorf("IsUnexpectedCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockingWriter struct {
|
||||||
|
c1, c2 chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w blockingWriter) Write(p []byte) (int, error) {
|
||||||
|
// Allow main to continue
|
||||||
|
close(w.c1)
|
||||||
|
// Wait for panic in main
|
||||||
|
<-w.c2
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentWritePanic(t *testing.T) {
|
||||||
|
w := blockingWriter{make(chan struct{}), make(chan struct{})}
|
||||||
|
c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024)
|
||||||
|
go func() {
|
||||||
|
c.WriteMessage(TextMessage, []byte{})
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait for goroutine to block in write.
|
||||||
|
<-w.c1
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(w.c2)
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.WriteMessage(TextMessage, []byte{})
|
||||||
|
t.Fatal("should not get here")
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingReader struct{}
|
||||||
|
|
||||||
|
func (r failingReader) Read(p []byte) (int, error) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailedConnectionReadPanic(t *testing.T) {
|
||||||
|
c := newConn(fakeNetConn{Reader: failingReader{}, Writer: nil}, false, 1024, 1024)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 20000; i++ {
|
||||||
|
c.ReadMessage()
|
||||||
|
}
|
||||||
|
t.Fatal("should not get here")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufioReuse(t *testing.T) {
|
||||||
|
brw := bufio.NewReadWriter(bufio.NewReader(nil), bufio.NewWriter(nil))
|
||||||
|
c := newConnBRW(nil, false, 0, 0, brw)
|
||||||
|
|
||||||
|
if c.br != brw.Reader {
|
||||||
|
t.Error("connection did not reuse bufio.Reader")
|
||||||
|
}
|
||||||
|
|
||||||
|
var wh writeHook
|
||||||
|
brw.Writer.Reset(&wh)
|
||||||
|
brw.WriteByte(0)
|
||||||
|
brw.Flush()
|
||||||
|
if &c.writeBuf[0] != &wh.p[0] {
|
||||||
|
t.Error("connection did not reuse bufio.Writer")
|
||||||
|
}
|
||||||
|
|
||||||
|
brw = bufio.NewReadWriter(bufio.NewReaderSize(nil, 0), bufio.NewWriterSize(nil, 0))
|
||||||
|
c = newConnBRW(nil, false, 0, 0, brw)
|
||||||
|
|
||||||
|
if c.br == brw.Reader {
|
||||||
|
t.Error("connection used bufio.Reader with small size")
|
||||||
|
}
|
||||||
|
|
||||||
|
brw.Writer.Reset(&wh)
|
||||||
|
brw.WriteByte(0)
|
||||||
|
brw.Flush()
|
||||||
|
if &c.writeBuf[0] != &wh.p[0] {
|
||||||
|
t.Error("connection used bufio.Writer with small size")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
187
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
187
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package websocket implements the WebSocket protocol defined in RFC 6455.
|
||||||
|
//
|
||||||
|
// Overview
|
||||||
|
//
|
||||||
|
// The Conn type represents a WebSocket connection. A server application calls
|
||||||
|
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||||
|
//
|
||||||
|
// var upgrader = websocket.Upgrader{
|
||||||
|
// ReadBufferSize: 1024,
|
||||||
|
// WriteBufferSize: 1024,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// ... Use conn to send and receive messages.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||||
|
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||||
|
// messages using these methods:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// messageType, p, err := conn.ReadMessage()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In above snippet of code, p is a []byte and messageType is an int with value
|
||||||
|
// websocket.BinaryMessage or websocket.TextMessage.
|
||||||
|
//
|
||||||
|
// An application can also send and receive messages using the io.WriteCloser
|
||||||
|
// and io.Reader interfaces. To send a message, call the connection NextWriter
|
||||||
|
// method to get an io.WriteCloser, write the message to the writer and close
|
||||||
|
// the writer when done. To receive a message, call the connection NextReader
|
||||||
|
// method to get an io.Reader and read until io.EOF is returned. This snippet
|
||||||
|
// shows how to echo messages using the NextWriter and NextReader methods:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// messageType, r, err := conn.NextReader()
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// w, err := conn.NextWriter(messageType)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if _, err := io.Copy(w, r); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if err := w.Close(); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Data Messages
|
||||||
|
//
|
||||||
|
// The WebSocket protocol distinguishes between text and binary data messages.
|
||||||
|
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
|
||||||
|
// binary messages is left to the application.
|
||||||
|
//
|
||||||
|
// This package uses the TextMessage and BinaryMessage integer constants to
|
||||||
|
// identify the two data message types. The ReadMessage and NextReader methods
|
||||||
|
// return the type of the received message. The messageType argument to the
|
||||||
|
// WriteMessage and NextWriter methods specifies the type of a sent message.
|
||||||
|
//
|
||||||
|
// It is the application's responsibility to ensure that text messages are
|
||||||
|
// valid UTF-8 encoded text.
|
||||||
|
//
|
||||||
|
// Control Messages
|
||||||
|
//
|
||||||
|
// The WebSocket protocol defines three types of control messages: close, ping
|
||||||
|
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||||
|
// methods to send a control message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received close messages by calling the handler function
|
||||||
|
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||||
|
// NextReader, ReadMessage or the message Read method. The default close
|
||||||
|
// handler sends a close message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received ping messages by calling the handler function
|
||||||
|
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||||
|
// message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received pong messages by calling the handler function
|
||||||
|
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||||
|
// If an application sends ping messages, then the application should set a
|
||||||
|
// pong handler to receive the corresponding pong.
|
||||||
|
//
|
||||||
|
// The control message handler functions are called from the NextReader,
|
||||||
|
// ReadMessage and message reader Read methods. The default close and ping
|
||||||
|
// handlers can block these methods for a short time when the handler writes to
|
||||||
|
// the connection.
|
||||||
|
//
|
||||||
|
// The application must read the connection to process close, ping and pong
|
||||||
|
// messages sent from the peer. If the application is not otherwise interested
|
||||||
|
// in messages from the peer, then the application should start a goroutine to
|
||||||
|
// read and discard messages from the peer. A simple example is:
|
||||||
|
//
|
||||||
|
// func readLoop(c *websocket.Conn) {
|
||||||
|
// for {
|
||||||
|
// if _, _, err := c.NextReader(); err != nil {
|
||||||
|
// c.Close()
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Concurrency
|
||||||
|
//
|
||||||
|
// Connections support one concurrent reader and one concurrent writer.
|
||||||
|
//
|
||||||
|
// Applications are responsible for ensuring that no more than one goroutine
|
||||||
|
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||||
|
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
|
||||||
|
// that no more than one goroutine calls the read methods (NextReader,
|
||||||
|
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
|
||||||
|
// concurrently.
|
||||||
|
//
|
||||||
|
// The Close and WriteControl methods can be called concurrently with all other
|
||||||
|
// methods.
|
||||||
|
//
|
||||||
|
// Origin Considerations
|
||||||
|
//
|
||||||
|
// Web browsers allow Javascript applications to open a WebSocket connection to
|
||||||
|
// any host. It's up to the server to enforce an origin policy using the Origin
|
||||||
|
// request header sent by the browser.
|
||||||
|
//
|
||||||
|
// The Upgrader calls the function specified in the CheckOrigin field to check
|
||||||
|
// the origin. If the CheckOrigin function returns false, then the Upgrade
|
||||||
|
// method fails the WebSocket handshake with HTTP status 403.
|
||||||
|
//
|
||||||
|
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||||
|
// the handshake if the Origin request header is present and not equal to the
|
||||||
|
// Host request header.
|
||||||
|
//
|
||||||
|
// An application can allow connections from any origin by specifying a
|
||||||
|
// function that always returns true:
|
||||||
|
//
|
||||||
|
// var upgrader = websocket.Upgrader{
|
||||||
|
// CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The deprecated package-level Upgrade function does not perform origin
|
||||||
|
// checking. The application is responsible for checking the Origin header
|
||||||
|
// before calling the Upgrade function.
|
||||||
|
//
|
||||||
|
// Compression EXPERIMENTAL
|
||||||
|
//
|
||||||
|
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||||
|
// by this package in a limited capacity. Setting the EnableCompression option
|
||||||
|
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
|
||||||
|
// support.
|
||||||
|
//
|
||||||
|
// var upgrader = websocket.Upgrader{
|
||||||
|
// EnableCompression: true,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If compression was successfully negotiated with the connection's peer, any
|
||||||
|
// message received in compressed form will be automatically decompressed.
|
||||||
|
// All Read methods will return uncompressed bytes.
|
||||||
|
//
|
||||||
|
// Per message compression of messages written to a connection can be enabled
|
||||||
|
// or disabled by calling the corresponding Conn method:
|
||||||
|
//
|
||||||
|
// conn.EnableWriteCompression(false)
|
||||||
|
//
|
||||||
|
// Currently this package does not support compression with "context takeover".
|
||||||
|
// This means that messages must be compressed and decompressed in isolation,
|
||||||
|
// without retaining sliding window or dictionary state across messages. For
|
||||||
|
// more details refer to RFC 7692.
|
||||||
|
//
|
||||||
|
// Use of compression is experimental and may result in decreased performance.
|
||||||
|
package websocket
|
46
vendor/github.com/gorilla/websocket/example_test.go
generated
vendored
Normal file
46
vendor/github.com/gorilla/websocket/example_test.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
c *websocket.Conn
|
||||||
|
req *http.Request
|
||||||
|
)
|
||||||
|
|
||||||
|
// The websocket.IsUnexpectedCloseError function is useful for identifying
|
||||||
|
// application and protocol errors.
|
||||||
|
//
|
||||||
|
// This server application works with a client application running in the
|
||||||
|
// browser. The client application does not explicitly close the websocket. The
|
||||||
|
// only expected close message from the client has the code
|
||||||
|
// websocket.CloseGoingAway. All other other close messages are likely the
|
||||||
|
// result of an application or protocol error and are logged to aid debugging.
|
||||||
|
func ExampleIsUnexpectedCloseError() {
|
||||||
|
|
||||||
|
for {
|
||||||
|
messageType, p, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
|
log.Printf("error: %v, user-agent: %v", err, req.Header.Get("User-Agent"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processMesage(messageType, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMesage(mt int, p []byte) {}
|
||||||
|
|
||||||
|
// TestX prevents godoc from showing this entire file in the example. Remove
|
||||||
|
// this function when a second example is added.
|
||||||
|
func TestX(t *testing.T) {}
|
13
vendor/github.com/gorilla/websocket/examples/autobahn/README.md
generated
vendored
Normal file
13
vendor/github.com/gorilla/websocket/examples/autobahn/README.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Test Server
|
||||||
|
|
||||||
|
This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite).
|
||||||
|
|
||||||
|
To test the server, run
|
||||||
|
|
||||||
|
go run server.go
|
||||||
|
|
||||||
|
and start the client test driver
|
||||||
|
|
||||||
|
wstest -m fuzzingclient -s fuzzingclient.json
|
||||||
|
|
||||||
|
When the client completes, it writes a report to reports/clients/index.html.
|
15
vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"options": {"failByDrop": false},
|
||||||
|
"outdir": "./reports/clients",
|
||||||
|
"servers": [
|
||||||
|
{"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}},
|
||||||
|
{"agent": "ReadAllWritePreparedMessage", "url": "ws://localhost:9000/p", "options": {"version": 18}},
|
||||||
|
{"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}},
|
||||||
|
{"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}},
|
||||||
|
{"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}}
|
||||||
|
],
|
||||||
|
"cases": ["*"],
|
||||||
|
"exclude-cases": [],
|
||||||
|
"exclude-agent-cases": {}
|
||||||
|
}
|
265
vendor/github.com/gorilla/websocket/examples/autobahn/server.go
generated
vendored
Normal file
265
vendor/github.com/gorilla/websocket/examples/autobahn/server.go
generated
vendored
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Command server is a test server for the Autobahn WebSockets Test Suite.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 4096,
|
||||||
|
WriteBufferSize: 4096,
|
||||||
|
EnableCompression: true,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// echoCopy echoes messages from the client using io.Copy.
|
||||||
|
func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
for {
|
||||||
|
mt, r, err := conn.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Println("NextReader:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mt == websocket.TextMessage {
|
||||||
|
r = &validator{r: r}
|
||||||
|
}
|
||||||
|
w, err := conn.NextWriter(mt)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("NextWriter:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mt == websocket.TextMessage {
|
||||||
|
r = &validator{r: r}
|
||||||
|
}
|
||||||
|
if writerOnly {
|
||||||
|
_, err = io.Copy(struct{ io.Writer }{w}, r)
|
||||||
|
} else {
|
||||||
|
_, err = io.Copy(w, r)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == errInvalidUTF8 {
|
||||||
|
conn.WriteControl(websocket.CloseMessage,
|
||||||
|
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
|
||||||
|
time.Time{})
|
||||||
|
}
|
||||||
|
log.Println("Copy:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Close:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) {
|
||||||
|
echoCopy(w, r, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoCopyFull(w http.ResponseWriter, r *http.Request) {
|
||||||
|
echoCopy(w, r, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// echoReadAll echoes messages from the client by reading the entire message
|
||||||
|
// with ioutil.ReadAll.
|
||||||
|
func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
for {
|
||||||
|
mt, b, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Println("NextReader:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mt == websocket.TextMessage {
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
conn.WriteControl(websocket.CloseMessage,
|
||||||
|
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
|
||||||
|
time.Time{})
|
||||||
|
log.Println("ReadAll: invalid utf8")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if writeMessage {
|
||||||
|
if !writePrepared {
|
||||||
|
err = conn.WriteMessage(mt, b)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("WriteMessage:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pm, err := websocket.NewPreparedMessage(mt, b)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("NewPreparedMessage:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WritePreparedMessage(pm)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("WritePreparedMessage:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w, err := conn.NextWriter(mt)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("NextWriter:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := w.Write(b); err != nil {
|
||||||
|
log.Println("Writer:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
log.Println("Close:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoReadAllWriter(w http.ResponseWriter, r *http.Request) {
|
||||||
|
echoReadAll(w, r, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
echoReadAll(w, r, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoReadAllWritePreparedMessage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
echoReadAll(w, r, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found.", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
io.WriteString(w, "<html><body>Echo Server</body></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr = flag.String("addr", ":9000", "http service address")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/c", echoCopyWriterOnly)
|
||||||
|
http.HandleFunc("/f", echoCopyFull)
|
||||||
|
http.HandleFunc("/r", echoReadAllWriter)
|
||||||
|
http.HandleFunc("/m", echoReadAllWriteMessage)
|
||||||
|
http.HandleFunc("/p", echoReadAllWritePreparedMessage)
|
||||||
|
err := http.ListenAndServe(*addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ListenAndServe: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type validator struct {
|
||||||
|
state int
|
||||||
|
x rune
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidUTF8 = errors.New("invalid utf8")
|
||||||
|
|
||||||
|
func (r *validator) Read(p []byte) (int, error) {
|
||||||
|
n, err := r.r.Read(p)
|
||||||
|
state := r.state
|
||||||
|
x := r.x
|
||||||
|
for _, b := range p[:n] {
|
||||||
|
state, x = decode(state, x, b)
|
||||||
|
if state == utf8Reject {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.state = state
|
||||||
|
r.x = x
|
||||||
|
if state == utf8Reject || (err == io.EOF && state != utf8Accept) {
|
||||||
|
return n, errInvalidUTF8
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||||
|
//
|
||||||
|
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
// deal in the Software without restriction, including without limitation the
|
||||||
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
// sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
// IN THE SOFTWARE.
|
||||||
|
var utf8d = [...]byte{
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
|
||||||
|
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
|
||||||
|
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
|
||||||
|
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
|
||||||
|
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||||
|
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||||
|
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||||
|
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
utf8Accept = 0
|
||||||
|
utf8Reject = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func decode(state int, x rune, b byte) (int, rune) {
|
||||||
|
t := utf8d[b]
|
||||||
|
if state != utf8Accept {
|
||||||
|
x = rune(b&0x3f) | (x << 6)
|
||||||
|
} else {
|
||||||
|
x = rune((0xff >> t) & b)
|
||||||
|
}
|
||||||
|
state = int(utf8d[256+state*16+int(t)])
|
||||||
|
return state, x
|
||||||
|
}
|
102
vendor/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
Normal file
102
vendor/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Chat Example
|
||||||
|
|
||||||
|
This application shows how to use the
|
||||||
|
[websocket](https://github.com/gorilla/websocket) package to implement a simple
|
||||||
|
web chat application.
|
||||||
|
|
||||||
|
## Running the example
|
||||||
|
|
||||||
|
The example requires a working Go development environment. The [Getting
|
||||||
|
Started](http://golang.org/doc/install) page describes how to install the
|
||||||
|
development environment.
|
||||||
|
|
||||||
|
Once you have Go up and running, you can download, build and run the example
|
||||||
|
using the following commands.
|
||||||
|
|
||||||
|
$ go get github.com/gorilla/websocket
|
||||||
|
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
|
||||||
|
$ go run *.go
|
||||||
|
|
||||||
|
To use the chat example, open http://localhost:8080/ in your browser.
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
The server application defines two types, `Client` and `Hub`. The server
|
||||||
|
creates an instance of the `Client` type for each websocket connection. A
|
||||||
|
`Client` acts as an intermediary between the websocket connection and a single
|
||||||
|
instance of the `Hub` type. The `Hub` maintains a set of registered clients and
|
||||||
|
broadcasts messages to the clients.
|
||||||
|
|
||||||
|
The application runs one goroutine for the `Hub` and two goroutines for each
|
||||||
|
`Client`. The goroutines communicate with each other using channels. The `Hub`
|
||||||
|
has channels for registering clients, unregistering clients and broadcasting
|
||||||
|
messages. A `Client` has a buffered channel of outbound messages. One of the
|
||||||
|
client's goroutines reads messages from this channel and writes the messages to
|
||||||
|
the websocket. The other client goroutine reads messages from the websocket and
|
||||||
|
sends them to the hub.
|
||||||
|
|
||||||
|
### Hub
|
||||||
|
|
||||||
|
The code for the `Hub` type is in
|
||||||
|
[hub.go](https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go).
|
||||||
|
The application's `main` function starts the hub's `run` method as a goroutine.
|
||||||
|
Clients send requests to the hub using the `register`, `unregister` and
|
||||||
|
`broadcast` channels.
|
||||||
|
|
||||||
|
The hub registers clients by adding the client pointer as a key in the
|
||||||
|
`clients` map. The map value is always true.
|
||||||
|
|
||||||
|
The unregister code is a little more complicated. In addition to deleting the
|
||||||
|
client pointer from the `clients` map, the hub closes the clients's `send`
|
||||||
|
channel to signal the client that no more messages will be sent to the client.
|
||||||
|
|
||||||
|
The hub handles messages by looping over the registered clients and sending the
|
||||||
|
message to the client's `send` channel. If the client's `send` buffer is full,
|
||||||
|
then the hub assumes that the client is dead or stuck. In this case, the hub
|
||||||
|
unregisters the client and closes the websocket.
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/master/examples/chat/client.go).
|
||||||
|
|
||||||
|
The `serveWs` function is registered by the application's `main` function as
|
||||||
|
an HTTP handler. The handler upgrades the HTTP connection to the WebSocket
|
||||||
|
protocol, creates a client, registers the client with the hub and schedules the
|
||||||
|
client to be unregistered using a defer statement.
|
||||||
|
|
||||||
|
Next, the HTTP handler starts the client's `writePump` method as a goroutine.
|
||||||
|
This method transfers messages from the client's send channel to the websocket
|
||||||
|
connection. The writer method exits when the channel is closed by the hub or
|
||||||
|
there's an error writing to the websocket connection.
|
||||||
|
|
||||||
|
Finally, the HTTP handler calls the client's `readPump` method. This method
|
||||||
|
transfers inbound messages from the websocket to the hub.
|
||||||
|
|
||||||
|
WebSocket connections [support one concurrent reader and one concurrent
|
||||||
|
writer](https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency). The
|
||||||
|
application ensures that these concurrency requirements are met by executing
|
||||||
|
all reads from the `readPump` goroutine and all writes from the `writePump`
|
||||||
|
goroutine.
|
||||||
|
|
||||||
|
To improve efficiency under high load, the `writePump` function coalesces
|
||||||
|
pending chat messages in the `send` channel to a single WebSocket message. This
|
||||||
|
reduces the number of system calls and the amount of data sent over the
|
||||||
|
network.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/master/examples/chat/home.html).
|
||||||
|
|
||||||
|
On document load, the script checks for websocket functionality in the browser.
|
||||||
|
If websocket functionality is available, then the script opens a connection to
|
||||||
|
the server and registers a callback to handle messages from the server. The
|
||||||
|
callback appends the message to the chat log using the appendLog function.
|
||||||
|
|
||||||
|
To allow the user to manually scroll through the chat log without interruption
|
||||||
|
from new messages, the `appendLog` function checks the scroll position before
|
||||||
|
adding new content. If the chat log is scrolled to the bottom, then the
|
||||||
|
function scrolls new content into view after adding the content. Otherwise, the
|
||||||
|
scroll position is not changed.
|
||||||
|
|
||||||
|
The form handler writes the user input to the websocket and clears the input
|
||||||
|
field.
|
137
vendor/github.com/gorilla/websocket/examples/chat/client.go
generated
vendored
Normal file
137
vendor/github.com/gorilla/websocket/examples/chat/client.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a message to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the peer.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
|
// Maximum message size allowed from peer.
|
||||||
|
maxMessageSize = 512
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
newline = []byte{'\n'}
|
||||||
|
space = []byte{' '}
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is a middleman between the websocket connection and the hub.
|
||||||
|
type Client struct {
|
||||||
|
hub *Hub
|
||||||
|
|
||||||
|
// The websocket connection.
|
||||||
|
conn *websocket.Conn
|
||||||
|
|
||||||
|
// Buffered channel of outbound messages.
|
||||||
|
send chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPump pumps messages from the websocket connection to the hub.
|
||||||
|
//
|
||||||
|
// The application runs readPump in a per-connection goroutine. The application
|
||||||
|
// ensures that there is at most one reader on a connection by executing all
|
||||||
|
// reads from this goroutine.
|
||||||
|
func (c *Client) readPump() {
|
||||||
|
defer func() {
|
||||||
|
c.hub.unregister <- c
|
||||||
|
c.conn.Close()
|
||||||
|
}()
|
||||||
|
c.conn.SetReadLimit(maxMessageSize)
|
||||||
|
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
for {
|
||||||
|
_, message, err := c.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
log.Printf("error: %v", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
|
||||||
|
c.hub.broadcast <- message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePump pumps messages from the hub to the websocket connection.
|
||||||
|
//
|
||||||
|
// A goroutine running writePump is started for each connection. The
|
||||||
|
// application ensures that there is at most one writer to a connection by
|
||||||
|
// executing all writes from this goroutine.
|
||||||
|
func (c *Client) writePump() {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
c.conn.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case message, ok := <-c.send:
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if !ok {
|
||||||
|
// The hub closed the channel.
|
||||||
|
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := c.conn.NextWriter(websocket.TextMessage)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(message)
|
||||||
|
|
||||||
|
// Add queued chat messages to the current websocket message.
|
||||||
|
n := len(c.send)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
w.Write(newline)
|
||||||
|
w.Write(<-c.send)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveWs handles websocket requests from the peer.
|
||||||
|
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
|
||||||
|
client.hub.register <- client
|
||||||
|
|
||||||
|
// Allow collection of memory referenced by the caller by doing all work in
|
||||||
|
// new goroutines.
|
||||||
|
go client.writePump()
|
||||||
|
go client.readPump()
|
||||||
|
}
|
98
vendor/github.com/gorilla/websocket/examples/chat/home.html
generated
vendored
Normal file
98
vendor/github.com/gorilla/websocket/examples/chat/home.html
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Chat Example</title>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload = function () {
|
||||||
|
var conn;
|
||||||
|
var msg = document.getElementById("msg");
|
||||||
|
var log = document.getElementById("log");
|
||||||
|
|
||||||
|
function appendLog(item) {
|
||||||
|
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
|
||||||
|
log.appendChild(item);
|
||||||
|
if (doScroll) {
|
||||||
|
log.scrollTop = log.scrollHeight - log.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("form").onsubmit = function () {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
conn.send(msg.value);
|
||||||
|
msg.value = "";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window["WebSocket"]) {
|
||||||
|
conn = new WebSocket("ws://" + document.location.host + "/ws");
|
||||||
|
conn.onclose = function (evt) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Connection closed.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
};
|
||||||
|
conn.onmessage = function (evt) {
|
||||||
|
var messages = evt.data.split('\n');
|
||||||
|
for (var i = 0; i < messages.length; i++) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerText = messages[i];
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
bottom: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<form id="form">
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
<input type="text" id="msg" size="64"/>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
53
vendor/github.com/gorilla/websocket/examples/chat/hub.go
generated
vendored
Normal file
53
vendor/github.com/gorilla/websocket/examples/chat/hub.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// hub maintains the set of active clients and broadcasts messages to the
|
||||||
|
// clients.
|
||||||
|
type Hub struct {
|
||||||
|
// Registered clients.
|
||||||
|
clients map[*Client]bool
|
||||||
|
|
||||||
|
// Inbound messages from the clients.
|
||||||
|
broadcast chan []byte
|
||||||
|
|
||||||
|
// Register requests from the clients.
|
||||||
|
register chan *Client
|
||||||
|
|
||||||
|
// Unregister requests from clients.
|
||||||
|
unregister chan *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHub() *Hub {
|
||||||
|
return &Hub{
|
||||||
|
broadcast: make(chan []byte),
|
||||||
|
register: make(chan *Client),
|
||||||
|
unregister: make(chan *Client),
|
||||||
|
clients: make(map[*Client]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hub) run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case client := <-h.register:
|
||||||
|
h.clients[client] = true
|
||||||
|
case client := <-h.unregister:
|
||||||
|
if _, ok := h.clients[client]; ok {
|
||||||
|
delete(h.clients, client)
|
||||||
|
close(client.send)
|
||||||
|
}
|
||||||
|
case message := <-h.broadcast:
|
||||||
|
for client := range h.clients {
|
||||||
|
select {
|
||||||
|
case client.send <- message:
|
||||||
|
default:
|
||||||
|
close(client.send)
|
||||||
|
delete(h.clients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
vendor/github.com/gorilla/websocket/examples/chat/main.go
generated
vendored
Normal file
40
vendor/github.com/gorilla/websocket/examples/chat/main.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", ":8080", "http service address")
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println(r.URL)
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.ServeFile(w, r, "home.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
hub := newHub()
|
||||||
|
go hub.run()
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serveWs(hub, w, r)
|
||||||
|
})
|
||||||
|
err := http.ListenAndServe(*addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ListenAndServe: ", err)
|
||||||
|
}
|
||||||
|
}
|
19
vendor/github.com/gorilla/websocket/examples/command/README.md
generated
vendored
Normal file
19
vendor/github.com/gorilla/websocket/examples/command/README.md
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Command example
|
||||||
|
|
||||||
|
This example connects a websocket connection to stdin and stdout of a command.
|
||||||
|
Received messages are written to stdin followed by a `\n`. Each line read from
|
||||||
|
standard out is sent as a message to the client.
|
||||||
|
|
||||||
|
$ go get github.com/gorilla/websocket
|
||||||
|
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command`
|
||||||
|
$ go run main.go <command and arguments to run>
|
||||||
|
# Open http://localhost:8080/ .
|
||||||
|
|
||||||
|
Try the following commands.
|
||||||
|
|
||||||
|
# Echo sent messages to the output area.
|
||||||
|
$ go run main.go cat
|
||||||
|
|
||||||
|
# Run a shell.Try sending "ls" and "cat main.go".
|
||||||
|
$ go run main.go sh
|
||||||
|
|
102
vendor/github.com/gorilla/websocket/examples/command/home.html
generated
vendored
Normal file
102
vendor/github.com/gorilla/websocket/examples/command/home.html
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Command Example</title>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload = function () {
|
||||||
|
var conn;
|
||||||
|
var msg = document.getElementById("msg");
|
||||||
|
var log = document.getElementById("log");
|
||||||
|
|
||||||
|
function appendLog(item) {
|
||||||
|
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
|
||||||
|
log.appendChild(item);
|
||||||
|
if (doScroll) {
|
||||||
|
log.scrollTop = log.scrollHeight - log.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("form").onsubmit = function () {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
conn.send(msg.value);
|
||||||
|
msg.value = "";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window["WebSocket"]) {
|
||||||
|
conn = new WebSocket("ws://" + document.location.host + "/ws");
|
||||||
|
conn.onclose = function (evt) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Connection closed.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
};
|
||||||
|
conn.onmessage = function (evt) {
|
||||||
|
var messages = evt.data.split('\n');
|
||||||
|
for (var i = 0; i < messages.length; i++) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerText = messages[i];
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
bottom: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<form id="form">
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
<input type="text" id="msg" size="64"/>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
193
vendor/github.com/gorilla/websocket/examples/command/main.go
generated
vendored
Normal file
193
vendor/github.com/gorilla/websocket/examples/command/main.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", "127.0.0.1:8080", "http service address")
|
||||||
|
cmdPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a message to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Maximum message size allowed from peer.
|
||||||
|
maxMessageSize = 8192
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the peer.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
|
// Time to wait before force close on connection.
|
||||||
|
closeGracePeriod = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func pumpStdin(ws *websocket.Conn, w io.Writer) {
|
||||||
|
defer ws.Close()
|
||||||
|
ws.SetReadLimit(maxMessageSize)
|
||||||
|
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
for {
|
||||||
|
_, message, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
message = append(message, '\n')
|
||||||
|
if _, err := w.Write(message); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
|
||||||
|
defer func() {
|
||||||
|
}()
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
for s.Scan() {
|
||||||
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
|
||||||
|
ws.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Err() != nil {
|
||||||
|
log.Println("scan:", s.Err())
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
time.Sleep(closeGracePeriod)
|
||||||
|
ws.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ping(ws *websocket.Conn, done chan struct{}) {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
|
||||||
|
log.Println("ping:", err)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalError(ws *websocket.Conn, msg string, err error) {
|
||||||
|
log.Println(msg, err)
|
||||||
|
ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
outr, outw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "stdout:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer outr.Close()
|
||||||
|
defer outw.Close()
|
||||||
|
|
||||||
|
inr, inw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "stdin:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer inr.Close()
|
||||||
|
defer inw.Close()
|
||||||
|
|
||||||
|
proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
|
||||||
|
Files: []*os.File{inr, outw, outw},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "start:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inr.Close()
|
||||||
|
outw.Close()
|
||||||
|
|
||||||
|
stdoutDone := make(chan struct{})
|
||||||
|
go pumpStdout(ws, outr, stdoutDone)
|
||||||
|
go ping(ws, stdoutDone)
|
||||||
|
|
||||||
|
pumpStdin(ws, inw)
|
||||||
|
|
||||||
|
// Some commands will exit when stdin is closed.
|
||||||
|
inw.Close()
|
||||||
|
|
||||||
|
// Other commands need a bonk on the head.
|
||||||
|
if err := proc.Signal(os.Interrupt); err != nil {
|
||||||
|
log.Println("inter:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stdoutDone:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
// A bigger bonk on the head.
|
||||||
|
if err := proc.Signal(os.Kill); err != nil {
|
||||||
|
log.Println("term:", err)
|
||||||
|
}
|
||||||
|
<-stdoutDone
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := proc.Wait(); err != nil {
|
||||||
|
log.Println("wait:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.ServeFile(w, r, "home.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
log.Fatal("must specify at least one argument")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
cmdPath, err = exec.LookPath(flag.Args()[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/ws", serveWs)
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
|
}
|
17
vendor/github.com/gorilla/websocket/examples/echo/README.md
generated
vendored
Normal file
17
vendor/github.com/gorilla/websocket/examples/echo/README.md
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Client and server example
|
||||||
|
|
||||||
|
This example shows a simple client and server.
|
||||||
|
|
||||||
|
The server echoes messages sent to it. The client sends a message every second
|
||||||
|
and prints all messages received.
|
||||||
|
|
||||||
|
To run the example, start the server:
|
||||||
|
|
||||||
|
$ go run server.go
|
||||||
|
|
||||||
|
Next, start the client:
|
||||||
|
|
||||||
|
$ go run client.go
|
||||||
|
|
||||||
|
The server includes a simple web client. To use the client, open
|
||||||
|
http://127.0.0.1:8080 in the browser and follow the instructions on the page.
|
81
vendor/github.com/gorilla/websocket/examples/echo/client.go
generated
vendored
Normal file
81
vendor/github.com/gorilla/websocket/examples/echo/client.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", "localhost:8080", "http service address")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
interrupt := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(interrupt, os.Interrupt)
|
||||||
|
|
||||||
|
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
|
||||||
|
log.Printf("connecting to %s", u.String())
|
||||||
|
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("dial:", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer c.Close()
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
_, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("recv: %s", message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case t := <-ticker.C:
|
||||||
|
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-interrupt:
|
||||||
|
log.Println("interrupt")
|
||||||
|
// To cleanly close a connection, a client should send a close
|
||||||
|
// frame and wait for the server to close the connection.
|
||||||
|
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write close:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
vendor/github.com/gorilla/websocket/examples/echo/server.go
generated
vendored
Normal file
133
vendor/github.com/gorilla/websocket/examples/echo/server.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", "localhost:8080", "http service address")
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{} // use default options
|
||||||
|
|
||||||
|
func echo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
for {
|
||||||
|
mt, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("recv: %s", message)
|
||||||
|
err = c.WriteMessage(mt, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
log.SetFlags(0)
|
||||||
|
http.HandleFunc("/echo", echo)
|
||||||
|
http.HandleFunc("/", home)
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var homeTemplate = template.Must(template.New("").Parse(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script>
|
||||||
|
window.addEventListener("load", function(evt) {
|
||||||
|
|
||||||
|
var output = document.getElementById("output");
|
||||||
|
var input = document.getElementById("input");
|
||||||
|
var ws;
|
||||||
|
|
||||||
|
var print = function(message) {
|
||||||
|
var d = document.createElement("div");
|
||||||
|
d.innerHTML = message;
|
||||||
|
output.appendChild(d);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("open").onclick = function(evt) {
|
||||||
|
if (ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws = new WebSocket("{{.}}");
|
||||||
|
ws.onopen = function(evt) {
|
||||||
|
print("OPEN");
|
||||||
|
}
|
||||||
|
ws.onclose = function(evt) {
|
||||||
|
print("CLOSE");
|
||||||
|
ws = null;
|
||||||
|
}
|
||||||
|
ws.onmessage = function(evt) {
|
||||||
|
print("RESPONSE: " + evt.data);
|
||||||
|
}
|
||||||
|
ws.onerror = function(evt) {
|
||||||
|
print("ERROR: " + evt.data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("send").onclick = function(evt) {
|
||||||
|
if (!ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
print("SEND: " + input.value);
|
||||||
|
ws.send(input.value);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("close").onclick = function(evt) {
|
||||||
|
if (!ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws.close();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr><td valign="top" width="50%">
|
||||||
|
<p>Click "Open" to create a connection to the server,
|
||||||
|
"Send" to send a message to the server and "Close" to close the connection.
|
||||||
|
You can change the message and send multiple times.
|
||||||
|
<p>
|
||||||
|
<form>
|
||||||
|
<button id="open">Open</button>
|
||||||
|
<button id="close">Close</button>
|
||||||
|
<p><input id="input" type="text" value="Hello world!">
|
||||||
|
<button id="send">Send</button>
|
||||||
|
</form>
|
||||||
|
</td><td valign="top" width="50%">
|
||||||
|
<div id="output"></div>
|
||||||
|
</td></tr></table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
9
vendor/github.com/gorilla/websocket/examples/filewatch/README.md
generated
vendored
Normal file
9
vendor/github.com/gorilla/websocket/examples/filewatch/README.md
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# File Watch example.
|
||||||
|
|
||||||
|
This example sends a file to the browser client for display whenever the file is modified.
|
||||||
|
|
||||||
|
$ go get github.com/gorilla/websocket
|
||||||
|
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch`
|
||||||
|
$ go run main.go <name of file to watch>
|
||||||
|
# Open http://localhost:8080/ .
|
||||||
|
# Modify the file to see it update in the browser.
|
193
vendor/github.com/gorilla/websocket/examples/filewatch/main.go
generated
vendored
Normal file
193
vendor/github.com/gorilla/websocket/examples/filewatch/main.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write the file to the client.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the client.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to client with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
|
// Poll file for changes with this period.
|
||||||
|
filePeriod = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", ":8080", "http service address")
|
||||||
|
homeTempl = template.Must(template.New("").Parse(homeHTML))
|
||||||
|
filename string
|
||||||
|
upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
|
||||||
|
fi, err := os.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, lastMod, err
|
||||||
|
}
|
||||||
|
if !fi.ModTime().After(lastMod) {
|
||||||
|
return nil, lastMod, nil
|
||||||
|
}
|
||||||
|
p, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fi.ModTime(), err
|
||||||
|
}
|
||||||
|
return p, fi.ModTime(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reader(ws *websocket.Conn) {
|
||||||
|
defer ws.Close()
|
||||||
|
ws.SetReadLimit(512)
|
||||||
|
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
for {
|
||||||
|
_, _, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writer(ws *websocket.Conn, lastMod time.Time) {
|
||||||
|
lastError := ""
|
||||||
|
pingTicker := time.NewTicker(pingPeriod)
|
||||||
|
fileTicker := time.NewTicker(filePeriod)
|
||||||
|
defer func() {
|
||||||
|
pingTicker.Stop()
|
||||||
|
fileTicker.Stop()
|
||||||
|
ws.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-fileTicker.C:
|
||||||
|
var p []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p, lastMod, err = readFileIfModified(lastMod)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if s := err.Error(); s != lastError {
|
||||||
|
lastError = s
|
||||||
|
p = []byte(lastError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastError = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-pingTicker.C:
|
||||||
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMod time.Time
|
||||||
|
if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil {
|
||||||
|
lastMod = time.Unix(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
go writer(ws, lastMod)
|
||||||
|
reader(ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
p, lastMod, err := readFileIfModified(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
p = []byte(err.Error())
|
||||||
|
lastMod = time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
var v = struct {
|
||||||
|
Host string
|
||||||
|
Data string
|
||||||
|
LastMod string
|
||||||
|
}{
|
||||||
|
r.Host,
|
||||||
|
string(p),
|
||||||
|
strconv.FormatInt(lastMod.UnixNano(), 16),
|
||||||
|
}
|
||||||
|
homeTempl.Execute(w, &v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if flag.NArg() != 1 {
|
||||||
|
log.Fatal("filename not specified")
|
||||||
|
}
|
||||||
|
filename = flag.Args()[0]
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/ws", serveWs)
|
||||||
|
if err := http.ListenAndServe(*addr, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeHTML = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>WebSocket Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="fileData">{{.Data}}</pre>
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function() {
|
||||||
|
var data = document.getElementById("fileData");
|
||||||
|
var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}");
|
||||||
|
conn.onclose = function(evt) {
|
||||||
|
data.textContent = 'Connection closed';
|
||||||
|
}
|
||||||
|
conn.onmessage = function(evt) {
|
||||||
|
console.log('file updated');
|
||||||
|
data.textContent = evt.data;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.WriteJSON instead.
|
||||||
|
func WriteJSON(c *Conn, v interface{}) error {
|
||||||
|
return c.WriteJSON(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// See the documentation for encoding/json Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
func (c *Conn) WriteJSON(v interface{}) error {
|
||||||
|
w, err := c.NextWriter(TextMessage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err1 := json.NewEncoder(w).Encode(v)
|
||||||
|
err2 := w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.ReadJSON instead.
|
||||||
|
func ReadJSON(c *Conn, v interface{}) error {
|
||||||
|
return c.ReadJSON(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for the encoding/json Unmarshal function for details
|
||||||
|
// about the conversion of JSON to a Go value.
|
||||||
|
func (c *Conn) ReadJSON(v interface{}) error {
|
||||||
|
_, r, err := c.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(r).Decode(v)
|
||||||
|
if err == io.EOF {
|
||||||
|
// One value is expected in the message.
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
119
vendor/github.com/gorilla/websocket/json_test.go
generated
vendored
Normal file
119
vendor/github.com/gorilla/websocket/json_test.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSON(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var actual, expect struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
expect.A = 1
|
||||||
|
expect.B = "hello"
|
||||||
|
|
||||||
|
if err := wc.WriteJSON(&expect); err != nil {
|
||||||
|
t.Fatal("write", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rc.ReadJSON(&actual); err != nil {
|
||||||
|
t.Fatal("read", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&actual, &expect) {
|
||||||
|
t.Fatal("equal", actual, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialJSONRead(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
v.A = 1
|
||||||
|
v.B = "hello"
|
||||||
|
|
||||||
|
messageCount := 0
|
||||||
|
|
||||||
|
// Partial JSON values.
|
||||||
|
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := len(data) - 1; i >= 0; i-- {
|
||||||
|
if err := wc.WriteMessage(TextMessage, data[:i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
messageCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitespace.
|
||||||
|
|
||||||
|
if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
messageCount++
|
||||||
|
|
||||||
|
// Close.
|
||||||
|
|
||||||
|
if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < messageCount; i++ {
|
||||||
|
err := rc.ReadJSON(&v)
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Error("read", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rc.ReadJSON(&v)
|
||||||
|
if _, ok := err.(*CloseError); !ok {
|
||||||
|
t.Error("final", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeprecatedJSON(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var actual, expect struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
expect.A = 1
|
||||||
|
expect.B = "hello"
|
||||||
|
|
||||||
|
if err := WriteJSON(wc, &expect); err != nil {
|
||||||
|
t.Fatal("write", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ReadJSON(rc, &actual); err != nil {
|
||||||
|
t.Fatal("read", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&actual, &expect) {
|
||||||
|
t.Fatal("equal", actual, expect)
|
||||||
|
}
|
||||||
|
}
|
54
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
54
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
// Mask one byte at a time for small buffers.
|
||||||
|
if len(b) < 2*wordSize {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time to word boundary.
|
||||||
|
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||||
|
n = wordSize - n
|
||||||
|
for i := range b[:n] {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create aligned word size key.
|
||||||
|
var k [wordSize]byte
|
||||||
|
for i := range k {
|
||||||
|
k[i] = key[(pos+i)&3]
|
||||||
|
}
|
||||||
|
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||||
|
|
||||||
|
// Mask one word at a time.
|
||||||
|
n := (len(b) / wordSize) * wordSize
|
||||||
|
for i := 0; i < n; i += wordSize {
|
||||||
|
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time for remaining bytes.
|
||||||
|
b = b[n:]
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos & 3
|
||||||
|
}
|
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
73
vendor/github.com/gorilla/websocket/mask_test.go
generated
vendored
Normal file
73
vendor/github.com/gorilla/websocket/mask_test.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
|
||||||
|
// Require 1.7 for sub-bencmarks
|
||||||
|
// +build go1.7,!appengine
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func maskBytesByByte(key [4]byte, pos int, b []byte) int {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func notzero(b []byte) int {
|
||||||
|
for i := range b {
|
||||||
|
if b[i] != 0 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaskBytes(t *testing.T) {
|
||||||
|
key := [4]byte{1, 2, 3, 4}
|
||||||
|
for size := 1; size <= 1024; size++ {
|
||||||
|
for align := 0; align < wordSize; align++ {
|
||||||
|
for pos := 0; pos < 4; pos++ {
|
||||||
|
b := make([]byte, size+align)[align:]
|
||||||
|
maskBytes(key, pos, b)
|
||||||
|
maskBytesByByte(key, pos, b)
|
||||||
|
if i := notzero(b); i >= 0 {
|
||||||
|
t.Errorf("size:%d, align:%d, pos:%d, offset:%d", size, align, pos, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMaskBytes(b *testing.B) {
|
||||||
|
for _, size := range []int{2, 4, 8, 16, 32, 512, 1024} {
|
||||||
|
b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
|
||||||
|
for _, align := range []int{wordSize / 2} {
|
||||||
|
b.Run(fmt.Sprintf("align-%d", align), func(b *testing.B) {
|
||||||
|
for _, fn := range []struct {
|
||||||
|
name string
|
||||||
|
fn func(key [4]byte, pos int, b []byte) int
|
||||||
|
}{
|
||||||
|
{"byte", maskBytesByByte},
|
||||||
|
{"word", maskBytes},
|
||||||
|
} {
|
||||||
|
b.Run(fn.name, func(b *testing.B) {
|
||||||
|
key := newMaskKey()
|
||||||
|
data := make([]byte, size+align)[align:]
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
fn.fn(key, 0, data)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(data)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
103
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
103
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreparedMessage caches on the wire representations of a message payload.
|
||||||
|
// Use PreparedMessage to efficiently send a message payload to multiple
|
||||||
|
// connections. PreparedMessage is especially useful when compression is used
|
||||||
|
// because the CPU and memory expensive compression operation can be executed
|
||||||
|
// once for a given set of compression options.
|
||||||
|
type PreparedMessage struct {
|
||||||
|
messageType int
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
mu sync.Mutex
|
||||||
|
frames map[prepareKey]*preparedFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
|
||||||
|
type prepareKey struct {
|
||||||
|
isServer bool
|
||||||
|
compress bool
|
||||||
|
compressionLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
// preparedFrame contains data in wire representation.
|
||||||
|
type preparedFrame struct {
|
||||||
|
once sync.Once
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
|
||||||
|
// it to connection using WritePreparedMessage method. Valid wire
|
||||||
|
// representation will be calculated lazily only once for a set of current
|
||||||
|
// connection options.
|
||||||
|
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
|
||||||
|
pm := &PreparedMessage{
|
||||||
|
messageType: messageType,
|
||||||
|
frames: make(map[prepareKey]*preparedFrame),
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare a plain server frame.
|
||||||
|
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// To protect against caller modifying the data argument, remember the data
|
||||||
|
// copied to the plain server frame.
|
||||||
|
pm.data = frameData[len(frameData)-len(data):]
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
frame, ok := pm.frames[key]
|
||||||
|
if !ok {
|
||||||
|
frame = &preparedFrame{}
|
||||||
|
pm.frames[key] = frame
|
||||||
|
}
|
||||||
|
pm.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
frame.once.Do(func() {
|
||||||
|
// Prepare a frame using a 'fake' connection.
|
||||||
|
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||||
|
// the frame.
|
||||||
|
mu := make(chan bool, 1)
|
||||||
|
mu <- true
|
||||||
|
var nc prepareConn
|
||||||
|
c := &Conn{
|
||||||
|
conn: &nc,
|
||||||
|
mu: mu,
|
||||||
|
isServer: key.isServer,
|
||||||
|
compressionLevel: key.compressionLevel,
|
||||||
|
enableWriteCompression: true,
|
||||||
|
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
|
||||||
|
}
|
||||||
|
if key.compress {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
}
|
||||||
|
err = c.WriteMessage(pm.messageType, pm.data)
|
||||||
|
frame.data = nc.buf.Bytes()
|
||||||
|
})
|
||||||
|
return pm.messageType, frame.data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type prepareConn struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
|
||||||
|
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
|
74
vendor/github.com/gorilla/websocket/prepared_test.go
generated
vendored
Normal file
74
vendor/github.com/gorilla/websocket/prepared_test.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var preparedMessageTests = []struct {
|
||||||
|
messageType int
|
||||||
|
isServer bool
|
||||||
|
enableWriteCompression bool
|
||||||
|
compressionLevel int
|
||||||
|
}{
|
||||||
|
// Server
|
||||||
|
{TextMessage, true, false, flate.BestSpeed},
|
||||||
|
{TextMessage, true, true, flate.BestSpeed},
|
||||||
|
{TextMessage, true, true, flate.BestCompression},
|
||||||
|
{PingMessage, true, false, flate.BestSpeed},
|
||||||
|
{PingMessage, true, true, flate.BestSpeed},
|
||||||
|
|
||||||
|
// Client
|
||||||
|
{TextMessage, false, false, flate.BestSpeed},
|
||||||
|
{TextMessage, false, true, flate.BestSpeed},
|
||||||
|
{TextMessage, false, true, flate.BestCompression},
|
||||||
|
{PingMessage, false, false, flate.BestSpeed},
|
||||||
|
{PingMessage, false, true, flate.BestSpeed},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparedMessage(t *testing.T) {
|
||||||
|
for _, tt := range preparedMessageTests {
|
||||||
|
var data = []byte("this is a test")
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := newConn(fakeNetConn{Reader: nil, Writer: &buf}, tt.isServer, 1024, 1024)
|
||||||
|
if tt.enableWriteCompression {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
}
|
||||||
|
c.SetCompressionLevel(tt.compressionLevel)
|
||||||
|
|
||||||
|
// Seed random number generator for consistent frame mask.
|
||||||
|
rand.Seed(1234)
|
||||||
|
|
||||||
|
if err := c.WriteMessage(tt.messageType, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := buf.String()
|
||||||
|
|
||||||
|
pm, err := NewPreparedMessage(tt.messageType, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scribble on data to ensure that NewPreparedMessage takes a snapshot.
|
||||||
|
copy(data, "hello world")
|
||||||
|
|
||||||
|
// Seed random number generator for consistent frame mask.
|
||||||
|
rand.Seed(1234)
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
if err := c.WritePreparedMessage(pm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := buf.String()
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("write message != prepared message for %+v", tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netDialerFunc func(netowrk, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return fn(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProxyDialer struct {
|
||||||
|
proxyURL *url.URL
|
||||||
|
fowardDial func(network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||||
|
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||||
|
conn, err := hpd.fowardDial(network, hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectHeader := make(http.Header)
|
||||||
|
if user := hpd.proxyURL.User; user != nil {
|
||||||
|
proxyUser := user.Username()
|
||||||
|
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||||
|
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||||
|
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{Opaque: addr},
|
||||||
|
Host: addr,
|
||||||
|
Header: connectHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := connectReq.Write(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response. It's OK to use and discard buffered reader here becaue
|
||||||
|
// the remote server does not speak until spoken to.
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
resp, err := http.ReadResponse(br, connectReq)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
conn.Close()
|
||||||
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
|
return nil, errors.New(f[1])
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
294
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
294
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandshakeError describes an error with the handshake from the peer.
|
||||||
|
type HandshakeError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e HandshakeError) Error() string { return e.message }
|
||||||
|
|
||||||
|
// Upgrader specifies parameters for upgrading an HTTP connection to a
|
||||||
|
// WebSocket connection.
|
||||||
|
type Upgrader struct {
|
||||||
|
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||||
|
HandshakeTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||||
|
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||||
|
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||||
|
// or received.
|
||||||
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// Subprotocols specifies the server's supported protocols in order of
|
||||||
|
// preference. If this field is set, then the Upgrade method negotiates a
|
||||||
|
// subprotocol by selecting the first match in this list with a protocol
|
||||||
|
// requested by the client.
|
||||||
|
Subprotocols []string
|
||||||
|
|
||||||
|
// Error specifies the function for generating HTTP error responses. If Error
|
||||||
|
// is nil, then http.Error is used to generate the HTTP response.
|
||||||
|
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||||
|
|
||||||
|
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
|
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||||
|
// must match the host of the request.
|
||||||
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
|
||||||
|
// EnableCompression specify if the server should attempt to negotiate per
|
||||||
|
// message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
||||||
|
err := HandshakeError{reason}
|
||||||
|
if u.Error != nil {
|
||||||
|
u.Error(w, r, status, err)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Sec-Websocket-Version", "13")
|
||||||
|
http.Error(w, http.StatusText(status), status)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
|
||||||
|
func checkSameOrigin(r *http.Request) bool {
|
||||||
|
origin := r.Header["Origin"]
|
||||||
|
if len(origin) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
u, err := url.Parse(origin[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalASCIIFold(u.Host, r.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||||
|
if u.Subprotocols != nil {
|
||||||
|
clientProtocols := Subprotocols(r)
|
||||||
|
for _, serverProtocol := range u.Subprotocols {
|
||||||
|
for _, clientProtocol := range clientProtocols {
|
||||||
|
if clientProtocol == serverProtocol {
|
||||||
|
return clientProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if responseHeader != nil {
|
||||||
|
return responseHeader.Get("Sec-Websocket-Protocol")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
|
//
|
||||||
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
|
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
||||||
|
//
|
||||||
|
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||||
|
// response.
|
||||||
|
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||||
|
const badHandshake = "websocket: the client is not using the websocket protocol: "
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOrigin := u.CheckOrigin
|
||||||
|
if checkOrigin == nil {
|
||||||
|
checkOrigin = checkSameOrigin
|
||||||
|
}
|
||||||
|
if !checkOrigin(r) {
|
||||||
|
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||||
|
if challengeKey == "" {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||||
|
|
||||||
|
// Negotiate PMCE
|
||||||
|
var compress bool
|
||||||
|
if u.EnableCompression {
|
||||||
|
for _, ext := range parseExtensions(r.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
compress = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
netConn net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
h, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||||
|
}
|
||||||
|
var brw *bufio.ReadWriter
|
||||||
|
netConn, brw, err = h.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if brw.Reader.Buffered() > 0 {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
||||||
|
c.subprotocol = subprotocol
|
||||||
|
|
||||||
|
if compress {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
c.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
}
|
||||||
|
|
||||||
|
p := c.writeBuf[:0]
|
||||||
|
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||||
|
p = append(p, computeAcceptKey(challengeKey)...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
if c.subprotocol != "" {
|
||||||
|
p = append(p, "Sec-Websocket-Protocol: "...)
|
||||||
|
p = append(p, c.subprotocol...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
}
|
||||||
|
if compress {
|
||||||
|
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||||
|
}
|
||||||
|
for k, vs := range responseHeader {
|
||||||
|
if k == "Sec-Websocket-Protocol" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v := range vs {
|
||||||
|
p = append(p, k...)
|
||||||
|
p = append(p, ": "...)
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
b := v[i]
|
||||||
|
if b <= 31 {
|
||||||
|
// prevent response splitting.
|
||||||
|
b = ' '
|
||||||
|
}
|
||||||
|
p = append(p, b)
|
||||||
|
}
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
|
||||||
|
// Clear deadlines set by HTTP server.
|
||||||
|
netConn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
if u.HandshakeTimeout > 0 {
|
||||||
|
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
|
||||||
|
}
|
||||||
|
if _, err = netConn.Write(p); err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.HandshakeTimeout > 0 {
|
||||||
|
netConn.SetWriteDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
|
//
|
||||||
|
// Deprecated: Use websocket.Upgrader instead.
|
||||||
|
//
|
||||||
|
// Upgrade does not perform origin checking. The application is responsible for
|
||||||
|
// checking the Origin header before calling Upgrade. An example implementation
|
||||||
|
// of the same origin policy check is:
|
||||||
|
//
|
||||||
|
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||||
|
// http.Error(w, "Origin not allowed", 403)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the endpoint supports subprotocols, then the application is responsible
|
||||||
|
// for negotiating the protocol used on the connection. Use the Subprotocols()
|
||||||
|
// function to get the subprotocols requested by the client. Use the
|
||||||
|
// Sec-Websocket-Protocol response header to specify the subprotocol selected
|
||||||
|
// by the application.
|
||||||
|
//
|
||||||
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
|
// negotiated subprotocol (Sec-Websocket-Protocol).
|
||||||
|
//
|
||||||
|
// The connection buffers IO to the underlying network connection. The
|
||||||
|
// readBufSize and writeBufSize parameters specify the size of the buffers to
|
||||||
|
// use. Messages can be larger than the buffers.
|
||||||
|
//
|
||||||
|
// If the request is not a valid WebSocket handshake, then Upgrade returns an
|
||||||
|
// error of type HandshakeError. Applications should handle this error by
|
||||||
|
// replying to the client with an HTTP error response.
|
||||||
|
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
|
||||||
|
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
|
||||||
|
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||||
|
// don't return errors to maintain backwards compatibility
|
||||||
|
}
|
||||||
|
u.CheckOrigin = func(r *http.Request) bool {
|
||||||
|
// allow all connections by default
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return u.Upgrade(w, r, responseHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subprotocols returns the subprotocols requested by the client in the
|
||||||
|
// Sec-Websocket-Protocol header.
|
||||||
|
func Subprotocols(r *http.Request) []string {
|
||||||
|
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
|
||||||
|
if h == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
protocols := strings.Split(h, ",")
|
||||||
|
for i := range protocols {
|
||||||
|
protocols[i] = strings.TrimSpace(protocols[i])
|
||||||
|
}
|
||||||
|
return protocols
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWebSocketUpgrade returns true if the client requested upgrade to the
|
||||||
|
// WebSocket protocol.
|
||||||
|
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||||
|
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||||
|
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||||
|
}
|
69
vendor/github.com/gorilla/websocket/server_test.go
generated
vendored
Normal file
69
vendor/github.com/gorilla/websocket/server_test.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var subprotocolTests = []struct {
|
||||||
|
h string
|
||||||
|
protocols []string
|
||||||
|
}{
|
||||||
|
{"", nil},
|
||||||
|
{"foo", []string{"foo"}},
|
||||||
|
{"foo,bar", []string{"foo", "bar"}},
|
||||||
|
{"foo, bar", []string{"foo", "bar"}},
|
||||||
|
{" foo, bar", []string{"foo", "bar"}},
|
||||||
|
{" foo, bar ", []string{"foo", "bar"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubprotocols(t *testing.T) {
|
||||||
|
for _, st := range subprotocolTests {
|
||||||
|
r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}}
|
||||||
|
protocols := Subprotocols(&r)
|
||||||
|
if !reflect.DeepEqual(st.protocols, protocols) {
|
||||||
|
t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isWebSocketUpgradeTests = []struct {
|
||||||
|
ok bool
|
||||||
|
h http.Header
|
||||||
|
}{
|
||||||
|
{false, http.Header{"Upgrade": {"websocket"}}},
|
||||||
|
{false, http.Header{"Connection": {"upgrade"}}},
|
||||||
|
{true, http.Header{"Connection": {"upgRade"}, "Upgrade": {"WebSocket"}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsWebSocketUpgrade(t *testing.T) {
|
||||||
|
for _, tt := range isWebSocketUpgradeTests {
|
||||||
|
ok := IsWebSocketUpgrade(&http.Request{Header: tt.h})
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("IsWebSocketUpgrade(%v) returned %v, want %v", tt.h, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkSameOriginTests = []struct {
|
||||||
|
ok bool
|
||||||
|
r *http.Request
|
||||||
|
}{
|
||||||
|
{false, &http.Request{Host: "example.org", Header: map[string][]string{"Origin": []string{"https://other.org"}}}},
|
||||||
|
{true, &http.Request{Host: "example.org", Header: map[string][]string{"Origin": []string{"https://example.org"}}}},
|
||||||
|
{true, &http.Request{Host: "Example.org", Header: map[string][]string{"Origin": []string{"https://example.org"}}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckSameOrigin(t *testing.T) {
|
||||||
|
for _, tt := range checkSameOriginTests {
|
||||||
|
ok := checkSameOrigin(tt.r)
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("checkSameOrigin(%+v) returned %v, want %v", tt.r, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
237
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
237
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 generateChallengeKey() (string, error) {
|
||||||
|
p := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, p); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Octet types from RFC 2616.
|
||||||
|
var octetTypes [256]byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
isTokenOctet = 1 << iota
|
||||||
|
isSpaceOctet
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// From RFC 2616
|
||||||
|
//
|
||||||
|
// OCTET = <any 8-bit sequence of data>
|
||||||
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||||
|
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||||
|
// CR = <US-ASCII CR, carriage return (13)>
|
||||||
|
// LF = <US-ASCII LF, linefeed (10)>
|
||||||
|
// SP = <US-ASCII SP, space (32)>
|
||||||
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||||
|
// <"> = <US-ASCII double-quote mark (34)>
|
||||||
|
// CRLF = CR LF
|
||||||
|
// LWS = [CRLF] 1*( SP | HT )
|
||||||
|
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||||
|
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||||
|
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||||
|
// token = 1*<any CHAR except CTLs or separators>
|
||||||
|
// qdtext = <any TEXT except <">>
|
||||||
|
|
||||||
|
for c := 0; c < 256; c++ {
|
||||||
|
var t byte
|
||||||
|
isCtl := c <= 31 || c == 127
|
||||||
|
isChar := 0 <= c && c <= 127
|
||||||
|
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||||
|
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||||
|
t |= isSpaceOctet
|
||||||
|
}
|
||||||
|
if isChar && !isCtl && !isSeparator {
|
||||||
|
t |= isTokenOctet
|
||||||
|
}
|
||||||
|
octetTypes[c] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipSpace(s string) (rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextToken(s string) (token, rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:i], s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||||
|
if !strings.HasPrefix(s, "\"") {
|
||||||
|
return nextToken(s)
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '"':
|
||||||
|
return s[:i], s[i+1:]
|
||||||
|
case '\\':
|
||||||
|
p := make([]byte, len(s)-1)
|
||||||
|
j := copy(p, s[:i])
|
||||||
|
escape := true
|
||||||
|
for i = i + 1; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
switch {
|
||||||
|
case escape:
|
||||||
|
escape = false
|
||||||
|
p[j] = b
|
||||||
|
j++
|
||||||
|
case b == '\\':
|
||||||
|
escape = true
|
||||||
|
case b == '"':
|
||||||
|
return string(p[:j]), s[i+1:]
|
||||||
|
default:
|
||||||
|
p[j] = b
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
||||||
|
func equalASCIIFold(s, t string) bool {
|
||||||
|
for s != "" && t != "" {
|
||||||
|
sr, size := utf8.DecodeRuneInString(s)
|
||||||
|
s = s[size:]
|
||||||
|
tr, size := utf8.DecodeRuneInString(t)
|
||||||
|
t = t[size:]
|
||||||
|
if sr == tr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= sr && sr <= 'Z' {
|
||||||
|
sr = sr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if 'A' <= tr && tr <= 'Z' {
|
||||||
|
tr = tr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if sr != tr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s == t
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenListContainsValue returns true if the 1#token header with the given
|
||||||
|
// name contains a token equal to value with ASCII case folding.
|
||||||
|
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||||
|
headers:
|
||||||
|
for _, s := range header[name] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
if equalASCIIFold(t, value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtensiosn parses WebSocket extensions from a header.
|
||||||
|
func parseExtensions(header http.Header) []map[string]string {
|
||||||
|
// From RFC 6455:
|
||||||
|
//
|
||||||
|
// Sec-WebSocket-Extensions = extension-list
|
||||||
|
// extension-list = 1#extension
|
||||||
|
// extension = extension-token *( ";" extension-param )
|
||||||
|
// extension-token = registered-token
|
||||||
|
// registered-token = token
|
||||||
|
// extension-param = token [ "=" (token | quoted-string) ]
|
||||||
|
// ;When using the quoted-string syntax variant, the value
|
||||||
|
// ;after quoted-string unescaping MUST conform to the
|
||||||
|
// ;'token' ABNF.
|
||||||
|
|
||||||
|
var result []map[string]string
|
||||||
|
headers:
|
||||||
|
for _, s := range header["Sec-Websocket-Extensions"] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext := map[string]string{"": t}
|
||||||
|
for {
|
||||||
|
s = skipSpace(s)
|
||||||
|
if !strings.HasPrefix(s, ";") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var k string
|
||||||
|
k, s = nextToken(skipSpace(s[1:]))
|
||||||
|
if k == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
var v string
|
||||||
|
if strings.HasPrefix(s, "=") {
|
||||||
|
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
|
||||||
|
s = skipSpace(s)
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' && s[0] != ';' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext[k] = v
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
result = append(result, ext)
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
95
vendor/github.com/gorilla/websocket/util_test.go
generated
vendored
Normal file
95
vendor/github.com/gorilla/websocket/util_test.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var equalASCIIFoldTests = []struct {
|
||||||
|
t, s string
|
||||||
|
eq bool
|
||||||
|
}{
|
||||||
|
{"WebSocket", "websocket", true},
|
||||||
|
{"websocket", "WebSocket", true},
|
||||||
|
{"Öyster", "öyster", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualASCIIFold(t *testing.T) {
|
||||||
|
for _, tt := range equalASCIIFoldTests {
|
||||||
|
eq := equalASCIIFold(tt.s, tt.t)
|
||||||
|
if eq != tt.eq {
|
||||||
|
t.Errorf("equalASCIIFold(%q, %q) = %v, want %v", tt.s, tt.t, eq, tt.eq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenListContainsValueTests = []struct {
|
||||||
|
value string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"WebSocket", true},
|
||||||
|
{"WEBSOCKET", true},
|
||||||
|
{"websocket", true},
|
||||||
|
{"websockets", false},
|
||||||
|
{"x websocket", false},
|
||||||
|
{"websocket x", false},
|
||||||
|
{"other,websocket,more", true},
|
||||||
|
{"other, websocket, more", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenListContainsValue(t *testing.T) {
|
||||||
|
for _, tt := range tokenListContainsValueTests {
|
||||||
|
h := http.Header{"Upgrade": {tt.value}}
|
||||||
|
ok := tokenListContainsValue(h, "Upgrade", "websocket")
|
||||||
|
if ok != tt.ok {
|
||||||
|
t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseExtensionTests = []struct {
|
||||||
|
value string
|
||||||
|
extensions []map[string]string
|
||||||
|
}{
|
||||||
|
{`foo`, []map[string]string{{"": "foo"}}},
|
||||||
|
{`foo, bar; baz=2`, []map[string]string{
|
||||||
|
{"": "foo"},
|
||||||
|
{"": "bar", "baz": "2"}}},
|
||||||
|
{`foo; bar="b,a;z"`, []map[string]string{
|
||||||
|
{"": "foo", "bar": "b,a;z"}}},
|
||||||
|
{`foo , bar; baz = 2`, []map[string]string{
|
||||||
|
{"": "foo"},
|
||||||
|
{"": "bar", "baz": "2"}}},
|
||||||
|
{`foo, bar; baz=2 junk`, []map[string]string{
|
||||||
|
{"": "foo"}}},
|
||||||
|
{`foo junk, bar; baz=2 junk`, nil},
|
||||||
|
{`mux; max-channels=4; flow-control, deflate-stream`, []map[string]string{
|
||||||
|
{"": "mux", "max-channels": "4", "flow-control": ""},
|
||||||
|
{"": "deflate-stream"}}},
|
||||||
|
{`permessage-foo; x="10"`, []map[string]string{
|
||||||
|
{"": "permessage-foo", "x": "10"}}},
|
||||||
|
{`permessage-foo; use_y, permessage-foo`, []map[string]string{
|
||||||
|
{"": "permessage-foo", "use_y": ""},
|
||||||
|
{"": "permessage-foo"}}},
|
||||||
|
{`permessage-deflate; client_max_window_bits; server_max_window_bits=10 , permessage-deflate; client_max_window_bits`, []map[string]string{
|
||||||
|
{"": "permessage-deflate", "client_max_window_bits": "", "server_max_window_bits": "10"},
|
||||||
|
{"": "permessage-deflate", "client_max_window_bits": ""}}},
|
||||||
|
{"permessage-deflate; server_no_context_takeover; client_max_window_bits=15", []map[string]string{
|
||||||
|
{"": "permessage-deflate", "server_no_context_takeover": "", "client_max_window_bits": "15"},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseExtensions(t *testing.T) {
|
||||||
|
for _, tt := range parseExtensionTests {
|
||||||
|
h := http.Header{http.CanonicalHeaderKey("Sec-WebSocket-Extensions"): {tt.value}}
|
||||||
|
extensions := parseExtensions(h)
|
||||||
|
if !reflect.DeepEqual(extensions, tt.extensions) {
|
||||||
|
t.Errorf("parseExtensions(%q)\n = %v,\nwant %v", tt.value, extensions, tt.extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
||||||
|
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
|
||||||
|
|
||||||
|
// Package proxy provides support for a variety of protocols to proxy network
|
||||||
|
// data.
|
||||||
|
//
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxy_direct struct{}
|
||||||
|
|
||||||
|
// Direct is a direct proxy: one that makes network connections directly.
|
||||||
|
var proxy_Direct = proxy_direct{}
|
||||||
|
|
||||||
|
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
|
// requested matches one of a number of exceptions.
|
||||||
|
type proxy_PerHost struct {
|
||||||
|
def, bypass proxy_Dialer
|
||||||
|
|
||||||
|
bypassNetworks []*net.IPNet
|
||||||
|
bypassIPs []net.IP
|
||||||
|
bypassZones []string
|
||||||
|
bypassHosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||||
|
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||||
|
// the configured rules.
|
||||||
|
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
|
||||||
|
return &proxy_PerHost{
|
||||||
|
def: defaultDialer,
|
||||||
|
bypass: bypass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.dialerForRequest(host).Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
for _, net := range p.bypassNetworks {
|
||||||
|
if net.Contains(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassIP := range p.bypassIPs {
|
||||||
|
if bypassIP.Equal(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range p.bypassZones {
|
||||||
|
if strings.HasSuffix(host, zone) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
if host == zone[1:] {
|
||||||
|
// For a zone ".example.com", we match "example.com"
|
||||||
|
// too.
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassHost := range p.bypassHosts {
|
||||||
|
if bypassHost == host {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromString parses a string that contains comma-separated values
|
||||||
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
|
// ignored.
|
||||||
|
func (p *proxy_PerHost) AddFromString(s string) {
|
||||||
|
hosts := strings.Split(s, ",")
|
||||||
|
for _, host := range hosts {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "/") {
|
||||||
|
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||||
|
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||||
|
p.AddNetwork(net)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
p.AddIP(ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
p.AddZone(host[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.AddHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match an IP.
|
||||||
|
func (p *proxy_PerHost) AddIP(ip net.IP) {
|
||||||
|
p.bypassIPs = append(p.bypassIPs, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match.
|
||||||
|
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
|
||||||
|
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||||
|
// "example.com" matches "example.com" and all of its subdomains.
|
||||||
|
func (p *proxy_PerHost) AddZone(zone string) {
|
||||||
|
if strings.HasSuffix(zone, ".") {
|
||||||
|
zone = zone[:len(zone)-1]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(zone, ".") {
|
||||||
|
zone = "." + zone
|
||||||
|
}
|
||||||
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
|
func (p *proxy_PerHost) AddHost(host string) {
|
||||||
|
if strings.HasSuffix(host, ".") {
|
||||||
|
host = host[:len(host)-1]
|
||||||
|
}
|
||||||
|
p.bypassHosts = append(p.bypassHosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer is a means to establish a connection.
|
||||||
|
type proxy_Dialer interface {
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth contains authentication parameters that specific Dialers may require.
|
||||||
|
type proxy_Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||||
|
// the environment.
|
||||||
|
func proxy_FromEnvironment() proxy_Dialer {
|
||||||
|
allProxy := proxy_allProxyEnv.Get()
|
||||||
|
if len(allProxy) == 0 {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(allProxy)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxy := proxy_noProxyEnv.Get()
|
||||||
|
if len(noProxy) == 0 {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
perHost := proxy_NewPerHost(proxy, proxy_Direct)
|
||||||
|
perHost.AddFromString(noProxy)
|
||||||
|
return perHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||||
|
// from a URL with such a scheme.
|
||||||
|
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
|
||||||
|
|
||||||
|
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||||
|
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||||
|
// by FromURL.
|
||||||
|
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
|
||||||
|
if proxy_proxySchemes == nil {
|
||||||
|
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
|
||||||
|
}
|
||||||
|
proxy_proxySchemes[scheme] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromURL returns a Dialer given a URL specification and an underlying
|
||||||
|
// Dialer for it to make network requests.
|
||||||
|
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
var auth *proxy_Auth
|
||||||
|
if u.User != nil {
|
||||||
|
auth = new(proxy_Auth)
|
||||||
|
auth.User = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
auth.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "socks5":
|
||||||
|
return proxy_SOCKS5("tcp", u.Host, auth, forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||||
|
// was registered by another package.
|
||||||
|
if proxy_proxySchemes != nil {
|
||||||
|
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
|
||||||
|
return f(u, forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
proxy_allProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
proxy_noProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type proxy_envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
|
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
s := &proxy_socks5{
|
||||||
|
network: network,
|
||||||
|
addr: addr,
|
||||||
|
forward: forward,
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
s.user = auth.User
|
||||||
|
s.password = auth.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy_socks5 struct {
|
||||||
|
user, password string
|
||||||
|
network, addr string
|
||||||
|
forward proxy_Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy_socks5Version = 5
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5AuthNone = 0
|
||||||
|
proxy_socks5AuthPassword = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const proxy_socks5Connect = 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5IP4 = 1
|
||||||
|
proxy_socks5Domain = 3
|
||||||
|
proxy_socks5IP6 = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxy_socks5Errors = []string{
|
||||||
|
"",
|
||||||
|
"general failure",
|
||||||
|
"connection forbidden",
|
||||||
|
"network unreachable",
|
||||||
|
"host unreachable",
|
||||||
|
"connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"command not supported",
|
||||||
|
"address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
|
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.forward.Dial(s.network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.connect(conn, addr); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
|
||||||
|
host, portStr, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, proxy_socks5Version)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
|
if buf[1] == proxy_socks5AuthPassword {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, proxy_socks5IP4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, proxy_socks5IP6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, proxy_socks5Domain)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(proxy_socks5Errors) {
|
||||||
|
failure = proxy_socks5Errors[buf[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case proxy_socks5IP4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case proxy_socks5IP6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case proxy_socks5Domain:
|
||||||
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also need to discard the port number
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user