mirror of https://github.com/gorilla/websocket.git
Compare commits
47 Commits
523c8697a4
...
a7a56527e5
Author | SHA1 | Date |
---|---|---|
Allen | a7a56527e5 | |
Canelo Hill | 70bf50955e | |
Konstantin Burkalev | f78ed9f987 | |
Konstantin Burkalev | 17f407278f | |
mstmdev | efaec3cbd1 | |
Canelo Hill | 688592ebe6 | |
tebuka | 7e5e9b5a25 | |
merlin | 8890e3e578 | |
merlin | c7502098b0 | |
Canelo Hill | a70cea529a | |
Canelo Hill | ac1b326ac0 | |
Daniel Holmes | 227456c3cc | |
apoorvajagtap | ce903f6d1d | |
apoorvajagtap | 9ec25ca502 | |
apoorvajagtap | 1bddf2e0db | |
apoorvajagtap | 750bf92096 | |
apoorvajagtap | b2c246b2ec | |
apoorvajagtap | 09a6bab466 | |
apoorvajagtap | 58af150309 | |
apoorvajagtap | e5f1a0aad0 | |
Martin Greenwald | b2a86a1744 | |
Rumen Nikiforov | 695e9095ce | |
rfyiamcool | d293aa53e1 | |
rfyiamcool | 0cfb2cafd0 | |
rfyiamcool | 316861440d | |
rfyiamcool | d08ee1ad9b | |
rfyiamcool | d15aba1e61 | |
Kenjiro Nakayama | cf50a3efbb | |
Kenjiro Nakayama | 7d5b8cce7e | |
Kenjiro Nakayama | 4965080703 | |
Kenjiro Nakayama | 9a2140519a | |
merlin | 0f0acefeac | |
merlin | 1e975a03ce | |
merlin | 4a5e66f763 | |
Alan Xu | 871f6bbc74 | |
Brendan Creane | dcea2f088c | |
fengyun.rui | 01b0aaed92 | |
fengyun.rui | 286c896192 | |
Henrik Hautakoski | 6f5d2139f4 | |
mstmdev | 629990daa3 | |
mstmdev | b6a0d77c05 | |
Konstantin Burkalev | aa976062fe | |
Corey Daley | ac0789be11 | |
Corey Daley | 78c34874c8 | |
Corey Daley | 666c197fc9 | |
Corey Daley | a889672aa4 | |
AllenX2018 | aa46640059 |
|
@ -67,4 +67,4 @@ workflows:
|
||||||
- test:
|
- test:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
version: ["1.18", "1.17", "1.16"]
|
version: ["1.22", "1.21", "1.20"]
|
||||||
|
|
12
README.md
12
README.md
|
@ -10,11 +10,10 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
||||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
* [Chat example](https://github.com/gorilla/websocket/tree/main/examples/chat)
|
||||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
* [Command example](https://github.com/gorilla/websocket/tree/main/examples/command)
|
||||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
* [Client and server example](https://github.com/gorilla/websocket/tree/main/examples/echo)
|
||||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
* [File watch example](https://github.com/gorilla/websocket/tree/main/examples/filewatch)
|
||||||
* [Write buffer pool example](https://github.com/gorilla/websocket/tree/master/examples/bufferpool)
|
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
|
@ -30,5 +29,4 @@ package API is stable.
|
||||||
|
|
||||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
subdirectory](https://github.com/gorilla/websocket/tree/main/examples/autobahn).
|
||||||
|
|
||||||
|
|
10
client.go
10
client.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
|
@ -110,6 +109,9 @@ type Dialer struct {
|
||||||
// If Jar is nil, cookies are not sent in requests and ignored
|
// If Jar is nil, cookies are not sent in requests and ignored
|
||||||
// in responses.
|
// in responses.
|
||||||
Jar http.CookieJar
|
Jar http.CookieJar
|
||||||
|
|
||||||
|
// Custom proxy connect header
|
||||||
|
ProxyConnectHeader http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial creates a new client connection by calling DialContext with a background context.
|
// Dial creates a new client connection by calling DialContext with a background context.
|
||||||
|
@ -304,7 +306,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if proxyURL != nil {
|
if proxyURL != nil {
|
||||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
dialer, err := proxy_FromURL(proxyURL, &netDialer{d.ProxyConnectHeader, netDial})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -400,7 +402,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||||
// debugging.
|
// debugging.
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
n, _ := io.ReadFull(resp.Body, buf)
|
n, _ := io.ReadFull(resp.Body, buf)
|
||||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
resp.Body = io.NopCloser(bytes.NewReader(buf[:n]))
|
||||||
return nil, resp, ErrBadHandshake
|
return nil, resp, ErrBadHandshake
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +420,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
|
||||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
|
||||||
netConn.SetDeadline(time.Time{})
|
netConn.SetDeadline(time.Time{})
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -24,6 +23,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -45,12 +45,15 @@ var cstDialer = Dialer{
|
||||||
HandshakeTimeout: 30 * time.Second,
|
HandshakeTimeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
type cstHandler struct{ *testing.T }
|
type cstHandler struct {
|
||||||
|
*testing.T
|
||||||
|
s *cstServer
|
||||||
|
}
|
||||||
|
|
||||||
type cstServer struct {
|
type cstServer struct {
|
||||||
*httptest.Server
|
|
||||||
URL string
|
URL string
|
||||||
t *testing.T
|
Server *httptest.Server
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -59,9 +62,15 @@ const (
|
||||||
cstRequestURI = cstPath + "?" + cstRawQuery
|
cstRequestURI = cstPath + "?" + cstRawQuery
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s *cstServer) Close() {
|
||||||
|
s.Server.Close()
|
||||||
|
// Wait for handler functions to complete.
|
||||||
|
s.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func newServer(t *testing.T) *cstServer {
|
func newServer(t *testing.T) *cstServer {
|
||||||
var s cstServer
|
var s cstServer
|
||||||
s.Server = httptest.NewServer(cstHandler{t})
|
s.Server = httptest.NewServer(cstHandler{T: t, s: &s})
|
||||||
s.Server.URL += cstRequestURI
|
s.Server.URL += cstRequestURI
|
||||||
s.URL = makeWsProto(s.Server.URL)
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
return &s
|
return &s
|
||||||
|
@ -69,13 +78,19 @@ func newServer(t *testing.T) *cstServer {
|
||||||
|
|
||||||
func newTLSServer(t *testing.T) *cstServer {
|
func newTLSServer(t *testing.T) *cstServer {
|
||||||
var s cstServer
|
var s cstServer
|
||||||
s.Server = httptest.NewTLSServer(cstHandler{t})
|
s.Server = httptest.NewTLSServer(cstHandler{T: t, s: &s})
|
||||||
s.Server.URL += cstRequestURI
|
s.Server.URL += cstRequestURI
|
||||||
s.URL = makeWsProto(s.Server.URL)
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Because tests wait for a response from a server, we are guaranteed that
|
||||||
|
// the wait group count is incremented before the test waits on the group
|
||||||
|
// in the call to (*cstServer).Close().
|
||||||
|
t.s.wg.Add(1)
|
||||||
|
defer t.s.wg.Done()
|
||||||
|
|
||||||
if r.URL.Path != cstPath {
|
if r.URL.Path != cstPath {
|
||||||
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
|
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
|
||||||
http.Error(w, "bad path", http.StatusBadRequest)
|
http.Error(w, "bad path", http.StatusBadRequest)
|
||||||
|
@ -157,6 +172,9 @@ func TestProxyDial(t *testing.T) {
|
||||||
|
|
||||||
cstDialer := cstDialer // make local copy for modification on next line.
|
cstDialer := cstDialer // make local copy for modification on next line.
|
||||||
cstDialer.Proxy = http.ProxyURL(surl)
|
cstDialer.Proxy = http.ProxyURL(surl)
|
||||||
|
cstDialer.ProxyConnectHeader = map[string][]string{
|
||||||
|
"User-Agents": {"xxx"},
|
||||||
|
}
|
||||||
|
|
||||||
connect := false
|
connect := false
|
||||||
origHandler := s.Server.Config.Handler
|
origHandler := s.Server.Config.Handler
|
||||||
|
@ -167,6 +185,10 @@ func TestProxyDial(t *testing.T) {
|
||||||
if r.Method == http.MethodConnect {
|
if r.Method == http.MethodConnect {
|
||||||
connect = true
|
connect = true
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if r.Header.Get("User-Agents") != "xxx" {
|
||||||
|
t.Log("xxx not found in the request header")
|
||||||
|
http.Error(w, "header xxx not found", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +571,7 @@ func TestRespOnBadHandshake(t *testing.T) {
|
||||||
t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
|
t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := ioutil.ReadAll(resp.Body)
|
p, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadFull(resp.Body) returned error %v", err)
|
t.Fatalf("ReadFull(resp.Body) returned error %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +41,7 @@ func textMessages(num int) [][]byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkWriteNoCompression(b *testing.B) {
|
func BenchmarkWriteNoCompression(b *testing.B) {
|
||||||
w := ioutil.Discard
|
w := io.Discard
|
||||||
c := newTestConn(nil, w, false)
|
c := newTestConn(nil, w, false)
|
||||||
messages := textMessages(100)
|
messages := textMessages(100)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -53,7 +52,7 @@ func BenchmarkWriteNoCompression(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkWriteWithCompression(b *testing.B) {
|
func BenchmarkWriteWithCompression(b *testing.B) {
|
||||||
w := ioutil.Discard
|
w := io.Discard
|
||||||
c := newTestConn(nil, w, false)
|
c := newTestConn(nil, w, false)
|
||||||
messages := textMessages(100)
|
messages := textMessages(100)
|
||||||
c.enableWriteCompression = true
|
c.enableWriteCompression = true
|
||||||
|
|
5
conn.go
5
conn.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -795,7 +794,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||||
// 1. Skip remainder of previous frame.
|
// 1. Skip remainder of previous frame.
|
||||||
|
|
||||||
if c.readRemaining > 0 {
|
if c.readRemaining > 0 {
|
||||||
if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil {
|
if _, err := io.CopyN(io.Discard, c.br, c.readRemaining); err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1094,7 +1093,7 @@ func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return messageType, nil, err
|
return messageType, nil, err
|
||||||
}
|
}
|
||||||
p, err = ioutil.ReadAll(r)
|
p, err = io.ReadAll(r)
|
||||||
return messageType, p, err
|
return messageType, p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +44,7 @@ func newBroadcastConn(c *Conn) *broadcastConn {
|
||||||
|
|
||||||
func newBroadcastBench(usePrepared, compression bool) *broadcastBench {
|
func newBroadcastBench(usePrepared, compression bool) *broadcastBench {
|
||||||
bench := &broadcastBench{
|
bench := &broadcastBench{
|
||||||
w: ioutil.Discard,
|
w: io.Discard,
|
||||||
doneCh: make(chan struct{}),
|
doneCh: make(chan struct{}),
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
usePrepared: usePrepared,
|
usePrepared: usePrepared,
|
||||||
|
|
11
conn_test.go
11
conn_test.go
|
@ -10,7 +10,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -125,7 +124,7 @@ func TestFraming(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("frame size: %d", n)
|
t.Logf("frame size: %d", n)
|
||||||
rbuf, err := ioutil.ReadAll(r)
|
rbuf, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s: ReadFull() returned rbuf, %v", name, err)
|
t.Errorf("%s: ReadFull() returned rbuf, %v", name, err)
|
||||||
continue
|
continue
|
||||||
|
@ -367,7 +366,7 @@ func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) {
|
||||||
if op != BinaryMessage || err != nil {
|
if op != BinaryMessage || err != nil {
|
||||||
t.Fatalf("NextReader() returned %d, %v", op, err)
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, r)
|
_, err = io.Copy(io.Discard, r)
|
||||||
if !reflect.DeepEqual(err, expectedErr) {
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
|
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
|
||||||
}
|
}
|
||||||
|
@ -401,7 +400,7 @@ func TestEOFWithinFrame(t *testing.T) {
|
||||||
if op != BinaryMessage || err != nil {
|
if op != BinaryMessage || err != nil {
|
||||||
t.Fatalf("%d: NextReader() returned %d, %v", n, op, err)
|
t.Fatalf("%d: NextReader() returned %d, %v", n, op, err)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, r)
|
_, err = io.Copy(io.Discard, r)
|
||||||
if err != errUnexpectedEOF {
|
if err != errUnexpectedEOF {
|
||||||
t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF)
|
t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF)
|
||||||
}
|
}
|
||||||
|
@ -426,7 +425,7 @@ func TestEOFBeforeFinalFrame(t *testing.T) {
|
||||||
if op != BinaryMessage || err != nil {
|
if op != BinaryMessage || err != nil {
|
||||||
t.Fatalf("NextReader() returned %d, %v", op, err)
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, r)
|
_, err = io.Copy(io.Discard, r)
|
||||||
if err != errUnexpectedEOF {
|
if err != errUnexpectedEOF {
|
||||||
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
|
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
|
||||||
}
|
}
|
||||||
|
@ -490,7 +489,7 @@ func TestReadLimit(t *testing.T) {
|
||||||
if op != BinaryMessage || err != nil {
|
if op != BinaryMessage || err != nil {
|
||||||
t.Fatalf("2: NextReader() returned %d, %v", op, err)
|
t.Fatalf("2: NextReader() returned %d, %v", op, err)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, r)
|
_, err = io.Copy(io.Discard, r)
|
||||||
if err != ErrReadLimit {
|
if err != ErrReadLimit {
|
||||||
t.Fatalf("io.Copy() returned %v", err)
|
t.Fatalf("io.Copy() returned %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ func echoCopyFull(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// echoReadAll echoes messages from the client by reading the entire message
|
// echoReadAll echoes messages from the client by reading the entire message
|
||||||
// with ioutil.ReadAll.
|
// with io.ReadAll.
|
||||||
func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) {
|
func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) {
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
//go:build ignore
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var addr = flag.String("addr", "localhost:8080", "http service address")
|
|
||||||
|
|
||||||
func runNewConn(wg *sync.WaitGroup) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
interrupt := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(interrupt, os.Interrupt)
|
|
||||||
|
|
||||||
u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"}
|
|
||||||
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 close(done)
|
|
||||||
for {
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("read:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("recv: %s", message)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Minute * 5)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
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")
|
|
||||||
|
|
||||||
// Cleanly close the connection by sending a close message and then
|
|
||||||
// waiting (with timeout) 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):
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
log.SetFlags(0)
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go runNewConn(wg)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
//go:build ignore
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "net/http/pprof"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var addr = flag.String("addr", "localhost:8080", "http service address")
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 256,
|
|
||||||
WriteBufferSize: 256,
|
|
||||||
WriteBufferPool: &sync.Pool{},
|
|
||||||
}
|
|
||||||
|
|
||||||
func process(c *websocket.Conn) {
|
|
||||||
defer c.Close()
|
|
||||||
for {
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("read:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Printf("recv: %s", message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("upgrade:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process connection in a new goroutine
|
|
||||||
go process(c)
|
|
||||||
|
|
||||||
// Let the http handler return, the 8k buffer created by it will be garbage collected
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
log.SetFlags(0)
|
|
||||||
http.HandleFunc("/ws", handler)
|
|
||||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
|
||||||
}
|
|
|
@ -38,7 +38,7 @@ sends them to the hub.
|
||||||
### Hub
|
### Hub
|
||||||
|
|
||||||
The code for the `Hub` type is in
|
The code for the `Hub` type is in
|
||||||
[hub.go](https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go).
|
[hub.go](https://github.com/gorilla/websocket/blob/main/examples/chat/hub.go).
|
||||||
The application's `main` function starts the hub's `run` method as a goroutine.
|
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
|
Clients send requests to the hub using the `register`, `unregister` and
|
||||||
`broadcast` channels.
|
`broadcast` channels.
|
||||||
|
@ -57,7 +57,7 @@ unregisters the client and closes the websocket.
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
||||||
The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/master/examples/chat/client.go).
|
The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/main/examples/chat/client.go).
|
||||||
|
|
||||||
The `serveWs` function is registered by the application's `main` function as
|
The `serveWs` function is registered by the application's `main` function as
|
||||||
an HTTP handler. The handler upgrades the HTTP connection to the WebSocket
|
an HTTP handler. The handler upgrades the HTTP connection to the WebSocket
|
||||||
|
@ -85,7 +85,7 @@ network.
|
||||||
|
|
||||||
## Frontend
|
## Frontend
|
||||||
|
|
||||||
The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/master/examples/chat/home.html).
|
The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/main/examples/chat/home.html).
|
||||||
|
|
||||||
On document load, the script checks for websocket functionality in the browser.
|
On document load, the script checks for websocket functionality in the browser.
|
||||||
If websocket functionality is available, then the script opens a connection to
|
If websocket functionality is available, then the script opens a connection to
|
||||||
|
|
|
@ -7,7 +7,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -49,7 +48,7 @@ func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
|
||||||
if !fi.ModTime().After(lastMod) {
|
if !fi.ModTime().After(lastMod) {
|
||||||
return nil, lastMod, nil
|
return nil, lastMod, nil
|
||||||
}
|
}
|
||||||
p, err := ioutil.ReadFile(filename)
|
p, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fi.ModTime(), err
|
return nil, fi.ModTime(), err
|
||||||
}
|
}
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -1,3 +1,7 @@
|
||||||
module github.com/gorilla/websocket
|
module github.com/gorilla/websocket
|
||||||
|
|
||||||
go 1.12
|
go 1.20
|
||||||
|
|
||||||
|
retract (
|
||||||
|
v1.5.2 // tag accidentally overwritten
|
||||||
|
)
|
||||||
|
|
35
proxy.go
35
proxy.go
|
@ -6,6 +6,7 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
@ -14,21 +15,29 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type netDialerFunc func(network, addr string) (net.Conn, error)
|
type netDialer struct {
|
||||||
|
proxyHeader http.Header
|
||||||
|
f func(network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
func (n netDialer) Dial(network, addr string) (net.Conn, error) {
|
||||||
return fn(network, addr)
|
return n.f(network, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||||
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
p, ok := forwardDialer.(*netDialer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("type assertion failed when ini proxy info")
|
||||||
|
}
|
||||||
|
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial, proxyHeader: p.proxyHeader}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpProxyDialer struct {
|
type httpProxyDialer struct {
|
||||||
proxyURL *url.URL
|
proxyURL *url.URL
|
||||||
forwardDial func(network, addr string) (net.Conn, error)
|
forwardDial func(network, addr string) (net.Conn, error)
|
||||||
|
proxyHeader http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||||
|
@ -47,6 +56,10 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k, v := range hpd.proxyHeader {
|
||||||
|
connectHeader[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
connectReq := &http.Request{
|
connectReq := &http.Request{
|
||||||
Method: http.MethodConnect,
|
Method: http.MethodConnect,
|
||||||
URL: &url.URL{Opaque: addr},
|
URL: &url.URL{Opaque: addr},
|
||||||
|
@ -68,8 +81,18 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
// Close the response body to silence false positives from linters. Reset
|
||||||
conn.Close()
|
// the buffered reader first to ensure that Close() does not read from
|
||||||
|
// conn.
|
||||||
|
// Note: Applications must call resp.Body.Close() on a response returned
|
||||||
|
// http.ReadResponse to inspect trailers or read another response from the
|
||||||
|
// buffered reader. The call to resp.Body.Close() does not release
|
||||||
|
// resources.
|
||||||
|
br.Reset(bytes.NewReader(nil))
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
_ = conn.Close()
|
||||||
f := strings.SplitN(resp.Status, " ", 2)
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
return nil, errors.New(f[1])
|
return nil, errors.New(f[1])
|
||||||
}
|
}
|
||||||
|
|
12
server.go
12
server.go
|
@ -101,8 +101,8 @@ func checkSameOrigin(r *http.Request) bool {
|
||||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||||
if u.Subprotocols != nil {
|
if u.Subprotocols != nil {
|
||||||
clientProtocols := Subprotocols(r)
|
clientProtocols := Subprotocols(r)
|
||||||
for _, serverProtocol := range u.Subprotocols {
|
|
||||||
for _, clientProtocol := range clientProtocols {
|
for _, clientProtocol := range clientProtocols {
|
||||||
|
for _, serverProtocol := range u.Subprotocols {
|
||||||
if clientProtocol == serverProtocol {
|
if clientProtocol == serverProtocol {
|
||||||
return clientProtocol
|
return clientProtocol
|
||||||
}
|
}
|
||||||
|
@ -172,14 +172,10 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h, ok := w.(http.Hijacker)
|
netConn, brw, err := http.NewResponseController(w).Hijack()
|
||||||
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 {
|
if err != nil {
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
return u.returnError(w, r, http.StatusInternalServerError,
|
||||||
|
"websocket: hijack: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if brw.Reader.Buffered() > 0 {
|
if brw.Reader.Buffered() > 0 {
|
||||||
|
|
|
@ -7,8 +7,10 @@ package websocket
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -54,6 +56,36 @@ func TestIsWebSocketUpgrade(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubProtocolSelection(t *testing.T) {
|
||||||
|
upgrader := Upgrader{
|
||||||
|
Subprotocols: []string{"foo", "bar", "baz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {"foo", "bar"}}}
|
||||||
|
s := upgrader.selectSubprotocol(&r, nil)
|
||||||
|
if s != "foo" {
|
||||||
|
t.Errorf("Upgrader.selectSubprotocol returned %v, want %v", s, "foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = http.Request{Header: http.Header{"Sec-Websocket-Protocol": {"bar", "foo"}}}
|
||||||
|
s = upgrader.selectSubprotocol(&r, nil)
|
||||||
|
if s != "bar" {
|
||||||
|
t.Errorf("Upgrader.selectSubprotocol returned %v, want %v", s, "bar")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = http.Request{Header: http.Header{"Sec-Websocket-Protocol": {"baz"}}}
|
||||||
|
s = upgrader.selectSubprotocol(&r, nil)
|
||||||
|
if s != "baz" {
|
||||||
|
t.Errorf("Upgrader.selectSubprotocol returned %v, want %v", s, "baz")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = http.Request{Header: http.Header{"Sec-Websocket-Protocol": {"quux"}}}
|
||||||
|
s = upgrader.selectSubprotocol(&r, nil)
|
||||||
|
if s != "" {
|
||||||
|
t.Errorf("Upgrader.selectSubprotocol returned %v, want %v", s, "empty string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var checkSameOriginTests = []struct {
|
var checkSameOriginTests = []struct {
|
||||||
ok bool
|
ok bool
|
||||||
r *http.Request
|
r *http.Request
|
||||||
|
@ -117,3 +149,23 @@ func TestBufioReuse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHijack_NotSupported(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
req.Header.Set("Upgrade", "websocket")
|
||||||
|
req.Header.Set("Connection", "upgrade")
|
||||||
|
req.Header.Set("Sec-Websocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
|
||||||
|
req.Header.Set("Sec-Websocket-Version", "13")
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
upgrader := Upgrader{}
|
||||||
|
_, err := upgrader.Upgrade(recorder, req, nil)
|
||||||
|
|
||||||
|
if want := (HandshakeError{}); !errors.As(err, &want) || recorder.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("want %T and status_code=%d", want, http.StatusInternalServerError)
|
||||||
|
t.Fatalf("got err=%T and status_code=%d", err, recorder.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue