├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── autobahn
├── fuzzingclient.json
├── go.mod
├── go.sum
└── server.go
├── bytes.go
├── client.go
├── client_test.go
├── conn.go
├── conn_test.go
├── error.go
├── examples
├── broadcast
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── broadcast_groups
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── broadcast_nethttp
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── client
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── whitelist
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── frame.go
├── frame_test.go
├── go.mod
├── go.sum
├── mask.go
├── mask_test.go
├── server.go
├── server_timing_test.go
├── stress-tests
├── dgrr.go
├── dgrr_net.go
├── gobwas.go
├── gorilla.go
└── nhooyr.go
├── strings.go
├── upgrader.go
├── upgrader_test.go
├── utils.go
└── utils_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | vendor/
8 | .idea/
9 |
10 | # Test binary, build with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 | autobahn/reports
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Darío
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | target: ;
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # websocket
2 |
3 | WebSocket library for [fasthttp](https://github.com/valyala/fasthttp) and [net/http](https://pkg.go.dev/net/http).
4 |
5 | Checkout [examples](https://github.com/dgrr/websocket/blob/master/examples) to inspire yourself.
6 |
7 | # Install
8 |
9 | ```bash
10 | go get github.com/dgrr/websocket
11 | ```
12 |
13 | # Why another WebSocket package?
14 |
15 | **Other WebSocket packages DON'T** allow concurrent Read/Write operations on servers
16 | and they do not provide low level access to WebSocket packet crafting.
17 | Those WebSocket packages try to emulate the Golang API by implementing
18 | io.Reader and io.Writer interfaces on their connections. io.Writer might be a
19 | good idea to use it, but no io.Reader, given that WebSocket is an async protocol
20 | by nature (all protocols are (?)).
21 |
22 | Sometimes, WebSocket servers are just cumbersome when we want to handle a lot of
23 | clients in an async manner. For example, **in other WebSocket packages to broadcast
24 | a message generated internally we'll need to do the following**:
25 |
26 | ```go
27 | type MyWebSocketService struct {
28 | clients sync.Map
29 | }
30 |
31 | type BlockingConn struct {
32 | lck sync.Mutex
33 | c websocketPackage.Conn
34 | }
35 |
36 | func (ws *MyWebSocketService) Broadcaster() {
37 | for msg := range messageProducerChannel {
38 | ws.clients.Range(func(_, v interface{}) bool {
39 | c := v.(*BlockingConn)
40 | c.lck.Lock() // oh, we need to block, otherwise we can break the program
41 | err := c.Write(msg)
42 | c.lck.Unlock()
43 |
44 | if err != nil {
45 | // we have an error, what can we do? Log it?
46 | // if the connection has been closed we'll receive that on
47 | // the Read call, so the connection will close automatically.
48 | }
49 |
50 | return true
51 | })
52 | }
53 | }
54 |
55 | func (ws *MyWebSocketService) Handle(request, response) {
56 | c, err := websocketPackage.Upgrade(request, response)
57 | if err != nil {
58 | // then it's clearly an error! Report back
59 | }
60 |
61 | bc := &BlockingConn{
62 | c: c,
63 | }
64 |
65 | ws.clients.Store(bc, struct{}{})
66 |
67 | // even though I just want to write, I need to block somehow
68 | for {
69 | content, err := bc.Read()
70 | if err != nil {
71 | // handle the error
72 | break
73 | }
74 | }
75 |
76 | ws.clients.Delete(bc)
77 | }
78 | ```
79 |
80 | First, we need to store every client upon connection,
81 | and whenever we want to send data we need to iterate over a list, and send the message.
82 | If while, writing we get an error, then we need to handle that client's error
83 | What if the writing operation is happening at the same time in 2 different coroutines?
84 | Then we need a sync.Mutex and block until we finish writing.
85 |
86 | To solve most of those problems [websocket](https://github.com/dgrr/websocket)
87 | uses channels and separated coroutines, one for reading and another one for writing.
88 | By following the [sharing principle](https://golang.org/doc/effective_go#sharing).
89 |
90 | > Do not communicate by sharing memory; instead, share memory by communicating.
91 |
92 | Following the fasthttp philosophy this library tries to take as much advantage
93 | of the Golang's multi-threaded model as possible,
94 | while keeping your code concurrently safe.
95 |
96 | To see an example of what this package CAN do that others DONT checkout [the broadcast example](https://github.com/dgrr/websocket/blob/master/examples/broadcast/main.go).
97 |
98 | # Server
99 |
100 | ## How can I launch a server?
101 |
102 | It's quite easy. You only need to create a [Server](https://pkg.go.dev/github.com/dgrr/websocket?utm_source=godoc#Server),
103 | set your callbacks by calling the [Handle*](https://pkg.go.dev/github.com/dgrr/websocket?utm_source=godoc#Server.HandleClose) methods
104 | and then specify your fasthttp handler as [Server.Upgrade](https://pkg.go.dev/github.com/dgrr/websocket?utm_source=godoc#Server.Upgrade).
105 |
106 | ```go
107 | package main
108 |
109 | import (
110 | "fmt"
111 |
112 | "github.com/valyala/fasthttp"
113 | "github.com/dgrr/websocket"
114 | )
115 |
116 | func main() {
117 | ws := websocket.Server{}
118 | ws.HandleData(OnMessage)
119 |
120 | fasthttp.ListenAndServe(":8080", ws.Upgrade)
121 | }
122 |
123 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
124 | fmt.Printf("Received data from %s: %s\n", c.RemoteAddr(), data)
125 | }
126 | ```
127 |
128 | ## How can I launch a server if I use net/http?
129 |
130 | ```go
131 | package main
132 |
133 | import (
134 | "fmt"
135 | "net/http"
136 |
137 | "github.com/dgrr/websocket"
138 | )
139 |
140 | func main() {
141 | ws := websocket.Server{}
142 | ws.HandleData(OnMessage)
143 |
144 | http.HandleFunc("/", ws.NetUpgrade)
145 | http.ListenAndServe(":8080", nil)
146 | }
147 |
148 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
149 | fmt.Printf("Received data from %s: %s\n", c.RemoteAddr(), data)
150 | }
151 | ```
152 |
153 | ## How can I handle pings?
154 |
155 | Pings are handle automatically by the library, but you can get the content of
156 | those pings setting the callback using [HandlePing](https://pkg.go.dev/github.com/dgrr/websocket?utm_source=godoc#Server.HandlePing).
157 |
158 | For example, let's try to get the round trip time to a client by using
159 | the PING frame. The website [http2.gofiber.io](https://http2.gofiber.io)
160 | uses this method to measure the round trip time displayed at the bottom of the webpage.
161 |
162 | ```go
163 | package main
164 |
165 | import (
166 | "sync"
167 | "encoding/binary"
168 | "log"
169 | "time"
170 |
171 | "github.com/valyala/fasthttp"
172 | "github.com/dgrr/websocket"
173 | )
174 |
175 | // Struct to keep the clients connected
176 | //
177 | // it should be safe to access the clients concurrently from Open and Close.
178 | type RTTMeasure struct {
179 | clients sync.Map
180 | }
181 |
182 | // just trigger the ping sender
183 | func (rtt *RTTMeasure) Start() {
184 | time.AfterFunc(time.Second * 2, rtt.sendPings)
185 | }
186 |
187 | func (rtt *RTTMeasure) sendPings() {
188 | var data [8]byte
189 |
190 | binary.BigEndian.PutUint64(data[:], uint64(
191 | time.Now().UnixNano()),
192 | )
193 |
194 | rtt.clients.Range(func(_, v interface{}) bool {
195 | c := v.(*websocket.Conn)
196 | c.Ping(data[:])
197 | return true
198 | })
199 |
200 | rtt.Start()
201 | }
202 |
203 | // register a connection when it's open
204 | func (rtt *RTTMeasure) RegisterConn(c *websocket.Conn) {
205 | rtt.clients.Store(c.ID(), c)
206 | log.Printf("Client %s connected\n", c.RemoteAddr())
207 | }
208 |
209 | // remove the connection when receiving the close
210 | func (rtt *RTTMeasure) RemoveConn(c *websocket.Conn, err error) {
211 | rtt.clients.Delete(c.ID())
212 | log.Printf("Client %s disconnected\n", c.RemoteAddr())
213 | }
214 |
215 | func main() {
216 | rtt := RTTMeasure{}
217 |
218 | ws := websocket.Server{}
219 | ws.HandleOpen(rtt.RegisterConn)
220 | ws.HandleClose(rtt.RemoveConn)
221 | ws.HandlePong(OnPong)
222 |
223 | // schedule the timer
224 | rtt.Start()
225 |
226 | fasthttp.ListenAndServe(":8080", ws.Upgrade)
227 | }
228 |
229 | // handle the pong message
230 | func OnPong(c *websocket.Conn, data []byte) {
231 | if len(data) == 8 {
232 | n := binary.BigEndian.Uint64(data)
233 | ts := time.Unix(0, int64(n))
234 |
235 | log.Printf("RTT with %s is %s\n", c.RemoteAddr(), time.Now().Sub(ts))
236 | }
237 | }
238 | ```
239 |
240 | # websocket vs gorilla vs nhooyr vs gobwas
241 |
242 | | Features | [websocket](https://github.com/dgrr/websocket) | [Gorilla](https://github.com/fasthttp/websocket)| [Nhooyr](https://github.com/nhooyr/websocket) | [gowabs](https://github.com/gobwas/ws) |
243 | | --- | --- | --- | --- | --- |
244 | | Concurrent R/W | Yes | No | No. Only writes | No |
245 | | Passes Autobahn Test Suite | Mostly | Yes | Yes | Mostly |
246 | | Receive fragmented message | Yes | Yes | Yes | Yes |
247 | | Send close message | Yes | Yes | Yes | Yes |
248 | | Send pings and receive pongs | Yes | Yes | Yes | Yes |
249 | | Get the type of a received data message | Yes | Yes | Yes | Yes |
250 | | Compression Extensions | No | Experimental | Yes | No (?) |
251 | | Read message using io.Reader | No | Yes | No | No (?) |
252 | | Write message using io.WriteCloser | Yes | Yes | No | No (?) |
253 |
254 | # Stress tests
255 |
256 | The following stress test were performed without timeouts:
257 |
258 | Executing `tcpkali --ws -c 100 -m 'hello world!!13212312!' -r 10k localhost:8081` the tests shows the following:
259 |
260 | ### Websocket:
261 | ```
262 | Total data sent: 267.7 MiB (280678466 bytes)
263 | Total data received: 229.5 MiB (240626600 bytes)
264 | Bandwidth per channel: 4.167⇅ Mbps (520.9 kBps)
265 | Aggregate bandwidth: 192.357↓, 224.375↑ Mbps
266 | Packet rate estimate: 247050.1↓, 61842.9↑ (1↓, 1↑ TCP MSS/op)
267 | Test duration: 10.0075 s.
268 | ```
269 |
270 | ### Websocket for net/http:
271 | ```
272 | Total data sent: 267.3 MiB (280320124 bytes)
273 | Total data received: 228.3 MiB (239396374 bytes)
274 | Bandwidth per channel: 4.156⇅ Mbps (519.5 kBps)
275 | Aggregate bandwidth: 191.442↓, 224.168↑ Mbps
276 | Packet rate estimate: 188107.1↓, 52240.7↑ (1↓, 1↑ TCP MSS/op)
277 | Test duration: 10.0039 s.
278 | ```
279 |
280 | Either for fasthttp and net/http should be quite close,
281 | the only difference is the way they both upgrade.
282 |
283 | ### Gorilla:
284 | ```
285 | Total data sent: 260.2 MiB (272886768 bytes)
286 | Total data received: 109.3 MiB (114632982 bytes)
287 | Bandwidth per channel: 3.097⇅ Mbps (387.1 kBps)
288 | Aggregate bandwidth: 91.615↓, 218.092↑ Mbps
289 | Packet rate estimate: 109755.3↓, 66807.4↑ (1↓, 1↑ TCP MSS/op)
290 | Test duration: 10.01 s.
291 | ```
292 |
293 | ### Nhooyr: (Don't know why is that low)
294 | ```
295 | Total data sent: 224.3 MiB (235184096 bytes)
296 | Total data received: 41.2 MiB (43209780 bytes)
297 | Bandwidth per channel: 2.227⇅ Mbps (278.3 kBps)
298 | Aggregate bandwidth: 34.559↓, 188.097↑ Mbps
299 | Packet rate estimate: 88474.0↓, 55256.1↑ (1↓, 1↑ TCP MSS/op)
300 | Test duration: 10.0027 s.
301 | ```
302 |
303 | ### Gobwas:
304 | ```
305 | Total data sent: 265.8 MiB (278718160 bytes)
306 | Total data received: 117.8 MiB (123548959 bytes)
307 | Bandwidth per channel: 3.218⇅ Mbps (402.2 kBps)
308 | Aggregate bandwidth: 98.825↓, 222.942↑ Mbps
309 | Packet rate estimate: 148231.6↓, 72106.1↑ (1↓, 1↑ TCP MSS/op)
310 | Test duration: 10.0015 s.
311 | ```
312 |
313 | The source files are in [this](https://github.com/dgrr/websocket/tree/master/stress-tests/) folder.
314 |
--------------------------------------------------------------------------------
/autobahn/fuzzingclient.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "options": {"failByDrop": false},
4 | "outdir": "./reports/clients",
5 | "servers": [
6 | {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}},
7 | {"agent": "ReadAllWritePreparedMessage", "url": "ws://localhost:9000/p", "options": {"version": 18}},
8 | {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}},
9 | {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}},
10 | {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}}
11 | ],
12 | "cases": ["*"],
13 | "exclude-cases": [],
14 | "exclude-agent-cases": {}
15 | }
16 |
--------------------------------------------------------------------------------
/autobahn/go.mod:
--------------------------------------------------------------------------------
1 | module broadcast
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.7
7 | github.com/valyala/fasthttp v1.28.0
8 | )
9 |
--------------------------------------------------------------------------------
/autobahn/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgrr/websocket v0.0.5 h1:gYxn5sjv7P1LJzqEfuJNp6VHSBO4DmiXQ3AkePu7d3c=
7 | github.com/dgrr/websocket v0.0.5/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
8 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
9 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
10 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
11 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
12 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
13 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
14 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
15 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
16 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
17 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
18 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
19 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
20 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
21 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
22 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
23 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
24 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
25 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
26 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
28 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
29 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
30 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
31 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
32 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
34 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
35 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
36 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
37 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
38 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
39 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
40 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
41 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
42 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
43 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
44 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
45 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
46 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
48 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
49 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
53 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
54 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
55 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
56 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
57 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
58 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
59 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
60 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
61 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
62 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
63 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
64 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
65 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
66 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
67 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
68 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
69 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
72 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
74 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
75 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
76 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
77 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
78 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
83 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
84 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
85 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
86 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
87 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
88 |
--------------------------------------------------------------------------------
/autobahn/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/dgrr/websocket"
5 |
6 | "github.com/valyala/fasthttp"
7 | )
8 |
9 | func main() {
10 | ws := websocket.Server{}
11 |
12 | ws.HandleData(wsHandler)
13 |
14 | fasthttp.ListenAndServe(":9000", ws.Upgrade)
15 | }
16 |
17 | func wsHandler(c *websocket.Conn, isBinary bool, data []byte) {
18 | c.Write(data)
19 | }
20 |
--------------------------------------------------------------------------------
/bytes.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import "sync"
4 |
5 | var bytePool = sync.Pool{
6 | New: func() interface{} {
7 | return make([]byte, 128)
8 | },
9 | }
10 |
11 | func extendByteSlice(b []byte, needLen int) []byte {
12 | b = b[:cap(b)]
13 |
14 | if n := needLen - cap(b); n > 0 {
15 | b = append(b, make([]byte, n)...)
16 | }
17 |
18 | return b[:needLen]
19 | }
20 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/rand"
7 | "crypto/tls"
8 | "errors"
9 | "net"
10 | "time"
11 |
12 | "github.com/valyala/fasthttp"
13 | )
14 |
15 | var (
16 | // ErrCannotUpgrade shows up when an error occurred when upgrading a connection.
17 | ErrCannotUpgrade = errors.New("cannot upgrade connection")
18 | )
19 |
20 | // MakeClient returns Conn using an existing connection.
21 | //
22 | // url must be a complete URL format i.e. http://localhost:8080/ws
23 | func MakeClient(c net.Conn, url string) (*Client, error) {
24 | return client(c, url, nil)
25 | }
26 |
27 | // ClientWithHeaders returns a Conn using an existing connection and sending custom headers.
28 | func ClientWithHeaders(c net.Conn, url string, req *fasthttp.Request) (*Client, error) {
29 | return client(c, url, req)
30 | }
31 |
32 | // UpgradeAsClient will upgrade the connection as a client
33 | //
34 | // This function should be used with connections that intend to use a
35 | // plain framing.
36 | //
37 | // r can be nil.
38 | func UpgradeAsClient(c net.Conn, url string, r *fasthttp.Request) error {
39 | req := fasthttp.AcquireRequest()
40 | res := fasthttp.AcquireResponse()
41 | uri := fasthttp.AcquireURI()
42 |
43 | defer fasthttp.ReleaseRequest(req)
44 | defer fasthttp.ReleaseResponse(res)
45 | defer fasthttp.ReleaseURI(uri)
46 |
47 | uri.Update(url)
48 |
49 | origin := bytePool.Get().([]byte)
50 | key := bytePool.Get().([]byte)
51 | defer bytePool.Put(origin)
52 | defer bytePool.Put(key)
53 |
54 | origin = prepareOrigin(origin, uri)
55 | key = makeRandKey(key[:0])
56 |
57 | if r != nil {
58 | r.CopyTo(req)
59 | }
60 |
61 | req.Header.SetMethod("GET")
62 | req.Header.AddBytesKV(originString, origin)
63 | req.Header.AddBytesKV(connectionString, upgradeString)
64 | req.Header.AddBytesKV(upgradeString, websocketString)
65 | req.Header.AddBytesKV(wsHeaderVersion, supportedVersions[0])
66 | req.Header.AddBytesKV(wsHeaderKey, key)
67 | // TODO: Add compression
68 |
69 | req.SetRequestURIBytes(uri.FullURI())
70 |
71 | br := bufio.NewReader(c)
72 | bw := bufio.NewWriter(c)
73 | req.Write(bw)
74 | bw.Flush()
75 |
76 | err := res.Read(br)
77 | if err == nil {
78 | if res.StatusCode() != 101 ||
79 | !equalsFold(res.Header.PeekBytes(upgradeString), websocketString) {
80 | err = ErrCannotUpgrade
81 | }
82 | }
83 |
84 | return err
85 | }
86 |
87 | func client(c net.Conn, url string, r *fasthttp.Request) (cl *Client, err error) {
88 | err = UpgradeAsClient(c, url, r)
89 | if err == nil {
90 | cl = &Client{
91 | c: c,
92 | brw: bufio.NewReadWriter(
93 | bufio.NewReader(c), bufio.NewWriter(c)),
94 | }
95 | }
96 |
97 | return cl, err
98 | }
99 |
100 | // Dial establishes a websocket connection as client.
101 | //
102 | // url parameter must follow the WebSocket URL format i.e. ws://host:port/path
103 | func Dial(url string) (*Client, error) {
104 | cnf := &tls.Config{
105 | InsecureSkipVerify: false,
106 | MinVersion: tls.VersionTLS11,
107 | MaxVersion: tls.VersionTLS13,
108 | }
109 |
110 | return dial(url, cnf, nil)
111 | }
112 |
113 | // DialTLS establishes a websocket connection as client with the
114 | // tls.Config. The config will be used if the URL is wss:// like.
115 | func DialTLS(url string, cnf *tls.Config) (*Client, error) {
116 | return dial(url, cnf, nil)
117 | }
118 |
119 | // DialWithHeaders establishes a websocket connection as client sending a personalized request.
120 | func DialWithHeaders(url string, req *fasthttp.Request) (*Client, error) {
121 | cnf := &tls.Config{
122 | InsecureSkipVerify: false,
123 | MinVersion: tls.VersionTLS11,
124 | }
125 |
126 | return dial(url, cnf, req)
127 | }
128 |
129 | func dial(url string, cnf *tls.Config, req *fasthttp.Request) (conn *Client, err error) {
130 | uri := fasthttp.AcquireURI()
131 | defer fasthttp.ReleaseURI(uri)
132 |
133 | uri.Update(url)
134 |
135 | scheme := "https"
136 | port := ":443"
137 | if bytes.Equal(uri.Scheme(), wsString) {
138 | scheme, port = "http", ":80"
139 | }
140 | uri.SetScheme(scheme)
141 |
142 | addr := bytePool.Get().([]byte)
143 | defer bytePool.Put(addr)
144 |
145 | addr = append(addr[:0], uri.Host()...)
146 | if n := bytes.LastIndexByte(addr, ':'); n == -1 {
147 | addr = append(addr, port...)
148 | }
149 |
150 | var c net.Conn
151 |
152 | if scheme == "http" {
153 | c, err = net.Dial("tcp", b2s(addr))
154 | } else {
155 | c, err = tls.Dial("tcp", b2s(addr), cnf)
156 | }
157 |
158 | if err == nil {
159 | conn, err = client(c, uri.String(), req)
160 | if err != nil {
161 | c.Close()
162 | }
163 | }
164 | return conn, err
165 | }
166 |
167 | func makeRandKey(b []byte) []byte {
168 | b = extendByteSlice(b, 16)
169 | rand.Read(b[:16])
170 | b = appendEncode(base64, b[:0], b[:16])
171 | return b
172 | }
173 |
174 | // Client holds a WebSocket connection.
175 | //
176 | // The client is NOT concurrently safe. It is intended to be
177 | // used with the Frame struct.
178 | type Client struct {
179 | c net.Conn
180 | brw *bufio.ReadWriter
181 | }
182 |
183 | // Write writes the content `b` as text.
184 | //
185 | // To send binary content use WriteBinary.
186 | func (c *Client) Write(b []byte) (int, error) {
187 | fr := AcquireFrame()
188 | defer ReleaseFrame(fr)
189 |
190 | fr.SetFin()
191 | fr.SetPayload(b)
192 | fr.SetText()
193 | fr.Mask()
194 |
195 | return c.WriteFrame(fr)
196 | }
197 |
198 | // WriteBinary writes the content `b` as binary.
199 | //
200 | // To send text content use Write.
201 | func (c *Client) WriteBinary(b []byte) (int, error) {
202 | fr := AcquireFrame()
203 | defer ReleaseFrame(fr)
204 |
205 | fr.SetFin()
206 | fr.SetPayload(b)
207 | fr.SetBinary()
208 | fr.Mask()
209 |
210 | return c.WriteFrame(fr)
211 | }
212 |
213 | // WriteFrame writes the frame into the WebSocket connection.
214 | func (c *Client) WriteFrame(fr *Frame) (int, error) {
215 | nn, err := fr.WriteTo(c.brw)
216 | if err == nil {
217 | err = c.brw.Flush()
218 | }
219 |
220 | return int(nn), err
221 | }
222 |
223 | // ReadFrame reads a frame from the connection.
224 | func (c *Client) ReadFrame(fr *Frame) (int, error) {
225 | n, err := fr.ReadFrom(c.brw)
226 | return int(n), err
227 | }
228 |
229 | // Close gracefully closes the websocket connection.
230 | func (c *Client) Close() error {
231 | fr := AcquireFrame()
232 | fr.SetClose()
233 | fr.SetFin()
234 |
235 | fr.SetStatus(StatusNone)
236 |
237 | _, err := c.WriteFrame(fr)
238 | if err != nil {
239 | return err
240 | }
241 |
242 | c.c.SetReadDeadline(time.Now().Add(time.Second * 3)) // wait 3 seconds before closing
243 | // just read the next message
244 | c.ReadFrame(fr)
245 |
246 | return c.c.Close()
247 | }
248 |
--------------------------------------------------------------------------------
/client_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 | "time"
8 |
9 | "github.com/valyala/fasthttp"
10 | "github.com/valyala/fasthttp/fasthttputil"
11 | )
12 |
13 | func BenchmarkRandKey(b *testing.B) {
14 | var bf []byte
15 | for i := 0; i < b.N; i++ {
16 | bf = makeRandKey(bf[:0])
17 | }
18 | }
19 |
20 | func TestDial(t *testing.T) {
21 | text := []byte("Make fasthttp great again")
22 | uri := "http://localhost:9843/"
23 | ln := fasthttputil.NewInmemoryListener()
24 |
25 | ws := Server{
26 | Origin: uri,
27 | }
28 |
29 | ws.HandleData(func(conn *Conn, isBinary bool, data []byte) {
30 | if !bytes.Equal(data, text) {
31 | panic(fmt.Sprintf("%s <> %s", data, text))
32 | }
33 | })
34 |
35 | ws.HandleClose(func(c *Conn, err error) {
36 | if err != nil && err.(Error).Status != StatusGoAway {
37 | t.Fatalf("Expected GoAway, got %s", err.(Error).Status)
38 | }
39 | })
40 |
41 | s := fasthttp.Server{
42 | Handler: ws.Upgrade,
43 | }
44 | ch := make(chan struct{}, 1)
45 | go func() {
46 | s.Serve(ln)
47 | ch <- struct{}{}
48 | }()
49 |
50 | c, err := ln.Dial()
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 |
55 | conn, err := MakeClient(c, uri)
56 | if err != nil {
57 | t.Fatal(err)
58 | }
59 |
60 | _, err = conn.Write(text)
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 |
65 | fr := AcquireFrame()
66 | fr.SetFin()
67 | fr.SetClose()
68 | fr.SetStatus(StatusGoAway)
69 | conn.WriteFrame(fr)
70 |
71 | ln.Close()
72 |
73 | select {
74 | case <-ch:
75 | case <-time.After(time.Second * 5):
76 | t.Fatal("timeout")
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/conn.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "io"
7 | "net"
8 | "sync"
9 | "time"
10 |
11 | "github.com/valyala/bytebufferpool"
12 | )
13 |
14 | // Conn represents a WebSocket connection on the server side.
15 | //
16 | // This handler is compatible with io.Writer.
17 | type Conn struct {
18 | c net.Conn
19 | br *bufio.Reader
20 | bw *bufio.Writer
21 |
22 | input chan *Frame
23 | output chan *Frame
24 |
25 | closer chan struct{}
26 | closeOnce sync.Once
27 |
28 | errch chan error
29 |
30 | // buffered messages
31 | buffered *bytebufferpool.ByteBuffer
32 |
33 | id uint64
34 |
35 | // ReadTimeout ...
36 | ReadTimeout time.Duration
37 |
38 | // WriteTimeout ...
39 | WriteTimeout time.Duration
40 |
41 | // MaxPayloadSize prevents huge memory allocation.
42 | //
43 | // By default MaxPayloadSize is DefaultPayloadSize.
44 | MaxPayloadSize uint64
45 |
46 | wg sync.WaitGroup
47 |
48 | ctx context.Context
49 | }
50 |
51 | // ID returns a unique identifier for the connection.
52 | func (c *Conn) ID() uint64 {
53 | return c.id
54 | }
55 |
56 | // UserValue returns the key associated value.
57 | func (c *Conn) UserValue(key string) interface{} {
58 | return c.ctx.Value(key)
59 | }
60 |
61 | // SetUserValue assigns a key to the given value
62 | func (c *Conn) SetUserValue(key string, value interface{}) {
63 | c.ctx = context.WithValue(c.ctx, key, value)
64 | }
65 |
66 | // LocalAddr returns local address.
67 | func (c *Conn) LocalAddr() net.Addr {
68 | return c.c.LocalAddr()
69 | }
70 |
71 | // RemoteAddr returns peer remote address.
72 | func (c *Conn) RemoteAddr() net.Addr {
73 | return c.c.RemoteAddr()
74 | }
75 |
76 | func acquireConn(c net.Conn) (conn *Conn) {
77 | conn = &Conn{}
78 | conn.reset(c)
79 | conn.wg.Add(2)
80 |
81 | go conn.readLoop()
82 | go conn.writeLoop()
83 |
84 | return conn
85 | }
86 |
87 | // DefaultPayloadSize defines the default payload size (when none was defined).
88 | const DefaultPayloadSize = 1 << 20
89 |
90 | // Reset resets conn values setting c as default connection endpoint.
91 | func (c *Conn) reset(conn net.Conn) {
92 | c.input = make(chan *Frame, 128)
93 | c.output = make(chan *Frame, 128)
94 | c.closer = make(chan struct{}, 1)
95 | c.errch = make(chan error, 2)
96 | c.ReadTimeout = 0
97 | c.WriteTimeout = 0
98 | c.MaxPayloadSize = DefaultPayloadSize
99 | c.ctx = nil
100 | c.c = conn
101 | c.br = bufio.NewReader(conn)
102 | c.bw = bufio.NewWriter(conn)
103 | }
104 |
105 | func (c *Conn) readLoop() {
106 | defer c.wg.Done()
107 |
108 | for {
109 | fr := AcquireFrame()
110 | fr.SetPayloadSize(c.MaxPayloadSize)
111 |
112 | // if c.ReadTimeout != 0 {
113 | // }
114 |
115 | _, err := fr.ReadFrom(c.br)
116 | if err != nil {
117 | select {
118 | case c.errch <- closeError{err: err}:
119 | default:
120 | }
121 |
122 | ReleaseFrame(fr)
123 |
124 | break
125 | }
126 |
127 | isClose := fr.IsClose()
128 |
129 | c.input <- fr
130 |
131 | if isClose {
132 | break
133 | }
134 | }
135 | }
136 |
137 | type closeError struct {
138 | err error
139 | }
140 |
141 | func (ce closeError) Unwrap() error {
142 | return ce.err
143 | }
144 |
145 | func (ce closeError) Error() string {
146 | return ce.err.Error()
147 | }
148 |
149 | func (c *Conn) writeLoop() {
150 | defer c.wg.Done()
151 |
152 | loop:
153 | for {
154 | select {
155 | case fr := <-c.output:
156 | if err := c.writeFrame(fr); err != nil {
157 | select {
158 | case c.errch <- closeError{err}:
159 | default:
160 | }
161 | }
162 |
163 | isClose := fr.IsClose()
164 |
165 | ReleaseFrame(fr)
166 |
167 | if isClose {
168 | return
169 | }
170 | case <-c.closer:
171 | break loop
172 | }
173 | }
174 |
175 | // flush all the frames
176 | for n := len(c.output); n >= 0; n-- {
177 | fr, ok := <-c.output
178 | if !ok {
179 | break
180 | }
181 |
182 | if err := c.writeFrame(fr); err != nil {
183 | break
184 | }
185 | }
186 | }
187 |
188 | func (c *Conn) writeFrame(fr *Frame) error {
189 | fr.SetPayloadSize(c.MaxPayloadSize)
190 |
191 | if c.WriteTimeout > 0 {
192 | c.c.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
193 | defer c.c.SetWriteDeadline(time.Time{})
194 | }
195 |
196 | _, err := fr.WriteTo(c.bw)
197 | if err == nil {
198 | err = c.bw.Flush()
199 | }
200 |
201 | return err
202 | }
203 |
204 | func (c *Conn) Ping(data []byte) {
205 | fr := AcquireFrame()
206 | fr.SetPing()
207 | fr.SetFin()
208 | fr.SetPayload(data)
209 |
210 | c.WriteFrame(fr)
211 | }
212 |
213 | func (c *Conn) Write(data []byte) (int, error) {
214 | n := len(data)
215 |
216 | fr := AcquireFrame()
217 |
218 | fr.SetFin()
219 | fr.SetPayload(data)
220 | fr.SetText()
221 |
222 | c.WriteFrame(fr)
223 |
224 | return n, nil
225 | }
226 |
227 | func (c *Conn) WriteFrame(fr *Frame) {
228 | c.output <- fr
229 | }
230 |
231 | func (c *Conn) Close() error {
232 | c.CloseDetail(StatusNone, "")
233 |
234 | return nil
235 | }
236 |
237 | func (c *Conn) CloseDetail(status StatusCode, reason string) {
238 | if !c.isClosed() {
239 | fr := AcquireFrame()
240 | fr.SetClose()
241 | fr.SetStatus(status)
242 | fr.SetFin()
243 |
244 | io.WriteString(fr, reason)
245 |
246 | c.WriteFrame(fr)
247 |
248 | c.closeOnce.Do(func() { close(c.closer) })
249 | }
250 |
251 | return
252 | }
253 |
254 | func (c *Conn) isClosed() bool {
255 | select {
256 | case <-c.closer:
257 | return true
258 | default:
259 | // if we reach this point, that means `closer` is not closed
260 | // so we still have the connection alive
261 | return false
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/conn_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "testing"
8 |
9 | "github.com/valyala/fasthttp"
10 | "github.com/valyala/fasthttp/fasthttputil"
11 | )
12 |
13 | func configureServer(t *testing.T) (*fasthttp.Server, *fasthttputil.InmemoryListener) {
14 | ln := fasthttputil.NewInmemoryListener()
15 |
16 | ws := Server{}
17 |
18 | stage := 0
19 |
20 | ws.HandleData(func(c *Conn, isBinary bool, data []byte) {
21 | switch stage {
22 | case 0:
23 | if isBinary {
24 | t.Fatal("Unexpected binary mode")
25 | }
26 |
27 | if string(data) != "Hello" {
28 | t.Fatalf("Expecting Hello, got %s", data)
29 | }
30 |
31 | io.WriteString(c, "Hello2")
32 | case 2:
33 | if string(data) != "Hello world" {
34 | t.Fatalf("Expecting Hello world, got %s", data)
35 | }
36 | }
37 |
38 | stage++
39 | })
40 |
41 | ws.HandlePing(func(c *Conn, data []byte) {
42 | if string(data) != "content" {
43 | t.Fatalf("Expecting content, got %s", data)
44 | }
45 |
46 | stage++
47 | })
48 |
49 | ws.HandleClose(func(c *Conn, err error) {
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | })
54 |
55 | s := &fasthttp.Server{
56 | Handler: ws.Upgrade,
57 | }
58 |
59 | return s, ln
60 | }
61 |
62 | func openConn(t *testing.T, ln *fasthttputil.InmemoryListener) *Client {
63 | c, err := ln.Dial()
64 | if err != nil {
65 | t.Fatal(err)
66 | }
67 |
68 | // TODO: UpgradeAsClient
69 | fmt.Fprintf(c, "GET / HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\n\r\n")
70 |
71 | br := bufio.NewReader(c)
72 | var res fasthttp.Response
73 | err = res.Read(br)
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | conn := &Client{
79 | c: c,
80 | brw: bufio.NewReadWriter(
81 | bufio.NewReader(c), bufio.NewWriter(c)),
82 | }
83 |
84 | return conn
85 | }
86 |
87 | func TestReadFrame(t *testing.T) {
88 | s, ln := configureServer(t)
89 | ch := make(chan struct{})
90 | go func() {
91 | s.Serve(ln)
92 | ch <- struct{}{}
93 | }()
94 |
95 | conn := openConn(t, ln)
96 | io.WriteString(conn, "Hello")
97 |
98 | fr := AcquireFrame()
99 |
100 | _, err := conn.ReadFrame(fr)
101 | if err != nil {
102 | t.Fatal(err)
103 | }
104 |
105 | b := fr.Payload()
106 | if string(b) != "Hello2" {
107 | t.Fatalf("Unexpected message: %s<>Hello2", b)
108 | }
109 |
110 | fr.Reset()
111 | fr.SetPing()
112 | fr.SetFin()
113 | fr.SetPayload([]byte("content"))
114 |
115 | conn.WriteFrame(fr)
116 |
117 | _, err = conn.ReadFrame(fr)
118 | if err != nil {
119 | t.Fatal(err)
120 | }
121 | if !fr.IsPong() {
122 | t.Fatal("Unexpected message: no pong")
123 | }
124 | fr.Reset()
125 |
126 | fr.SetText()
127 | fr.SetPayload([]byte("Hello"))
128 | fr.Mask()
129 | _, err = conn.WriteFrame(fr)
130 | if err != nil {
131 | t.Fatal(err)
132 | }
133 | fr.Reset()
134 |
135 | fr.SetContinuation()
136 | fr.SetFin()
137 | fr.SetPayload([]byte(" world"))
138 | fr.Mask()
139 | _, err = conn.WriteFrame(fr)
140 | if err != nil {
141 | t.Fatal(err)
142 | }
143 |
144 | fr.Reset()
145 | fr.SetClose()
146 | fr.SetFin()
147 | fr.SetStatus(StatusNone)
148 | fr.SetPayload([]byte("Bye"))
149 | fr.Mask()
150 |
151 | _, err = conn.WriteFrame(fr)
152 | if err != nil {
153 | t.Fatal(err)
154 | }
155 |
156 | fr.Reset()
157 | _, err = conn.ReadFrame(fr)
158 | if !fr.IsContinuation() {
159 | t.Fatalf("Unexpected frame %s", fr.Code())
160 | }
161 |
162 | ln.Close()
163 | <-ch
164 | }
165 |
166 | // func TestUserValue(t *testing.T) {
167 | // var uri = "http://localhost:9843/"
168 | // var text = "Hello user!!"
169 | // ln := fasthttputil.NewInmemoryListener()
170 | // upgr := Upgrader{
171 | // Origin: uri,
172 | // Handler: func(conn *Conn) {
173 | // v := conn.UserValue("custom")
174 | // if v == nil {
175 | // t.Fatal("custom is nil")
176 | // }
177 | // conn.WriteString(v.(string))
178 | // },
179 | // }
180 | // s := fasthttp.Server{
181 | // Handler: func(ctx *fasthttp.RequestCtx) {
182 | // ctx.SetUserValue("custom", text)
183 | // upgr.Upgrade(ctx)
184 | // },
185 | // }
186 | // ch := make(chan struct{}, 1)
187 | // go func() {
188 | // s.Serve(ln)
189 | // ch <- struct{}{}
190 | // }()
191 | //
192 | // c, err := ln.Dial()
193 | // if err != nil {
194 | // t.Fatal(err)
195 | // }
196 | //
197 | // conn, err := Client(c, uri)
198 | // if err != nil {
199 | // t.Fatal(err)
200 | // }
201 | //
202 | // _, b, err := conn.ReadMessage(nil)
203 | // if err != nil {
204 | // t.Fatal(err)
205 | // }
206 | //
207 | // if string(b) != text {
208 | // t.Fatalf("client expecting %s. Got %s", text, b)
209 | // }
210 | //
211 | // conn.Close()
212 | // ln.Close()
213 | //
214 | // select {
215 | // case <-ch:
216 | // case <-time.After(time.Second * 5):
217 | // t.Fatal("timeout")
218 | // }
219 | // }
220 |
--------------------------------------------------------------------------------
/error.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import "fmt"
4 |
5 | type Error struct {
6 | Status StatusCode
7 | Reason string
8 | }
9 |
10 | func (e Error) Error() string {
11 | return fmt.Sprintf("%s: %s", e.Status, e.Reason)
12 | }
13 |
--------------------------------------------------------------------------------
/examples/broadcast/go.mod:
--------------------------------------------------------------------------------
1 | module broadcast
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.7
7 | github.com/fasthttp/router v1.4.0
8 | github.com/valyala/fasthttp v1.28.0
9 | )
10 |
--------------------------------------------------------------------------------
/examples/broadcast/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgrr/websocket v0.0.5 h1:gYxn5sjv7P1LJzqEfuJNp6VHSBO4DmiXQ3AkePu7d3c=
7 | github.com/dgrr/websocket v0.0.5/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
8 | github.com/fasthttp/router v1.4.0 h1:sWMk0q7M6Qj73eLIolh/934mKTNZIWDrEPDhZUF1pAg=
9 | github.com/fasthttp/router v1.4.0/go.mod h1:uTM3xaLINfEk/uqId8rv8tzwr47+HZuxopzUWfwD4qg=
10 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
12 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
13 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
14 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
15 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
16 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
17 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
18 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
19 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
20 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
21 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
22 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
23 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
24 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
25 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
26 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
27 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
28 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
29 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
30 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
31 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
32 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
33 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
36 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
37 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
38 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
39 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
40 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
41 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
42 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
43 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
44 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
45 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
46 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
47 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
48 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
49 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
50 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
51 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
52 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o=
56 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8=
57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
58 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
59 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
60 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
61 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
62 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
63 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
64 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
65 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
66 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
67 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
68 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
69 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
70 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
71 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
72 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
73 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
74 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
78 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
79 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
80 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
81 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
82 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
83 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
85 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
86 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
89 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
90 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
91 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
92 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
93 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
94 |
--------------------------------------------------------------------------------
/examples/broadcast/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sync"
7 | "time"
8 |
9 | "github.com/dgrr/websocket"
10 | "github.com/fasthttp/router"
11 | "github.com/valyala/fasthttp"
12 | )
13 |
14 | type Broadcaster struct {
15 | cs sync.Map
16 | }
17 |
18 | func (b *Broadcaster) OnOpen(c *websocket.Conn) {
19 | b.cs.Store(c.ID(), c)
20 |
21 | log.Printf("%s connected\n", c.RemoteAddr())
22 | }
23 |
24 | func (b *Broadcaster) OnClose(c *websocket.Conn, err error) {
25 | if err != nil {
26 | log.Printf("%d closed with error: %s\n", c.ID(), err)
27 | } else {
28 | log.Printf("%d closed the connection\n", c.ID())
29 | }
30 |
31 | b.cs.Delete(c.ID())
32 | }
33 |
34 | func (b *Broadcaster) Start(i int) {
35 | time.AfterFunc(time.Second, b.sendData(i))
36 | }
37 |
38 | func (b *Broadcaster) sendData(i int) func() {
39 | return func() {
40 | b.cs.Range(func(_, v interface{}) bool {
41 | nc := v.(*websocket.Conn)
42 | fmt.Fprintf(nc, "Sending message number %d\n", i)
43 |
44 | return true
45 | })
46 |
47 | b.Start(i+1)
48 | }
49 | }
50 |
51 | func main() {
52 | b := &Broadcaster{}
53 |
54 | wServer := websocket.Server{}
55 | wServer.HandleOpen(b.OnOpen)
56 | wServer.HandleClose(b.OnClose)
57 |
58 | router := router.New()
59 | router.GET("/", rootHandler)
60 | router.GET("/ws", wServer.Upgrade)
61 |
62 | server := fasthttp.Server{
63 | Handler: router.Handler,
64 | }
65 |
66 | b.Start(0)
67 |
68 | server.ListenAndServe(":8080")
69 | }
70 |
71 | func rootHandler(ctx *fasthttp.RequestCtx) {
72 | ctx.SetContentType("text/html")
73 | fmt.Fprintln(ctx, `
74 |
75 |
76 |
77 | Sample of websocket with Golang
78 |
79 |
80 |
81 |
95 |
96 | `)
97 | }
98 |
--------------------------------------------------------------------------------
/examples/broadcast_groups/go.mod:
--------------------------------------------------------------------------------
1 | module broadcast
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.7
7 | github.com/fasthttp/router v1.4.0
8 | github.com/valyala/fasthttp v1.28.0
9 | )
10 |
--------------------------------------------------------------------------------
/examples/broadcast_groups/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/dgrr/websocket v0.0.7 h1:cC5iErIMYpu+W1tWB1NOfVPKhgAASf+Tl0sAdEHAxkQ=
6 | github.com/dgrr/websocket v0.0.7/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
7 | github.com/fasthttp/router v1.4.0 h1:sWMk0q7M6Qj73eLIolh/934mKTNZIWDrEPDhZUF1pAg=
8 | github.com/fasthttp/router v1.4.0/go.mod h1:uTM3xaLINfEk/uqId8rv8tzwr47+HZuxopzUWfwD4qg=
9 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
10 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
11 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
12 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
14 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
15 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
16 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
17 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
18 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
19 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
20 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
21 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
22 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
23 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
24 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
25 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
26 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
27 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
28 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
29 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
30 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
31 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
32 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
33 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
34 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
35 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
36 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
37 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
38 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
40 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o=
41 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8=
42 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
43 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
44 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
45 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
46 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
47 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
48 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
49 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
50 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
51 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
52 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
53 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
54 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
55 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
56 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
57 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
58 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
59 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
60 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
62 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
63 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
64 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
65 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
66 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
67 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
69 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
70 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
71 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
72 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
73 |
--------------------------------------------------------------------------------
/examples/broadcast_groups/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sync"
7 | "time"
8 |
9 | "github.com/dgrr/websocket"
10 | "github.com/fasthttp/router"
11 | "github.com/valyala/fasthttp"
12 | )
13 |
14 | type Broadcaster struct {
15 | cs sync.Map
16 | }
17 |
18 | func (b *Broadcaster) Broadcast(i int) *Broadcaster {
19 | if b == nil {
20 | return nil
21 | }
22 | b.cs.Range(func(_, v interface{}) bool {
23 | nc := v.(*websocket.Conn)
24 | fmt.Fprintf(nc, "Sending message number %d\n", i)
25 |
26 | return true
27 | })
28 | return b
29 | }
30 |
31 | type BroadcasterGroup struct {
32 | broadcasters sync.Map
33 | broadcastersByConnections sync.Map
34 | }
35 |
36 | func (g *BroadcasterGroup) GetBroadcaster(id interface{}) *Broadcaster {
37 | b, ok := g.broadcasters.Load(id)
38 | if !ok {
39 | return nil
40 | }
41 | return b.(*Broadcaster)
42 | }
43 |
44 | func (g *BroadcasterGroup) Join(id interface{}, c *websocket.Conn) *Broadcaster {
45 | bb, _ := g.broadcasters.LoadOrStore(id, &Broadcaster{})
46 | b := bb.(*Broadcaster)
47 | b.cs.Store(c.ID(), c)
48 |
49 | mm, _ := g.broadcastersByConnections.LoadOrStore(c.ID(), &sync.Map{})
50 | m := mm.(*sync.Map)
51 | m.Store(id, true)
52 |
53 | return b
54 | }
55 |
56 | func (g *BroadcasterGroup) Leave(id interface{}, c *websocket.Conn) {
57 | b := g.GetBroadcaster(id)
58 | if b == nil {
59 | return
60 | }
61 |
62 | mm, ok := g.broadcastersByConnections.Load(c.ID())
63 | if ok {
64 | m := mm.(*sync.Map)
65 | m.Delete(id)
66 | }
67 |
68 | b.cs.Delete(c.ID())
69 |
70 | isEmpty := true
71 | b.cs.Range(func(key, value interface{}) bool {
72 | isEmpty = false
73 | return false
74 | })
75 | if isEmpty {
76 | bb, loaded := g.broadcasters.LoadAndDelete(id)
77 | if loaded {
78 | bb.(*Broadcaster).cs.Range(func(key, value interface{}) bool {
79 | g.Join(id, value.(*websocket.Conn))
80 | return true
81 | })
82 | }
83 | }
84 | }
85 |
86 | func (g *BroadcasterGroup) LeaveAll(c *websocket.Conn) {
87 | mm, loaded := g.broadcastersByConnections.LoadAndDelete(c.ID())
88 | if !loaded {
89 | return
90 | }
91 | m := mm.(*sync.Map)
92 | m.Range(func(key, value interface{}) bool {
93 | g.Leave(key, c)
94 | return true
95 | })
96 | }
97 |
98 | var group = &BroadcasterGroup{}
99 |
100 | func OnOpen(c *websocket.Conn) {
101 | roomID := c.ID() % 3
102 | group.Join(roomID, c)
103 |
104 | log.Printf("%s joined room %d\n", c.RemoteAddr(), roomID)
105 | }
106 |
107 | func OnClose(c *websocket.Conn, err error) {
108 | if err != nil {
109 | log.Printf("%d closed with error: %s\n", c.ID(), err)
110 | } else {
111 | log.Printf("%d closed the connection\n", c.ID())
112 | }
113 |
114 | group.LeaveAll(c)
115 | }
116 |
117 | func (g *BroadcasterGroup) Start(i int) {
118 | time.AfterFunc(time.Second, g.sendData(i))
119 | }
120 |
121 | func (g *BroadcasterGroup) sendData(i int) func() {
122 | return func() {
123 | g.broadcasters.Range(func(k, v interface{}) bool {
124 | r := k.(uint64)
125 | if r == uint64(i%3) {
126 | v.(*Broadcaster).Broadcast(i)
127 | log.Printf("Sending message number %d to room %d\n", i, k.(uint64))
128 | }
129 |
130 | return true
131 | })
132 |
133 | g.Start(i + 1)
134 | }
135 | }
136 |
137 | func main() {
138 | wServer := websocket.Server{}
139 | wServer.HandleOpen(OnOpen)
140 | wServer.HandleClose(OnClose)
141 |
142 | router := router.New()
143 | router.GET("/", rootHandler)
144 | router.GET("/ws", wServer.Upgrade)
145 |
146 | server := fasthttp.Server{
147 | Handler: router.Handler,
148 | }
149 |
150 | group.Start(0)
151 |
152 | server.ListenAndServe(":8080")
153 | }
154 |
155 | func rootHandler(ctx *fasthttp.RequestCtx) {
156 | ctx.SetContentType("text/html")
157 | fmt.Fprintln(ctx, `
158 |
159 |
160 |
161 | Sample of websocket with Golang
162 |
163 |
164 |
165 |
179 |
180 | `)
181 | }
182 |
--------------------------------------------------------------------------------
/examples/broadcast_nethttp/go.mod:
--------------------------------------------------------------------------------
1 | module broadcast
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.9
7 | )
8 |
--------------------------------------------------------------------------------
/examples/broadcast_nethttp/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgrr/websocket v0.0.5 h1:gYxn5sjv7P1LJzqEfuJNp6VHSBO4DmiXQ3AkePu7d3c=
7 | github.com/dgrr/websocket v0.0.5/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
8 | github.com/fasthttp/router v1.4.0 h1:sWMk0q7M6Qj73eLIolh/934mKTNZIWDrEPDhZUF1pAg=
9 | github.com/fasthttp/router v1.4.0/go.mod h1:uTM3xaLINfEk/uqId8rv8tzwr47+HZuxopzUWfwD4qg=
10 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
12 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
13 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
14 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
15 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
16 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
17 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
18 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
19 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
20 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
21 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
22 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
23 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
24 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
25 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
26 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
27 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
28 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
29 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
30 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
31 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
32 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
33 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
36 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
37 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
38 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
39 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
40 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
41 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
42 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
43 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
44 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
45 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
46 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
47 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
48 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
49 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
50 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
51 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
52 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o=
56 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8=
57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
58 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
59 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
60 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
61 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
62 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
63 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
64 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
65 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
66 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
67 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
68 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
69 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
70 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
71 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
72 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
73 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
74 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
78 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
79 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
80 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
81 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
82 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
83 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
85 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
86 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
89 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
90 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
91 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
92 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
93 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
94 |
--------------------------------------------------------------------------------
/examples/broadcast_nethttp/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "sync"
8 | "time"
9 |
10 | "github.com/dgrr/websocket"
11 | )
12 |
13 | type Broadcaster struct {
14 | cs sync.Map
15 | }
16 |
17 | func (b *Broadcaster) OnOpen(c *websocket.Conn) {
18 | b.cs.Store(c.ID(), c)
19 |
20 | log.Printf("%s connected\n", c.RemoteAddr())
21 | }
22 |
23 | func (b *Broadcaster) OnClose(c *websocket.Conn, err error) {
24 | if err != nil {
25 | log.Printf("%d closed with error: %s\n", c.ID(), err)
26 | } else {
27 | log.Printf("%d closed the connection\n", c.ID())
28 | }
29 |
30 | b.cs.Delete(c.ID())
31 | }
32 |
33 | func (b *Broadcaster) Start(i int) {
34 | time.AfterFunc(time.Second, b.sendData(i))
35 | }
36 |
37 | func (b *Broadcaster) sendData(i int) func() {
38 | return func() {
39 | b.cs.Range(func(_, v interface{}) bool {
40 | nc := v.(*websocket.Conn)
41 | fmt.Fprintf(nc, "Sending message number %d\n", i)
42 |
43 | return true
44 | })
45 |
46 | b.Start(i+1)
47 | }
48 | }
49 |
50 | func main() {
51 | b := &Broadcaster{}
52 |
53 | wServer := websocket.Server{}
54 | wServer.HandleOpen(b.OnOpen)
55 | wServer.HandleClose(b.OnClose)
56 |
57 | http.HandleFunc("/", rootHandler)
58 | http.HandleFunc("/ws", wServer.NetUpgrade)
59 |
60 | b.Start(0)
61 |
62 | http.ListenAndServe(":8080", nil)
63 | }
64 |
65 | func rootHandler(w http.ResponseWriter, r *http.Request) {
66 | fmt.Fprintln(w, `
67 |
68 |
69 |
70 | Sample of websocket with Golang
71 |
72 |
73 |
74 |
88 |
89 | `)
90 | }
91 |
--------------------------------------------------------------------------------
/examples/client/go.mod:
--------------------------------------------------------------------------------
1 | module client
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.7
7 | github.com/valyala/fasthttp v1.28.0
8 | )
9 |
--------------------------------------------------------------------------------
/examples/client/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgrr/websocket v0.0.5 h1:gYxn5sjv7P1LJzqEfuJNp6VHSBO4DmiXQ3AkePu7d3c=
7 | github.com/dgrr/websocket v0.0.5/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
8 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
9 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
10 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
11 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
12 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
13 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
14 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
15 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
16 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
17 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
18 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
19 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
20 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
21 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
22 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
23 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
24 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
25 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
26 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
28 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
29 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
30 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
31 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
32 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
34 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
35 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
36 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
37 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
38 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
39 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
40 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
41 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
42 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
43 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
44 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
45 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
46 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
48 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
49 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
53 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
54 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
55 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
56 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
57 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
58 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
59 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
60 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
61 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
62 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
63 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
64 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
65 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
66 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
67 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
68 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
69 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
72 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
74 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
75 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
76 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
77 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
78 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
83 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
84 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
85 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
86 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
87 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
88 |
--------------------------------------------------------------------------------
/examples/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "time"
7 |
8 |
9 | "github.com/dgrr/websocket"
10 | "github.com/valyala/fasthttp"
11 | )
12 |
13 | func main() {
14 | go startServer(":8080")
15 |
16 | c, err := websocket.Dial("ws://localhost:8080/echo")
17 | if err != nil {
18 | log.Fatalln(err)
19 | }
20 |
21 | if _, err := io.WriteString(c, "Hello"); err != nil {
22 | panic(err)
23 | }
24 |
25 | fr := websocket.AcquireFrame()
26 | defer websocket.ReleaseFrame(fr)
27 |
28 | for i := 0; i < 5; i++ {
29 | fr.Reset()
30 |
31 | _, err := c.ReadFrame(fr)
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | time.Sleep(time.Second)
37 |
38 | c.WriteFrame(fr)
39 | }
40 |
41 | c.Close()
42 | }
43 |
44 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
45 | log.Printf("Received: %s\n", data)
46 | c.Write(data)
47 | }
48 |
49 | func startServer(addr string) {
50 | wServer := websocket.Server{}
51 |
52 | wServer.HandleData(OnMessage)
53 |
54 | if err := fasthttp.ListenAndServe(addr, wServer.Upgrade); err != nil {
55 | log.Fatalln(err)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/whitelist/go.mod:
--------------------------------------------------------------------------------
1 | module whitelist
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/dgrr/websocket v0.0.8
7 | github.com/valyala/fasthttp v1.28.0
8 | )
9 |
--------------------------------------------------------------------------------
/examples/whitelist/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgrr/websocket v0.0.8 h1:8zQndnzaeo8wlxbbQXO/L9zWA9CNoTfD1dASdiSbJ34=
7 | github.com/dgrr/websocket v0.0.8/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc=
8 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
9 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
10 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
11 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
12 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
13 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
14 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
15 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
16 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
17 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
18 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
19 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
20 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
21 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
22 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
23 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
24 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
25 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
26 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
28 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
29 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
30 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
31 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
32 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
34 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
35 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
36 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
37 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
38 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
39 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
40 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
41 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
42 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
43 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
44 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
45 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
46 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
48 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
49 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
53 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
54 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
55 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
56 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
57 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
58 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
59 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
60 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
61 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
62 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
63 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
64 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
65 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
66 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
67 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
68 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
69 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
72 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
74 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
75 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
76 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
77 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
78 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
83 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
84 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
85 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
86 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
87 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
88 |
--------------------------------------------------------------------------------
/examples/whitelist/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "time"
7 |
8 |
9 | "github.com/dgrr/websocket"
10 | "github.com/valyala/fasthttp"
11 | )
12 |
13 | func main() {
14 | go startServer(":8080")
15 |
16 | startClient()
17 | }
18 |
19 | func startClient() {
20 | req := fasthttp.AcquireRequest()
21 | req.Header.DisableNormalizing()
22 |
23 | req.Header.Add("private-key", "123")
24 |
25 | log.Printf("Trying with key 123\n")
26 |
27 | c, err := websocket.DialWithHeaders("ws://localhost:8080/private_access", req)
28 | if err == nil {
29 | log.Fatalln("Should've failed")
30 | } else {
31 | log.Printf("Now trying with key 1234\n")
32 |
33 | req.Header.Set("private-key", "1234")
34 | c, err = websocket.DialWithHeaders("ws://localhost:8080/private_access", req)
35 | }
36 |
37 | if err != nil {
38 | log.Fatalln(err)
39 | }
40 |
41 | if _, err := io.WriteString(c, "Hello"); err != nil {
42 | panic(err)
43 | }
44 |
45 | fr := websocket.AcquireFrame()
46 | defer websocket.ReleaseFrame(fr)
47 |
48 | for i := 0; i < 5; i++ {
49 | fr.Reset()
50 |
51 | _, err := c.ReadFrame(fr)
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | time.Sleep(time.Second)
57 |
58 | c.WriteFrame(fr)
59 | }
60 |
61 | c.Close()
62 | }
63 |
64 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
65 | log.Printf("Received: %s\n", data)
66 | c.Write(data)
67 | }
68 |
69 | func checkAccess(ctx *fasthttp.RequestCtx) bool {
70 | return string(ctx.Request.Header.Peek("private-key")) == "1234"
71 | }
72 |
73 | func startServer(addr string) {
74 | wServer := websocket.Server{
75 | UpgradeHandler: checkAccess,
76 | }
77 |
78 | wServer.HandleData(OnMessage)
79 |
80 | if err := fasthttp.ListenAndServe(addr, wServer.Upgrade); err != nil {
81 | log.Fatalln(err)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/frame.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "strconv"
9 | "sync"
10 | )
11 |
12 | // StatusCode is sent when closing a connection.
13 | //
14 | // The following constants have been defined by the RFC.
15 | type StatusCode uint16
16 |
17 | const (
18 | // StatusNone is used to let the peer know nothing happened.
19 | StatusNone StatusCode = 1000
20 | // StatusGoAway peer's error.
21 | StatusGoAway = 1001
22 | // StatusProtocolError problem with the peer's way to communicate.
23 | StatusProtocolError = 1002
24 | // StatusNotAcceptable when a request is not acceptable
25 | StatusNotAcceptable = 1003
26 | // StatusReserved when a reserved field have been used
27 | StatusReserved = 1004
28 | // StatusNotConsistent IDK
29 | StatusNotConsistent = 1007
30 | // StatusViolation a violation of the protocol happened
31 | StatusViolation = 1008
32 | // StatusTooBig payload bigger than expected
33 | StatusTooBig = 1009
34 | // StatuseExtensionsNeeded IDK
35 | StatuseExtensionsNeeded = 1010
36 | // StatusUnexpected IDK
37 | StatusUnexpected = 1011
38 | )
39 |
40 | func (status StatusCode) String() string {
41 | switch status {
42 | case StatusNone:
43 | return "None"
44 | case StatusGoAway:
45 | return "GoAway"
46 | case StatusProtocolError:
47 | return "ProtocolError"
48 | case StatusNotAcceptable:
49 | return "NotAcceptable"
50 | case StatusReserved:
51 | return "Reserved"
52 | case StatusNotConsistent:
53 | return "NotConsistent"
54 | case StatusViolation:
55 | return "Violation"
56 | case StatusTooBig:
57 | return "TooBig"
58 | case StatuseExtensionsNeeded:
59 | return "ExtensionsNeeded"
60 | case StatusUnexpected:
61 | return "Unexpected"
62 | }
63 |
64 | return strconv.FormatInt(int64(status), 10)
65 | }
66 |
67 | // Code to send.
68 | type Code uint8
69 |
70 | const (
71 | // CodeContinuation defines the continuation code
72 | CodeContinuation Code = 0x0
73 | // CodeText defines the text code
74 | CodeText Code = 0x1
75 | // CodeBinary defines the binary code
76 | CodeBinary Code = 0x2
77 | // CodeClose defines the close code
78 | CodeClose Code = 0x8
79 | // CodePing defines the ping code
80 | CodePing Code = 0x9
81 | // CodePong defines the pong code
82 | CodePong Code = 0xA
83 | )
84 |
85 | func (code Code) String() string {
86 | switch code {
87 | case CodeContinuation:
88 | return "Continuation"
89 | case CodeText:
90 | return "Text"
91 | case CodeBinary:
92 | return "Binary"
93 | case CodeClose:
94 | return "Close"
95 | case CodePing:
96 | return "Ping"
97 | case CodePong:
98 | return "Pong"
99 | }
100 | return ""
101 | }
102 |
103 | var zeroBytes = func() []byte {
104 | b := make([]byte, 10)
105 | for i := range b {
106 | b[i] = 0
107 | }
108 | return b
109 | }()
110 |
111 | const (
112 | finBit = byte(1 << 7)
113 | rsv1Bit = byte(1 << 6)
114 | rsv2Bit = byte(1 << 5)
115 | rsv3Bit = byte(1 << 4)
116 | maskBit = byte(1 << 7)
117 | )
118 |
119 | // Frame is the unit used to transfer message
120 | // between endpoints using the websocket protocol.
121 | type Frame struct {
122 | max uint64
123 | op []byte
124 | mask []byte
125 | b []byte
126 | statusDefined bool
127 | }
128 |
129 | // CopyTo copies the frame `fr` to `fr2`
130 | func (fr *Frame) CopyTo(fr2 *Frame) {
131 | fr2.max = fr.max
132 | fr2.op = append(fr2.op[:0], fr.op...)
133 | fr2.mask = append(fr2.mask[:0], fr.mask...)
134 | fr2.b = append(fr2.b[:0], fr.b...)
135 | }
136 |
137 | // String returns a representation of Frame in a human-readable string format.
138 | func (fr *Frame) String() string {
139 | return fmt.Sprintf(`FIN: %v
140 | RSV1: %v
141 | RSV2: %v
142 | RSV3: %v
143 | --------
144 | OPCODE: %d
145 | --------
146 | MASK: %v
147 | --------
148 | LENGTH: %d
149 | --------
150 | KEY: %v
151 | --------
152 | Data: %v`,
153 | fr.IsFin(), fr.HasRSV1(), fr.HasRSV2(), fr.HasRSV3(),
154 | fr.Code(), fr.IsMasked(), fr.Len(), fr.MaskKey(),
155 | fr.Payload(),
156 | )
157 | }
158 |
159 | var framePool = sync.Pool{
160 | New: func() interface{} {
161 | fr := &Frame{
162 | max: DefaultPayloadSize,
163 | op: make([]byte, opSize),
164 | mask: make([]byte, maskSize),
165 | b: make([]byte, 0, 128),
166 | }
167 |
168 | return fr
169 | },
170 | }
171 |
172 | // AcquireFrame gets Frame from the global pool.
173 | func AcquireFrame() *Frame {
174 | return framePool.Get().(*Frame)
175 | }
176 |
177 | // ReleaseFrame puts fr Frame into the global pool.
178 | func ReleaseFrame(fr *Frame) {
179 | fr.Reset()
180 | framePool.Put(fr)
181 | }
182 |
183 | func (fr *Frame) resetPayload() {
184 | fr.b = fr.b[:0]
185 | }
186 |
187 | const (
188 | maskSize = 4
189 | opSize = 10
190 | )
191 |
192 | func (fr *Frame) resetHeader() {
193 | copy(fr.op, zeroBytes)
194 | copy(fr.mask, zeroBytes)
195 | fr.statusDefined = false
196 | }
197 |
198 | // Reset resets all Frame values to the default.
199 | func (fr *Frame) Reset() {
200 | fr.resetHeader()
201 | fr.resetPayload()
202 | }
203 |
204 | // IsFin checks if FIN bit is set.
205 | func (fr *Frame) IsFin() bool {
206 | return fr.op[0]&finBit != 0
207 | }
208 |
209 | // HasRSV1 checks if RSV1 bit is set.
210 | func (fr *Frame) HasRSV1() bool {
211 | return fr.op[0]&rsv1Bit != 0
212 | }
213 |
214 | // HasRSV2 checks if RSV2 bit is set.
215 | func (fr *Frame) HasRSV2() bool {
216 | return fr.op[0]&rsv2Bit != 0
217 | }
218 |
219 | // HasRSV3 checks if RSV3 bit is set.
220 | func (fr *Frame) HasRSV3() bool {
221 | return fr.op[0]&rsv3Bit != 0
222 | }
223 |
224 | // Code returns the code set in fr.
225 | func (fr *Frame) Code() Code {
226 | return Code(fr.op[0] & 15)
227 | }
228 |
229 | // IsPing returns true if Code is CodePing.
230 | func (fr *Frame) IsPing() bool {
231 | return fr.Code() == CodePing
232 | }
233 |
234 | // IsPong returns true if Code is CodePong.
235 | func (fr *Frame) IsPong() bool {
236 | return fr.Code() == CodePong
237 | }
238 |
239 | // IsContinuation returns true if the Frame code is Continuation
240 | func (fr *Frame) IsContinuation() bool {
241 | return fr.Code() == CodeContinuation
242 | }
243 |
244 | // IsClose returns true if Code is CodeClose.
245 | func (fr *Frame) IsClose() bool {
246 | return fr.Code() == CodeClose
247 | }
248 |
249 | // IsControl returns whether the Frame is a control frame or not.
250 | // That means if it's a Close, Ping or Pong frame.
251 | func (fr *Frame) IsControl() bool {
252 | return fr.IsClose() || fr.IsPing() || fr.IsPong()
253 | }
254 |
255 | // IsMasked checks if Mask bit is set.
256 | func (fr *Frame) IsMasked() bool {
257 | return fr.op[1]&maskBit != 0
258 | }
259 |
260 | // Len returns the length of the payload based on the header bits.
261 | //
262 | // If you want to know the actual payload length use #PayloadLen
263 | func (fr *Frame) Len() (length uint64) {
264 | length = uint64(fr.op[1] & 127)
265 | switch length {
266 | case 126:
267 | length = uint64(binary.BigEndian.Uint16(fr.op[2:]))
268 | case 127:
269 | length = binary.BigEndian.Uint64(fr.op[2:])
270 | }
271 |
272 | return
273 | }
274 |
275 | // MaskKey returns mask key.
276 | //
277 | // Returns zero-padded if doesn't have a mask
278 | func (fr *Frame) MaskKey() []byte {
279 | return fr.mask[:4]
280 | }
281 |
282 | // Payload returns the frame payload.
283 | func (fr *Frame) Payload() []byte {
284 | if fr.IsClose() && len(fr.b) != 0 {
285 | return fr.b[2:]
286 | }
287 |
288 | return fr.b
289 | }
290 |
291 | // PayloadLen returns the actual payload length
292 | func (fr *Frame) PayloadLen() int {
293 | return len(fr.b)
294 | }
295 |
296 | // PayloadSize returns the max payload size
297 | func (fr *Frame) PayloadSize() uint64 {
298 | return fr.max
299 | }
300 |
301 | // SetPayloadSize sets max payload size
302 | func (fr *Frame) SetPayloadSize(size uint64) {
303 | fr.max = size
304 | }
305 |
306 | // SetFin sets FIN bit.
307 | func (fr *Frame) SetFin() {
308 | fr.op[0] |= finBit
309 | }
310 |
311 | // SetRSV1 sets RSV1 bit.
312 | func (fr *Frame) SetRSV1() {
313 | fr.op[0] |= rsv1Bit
314 | }
315 |
316 | // SetRSV2 sets RSV2 bit.
317 | func (fr *Frame) SetRSV2() {
318 | fr.op[0] |= rsv2Bit
319 | }
320 |
321 | // SetRSV3 sets RSV3 bit.
322 | func (fr *Frame) SetRSV3() {
323 | fr.op[0] |= rsv3Bit
324 | }
325 |
326 | // SetCode sets code bits.
327 | func (fr *Frame) SetCode(code Code) {
328 | code &= 15
329 | fr.op[0] &= 15 << 4
330 | fr.op[0] |= uint8(code)
331 | }
332 |
333 | // SetContinuation sets CodeContinuation in Code field.
334 | func (fr *Frame) SetContinuation() {
335 | fr.SetCode(CodeContinuation)
336 | }
337 |
338 | // SetText sets CodeText in Code field.
339 | func (fr *Frame) SetText() {
340 | fr.SetCode(CodeText)
341 | }
342 |
343 | // SetBinary sets CodeText in Code field.
344 | func (fr *Frame) SetBinary() {
345 | fr.SetCode(CodeBinary)
346 | }
347 |
348 | // SetClose sets CodeClose in Code field.
349 | func (fr *Frame) SetClose() {
350 | fr.SetCode(CodeClose)
351 | }
352 |
353 | // SetPing sets CodePing in Code field.
354 | func (fr *Frame) SetPing() {
355 | fr.SetCode(CodePing)
356 | }
357 |
358 | // SetPong sets CodePong in Code field.
359 | func (fr *Frame) SetPong() {
360 | fr.SetCode(CodePong)
361 | }
362 |
363 | // SetMask sets the first 4 parsed bytes as mask key
364 | // and enables the mask bit
365 | func (fr *Frame) SetMask(b []byte) {
366 | fr.op[1] |= maskBit
367 | copy(fr.mask, b[:4])
368 | }
369 |
370 | // UnsetMask only drops the mask bit.
371 | func (fr *Frame) UnsetMask() {
372 | fr.op[1] ^= maskBit
373 | }
374 |
375 | // Write appends the parsed bytes to the frame's payload
376 | func (fr *Frame) Write(b []byte) (int, error) {
377 | n := len(b)
378 | fr.b = append(fr.b, b...)
379 | return n, nil
380 | }
381 |
382 | // SetPayload sets the parsed bytes as frame's payload
383 | func (fr *Frame) SetPayload(b []byte) {
384 | n := 0
385 | if fr.IsClose() {
386 | n = 2
387 |
388 | if cap(fr.b) < 2 {
389 | fr.b = append(fr.b, make([]byte, 2)...)
390 | }
391 | }
392 |
393 | fr.b = append(fr.b[:n], b...)
394 | }
395 |
396 | // setPayloadLen returns the number of bytes the header will use
397 | // for sending out the payload's length.
398 | func (fr *Frame) setPayloadLen() (s int) {
399 | n := len(fr.b)
400 |
401 | switch {
402 | case n > 65535:
403 | s = 8
404 | fr.setLength(127)
405 | binary.BigEndian.PutUint64(fr.op[2:], uint64(n))
406 | case n > 125:
407 | s = 2
408 | fr.setLength(126)
409 | binary.BigEndian.PutUint16(fr.op[2:], uint16(n))
410 | default:
411 | fr.setLength(n)
412 | s = 0 // assumed but ok
413 | }
414 |
415 | return
416 | }
417 |
418 | func (fr *Frame) setLength(n int) {
419 | fr.op[1] |= uint8(n)
420 | }
421 |
422 | // Mask performs the masking of the current payload
423 | func (fr *Frame) Mask() {
424 | fr.op[1] |= maskBit
425 |
426 | readMask(fr.mask)
427 | if len(fr.b) != 0 {
428 | mask(fr.mask, fr.b)
429 | }
430 | }
431 |
432 | // Unmask performs the unmasking of the current payload
433 | func (fr *Frame) Unmask() {
434 | if len(fr.b) != 0 {
435 | key := fr.MaskKey()
436 | mask(key, fr.b)
437 | }
438 |
439 | fr.UnsetMask()
440 | }
441 |
442 | // WriteTo writes the frame into wr.
443 | func (fr *Frame) WriteTo(wr io.Writer) (n int64, err error) {
444 | var ni int
445 | s := fr.setPayloadLen()
446 |
447 | // +2 because we must include the
448 | // first two bytes (stuff + opcode + mask + payload len)
449 | ni, err = wr.Write(fr.op[:s+2])
450 | if err == nil {
451 | n += int64(ni)
452 |
453 | if fr.IsMasked() {
454 | ni, err = wr.Write(fr.mask)
455 | if ni > 0 {
456 | n += int64(ni)
457 | }
458 | }
459 |
460 | if err == nil && len(fr.b) != 0 {
461 | ni, err = wr.Write(fr.b)
462 | if ni > 0 {
463 | n += int64(ni)
464 | }
465 | }
466 | }
467 |
468 | return
469 | }
470 |
471 | // Status returns StatusCode.
472 | func (fr *Frame) Status() (status StatusCode) {
473 | if len(fr.b) < 2 {
474 | return StatusNone
475 | }
476 |
477 | u := binary.BigEndian.Uint16(fr.b[:2])
478 |
479 | status = StatusCode(u)
480 |
481 | return
482 | }
483 |
484 | // SetStatus sets status code.
485 | //
486 | // Status code is usually used in Close request.
487 | func (fr *Frame) SetStatus(status StatusCode) {
488 | if !fr.statusDefined {
489 | if cap(fr.b) < 2 {
490 | fr.b = append(fr.b, make([]byte, 2)...)
491 | }
492 |
493 | fr.b = append(fr.b[:2], fr.b...)
494 | }
495 | fr.statusDefined = true
496 |
497 | binary.BigEndian.PutUint16(fr.b[:2], uint16(status))
498 | }
499 |
500 | // mustRead returns the number of bytes that must be
501 | // read to decode the length of the payload.
502 | func (fr *Frame) mustRead() (n int) {
503 | switch fr.op[1] & 127 {
504 | case 127:
505 | n = 8
506 | case 126:
507 | n = 2
508 | default:
509 | n = 0
510 | }
511 |
512 | return
513 | }
514 |
515 | var (
516 | // EOF represents an io.EOF error.
517 | errMalformedHeader = errors.New("malformed header")
518 | errBadHeaderSize = errors.New("header size is insufficient")
519 | )
520 |
521 | // ReadFrom fills fr reading from rd.
522 | func (fr *Frame) ReadFrom(rd io.Reader) (int64, error) {
523 | return fr.readFrom(rd)
524 | }
525 |
526 | var (
527 | errReadingHeader = errors.New("error reading frame header")
528 | errReadingLen = errors.New("error reading b length")
529 | errReadingMask = errors.New("error reading mask")
530 | errLenTooBig = errors.New("message length is bigger than expected")
531 | errStatusLen = errors.New("length of the status must be = 2")
532 | )
533 |
534 | const limitLen = 1 << 32
535 |
536 | func (fr *Frame) readFrom(r io.Reader) (int64, error) {
537 | var err error
538 | var n, m int
539 |
540 | // read the first 2 bytes (stuff + opcode + maskbit + payload len)
541 | n, err = io.ReadFull(r, fr.op[:2])
542 | if err == io.ErrUnexpectedEOF {
543 | err = errReadingHeader
544 | }
545 |
546 | if err == nil {
547 | // get how many bytes we should read to read the length
548 | m = fr.mustRead() + 2
549 |
550 | if m > 2 { // reading length
551 | n, err = io.ReadFull(r, fr.op[2:m]) // start from 2 to fill in 2:m
552 | if err == io.ErrUnexpectedEOF {
553 | err = errReadingLen
554 | }
555 | }
556 |
557 | if err == nil && fr.IsMasked() { // reading mask
558 | n, err = io.ReadFull(r, fr.mask[:4])
559 | if err == io.ErrUnexpectedEOF {
560 | err = errReadingMask
561 | }
562 | }
563 |
564 | if err == nil {
565 | // reading the payload
566 | if frameSize := fr.Len(); (fr.max > 0 && frameSize > fr.max) || frameSize > limitLen {
567 | err = errLenTooBig
568 | } else if frameSize > 0 { // read the payload
569 | nn := int64(frameSize)
570 | if nn < 0 {
571 | panic("uint64 to int64 conversion gave a negative number")
572 | }
573 |
574 | if nn > 0 {
575 | if rLen := nn - int64(cap(fr.b)); rLen > 0 {
576 | fr.b = append(fr.b[:cap(fr.b)], make([]byte, rLen)...)
577 | }
578 |
579 | fr.b = fr.b[:nn]
580 | n, err = io.ReadFull(r, fr.b)
581 | }
582 | }
583 | }
584 | }
585 |
586 | return int64(n), err
587 | }
588 |
--------------------------------------------------------------------------------
/frame_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "io"
7 | "testing"
8 | )
9 |
10 | var (
11 | littlePacket = []byte{0x81, 0x05, 72, 101, 108, 108, 111}
12 | hugePacket = []byte{0x81, 126, 0, 252, 182, 94, 215, 80, 169, 88, 21, 155, 191, 28, 138, 68, 178, 44, 170, 219, 130, 133, 85, 166, 58, 147, 227, 86, 70, 176, 19, 210, 89, 220, 34, 155, 41, 22, 253, 72, 204, 255, 114, 37, 226, 180, 45, 102, 239, 178, 111, 33, 88, 192, 211, 248, 146, 132, 97, 230, 107, 155, 60, 230, 156, 114, 247, 95, 247, 124, 57, 5, 172, 104, 140, 156, 245, 201, 126, 165, 106, 4, 92, 168, 20, 90, 50, 179, 33, 126, 165, 195, 225, 43, 65, 177, 17, 165, 30, 147, 38, 24, 65, 148, 44, 241, 53, 200, 182, 137, 232, 56, 201, 227, 189, 215, 16, 110, 211, 219, 221, 103, 154, 165, 246, 186, 27, 28, 80, 82, 39, 201, 58, 11, 249, 194, 59, 230, 69, 166, 185, 62, 3, 15, 249, 56, 175, 174, 200, 33, 104, 106, 127, 222, 84, 140, 85, 209, 138, 181, 90, 168, 0, 250, 15, 75, 10, 3, 30, 161, 153, 150, 60, 7, 56, 218, 140, 189, 121, 23, 102, 40, 183, 242, 84, 37, 166, 219, 117, 99, 219, 0, 88, 228, 55, 113, 112, 158, 26, 26, 107, 97, 247, 138, 7, 19, 86, 138, 17, 4, 168, 44, 120, 19, 89, 179, 237, 167, 198, 71, 208, 154, 12, 149, 236, 90, 37, 39, 111, 180, 173, 11, 30, 209, 93, 226, 148, 122, 198, 26, 97, 60, 61, 190, 227, 151, 60, 2, 119, 174, 123, 76, 107, 253, 78, 61}
13 | )
14 |
15 | func TestIssue11(t *testing.T) {
16 | fr := AcquireFrame()
17 | fr.SetClose()
18 | fr.SetFin()
19 | fr.setLength(1)
20 |
21 | bf := bytes.NewBuffer(nil)
22 | fr.WriteTo(bf)
23 | bf.Write([]byte("some garbage"))
24 |
25 | fr.Reset()
26 | fr.readFrom(bf) // should panic before the commit fixing this thing
27 | }
28 |
29 | func TestReadBufio(t *testing.T) {
30 | reader := bufio.NewReader(
31 | bytes.NewBuffer(littlePacket),
32 | )
33 | fr := AcquireFrame()
34 |
35 | _, err := fr.ReadFrom(reader)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 | checkValues(fr, t, false, true, littlePacket[2:])
40 |
41 | ReleaseFrame(fr)
42 | }
43 |
44 | func TestReadHugeBufio(t *testing.T) {
45 | reader := bufio.NewReader(
46 | bytes.NewBuffer(hugePacket),
47 | )
48 | fr := AcquireFrame()
49 |
50 | _, err := fr.ReadFrom(reader)
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 | checkValues(fr, t, false, true, hugePacket[4:])
55 |
56 | ReleaseFrame(fr)
57 | }
58 |
59 | func TestReadStd(t *testing.T) {
60 | fr := AcquireFrame()
61 | _, err := fr.ReadFrom(
62 | bytes.NewBuffer(littlePacket),
63 | )
64 | if err != nil {
65 | t.Fatal(err)
66 | }
67 | checkValues(fr, t, false, true, littlePacket[2:])
68 | ReleaseFrame(fr)
69 | }
70 |
71 | func TestReadHugeStd(t *testing.T) {
72 | fr := AcquireFrame()
73 | _, err := fr.ReadFrom(
74 | bytes.NewBuffer(hugePacket),
75 | )
76 | if err != nil {
77 | t.Fatal(err)
78 | }
79 | checkValues(fr, t, false, true, hugePacket[4:])
80 | ReleaseFrame(fr)
81 | }
82 |
83 | func checkValues(fr *Frame, t *testing.T, c, fin bool, payload []byte) {
84 | if fin && !fr.IsFin() {
85 | t.Fatal("Is not fin")
86 | }
87 |
88 | if c && !fr.IsContinuation() {
89 | t.Fatal("Is not continuation")
90 | }
91 |
92 | if int(fr.Len()) != len(payload) {
93 | t.Fatalf("Incorrect length: %d<>%d", fr.Len(), len(payload))
94 | }
95 |
96 | p := fr.Payload()
97 | if !bytes.Equal(p, payload) {
98 | t.Fatalf("Bad payload %s<>%s", p, payload)
99 | }
100 | }
101 |
102 | func BenchmarkRead(b *testing.B) {
103 | b.ReportAllocs()
104 |
105 | b.RunParallel(func(pb *testing.PB) {
106 | var err error
107 | var r = bytes.NewBuffer(littlePacket)
108 | reader := bufio.NewReader(r)
109 |
110 | fr := AcquireFrame()
111 | for pb.Next() {
112 | _, err = fr.ReadFrom(reader)
113 | if err != nil && err != io.EOF {
114 | break
115 | }
116 | fr.Reset()
117 | reader.Reset(r)
118 | err = nil
119 | }
120 | if err != nil {
121 | b.Fatal(err)
122 | }
123 | ReleaseFrame(fr)
124 | })
125 | }
126 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dgrr/websocket
2 |
3 | go 1.6
4 |
5 | require (
6 | github.com/gobwas/ws v1.0.4
7 | github.com/gorilla/websocket v1.4.2
8 | github.com/valyala/bytebufferpool v1.0.0
9 | github.com/valyala/fasthttp v1.28.0
10 | nhooyr.io/websocket v1.8.6
11 | )
12 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
7 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
8 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
9 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
10 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
11 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
12 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
13 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
14 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
15 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
16 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
17 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
18 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
19 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
20 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
21 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
22 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
23 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
24 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
25 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
26 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
27 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
28 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
29 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
30 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
31 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
32 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
33 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
34 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
35 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
36 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
37 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
38 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
39 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
40 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
41 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
42 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
43 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
44 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
45 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
46 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
47 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
48 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
49 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
51 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
52 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
53 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
54 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
55 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
56 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
57 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
58 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
59 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
60 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
61 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
62 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
63 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
64 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
65 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
66 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
67 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
68 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
69 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
70 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
71 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
72 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
73 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
74 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
75 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
76 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
77 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
79 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
80 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
81 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
82 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
83 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
84 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
85 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
86 |
--------------------------------------------------------------------------------
/mask.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "crypto/rand"
5 | )
6 |
7 | func mask(mask, b []byte) {
8 | for i := range b {
9 | b[i] ^= mask[i&3]
10 | }
11 | }
12 |
13 | func readMask(b []byte) {
14 | rand.Read(b)
15 | }
16 |
--------------------------------------------------------------------------------
/mask_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | var (
9 | toUnmask = []byte{68, 5, 230, 92, 99, 64, 253, 95, 126, 12, 238, 16, 124, 20, 229, 95, 99}
10 | unmasked = []byte("Hello world ptooo")
11 | )
12 |
13 | func TestUnmask(t *testing.T) {
14 | m := make([]byte, len(toUnmask))
15 | key := []byte{12, 96, 138, 48, 255}
16 | copy(m, toUnmask)
17 | mask(key, m)
18 | if !bytes.Equal(unmasked, m) {
19 | t.Fatalf("%v <> %s", m, unmasked)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "io"
7 | "net"
8 | "net/http"
9 | "strings"
10 | "sync"
11 | "sync/atomic"
12 |
13 | "github.com/valyala/bytebufferpool"
14 | "github.com/valyala/fasthttp"
15 | )
16 |
17 | type (
18 | // OpenHandler handles when a connection is open.
19 | OpenHandler func(c *Conn)
20 | // PingHandler handles the data from a ping frame.
21 | PingHandler func(c *Conn, data []byte)
22 | // PongHandler receives the data from a pong frame.
23 | PongHandler func(c *Conn, data []byte)
24 | // MessageHandler receives the payload content of a data frame
25 | // indicating whether the content is binary or not.
26 | MessageHandler func(c *Conn, isBinary bool, data []byte)
27 | // FrameHandler receives the raw frame. This handler is optional,
28 | // if none is specified the server will run a default handler.
29 | //
30 | // If the user specifies a FrameHandler, then it is going to receive all incoming frames.
31 | FrameHandler func(c *Conn, fr *Frame)
32 | // CloseHandler fires when a connection has been closed.
33 | CloseHandler func(c *Conn, err error)
34 | // ErrorHandler fires when an unknown error happens.
35 | ErrorHandler func(c *Conn, err error)
36 | )
37 |
38 | // Server represents the WebSocket server.
39 | //
40 | // Server is going to be in charge of upgrading the connection, is not a server per-se.
41 | type Server struct {
42 | // UpgradeHandler allows the user to handle RequestCtx when upgrading for fasthttp.
43 | //
44 | // If UpgradeHandler returns false the connection won't be upgraded.
45 | UpgradeHandler UpgradeHandler
46 |
47 | // UpgradeHandler allows the user to handle the request when upgrading for net/http.
48 | //
49 | // If UpgradeNetHandler returns false, the connection won't be upgraded.
50 | UpgradeNetHandler UpgradeNetHandler
51 |
52 | // Protocols are the supported protocols.
53 | Protocols []string
54 |
55 | // Origin is used to limit the clients coming from the defined origin
56 | Origin string
57 |
58 | nextID uint64
59 |
60 | openHandler OpenHandler
61 | frHandler FrameHandler
62 | closeHandler CloseHandler
63 | msgHandler MessageHandler
64 | pingHandler PingHandler
65 | pongHandler PongHandler
66 | errHandler ErrorHandler
67 |
68 | once sync.Once
69 | }
70 |
71 | func (s *Server) initServer() {
72 | if s.frHandler != nil {
73 | return
74 | }
75 |
76 | s.frHandler = s.handleFrame
77 | }
78 |
79 | // HandleData sets the MessageHandler.
80 | func (s *Server) HandleData(msgHandler MessageHandler) {
81 | s.msgHandler = msgHandler
82 | }
83 |
84 | // HandleOpen sets a callback for handling opening connections.
85 | func (s *Server) HandleOpen(openHandler OpenHandler) {
86 | s.openHandler = openHandler
87 | }
88 |
89 | // HandleClose sets a callback for handling connection close.
90 | func (s *Server) HandleClose(closeHandler CloseHandler) {
91 | s.closeHandler = closeHandler
92 | }
93 |
94 | // HandlePing sets a callback for handling the data of the ping frames.
95 | //
96 | // The server is in charge of replying to the PING frames, thus the client
97 | // MUST not reply to any control frame.
98 | func (s *Server) HandlePing(pingHandler PingHandler) {
99 | s.pingHandler = pingHandler
100 | }
101 |
102 | // HandlePong sets a callback for handling the data of the pong frames.
103 | func (s *Server) HandlePong(pongHandler PongHandler) {
104 | s.pongHandler = pongHandler
105 | }
106 |
107 | // HandleError ...
108 | func (s *Server) HandleError(errHandler ErrorHandler) {
109 | s.errHandler = errHandler
110 | }
111 |
112 | // HandleFrame sets a callback for handling all the incoming Frames.
113 | //
114 | // If none is specified, the server will run a default handler.
115 | func (s *Server) HandleFrame(frameHandler FrameHandler) {
116 | s.frHandler = frameHandler
117 | }
118 |
119 | // Upgrade upgrades websocket connections.
120 | func (s *Server) Upgrade(ctx *fasthttp.RequestCtx) {
121 | if !ctx.IsGet() {
122 | ctx.SetStatusCode(fasthttp.StatusBadRequest)
123 | return
124 | }
125 |
126 | s.once.Do(s.initServer)
127 |
128 | // Checking Origin header if needed
129 | origin := ctx.Request.Header.Peek("Origin")
130 | if s.Origin != "" {
131 | uri := fasthttp.AcquireURI()
132 | uri.Update(s.Origin)
133 |
134 | b := bytePool.Get().([]byte)
135 | b = prepareOrigin(b, uri)
136 | fasthttp.ReleaseURI(uri)
137 |
138 | if !equalsFold(b, origin) {
139 | ctx.SetStatusCode(fasthttp.StatusForbidden)
140 | bytePool.Put(b)
141 | return
142 | }
143 |
144 | bytePool.Put(b)
145 | }
146 |
147 | // Normalizing must be disabled because of WebSocket header fields.
148 | // (This is not a fasthttp bug).
149 | ctx.Response.Header.DisableNormalizing()
150 |
151 | // Connection.Value == Upgrade
152 | if ctx.Request.Header.ConnectionUpgrade() {
153 | // Peek sade header field.
154 | hup := ctx.Request.Header.PeekBytes(upgradeString)
155 | // Compare with websocket string defined by the RFC
156 | if equalsFold(hup, websocketString) {
157 | // Checking websocket version
158 | hversion := ctx.Request.Header.PeekBytes(wsHeaderVersion)
159 |
160 | // Peeking websocket key.
161 | hkey := ctx.Request.Header.PeekBytes(wsHeaderKey)
162 | hprotos := bytes.Split( // TODO: Reduce allocations. Do not split. Use IndexByte
163 | ctx.Request.Header.PeekBytes(wsHeaderProtocol), commaString,
164 | )
165 |
166 | supported := false
167 | // Checking versions
168 | for i := range supportedVersions {
169 | if bytes.Contains(supportedVersions[i], hversion) {
170 | supported = true
171 | break
172 | }
173 | }
174 |
175 | if !supported {
176 | ctx.Error("Versions not supported", fasthttp.StatusBadRequest)
177 | return
178 | }
179 |
180 | if s.UpgradeHandler != nil {
181 | if !s.UpgradeHandler(ctx) {
182 | return
183 | }
184 | }
185 | // TODO: compression
186 | // compress := mustCompress(exts)
187 |
188 | // Setting response headers
189 | ctx.Response.SetStatusCode(fasthttp.StatusSwitchingProtocols)
190 | ctx.Response.Header.AddBytesKV(connectionString, upgradeString)
191 | ctx.Response.Header.AddBytesKV(upgradeString, websocketString)
192 | ctx.Response.Header.AddBytesKV(wsHeaderAccept, makeKey(hkey, hkey))
193 |
194 | // TODO: implement bad websocket version
195 | // https://tools.ietf.org/html/rfc6455#section-4.4
196 | if proto := selectProtocol(hprotos, s.Protocols); proto != "" {
197 | ctx.Response.Header.AddBytesK(wsHeaderProtocol, proto)
198 | }
199 |
200 | nctx := context.Background()
201 | ctx.VisitUserValues(func(k []byte, v interface{}) {
202 | nctx = context.WithValue(nctx, string(k), v)
203 | })
204 |
205 | ctx.Hijack(func(c net.Conn) {
206 | if nc, ok := c.(interface {
207 | UnsafeConn() net.Conn
208 | }); ok {
209 | c = nc.UnsafeConn()
210 | }
211 |
212 | conn := acquireConn(c)
213 | conn.id = atomic.AddUint64(&s.nextID, 1)
214 | // establishing default options
215 | conn.ctx = nctx
216 |
217 | if s.openHandler != nil {
218 | s.openHandler(conn)
219 | }
220 |
221 | s.serveConn(conn)
222 | })
223 | }
224 | }
225 | }
226 |
227 | // NetUpgrade upgrades the websocket connection for net/http.
228 | func (s *Server) NetUpgrade(resp http.ResponseWriter, req *http.Request) {
229 | if req.Method != "GET" {
230 | resp.WriteHeader(http.StatusBadRequest)
231 | return
232 | }
233 |
234 | rs := fasthttp.AcquireResponse()
235 | defer fasthttp.ReleaseResponse(rs)
236 |
237 | s.once.Do(s.initServer)
238 |
239 | // Checking Origin header if needed
240 | origin := req.Header.Get("Origin")
241 | if s.Origin != "" {
242 | uri := fasthttp.AcquireURI()
243 | uri.Update(s.Origin)
244 |
245 | b := bytePool.Get().([]byte)
246 | b = prepareOrigin(b, uri)
247 | fasthttp.ReleaseURI(uri)
248 |
249 | if !equalsFold(b, s2b(origin)) {
250 | resp.WriteHeader(http.StatusForbidden)
251 | bytePool.Put(b)
252 | return
253 | }
254 |
255 | bytePool.Put(b)
256 | }
257 |
258 | // Normalizing must be disabled because of WebSocket header fields.
259 | // (This is not a fasthttp bug).
260 | rs.Header.DisableNormalizing()
261 |
262 | hasUpgrade := func() bool {
263 | for _, v := range req.Header["Connection"] {
264 | if strings.Contains(v, "Upgrade") {
265 | return true
266 | }
267 | }
268 | return false
269 | }()
270 |
271 | // Connection.Value == Upgrade
272 | if hasUpgrade {
273 | // Peek sade header field.
274 | hup := req.Header.Get("Upgrade")
275 | // Compare with websocket string defined by the RFC
276 | if equalsFold(s2b(hup), websocketString) {
277 | // Checking websocket version
278 | hversion := req.Header.Get(b2s(wsHeaderVersion))
279 | // Peeking websocket key.
280 | hkey := req.Header.Get(b2s(wsHeaderKey))
281 | hprotos := bytes.Split( // TODO: Reduce allocations. Do not split. Use IndexByte
282 | s2b(req.Header.Get(b2s(wsHeaderProtocol))), commaString,
283 | )
284 | supported := false
285 | // Checking versions
286 | for i := range supportedVersions {
287 | if bytes.Contains(supportedVersions[i], s2b(hversion)) {
288 | supported = true
289 | break
290 | }
291 | }
292 | if !supported {
293 | resp.WriteHeader(http.StatusBadRequest)
294 | io.WriteString(resp, "Versions not supported")
295 | return
296 | }
297 |
298 | if s.UpgradeNetHandler != nil {
299 | if !s.UpgradeNetHandler(resp, req) {
300 | return
301 | }
302 | }
303 | // TODO: compression
304 |
305 | h, ok := resp.(http.Hijacker)
306 | if !ok {
307 | resp.WriteHeader(http.StatusInternalServerError)
308 | return
309 | }
310 |
311 | c, _, err := h.Hijack()
312 | if err != nil {
313 | io.WriteString(resp, err.Error())
314 | return
315 | }
316 |
317 | // Setting response headers
318 | rs.SetStatusCode(fasthttp.StatusSwitchingProtocols)
319 | rs.Header.AddBytesKV(connectionString, upgradeString)
320 | rs.Header.AddBytesKV(upgradeString, websocketString)
321 | rs.Header.AddBytesKV(wsHeaderAccept, makeKey(s2b(hkey), s2b(hkey)))
322 | // TODO: implement bad websocket version
323 | // https://tools.ietf.org/html/rfc6455#section-4.4
324 | if proto := selectProtocol(hprotos, s.Protocols); proto != "" {
325 | rs.Header.AddBytesK(wsHeaderProtocol, proto)
326 | }
327 |
328 | _, err = rs.WriteTo(c)
329 | if err != nil {
330 | c.Close()
331 | return
332 | }
333 |
334 | go func(ctx context.Context) {
335 | conn := acquireConn(c)
336 | conn.id = atomic.AddUint64(&s.nextID, 1)
337 | conn.ctx = ctx
338 |
339 | if s.openHandler != nil {
340 | s.openHandler(conn)
341 | }
342 |
343 | s.serveConn(conn)
344 | }(req.Context())
345 | }
346 | }
347 | }
348 |
349 | func (s *Server) serveConn(c *Conn) {
350 | var closeErr error
351 |
352 | loop:
353 | for {
354 | select {
355 | case fr := <-c.input:
356 | s.frHandler(c, fr)
357 | case err := <-c.errch:
358 | if err == nil {
359 | break loop
360 | }
361 |
362 | if ce, ok := err.(closeError); ok {
363 | closeErr = ce.err
364 | break loop
365 | }
366 |
367 | if ce, ok := err.(Error); ok {
368 | closeErr = ce
369 | break loop
370 | }
371 |
372 | if s.errHandler != nil {
373 | s.errHandler(c, err)
374 | }
375 | case <-c.closer:
376 | break loop
377 | }
378 | }
379 |
380 | if s.closeHandler != nil {
381 | s.closeHandler(c, closeErr)
382 | }
383 |
384 | c.c.Close()
385 |
386 | c.wg.Wait()
387 | }
388 |
389 | func (s *Server) handleFrame(c *Conn, fr *Frame) {
390 | // TODO: error if not masked
391 | if fr.IsMasked() {
392 | fr.Unmask()
393 | }
394 |
395 | if fr.IsControl() {
396 | s.handleControl(c, fr)
397 | } else {
398 | s.handleFrameData(c, fr)
399 | }
400 | }
401 |
402 | func (s *Server) handleFrameData(c *Conn, fr *Frame) {
403 | var data []byte
404 |
405 | isBinary := fr.Code() == CodeBinary
406 |
407 | bf := c.buffered
408 | if bf == nil {
409 | if fr.IsFin() {
410 | data = fr.Payload()
411 | } else {
412 | bf = bytebufferpool.Get()
413 | bf.Reset()
414 |
415 | c.buffered = bf
416 | bf.Write(fr.Payload())
417 | }
418 | } else {
419 | bf.Write(fr.Payload())
420 | if fr.IsFin() {
421 | data = bf.B
422 | c.buffered = nil
423 | defer bytebufferpool.Put(bf)
424 | }
425 | }
426 |
427 | if len(data) != 0 && s.msgHandler != nil {
428 | s.msgHandler(c, isBinary, data)
429 | }
430 |
431 | ReleaseFrame(fr)
432 | }
433 |
434 | func (s *Server) handleControl(c *Conn, fr *Frame) {
435 | switch {
436 | case fr.IsPing():
437 | s.handlePing(c, fr.Payload())
438 | case fr.IsPong():
439 | s.handlePong(c, fr.Payload())
440 | case fr.IsClose():
441 | s.handleClose(c, fr)
442 | }
443 | }
444 |
445 | func (s *Server) handlePing(c *Conn, data []byte) {
446 | if s.pingHandler != nil {
447 | s.pingHandler(c, data)
448 | }
449 |
450 | pong := AcquireFrame()
451 | pong.SetCode(CodePong)
452 | pong.SetPayload(data)
453 | pong.SetFin()
454 |
455 | c.WriteFrame(pong)
456 | }
457 |
458 | func (s *Server) handlePong(c *Conn, data []byte) {
459 | if s.pongHandler != nil {
460 | s.pongHandler(c, data)
461 | }
462 | }
463 |
464 | func (s *Server) handleClose(c *Conn, fr *Frame) {
465 | defer c.closeOnce.Do(func() { close(c.closer) })
466 | c.errch <- func() error {
467 | if fr.Status() != StatusNone {
468 | return Error{
469 | Status: fr.Status(),
470 | Reason: string(fr.Payload()),
471 | }
472 | }
473 |
474 | return nil
475 | }()
476 |
477 | status := fr.Status()
478 |
479 | fr = AcquireFrame()
480 | fr.SetClose()
481 | fr.SetStatus(status)
482 | fr.SetFin()
483 |
484 | // reply back
485 | c.WriteFrame(fr)
486 | }
487 |
--------------------------------------------------------------------------------
/server_timing_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "context"
5 | "io"
6 | "net"
7 | "net/http"
8 | "runtime"
9 | "sync"
10 | "sync/atomic"
11 | "testing"
12 | "time"
13 |
14 | "github.com/gobwas/ws"
15 | "github.com/gobwas/ws/wsutil"
16 | "github.com/gorilla/websocket"
17 | "github.com/valyala/fasthttp"
18 | nyws "nhooyr.io/websocket"
19 | )
20 |
21 | type fakeServerConn struct {
22 | net.TCPConn
23 | ln *fakeListener
24 | requestsCount int
25 | pos int
26 | closed uint32
27 | }
28 |
29 | func (c *fakeServerConn) Read(b []byte) (int, error) {
30 | nn := 0
31 | reqLen := len(c.ln.request)
32 | for len(b) > 0 {
33 | if c.requestsCount == 0 {
34 | if nn == 0 {
35 | return 0, io.EOF
36 | }
37 | return nn, nil
38 | }
39 | pos := c.pos % reqLen
40 | n := copy(b, c.ln.request[pos:])
41 | b = b[n:]
42 | nn += n
43 | c.pos += n
44 | if n+pos == reqLen {
45 | c.requestsCount--
46 | }
47 | }
48 | return nn, nil
49 | }
50 |
51 | func (c *fakeServerConn) Write(b []byte) (int, error) {
52 | return len(b), nil
53 | }
54 |
55 | var fakeAddr = net.TCPAddr{
56 | IP: []byte{1, 2, 3, 4},
57 | Port: 12345,
58 | }
59 |
60 | func (c *fakeServerConn) RemoteAddr() net.Addr {
61 | return &fakeAddr
62 | }
63 |
64 | func (c *fakeServerConn) Close() error {
65 | if atomic.AddUint32(&c.closed, 1) == 1 {
66 | c.ln.ch <- c
67 | }
68 | return nil
69 | }
70 |
71 | func (c *fakeServerConn) SetReadDeadline(t time.Time) error {
72 | return nil
73 | }
74 |
75 | func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {
76 | return nil
77 | }
78 |
79 | type fakeListener struct {
80 | lock sync.Mutex
81 | requestsCount int
82 | requestsPerConn int
83 | request []byte
84 | ch chan *fakeServerConn
85 | done chan struct{}
86 | closed bool
87 | }
88 |
89 | func (ln *fakeListener) Accept() (net.Conn, error) {
90 | ln.lock.Lock()
91 | if ln.requestsCount == 0 {
92 | ln.lock.Unlock()
93 | for len(ln.ch) < cap(ln.ch) {
94 | time.Sleep(10 * time.Millisecond)
95 | }
96 | ln.lock.Lock()
97 | if !ln.closed {
98 | close(ln.done)
99 | ln.closed = true
100 | }
101 | ln.lock.Unlock()
102 | return nil, io.EOF
103 | }
104 | requestsCount := ln.requestsPerConn
105 | if requestsCount > ln.requestsCount {
106 | requestsCount = ln.requestsCount
107 | }
108 | ln.requestsCount -= requestsCount
109 | ln.lock.Unlock()
110 |
111 | c := <-ln.ch
112 | c.requestsCount = requestsCount
113 | c.closed = 0
114 | c.pos = 0
115 |
116 | return c, nil
117 | }
118 |
119 | func (ln *fakeListener) Close() error {
120 | return nil
121 | }
122 |
123 | func (ln *fakeListener) Addr() net.Addr {
124 | return &fakeAddr
125 | }
126 |
127 | func newFakeListener(requestsCount, clientsCount, requestsPerConn int, request []byte) *fakeListener {
128 | ln := &fakeListener{
129 | requestsCount: requestsCount,
130 | requestsPerConn: requestsPerConn,
131 | request: request,
132 | ch: make(chan *fakeServerConn, clientsCount),
133 | done: make(chan struct{}),
134 | }
135 | for i := 0; i < clientsCount; i++ {
136 | ln.ch <- &fakeServerConn{
137 | ln: ln,
138 | }
139 | }
140 | return ln
141 | }
142 |
143 | func buildUpgrade() (s []byte) {
144 | key := makeRandKey(nil)
145 | s = append(s, "GET / HTPP/1.1\r\n"+
146 | "Host: localhost:8080\r\n"+
147 | "Connection: Upgrade\r\n"+
148 | "Upgrade: websocket\r\n"+
149 | "Sec-WebSocket-Version: 13\r\n"+
150 | "Sec-WebSocket-Key: "+string(key)+"\r\n\r\n"...)
151 | s = append(s, []byte{129, 37, 109, 97, 107, 101, 32, 102, 97, 115, 116, 104, 116, 116, 112, 32, 103, 114, 101, 97, 116, 32, 97, 103, 97, 105, 110, 32, 119, 105, 116, 104, 32, 72, 84, 84, 80, 47, 50}...)
152 | return s
153 | }
154 |
155 | func benchmarkFastServer(b *testing.B, clients, count int) {
156 | ws := Server{}
157 |
158 | s := fasthttp.Server{
159 | Handler: ws.Upgrade,
160 | }
161 | ch := make(chan struct{}, 1)
162 | ln := newFakeListener(b.N, clients, count, buildUpgrade())
163 |
164 | go func() {
165 | s.Serve(ln)
166 | ch <- struct{}{}
167 | }()
168 |
169 | <-ln.done
170 |
171 | select {
172 | case <-ch:
173 | case <-time.After(time.Second * 10):
174 | b.Fatal("timeout")
175 | }
176 | }
177 |
178 | func benchmarkGorillaServer(b *testing.B, clients, count int) {
179 | s := http.Server{
180 | Handler: &handler{b},
181 | }
182 | ch := make(chan struct{}, 1)
183 | ln := newFakeListener(b.N, clients, count, buildUpgrade())
184 |
185 | go func() {
186 | s.Serve(ln)
187 | ch <- struct{}{}
188 | }()
189 |
190 | <-ln.done
191 | select {
192 | case <-ch:
193 | case <-time.After(time.Second * 10):
194 | b.Fatal("timeout")
195 | }
196 | }
197 |
198 | var upgrader = websocket.Upgrader{}
199 |
200 | type handler struct {
201 | b *testing.B
202 | }
203 |
204 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
205 | c, err := upgrader.Upgrade(w, r, nil)
206 | if err != nil {
207 | h.b.Fatal(err)
208 | return
209 | }
210 | for {
211 | _, _, err := c.ReadMessage()
212 | if err != nil {
213 | if err == io.EOF {
214 | break
215 | }
216 | panic(err)
217 | }
218 | }
219 | c.Close()
220 | }
221 |
222 | type handlerNhooyr struct {
223 | b *testing.B
224 | }
225 |
226 | func (h *handlerNhooyr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
227 | c, err := nyws.Accept(w, r, &nyws.AcceptOptions{
228 | CompressionMode: nyws.CompressionDisabled, // disable compression to be fair
229 | })
230 | if err != nil {
231 | h.b.Fatal(err)
232 | return
233 | }
234 |
235 | ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
236 | defer cancel()
237 |
238 | for {
239 | _, _, err := c.Read(ctx)
240 | if err != nil {
241 | if err == io.EOF {
242 | break
243 | }
244 | panic(err)
245 | }
246 | }
247 | c.Close(nyws.StatusNormalClosure, "")
248 | }
249 |
250 | func benchmarkNhooyrServer(b *testing.B, clients, count int) {
251 | s := http.Server{
252 | Handler: &handlerNhooyr{b},
253 | }
254 | ch := make(chan struct{}, 1)
255 | ln := newFakeListener(b.N, clients, count, buildUpgrade())
256 |
257 | go func() {
258 | s.Serve(ln)
259 | ch <- struct{}{}
260 | }()
261 |
262 | <-ln.done
263 | select {
264 | case <-ch:
265 | case <-time.After(time.Second * 10):
266 | b.Fatal("timeout")
267 | }
268 | }
269 |
270 | type handlerGobwas struct {
271 | b *testing.B
272 | }
273 |
274 | func benchmarkGobwasServer(b *testing.B, clients, count int) {
275 | s := http.Server{
276 | Handler: &handlerGobwas{b},
277 | }
278 | ch := make(chan struct{}, 1)
279 | ln := newFakeListener(b.N, clients, count, buildUpgrade())
280 |
281 | go func() {
282 | s.Serve(ln)
283 | ch <- struct{}{}
284 | }()
285 |
286 | <-ln.done
287 | select {
288 | case <-ch:
289 | case <-time.After(time.Second * 10):
290 | b.Fatal("timeout")
291 | }
292 | }
293 |
294 | func (h *handlerGobwas) ServeHTTP(w http.ResponseWriter, r *http.Request) {
295 | c, _, _, err := ws.UpgradeHTTP(r, w)
296 | if err != nil {
297 | h.b.Fatal(err)
298 | return
299 | }
300 | for {
301 | _, _, err := wsutil.ReadClientData(c)
302 | if err != nil {
303 | if err == io.EOF {
304 | break
305 | }
306 | panic(err)
307 | }
308 | }
309 | c.Close()
310 | }
311 |
312 | func Benchmark1000FastClientsPer10Messages(b *testing.B) {
313 | benchmarkFastServer(b, 1000, 10)
314 | }
315 |
316 | func Benchmark1000FastClientsPer100Messages(b *testing.B) {
317 | benchmarkFastServer(b, 1000, 100)
318 | }
319 |
320 | func Benchmark1000FastClientsPer1000Messages(b *testing.B) {
321 | benchmarkFastServer(b, 1000, 1000)
322 | }
323 |
324 | func Benchmark1000GorillaClientsPer10Messages(b *testing.B) {
325 | benchmarkGorillaServer(b, 1000, 10)
326 | }
327 |
328 | func Benchmark1000GorillaClientsPer100Messages(b *testing.B) {
329 | benchmarkGorillaServer(b, 1000, 100)
330 | }
331 |
332 | func Benchmark1000GorillaClientsPer1000Messages(b *testing.B) {
333 | benchmarkGorillaServer(b, 1000, 1000)
334 | }
335 |
336 | func Benchmark1000NhooyrClientsPer10Messages(b *testing.B) {
337 | benchmarkNhooyrServer(b, 1000, 10)
338 | }
339 |
340 | func Benchmark1000NhooyrClientsPer100Messages(b *testing.B) {
341 | benchmarkNhooyrServer(b, 1000, 100)
342 | }
343 |
344 | func Benchmark1000NhooyrClientsPer1000Messages(b *testing.B) {
345 | benchmarkNhooyrServer(b, 1000, 1000)
346 | }
347 |
348 | func Benchmark1000GobwasClientsPer10Messages(b *testing.B) {
349 | benchmarkGobwasServer(b, 1000, 10)
350 | }
351 |
352 | func Benchmark1000GobwasClientsPer100Messages(b *testing.B) {
353 | benchmarkGobwasServer(b, 1000, 100)
354 | }
355 |
356 | func Benchmark1000GobwasClientsPer1000Messages(b *testing.B) {
357 | benchmarkGobwasServer(b, 1000, 1000)
358 | }
359 |
360 | func Benchmark100FastMsgsPerConn(b *testing.B) {
361 | benchmarkFastServer(b, runtime.NumCPU(), 100)
362 | }
363 |
364 | func Benchmark1000FastMsgsPerConn(b *testing.B) {
365 | benchmarkFastServer(b, runtime.NumCPU(), 1000)
366 | }
367 |
368 | func Benchmark10000FastMsgsPerConn(b *testing.B) {
369 | benchmarkFastServer(b, runtime.NumCPU(), 10000)
370 | }
371 |
372 | func Benchmark100000FastMsgsPerConn(b *testing.B) {
373 | benchmarkFastServer(b, runtime.NumCPU(), 100000)
374 | }
375 |
376 | func Benchmark100GorillaMsgsPerConn(b *testing.B) {
377 | benchmarkGorillaServer(b, runtime.NumCPU(), 100)
378 | }
379 |
380 | func Benchmark1000GorillaMsgsPerConn(b *testing.B) {
381 | benchmarkGorillaServer(b, runtime.NumCPU(), 1000)
382 | }
383 |
384 | func Benchmark10000GorillaMsgsPerConn(b *testing.B) {
385 | benchmarkGorillaServer(b, runtime.NumCPU(), 10000)
386 | }
387 |
388 | func Benchmark100000GorillaMsgsPerConn(b *testing.B) {
389 | benchmarkGorillaServer(b, runtime.NumCPU(), 100000)
390 | }
391 |
392 | func Benchmark100NhooyrMsgsPerConn(b *testing.B) {
393 | benchmarkNhooyrServer(b, runtime.NumCPU(), 100)
394 | }
395 |
396 | func Benchmark1000NhooyrMsgsPerConn(b *testing.B) {
397 | benchmarkNhooyrServer(b, runtime.NumCPU(), 1000)
398 | }
399 |
400 | func Benchmark10000NhooyrMsgsPerConn(b *testing.B) {
401 | benchmarkNhooyrServer(b, runtime.NumCPU(), 10000)
402 | }
403 |
404 | func Benchmark100000NhooyrMsgsPerConn(b *testing.B) {
405 | benchmarkNhooyrServer(b, runtime.NumCPU(), 100000)
406 | }
407 |
408 | func Benchmark100GobwasMsgsPerConn(b *testing.B) {
409 | benchmarkGobwasServer(b, runtime.NumCPU(), 100)
410 | }
411 |
412 | func Benchmark1000GobwasMsgsPerConn(b *testing.B) {
413 | benchmarkGobwasServer(b, runtime.NumCPU(), 1000)
414 | }
415 |
416 | func Benchmark10000GobwasMsgsPerConn(b *testing.B) {
417 | benchmarkGobwasServer(b, runtime.NumCPU(), 10000)
418 | }
419 |
420 | func Benchmark100000GobwasMsgsPerConn(b *testing.B) {
421 | benchmarkGobwasServer(b, runtime.NumCPU(), 100000)
422 | }
423 |
--------------------------------------------------------------------------------
/stress-tests/dgrr.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/dgrr/websocket"
6 | "github.com/valyala/fasthttp"
7 | )
8 |
9 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
10 | c.Write(data)
11 | }
12 |
13 | func main() {
14 | wS := websocket.Server{}
15 | wS.HandleData(OnMessage)
16 |
17 | s := fasthttp.Server{
18 | Handler: wS.Upgrade,
19 | }
20 |
21 | fmt.Println(s.ListenAndServe(":8081"))
22 | }
23 |
--------------------------------------------------------------------------------
/stress-tests/dgrr_net.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/dgrr/websocket"
8 | )
9 |
10 | func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
11 | c.Write(data)
12 | }
13 |
14 | func main() {
15 | wS := websocket.Server{}
16 | wS.HandleData(OnMessage)
17 |
18 | s := http.Server{
19 | Addr: ":8081",
20 | Handler: http.HandlerFunc(wS.NetUpgrade),
21 | }
22 |
23 | fmt.Println(s.ListenAndServe())
24 | }
25 |
--------------------------------------------------------------------------------
/stress-tests/gobwas.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gobwas/ws"
7 | "github.com/gobwas/ws/wsutil"
8 | )
9 |
10 | type handler struct{}
11 |
12 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
13 | c, br, _, err := ws.UpgradeHTTP(r, w)
14 | if err != nil {
15 | return
16 | }
17 |
18 | for {
19 | b, t, err := wsutil.ReadClientData(br)
20 | if err != nil {
21 | break
22 | }
23 | wsutil.WriteServerMessage(br, t, b)
24 | br.Flush()
25 | }
26 |
27 | c.Close()
28 | }
29 |
30 | func main() {
31 | s := http.Server{
32 | Addr: ":8081",
33 | Handler: &handler{},
34 | }
35 | s.ListenAndServe()
36 | }
37 |
--------------------------------------------------------------------------------
/stress-tests/gorilla.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gorilla/websocket"
7 | )
8 |
9 | var upgrader = websocket.Upgrader{}
10 |
11 | type handler struct{}
12 |
13 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
14 | c, err := upgrader.Upgrade(w, r, nil)
15 | if err != nil {
16 | panic(err)
17 | }
18 |
19 | for {
20 | t, msg, err := c.ReadMessage()
21 | if err != nil {
22 | break
23 | }
24 | c.WriteMessage(t, msg)
25 | }
26 | c.Close()
27 | }
28 |
29 | func main() {
30 | s := http.Server{
31 | Addr: ":8081",
32 | Handler: &handler{},
33 | }
34 | s.ListenAndServe()
35 | }
36 |
--------------------------------------------------------------------------------
/stress-tests/nhooyr.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | nyws "nhooyr.io/websocket"
8 | )
9 |
10 | type handler struct{}
11 |
12 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
13 | c, err := nyws.Accept(w, r, &nyws.AcceptOptions{
14 | CompressionMode: nyws.CompressionDisabled, // disable compression to be fair
15 | })
16 | if err != nil {
17 | return
18 | }
19 |
20 | ctx := context.TODO()
21 |
22 | for {
23 | t, b, err := c.Read(ctx)
24 | if err != nil {
25 | break
26 | }
27 | c.Write(ctx, t, b)
28 | }
29 | c.Close(nyws.StatusNormalClosure, "")
30 | }
31 |
32 | func main() {
33 | s := http.Server{
34 | Addr: ":8081",
35 | Handler: &handler{},
36 | }
37 | s.ListenAndServe()
38 | }
39 |
--------------------------------------------------------------------------------
/strings.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | var (
4 | wsString = []byte("ws")
5 | wssString = []byte("wss")
6 | originString = []byte("Origin")
7 | connectionString = []byte("Connection")
8 | upgradeString = []byte("Upgrade")
9 | websocketString = []byte("WebSocket")
10 | commaString = []byte(",")
11 | wsHeaderVersion = []byte("Sec-WebSocket-Version")
12 | wsHeaderKey = []byte("Sec-WebSocket-Key")
13 | wsHeaderProtocol = []byte("Sec-Websocket-Protocol")
14 | wsHeaderAccept = []byte("Sec-Websocket-Accept")
15 | wsHeaderExtensions = []byte("Sec-WebSocket-Extensions")
16 | permessageDeflate = []byte("permessage-deflate")
17 | serverNoCtxTakeover = []byte("server_no_context_takeover")
18 | clientNoCtxTakeover = []byte("client_no_context_takeover")
19 | uidKey = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
20 | supportedVersions = [][]byte{ // must be slice for future implementations
21 | []byte("13"),
22 | }
23 | )
24 |
--------------------------------------------------------------------------------
/upgrader.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "crypto/sha1"
5 | b64 "encoding/base64"
6 | "github.com/valyala/fasthttp"
7 | "hash"
8 | "net/http"
9 | "sync"
10 | )
11 |
12 | type (
13 | // RequestHandler is the websocket connection handler.
14 | RequestHandler func(conn *Conn)
15 | // UpgradeHandler is a middleware callback that determines whether the
16 | // WebSocket connection should be upgraded or not. If UpgradeHandler returns false,
17 | // the connection is not upgraded.
18 | UpgradeHandler func(*fasthttp.RequestCtx) bool
19 | // UpgradeNetHandler is like UpgradeHandler but for net/http.
20 | UpgradeNetHandler func(resp http.ResponseWriter, req *http.Request) bool
21 | )
22 |
23 | func prepareOrigin(b []byte, uri *fasthttp.URI) []byte {
24 | b = append(b[:0], uri.Scheme()...)
25 | b = append(b, "://"...)
26 | return append(b, uri.Host()...)
27 | }
28 |
29 | var shaPool = sync.Pool{
30 | New: func() interface{} {
31 | return sha1.New()
32 | },
33 | }
34 |
35 | var base64 = b64.StdEncoding
36 |
37 | func makeKey(dst, key []byte) []byte {
38 | h := shaPool.Get().(hash.Hash)
39 | h.Reset()
40 | defer shaPool.Put(h)
41 |
42 | h.Write(key)
43 | h.Write(uidKey)
44 | dst = h.Sum(dst[:0])
45 | dst = appendEncode(base64, dst, dst)
46 | return dst
47 | }
48 |
49 | // Thank you @valyala
50 | //
51 | // https://go-review.googlesource.com/c/go/+/37639
52 | func appendEncode(enc *b64.Encoding, dst, src []byte) []byte {
53 | n := enc.EncodedLen(len(src)) + len(dst)
54 | b := extendByteSlice(dst, n)
55 | n = len(dst)
56 | enc.Encode(b[n:], src)
57 | return b[n:]
58 | }
59 |
60 | func appendDecode(enc *b64.Encoding, dst, src []byte) ([]byte, error) {
61 | needLen := enc.DecodedLen(len(src)) + len(dst)
62 | b := extendByteSlice(dst, needLen)
63 | n, err := enc.Decode(b[len(dst):], src)
64 | return b[:len(dst)+n], err
65 | }
66 |
67 | func selectProtocol(protos [][]byte, accepted []string) string {
68 | if len(protos) == 0 {
69 | return ""
70 | }
71 |
72 | for _, proto := range protos {
73 | for _, accept := range accepted {
74 | if b2s(proto) == accept {
75 | return accept
76 | }
77 | }
78 | }
79 | return string(protos[0])
80 | }
81 |
--------------------------------------------------------------------------------
/upgrader_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | var (
9 | decodedString = []byte("hello world")
10 | encodedString = []byte("aGVsbG8gd29ybGQ=")
11 | )
12 |
13 | func TestBase64Encoding(t *testing.T) {
14 | var b = make([]byte, len(decodedString))
15 | copy(b, decodedString)
16 | b = appendEncode(base64, b[:0], b)
17 | if !bytes.Equal(b, encodedString) {
18 | t.Fatalf("bad encoding: %s <> %s", b, encodedString)
19 | }
20 | }
21 |
22 | func TestBase64Decoding(t *testing.T) {
23 | var b = make([]byte, len(encodedString))
24 | var err error
25 | copy(b, encodedString)
26 | b, err = appendDecode(base64, b[:0], b)
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 | if !bytes.Equal(b, decodedString) {
31 | t.Fatalf("bad decoding: %s <> %s", b, decodedString)
32 | }
33 | }
34 |
35 | func BenchmarkBase64Encoding(b *testing.B) {
36 | var bf []byte
37 | for i := 0; i < b.N; i++ {
38 | bf = appendEncode(base64, bf[:0], decodedString)
39 | if !bytes.Equal(bf, encodedString) {
40 | b.Fatalf("%s <> %s", bf, encodedString)
41 | }
42 | }
43 | }
44 |
45 | func BenchmarkBase64Decoding(b *testing.B) {
46 | var bf []byte
47 | var err error
48 | for i := 0; i < b.N; i++ {
49 | bf, err = appendDecode(base64, bf[:0], encodedString)
50 | if err != nil {
51 | b.Fatal(err)
52 | }
53 | if !bytes.Equal(bf, decodedString) {
54 | b.Fatalf("%s <> %s", bf, decodedString)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "reflect"
5 | "unsafe"
6 | )
7 |
8 | func b2s(b []byte) string {
9 | return *(*string)(unsafe.Pointer(&b))
10 | }
11 |
12 | func s2b(s string) []byte {
13 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
14 | bh := reflect.SliceHeader{
15 | Data: sh.Data,
16 | Len: sh.Len,
17 | Cap: sh.Len,
18 | }
19 | return *(*[]byte)(unsafe.Pointer(&bh))
20 | }
21 |
22 | func equalsFold(b, s []byte) (equals bool) {
23 | n := len(b)
24 |
25 | if equals = n == len(s); equals {
26 | for i := 0; i < n; i++ {
27 | if equals = b[i]|0x20 == s[i]|0x20; !equals {
28 | break
29 | }
30 | }
31 | }
32 |
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/utils_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | var (
9 | bstr = []byte("This string must be equals")
10 | cstr = []byte("This StrING Must bE equAls")
11 | )
12 |
13 | func TestEqualsFold(t *testing.T) {
14 | if !equalsFold(bstr, cstr) {
15 | t.Fatal("equalsFold error")
16 | }
17 | }
18 |
19 | func BenchmarkMineEqualFold(b *testing.B) {
20 | for i := 0; i < b.N; i++ {
21 | if !equalsFold(bstr, cstr) {
22 | b.Fatal("error checking equality")
23 | }
24 | }
25 | }
26 |
27 | func BenchmarkBytesEqualFold(b *testing.B) {
28 | for i := 0; i < b.N; i++ {
29 | if !bytes.EqualFold(bstr, cstr) {
30 | b.Fatal("error checking equality")
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------