├── .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 | --------------------------------------------------------------------------------