├── .gitignore ├── AUTHORS ├── vendor └── github.com │ ├── gorilla │ └── websocket │ │ ├── AUTHORS │ │ ├── mask_safe.go │ │ ├── client_clone.go │ │ ├── conn_read.go │ │ ├── conn_read_legacy.go │ │ ├── LICENSE │ │ ├── mask.go │ │ ├── client_clone_legacy.go │ │ ├── json.go │ │ ├── prepared.go │ │ ├── compression.go │ │ ├── README.md │ │ ├── util.go │ │ ├── doc.go │ │ ├── server.go │ │ ├── client.go │ │ └── conn.go │ ├── iris-contrib │ └── go.uuid │ │ ├── LICENSE │ │ ├── sql.go │ │ ├── uuid.go │ │ ├── codec.go │ │ └── generator.go │ └── valyala │ └── bytebufferpool │ ├── LICENSE │ ├── bytebuffer.go │ └── pool.go ├── websocket.go ├── .travis.yml ├── _examples └── chat │ ├── templates │ └── client.html │ ├── static │ └── js │ │ └── chat.js │ └── main.go ├── LICENSE ├── emitter.go ├── client.min.js ├── message.go ├── config.go ├── client.js ├── client.ts ├── client.go ├── README.md ├── server.go └── connection.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Go-Websocket authors for copyright 2 | # purposes. 3 | 4 | Gerasimos Maropoulos 5 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Gorilla WebSocket authors for copyright 2 | # purposes. 3 | # 4 | # Please keep the list sorted. 5 | 6 | Gary Burd 7 | Joachim Bauch 8 | 9 | -------------------------------------------------------------------------------- /websocket.go: -------------------------------------------------------------------------------- 1 | // Package websocket provides an easy way to setup a rich Websocket server and client side. 2 | // See _examples/chat for more. 3 | package websocket 4 | 5 | const ( 6 | // Version is the current go-websocket semantic version. 7 | Version = "0.2.0" 8 | ) 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | os: 3 | - linux 4 | - osx 5 | go: 6 | - 1.9.x 7 | - 1.10.x 8 | - 1.11.x 9 | go_import_path: github.com/kataras/go-websocket 10 | script: 11 | - go test -v -cover ./... 12 | after_script: 13 | # examples 14 | - cd ./_examples 15 | - go get ./... 16 | - go test -v -cover ./... -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/mask_safe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of 2 | // this source code is governed by a BSD-style license that can be found in the 3 | // LICENSE file. 4 | 5 | // +build appengine 6 | 7 | package websocket 8 | 9 | func maskBytes(key [4]byte, pos int, b []byte) int { 10 | for i := range b { 11 | b[i] ^= key[pos&3] 12 | pos++ 13 | } 14 | return pos & 3 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client_clone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.8 6 | 7 | package websocket 8 | 9 | import "crypto/tls" 10 | 11 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 12 | if cfg == nil { 13 | return &tls.Config{} 14 | } 15 | return cfg.Clone() 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.5 6 | 7 | package websocket 8 | 9 | import "io" 10 | 11 | func (c *Conn) read(n int) ([]byte, error) { 12 | p, err := c.br.Peek(n) 13 | if err == io.EOF { 14 | err = errUnexpectedEOF 15 | } 16 | c.br.Discard(len(p)) 17 | return p, err 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_read_legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.5 6 | 7 | package websocket 8 | 9 | import "io" 10 | 11 | func (c *Conn) read(n int) ([]byte, error) { 12 | p, err := c.br.Peek(n) 13 | if err == io.EOF { 14 | err = errUnexpectedEOF 15 | } 16 | if len(p) > 0 { 17 | // advance over the bytes just read 18 | io.ReadFull(c.br, p) 19 | } 20 | return p, err 21 | } 22 | -------------------------------------------------------------------------------- /_examples/chat/templates/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ .Title}} 5 | 6 | 7 | 8 |
10 | 11 |
12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /_examples/chat/static/js/chat.js: -------------------------------------------------------------------------------- 1 | var messageTxt; 2 | var messages; 3 | 4 | $(function () { 5 | 6 | messageTxt = $("#messageTxt"); 7 | messages = $("#messages"); 8 | 9 | 10 | w = new Ws("ws://" + HOST + "/my_endpoint"); 11 | w.OnConnect(function () { 12 | console.log("Websocket connection established"); 13 | }); 14 | 15 | w.OnDisconnect(function () { 16 | appendMessage($("

Disconnected

")); 17 | }); 18 | 19 | w.On("chat", function (message) { 20 | appendMessage($("
" + message + "
")); 21 | }); 22 | 23 | $("#sendBtn").click(function () { 24 | w.Emit("chat", messageTxt.val().toString()); 25 | messageTxt.val(""); 26 | }); 27 | 28 | }) 29 | 30 | 31 | function appendMessage(messageDiv) { 32 | var theDiv = messages[0]; 33 | var doScroll = theDiv.scrollTop == theDiv.scrollHeight - theDiv.clientHeight; 34 | messageDiv.appendTo(messages); 35 | if (doScroll) { 36 | theDiv.scrollTop = theDiv.scrollHeight - theDiv.clientHeight; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Gerasimos Maropoulos 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 | -------------------------------------------------------------------------------- /emitter.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | const ( 4 | // All is the string which the Emitter use to send a message to all. 5 | All = "" 6 | // Broadcast is the string which the Emitter use to send a message to all except this connection. 7 | Broadcast = ";to;all;except;me;" 8 | ) 9 | 10 | type ( 11 | // Emitter is the message/or/event manager 12 | Emitter interface { 13 | // EmitMessage sends a native websocket message 14 | EmitMessage([]byte) error 15 | // Emit sends a message on a particular event 16 | Emit(string, interface{}) error 17 | } 18 | 19 | emitter struct { 20 | conn *connection 21 | to string 22 | } 23 | ) 24 | 25 | var _ Emitter = &emitter{} 26 | 27 | func newEmitter(c *connection, to string) *emitter { 28 | return &emitter{conn: c, to: to} 29 | } 30 | 31 | func (e *emitter) EmitMessage(nativeMessage []byte) error { 32 | e.conn.server.emitMessage(e.conn.id, e.to, nativeMessage) 33 | return nil 34 | } 35 | 36 | func (e *emitter) Emit(event string, data interface{}) error { 37 | message, err := e.conn.server.messageSerializer.serialize(event, data) 38 | if err != nil { 39 | return err 40 | } 41 | e.EmitMessage(message) 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /vendor/github.com/iris-contrib/go.uuid/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2018 by Maxim Bublis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/github.com/valyala/bytebufferpool/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia 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 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/mask.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of 2 | // this source code is governed by a BSD-style license that can be found in the 3 | // LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package websocket 8 | 9 | import "unsafe" 10 | 11 | const wordSize = int(unsafe.Sizeof(uintptr(0))) 12 | 13 | func maskBytes(key [4]byte, pos int, b []byte) int { 14 | 15 | // Mask one byte at a time for small buffers. 16 | if len(b) < 2*wordSize { 17 | for i := range b { 18 | b[i] ^= key[pos&3] 19 | pos++ 20 | } 21 | return pos & 3 22 | } 23 | 24 | // Mask one byte at a time to word boundary. 25 | if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { 26 | n = wordSize - n 27 | for i := range b[:n] { 28 | b[i] ^= key[pos&3] 29 | pos++ 30 | } 31 | b = b[n:] 32 | } 33 | 34 | // Create aligned word size key. 35 | var k [wordSize]byte 36 | for i := range k { 37 | k[i] = key[(pos+i)&3] 38 | } 39 | kw := *(*uintptr)(unsafe.Pointer(&k)) 40 | 41 | // Mask one word at a time. 42 | n := (len(b) / wordSize) * wordSize 43 | for i := 0; i < n; i += wordSize { 44 | *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw 45 | } 46 | 47 | // Mask one byte at a time for remaining bytes. 48 | b = b[n:] 49 | for i := range b { 50 | b[i] ^= key[pos&3] 51 | pos++ 52 | } 53 | 54 | return pos & 3 55 | } 56 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client_clone_legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.8 6 | 7 | package websocket 8 | 9 | import "crypto/tls" 10 | 11 | // cloneTLSConfig clones all public fields except the fields 12 | // SessionTicketsDisabled and SessionTicketKey. This avoids copying the 13 | // sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a 14 | // config in active use. 15 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 16 | if cfg == nil { 17 | return &tls.Config{} 18 | } 19 | return &tls.Config{ 20 | Rand: cfg.Rand, 21 | Time: cfg.Time, 22 | Certificates: cfg.Certificates, 23 | NameToCertificate: cfg.NameToCertificate, 24 | GetCertificate: cfg.GetCertificate, 25 | RootCAs: cfg.RootCAs, 26 | NextProtos: cfg.NextProtos, 27 | ServerName: cfg.ServerName, 28 | ClientAuth: cfg.ClientAuth, 29 | ClientCAs: cfg.ClientCAs, 30 | InsecureSkipVerify: cfg.InsecureSkipVerify, 31 | CipherSuites: cfg.CipherSuites, 32 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 33 | ClientSessionCache: cfg.ClientSessionCache, 34 | MinVersion: cfg.MinVersion, 35 | MaxVersion: cfg.MaxVersion, 36 | CurvePreferences: cfg.CurvePreferences, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | // WriteJSON is deprecated, use c.WriteJSON instead. 13 | func WriteJSON(c *Conn, v interface{}) error { 14 | return c.WriteJSON(v) 15 | } 16 | 17 | // WriteJSON writes the JSON encoding of v to the connection. 18 | // 19 | // See the documentation for encoding/json Marshal for details about the 20 | // conversion of Go values to JSON. 21 | func (c *Conn) WriteJSON(v interface{}) error { 22 | w, err := c.NextWriter(TextMessage) 23 | if err != nil { 24 | return err 25 | } 26 | err1 := json.NewEncoder(w).Encode(v) 27 | err2 := w.Close() 28 | if err1 != nil { 29 | return err1 30 | } 31 | return err2 32 | } 33 | 34 | // ReadJSON is deprecated, use c.ReadJSON instead. 35 | func ReadJSON(c *Conn, v interface{}) error { 36 | return c.ReadJSON(v) 37 | } 38 | 39 | // ReadJSON reads the next JSON-encoded message from the connection and stores 40 | // it in the value pointed to by v. 41 | // 42 | // See the documentation for the encoding/json Unmarshal function for details 43 | // about the conversion of JSON to a Go value. 44 | func (c *Conn) ReadJSON(v interface{}) error { 45 | _, r, err := c.NextReader() 46 | if err != nil { 47 | return err 48 | } 49 | err = json.NewDecoder(r).Decode(v) 50 | if err == io.EOF { 51 | // One value is expected in the message. 52 | err = io.ErrUnexpectedEOF 53 | } 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /vendor/github.com/iris-contrib/go.uuid/sql.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "database/sql/driver" 26 | "fmt" 27 | ) 28 | 29 | // Value implements the driver.Valuer interface. 30 | func (u UUID) Value() (driver.Value, error) { 31 | return u.String(), nil 32 | } 33 | 34 | // Scan implements the sql.Scanner interface. 35 | // A 16-byte slice is handled by UnmarshalBinary, while 36 | // a longer byte slice or a string is handled by UnmarshalText. 37 | func (u *UUID) Scan(src interface{}) error { 38 | switch src := src.(type) { 39 | case []byte: 40 | if len(src) == Size { 41 | return u.UnmarshalBinary(src) 42 | } 43 | return u.UnmarshalText(src) 44 | 45 | case string: 46 | return u.UnmarshalText([]byte(src)) 47 | } 48 | 49 | return fmt.Errorf("uuid: cannot convert %T to UUID", src) 50 | } 51 | 52 | // NullUUID can be used with the standard sql package to represent a 53 | // UUID value that can be NULL in the database 54 | type NullUUID struct { 55 | UUID UUID 56 | Valid bool 57 | } 58 | 59 | // Value implements the driver.Valuer interface. 60 | func (u NullUUID) Value() (driver.Value, error) { 61 | if !u.Valid { 62 | return nil, nil 63 | } 64 | // Delegate to UUID Value function 65 | return u.UUID.Value() 66 | } 67 | 68 | // Scan implements the sql.Scanner interface. 69 | func (u *NullUUID) Scan(src interface{}) error { 70 | if src == nil { 71 | u.UUID, u.Valid = Nil, false 72 | return nil 73 | } 74 | 75 | // Delegate to UUID Scan function 76 | u.Valid = true 77 | return u.UUID.Scan(src) 78 | } 79 | -------------------------------------------------------------------------------- /vendor/github.com/valyala/bytebufferpool/bytebuffer.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import "io" 4 | 5 | // ByteBuffer provides byte buffer, which can be used for minimizing 6 | // memory allocations. 7 | // 8 | // ByteBuffer may be used with functions appending data to the given []byte 9 | // slice. See example code for details. 10 | // 11 | // Use Get for obtaining an empty byte buffer. 12 | type ByteBuffer struct { 13 | 14 | // B is a byte buffer to use in append-like workloads. 15 | // See example code for details. 16 | B []byte 17 | } 18 | 19 | // Len returns the size of the byte buffer. 20 | func (b *ByteBuffer) Len() int { 21 | return len(b.B) 22 | } 23 | 24 | // ReadFrom implements io.ReaderFrom. 25 | // 26 | // The function appends all the data read from r to b. 27 | func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) { 28 | p := b.B 29 | nStart := int64(len(p)) 30 | nMax := int64(cap(p)) 31 | n := nStart 32 | if nMax == 0 { 33 | nMax = 64 34 | p = make([]byte, nMax) 35 | } else { 36 | p = p[:nMax] 37 | } 38 | for { 39 | if n == nMax { 40 | nMax *= 2 41 | bNew := make([]byte, nMax) 42 | copy(bNew, p) 43 | p = bNew 44 | } 45 | nn, err := r.Read(p[n:]) 46 | n += int64(nn) 47 | if err != nil { 48 | b.B = p[:n] 49 | n -= nStart 50 | if err == io.EOF { 51 | return n, nil 52 | } 53 | return n, err 54 | } 55 | } 56 | } 57 | 58 | // WriteTo implements io.WriterTo. 59 | func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) { 60 | n, err := w.Write(b.B) 61 | return int64(n), err 62 | } 63 | 64 | // Bytes returns b.B, i.e. all the bytes accumulated in the buffer. 65 | // 66 | // The purpose of this function is bytes.Buffer compatibility. 67 | func (b *ByteBuffer) Bytes() []byte { 68 | return b.B 69 | } 70 | 71 | // Write implements io.Writer - it appends p to ByteBuffer.B 72 | func (b *ByteBuffer) Write(p []byte) (int, error) { 73 | b.B = append(b.B, p...) 74 | return len(p), nil 75 | } 76 | 77 | // WriteByte appends the byte c to the buffer. 78 | // 79 | // The purpose of this function is bytes.Buffer compatibility. 80 | // 81 | // The function always returns nil. 82 | func (b *ByteBuffer) WriteByte(c byte) error { 83 | b.B = append(b.B, c) 84 | return nil 85 | } 86 | 87 | // WriteString appends s to ByteBuffer.B. 88 | func (b *ByteBuffer) WriteString(s string) (int, error) { 89 | b.B = append(b.B, s...) 90 | return len(s), nil 91 | } 92 | 93 | // Set sets ByteBuffer.B to p. 94 | func (b *ByteBuffer) Set(p []byte) { 95 | b.B = append(b.B[:0], p...) 96 | } 97 | 98 | // SetString sets ByteBuffer.B to s. 99 | func (b *ByteBuffer) SetString(s string) { 100 | b.B = append(b.B[:0], s...) 101 | } 102 | 103 | // String returns string representation of ByteBuffer.B. 104 | func (b *ByteBuffer) String() string { 105 | return string(b.B) 106 | } 107 | 108 | // Reset makes ByteBuffer.B empty. 109 | func (b *ByteBuffer) Reset() { 110 | b.B = b.B[:0] 111 | } 112 | -------------------------------------------------------------------------------- /_examples/chat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | 8 | "github.com/kataras/go-websocket" 9 | ) 10 | 11 | type clientPage struct { 12 | Title string 13 | Host string 14 | } 15 | 16 | var host = "localhost:8080" 17 | 18 | func main() { 19 | 20 | // serve our javascript files 21 | staticHandler := http.FileServer(http.Dir("static")) 22 | http.Handle("/static/", http.StripPrefix("/static/", staticHandler)) 23 | 24 | // Start Websocket example code 25 | 26 | // init our websocket 27 | ws := websocket.New(websocket.Config{}) // with the default configuration 28 | // the path which the websocket client should listen/registered to, 29 | // see ./templates/client.html line: 21 30 | // http.HandleFunc("/my_endpoint", ws.Handler) 31 | // ws.OnConnection(handleWebsocketConnection) // register the connection handler, which will fire on each new connected websocket client. 32 | // OR if you don't want to use the `OnConnection` 33 | // and you want to handle the Upgradation from simple http to websocket protocol do that: 34 | http.HandleFunc("/my_endpoint", func(w http.ResponseWriter, r *http.Request) { 35 | c := ws.Upgrade(w, r) 36 | if err := c.Err(); err != nil { 37 | http.Error(w, fmt.Sprintf("websocket error: %v\n", err), http.StatusServiceUnavailable) 38 | return 39 | } 40 | 41 | // handle the connection. 42 | handleWebsocketConnection(c) 43 | 44 | // start the ping and the messages reader, this is blocking the http handler from exiting too. 45 | c.Wait() 46 | }) 47 | 48 | // serve our client-side source code go-websocket. See ./templates/client.html line: 19 49 | http.HandleFunc("/go-websocket.js", func(w http.ResponseWriter, r *http.Request) { 50 | w.Write(ws.ClientSource) 51 | }) 52 | 53 | // End Websocket example code 54 | 55 | // parse our view (the template file) 56 | clientHTML, err := template.New("").ParseFiles("./templates/client.html") 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | // serve our html page to / 62 | http.Handle("/", http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 63 | if req.URL.Path == "/" || req.URL.Path == "" { 64 | err := clientHTML.ExecuteTemplate(res, "client.html", clientPage{"Client Page", host}) 65 | if err != nil { 66 | res.Write([]byte(err.Error())) 67 | } 68 | } 69 | })) 70 | 71 | println("Server is up & running, open two browser tabs/or/and windows and navigate to http://" + host) 72 | http.ListenAndServe(host, nil) 73 | } 74 | 75 | var myChatRoom = "room1" 76 | 77 | func handleWebsocketConnection(c websocket.Connection) { 78 | 79 | c.Join(myChatRoom) 80 | 81 | c.On("chat", func(message string) { 82 | // to all except this connection (broadcast) -> 83 | // c.To(websocket.NotMe).Emit("chat", "Message from: "+c.ID()+"-> "+message) 84 | // to all: c.To(websocket.All). 85 | // to client itself-> 86 | //c.Emit("chat", "Message from myself: "+message) 87 | 88 | //send the message to the whole room, 89 | //all connections are inside this room will receive this message 90 | c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message) 91 | }) 92 | 93 | c.OnDisconnect(func() { 94 | fmt.Printf("\nConnection with ID: %s has been disconnected!", c.ID()) 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/prepared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bytes" 9 | "net" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // PreparedMessage caches on the wire representations of a message payload. 15 | // Use PreparedMessage to efficiently send a message payload to multiple 16 | // connections. PreparedMessage is especially useful when compression is used 17 | // because the CPU and memory expensive compression operation can be executed 18 | // once for a given set of compression options. 19 | type PreparedMessage struct { 20 | messageType int 21 | data []byte 22 | err error 23 | mu sync.Mutex 24 | frames map[prepareKey]*preparedFrame 25 | } 26 | 27 | // prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. 28 | type prepareKey struct { 29 | isServer bool 30 | compress bool 31 | compressionLevel int 32 | } 33 | 34 | // preparedFrame contains data in wire representation. 35 | type preparedFrame struct { 36 | once sync.Once 37 | data []byte 38 | } 39 | 40 | // NewPreparedMessage returns an initialized PreparedMessage. You can then send 41 | // it to connection using WritePreparedMessage method. Valid wire 42 | // representation will be calculated lazily only once for a set of current 43 | // connection options. 44 | func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { 45 | pm := &PreparedMessage{ 46 | messageType: messageType, 47 | frames: make(map[prepareKey]*preparedFrame), 48 | data: data, 49 | } 50 | 51 | // Prepare a plain server frame. 52 | _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // To protect against caller modifying the data argument, remember the data 58 | // copied to the plain server frame. 59 | pm.data = frameData[len(frameData)-len(data):] 60 | return pm, nil 61 | } 62 | 63 | func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { 64 | pm.mu.Lock() 65 | frame, ok := pm.frames[key] 66 | if !ok { 67 | frame = &preparedFrame{} 68 | pm.frames[key] = frame 69 | } 70 | pm.mu.Unlock() 71 | 72 | var err error 73 | frame.once.Do(func() { 74 | // Prepare a frame using a 'fake' connection. 75 | // TODO: Refactor code in conn.go to allow more direct construction of 76 | // the frame. 77 | mu := make(chan bool, 1) 78 | mu <- true 79 | var nc prepareConn 80 | c := &Conn{ 81 | conn: &nc, 82 | mu: mu, 83 | isServer: key.isServer, 84 | compressionLevel: key.compressionLevel, 85 | enableWriteCompression: true, 86 | writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), 87 | } 88 | if key.compress { 89 | c.newCompressionWriter = compressNoContextTakeover 90 | } 91 | err = c.WriteMessage(pm.messageType, pm.data) 92 | frame.data = nc.buf.Bytes() 93 | }) 94 | return pm.messageType, frame.data, err 95 | } 96 | 97 | type prepareConn struct { 98 | buf bytes.Buffer 99 | net.Conn 100 | } 101 | 102 | func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } 103 | func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } 104 | -------------------------------------------------------------------------------- /vendor/github.com/valyala/bytebufferpool/pool.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | minBitSize = 6 // 2**6=64 is a CPU cache line size 11 | steps = 20 12 | 13 | minSize = 1 << minBitSize 14 | maxSize = 1 << (minBitSize + steps - 1) 15 | 16 | calibrateCallsThreshold = 42000 17 | maxPercentile = 0.95 18 | ) 19 | 20 | // Pool represents byte buffer pool. 21 | // 22 | // Distinct pools may be used for distinct types of byte buffers. 23 | // Properly determined byte buffer types with their own pools may help reducing 24 | // memory waste. 25 | type Pool struct { 26 | calls [steps]uint64 27 | calibrating uint64 28 | 29 | defaultSize uint64 30 | maxSize uint64 31 | 32 | pool sync.Pool 33 | } 34 | 35 | var defaultPool Pool 36 | 37 | // Get returns an empty byte buffer from the pool. 38 | // 39 | // Got byte buffer may be returned to the pool via Put call. 40 | // This reduces the number of memory allocations required for byte buffer 41 | // management. 42 | func Get() *ByteBuffer { return defaultPool.Get() } 43 | 44 | // Get returns new byte buffer with zero length. 45 | // 46 | // The byte buffer may be returned to the pool via Put after the use 47 | // in order to minimize GC overhead. 48 | func (p *Pool) Get() *ByteBuffer { 49 | v := p.pool.Get() 50 | if v != nil { 51 | return v.(*ByteBuffer) 52 | } 53 | return &ByteBuffer{ 54 | B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), 55 | } 56 | } 57 | 58 | // Put returns byte buffer to the pool. 59 | // 60 | // ByteBuffer.B mustn't be touched after returning it to the pool. 61 | // Otherwise data races will occur. 62 | func Put(b *ByteBuffer) { defaultPool.Put(b) } 63 | 64 | // Put releases byte buffer obtained via Get to the pool. 65 | // 66 | // The buffer mustn't be accessed after returning to the pool. 67 | func (p *Pool) Put(b *ByteBuffer) { 68 | idx := index(len(b.B)) 69 | 70 | if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold { 71 | p.calibrate() 72 | } 73 | 74 | maxSize := int(atomic.LoadUint64(&p.maxSize)) 75 | if maxSize == 0 || cap(b.B) <= maxSize { 76 | b.Reset() 77 | p.pool.Put(b) 78 | } 79 | } 80 | 81 | func (p *Pool) calibrate() { 82 | if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) { 83 | return 84 | } 85 | 86 | a := make(callSizes, 0, steps) 87 | var callsSum uint64 88 | for i := uint64(0); i < steps; i++ { 89 | calls := atomic.SwapUint64(&p.calls[i], 0) 90 | callsSum += calls 91 | a = append(a, callSize{ 92 | calls: calls, 93 | size: minSize << i, 94 | }) 95 | } 96 | sort.Sort(a) 97 | 98 | defaultSize := a[0].size 99 | maxSize := defaultSize 100 | 101 | maxSum := uint64(float64(callsSum) * maxPercentile) 102 | callsSum = 0 103 | for i := 0; i < steps; i++ { 104 | if callsSum > maxSum { 105 | break 106 | } 107 | callsSum += a[i].calls 108 | size := a[i].size 109 | if size > maxSize { 110 | maxSize = size 111 | } 112 | } 113 | 114 | atomic.StoreUint64(&p.defaultSize, defaultSize) 115 | atomic.StoreUint64(&p.maxSize, maxSize) 116 | 117 | atomic.StoreUint64(&p.calibrating, 0) 118 | } 119 | 120 | type callSize struct { 121 | calls uint64 122 | size uint64 123 | } 124 | 125 | type callSizes []callSize 126 | 127 | func (ci callSizes) Len() int { 128 | return len(ci) 129 | } 130 | 131 | func (ci callSizes) Less(i, j int) bool { 132 | return ci[i].calls > ci[j].calls 133 | } 134 | 135 | func (ci callSizes) Swap(i, j int) { 136 | ci[i], ci[j] = ci[j], ci[i] 137 | } 138 | 139 | func index(n int) int { 140 | n-- 141 | n >>= minBitSize 142 | idx := 0 143 | for n > 0 { 144 | n >>= 1 145 | idx++ 146 | } 147 | if idx >= steps { 148 | idx = steps - 1 149 | } 150 | return idx 151 | } 152 | -------------------------------------------------------------------------------- /client.min.js: -------------------------------------------------------------------------------- 1 | var websocketStringMessageType=0,websocketIntMessageType=1,websocketBoolMessageType=2,websocketJSONMessageType=4,websocketMessagePrefix="go-websocket-message:",websocketMessageSeparator=";",websocketMessagePrefixLen=websocketMessagePrefix.length,websocketMessageSeparatorLen=websocketMessageSeparator.length,websocketMessagePrefixAndSepIdx=websocketMessagePrefixLen+websocketMessageSeparatorLen-1,websocketMessagePrefixIdx=websocketMessagePrefixLen-1,websocketMessageSeparatorIdx=websocketMessageSeparatorLen-1,Ws=function(){function e(e,s){var t=this;this.connectListeners=[],this.disconnectListeners=[],this.nativeMessageListeners=[],this.messageListeners={},window.WebSocket&&(-1==e.indexOf("ws")&&(e="ws://"+e),null!=s&&0 len(w.p) { 79 | m = len(w.p) 80 | } 81 | 82 | if nn, err := w.w.Write(w.p[:m]); err != nil { 83 | return n + nn, err 84 | } 85 | 86 | copy(w.p[:], w.p[m:]) 87 | copy(w.p[len(w.p)-m:], p[len(p)-m:]) 88 | nn, err := w.w.Write(p[:len(p)-m]) 89 | return n + nn, err 90 | } 91 | 92 | type flateWriteWrapper struct { 93 | fw *flate.Writer 94 | tw *truncWriter 95 | p *sync.Pool 96 | } 97 | 98 | func (w *flateWriteWrapper) Write(p []byte) (int, error) { 99 | if w.fw == nil { 100 | return 0, errWriteClosed 101 | } 102 | return w.fw.Write(p) 103 | } 104 | 105 | func (w *flateWriteWrapper) Close() error { 106 | if w.fw == nil { 107 | return errWriteClosed 108 | } 109 | err1 := w.fw.Flush() 110 | w.p.Put(w.fw) 111 | w.fw = nil 112 | if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { 113 | return errors.New("websocket: internal error, unexpected bytes at end of flate stream") 114 | } 115 | err2 := w.tw.w.Close() 116 | if err1 != nil { 117 | return err1 118 | } 119 | return err2 120 | } 121 | 122 | type flateReadWrapper struct { 123 | fr io.ReadCloser 124 | } 125 | 126 | func (r *flateReadWrapper) Read(p []byte) (int, error) { 127 | if r.fr == nil { 128 | return 0, io.ErrClosedPipe 129 | } 130 | n, err := r.fr.Read(p) 131 | if err == io.EOF { 132 | // Preemptively place the reader back in the pool. This helps with 133 | // scenarios where the application does not call NextReader() soon after 134 | // this final read. 135 | r.Close() 136 | } 137 | return n, err 138 | } 139 | 140 | func (r *flateReadWrapper) Close() error { 141 | if r.fr == nil { 142 | return io.ErrClosedPipe 143 | } 144 | err := r.fr.Close() 145 | flateReaderPool.Put(r.fr) 146 | r.fr = nil 147 | return err 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/README.md: -------------------------------------------------------------------------------- 1 | # Gorilla WebSocket 2 | 3 | Gorilla WebSocket is a [Go](http://golang.org/) implementation of the 4 | [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. 5 | 6 | [![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) 7 | [![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) 8 | 9 | ### Documentation 10 | 11 | * [API Reference](http://godoc.org/github.com/gorilla/websocket) 12 | * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) 13 | * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) 14 | * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) 15 | * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) 16 | 17 | ### Status 18 | 19 | The Gorilla WebSocket package provides a complete and tested implementation of 20 | the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The 21 | package API is stable. 22 | 23 | ### Installation 24 | 25 | go get github.com/gorilla/websocket 26 | 27 | ### Protocol Compliance 28 | 29 | The Gorilla WebSocket package passes the server tests in the [Autobahn Test 30 | Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn 31 | subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). 32 | 33 | ### Gorilla WebSocket compared with other packages 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
github.com/gorillagolang.org/x/net
RFC 6455 Features
Passes Autobahn Test SuiteYesNo
Receive fragmented messageYesNo, see note 1
Send close messageYesNo
Send pings and receive pongsYesNo
Get the type of a received data messageYesYes, see note 2
Other Features
Compression ExtensionsExperimentalNo
Read message using io.ReaderYesNo, see note 3
Write message using io.WriteCloserYesNo, see note 3
53 | 54 | Notes: 55 | 56 | 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 57 | 2. The application can get the type of a received data message by implementing 58 | a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) 59 | function. 60 | 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. 61 | Read returns when the input buffer is full or a frame boundary is 62 | encountered. Each call to Write sends a single frame message. The Gorilla 63 | io.Reader and io.WriteCloser operate on a single WebSocket message. 64 | 65 | -------------------------------------------------------------------------------- /vendor/github.com/iris-contrib/go.uuid/uuid.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // Package uuid provides implementation of Universally Unique Identifier (UUID). 23 | // Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and 24 | // version 2 (as specified in DCE 1.1). 25 | package uuid 26 | 27 | import ( 28 | "bytes" 29 | "encoding/hex" 30 | ) 31 | 32 | // Size of a UUID in bytes. 33 | const Size = 16 34 | 35 | // UUID representation compliant with specification 36 | // described in RFC 4122. 37 | type UUID [Size]byte 38 | 39 | // UUID versions 40 | const ( 41 | _ byte = iota 42 | V1 43 | V2 44 | V3 45 | V4 46 | V5 47 | ) 48 | 49 | // UUID layout variants. 50 | const ( 51 | VariantNCS byte = iota 52 | VariantRFC4122 53 | VariantMicrosoft 54 | VariantFuture 55 | ) 56 | 57 | // UUID DCE domains. 58 | const ( 59 | DomainPerson = iota 60 | DomainGroup 61 | DomainOrg 62 | ) 63 | 64 | // String parse helpers. 65 | var ( 66 | urnPrefix = []byte("urn:uuid:") 67 | byteGroups = []int{8, 4, 4, 4, 12} 68 | ) 69 | 70 | // Nil is special form of UUID that is specified to have all 71 | // 128 bits set to zero. 72 | var Nil = UUID{} 73 | 74 | // Predefined namespace UUIDs. 75 | var ( 76 | NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) 77 | NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) 78 | NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) 79 | NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) 80 | ) 81 | 82 | // Equal returns true if u1 and u2 equals, otherwise returns false. 83 | func Equal(u1 UUID, u2 UUID) bool { 84 | return bytes.Equal(u1[:], u2[:]) 85 | } 86 | 87 | // Version returns algorithm version used to generate UUID. 88 | func (u UUID) Version() byte { 89 | return u[6] >> 4 90 | } 91 | 92 | // Variant returns UUID layout variant. 93 | func (u UUID) Variant() byte { 94 | switch { 95 | case (u[8] >> 7) == 0x00: 96 | return VariantNCS 97 | case (u[8] >> 6) == 0x02: 98 | return VariantRFC4122 99 | case (u[8] >> 5) == 0x06: 100 | return VariantMicrosoft 101 | case (u[8] >> 5) == 0x07: 102 | fallthrough 103 | default: 104 | return VariantFuture 105 | } 106 | } 107 | 108 | // Bytes returns bytes slice representation of UUID. 109 | func (u UUID) Bytes() []byte { 110 | return u[:] 111 | } 112 | 113 | // Returns canonical string representation of UUID: 114 | // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. 115 | func (u UUID) String() string { 116 | buf := make([]byte, 36) 117 | 118 | hex.Encode(buf[0:8], u[0:4]) 119 | buf[8] = '-' 120 | hex.Encode(buf[9:13], u[4:6]) 121 | buf[13] = '-' 122 | hex.Encode(buf[14:18], u[6:8]) 123 | buf[18] = '-' 124 | hex.Encode(buf[19:23], u[8:10]) 125 | buf[23] = '-' 126 | hex.Encode(buf[24:], u[10:]) 127 | 128 | return string(buf) 129 | } 130 | 131 | // SetVersion sets version bits. 132 | func (u *UUID) SetVersion(v byte) { 133 | u[6] = (u[6] & 0x0f) | (v << 4) 134 | } 135 | 136 | // SetVariant sets variant bits. 137 | func (u *UUID) SetVariant(v byte) { 138 | switch v { 139 | case VariantNCS: 140 | u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) 141 | case VariantRFC4122: 142 | u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) 143 | case VariantMicrosoft: 144 | u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) 145 | case VariantFuture: 146 | fallthrough 147 | default: 148 | u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) 149 | } 150 | } 151 | 152 | // Must is a helper that wraps a call to a function returning (UUID, error) 153 | // and panics if the error is non-nil. It is intended for use in variable 154 | // initializations such as 155 | // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); 156 | func Must(u UUID, err error) UUID { 157 | if err != nil { 158 | panic(err) 159 | } 160 | return u 161 | } 162 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "fmt" 8 | "strconv" 9 | 10 | "github.com/valyala/bytebufferpool" 11 | ) 12 | 13 | type ( 14 | messageType uint8 15 | ) 16 | 17 | func (m messageType) String() string { 18 | return strconv.Itoa(int(m)) 19 | } 20 | 21 | func (m messageType) Name() string { 22 | switch m { 23 | case messageTypeString: 24 | return "string" 25 | case messageTypeInt: 26 | return "int" 27 | case messageTypeBool: 28 | return "bool" 29 | case messageTypeBytes: 30 | return "[]byte" 31 | case messageTypeJSON: 32 | return "json" 33 | default: 34 | return "Invalid(" + m.String() + ")" 35 | } 36 | } 37 | 38 | // The same values are exists on client side too. 39 | const ( 40 | messageTypeString messageType = iota 41 | messageTypeInt 42 | messageTypeBool 43 | messageTypeBytes 44 | messageTypeJSON 45 | ) 46 | 47 | const ( 48 | messageSeparator = ";" 49 | ) 50 | 51 | var messageSeparatorByte = messageSeparator[0] 52 | 53 | type messageSerializer struct { 54 | prefix []byte 55 | 56 | prefixLen int 57 | separatorLen int 58 | prefixAndSepIdx int 59 | prefixIdx int 60 | separatorIdx int 61 | 62 | buf *bytebufferpool.Pool 63 | } 64 | 65 | func newMessageSerializer(messagePrefix []byte) *messageSerializer { 66 | return &messageSerializer{ 67 | prefix: messagePrefix, 68 | prefixLen: len(messagePrefix), 69 | separatorLen: len(messageSeparator), 70 | prefixAndSepIdx: len(messagePrefix) + len(messageSeparator) - 1, 71 | prefixIdx: len(messagePrefix) - 1, 72 | separatorIdx: len(messageSeparator) - 1, 73 | 74 | buf: new(bytebufferpool.Pool), 75 | } 76 | } 77 | 78 | var ( 79 | boolTrueB = []byte("true") 80 | boolFalseB = []byte("false") 81 | ) 82 | 83 | // websocketMessageSerialize serializes a custom websocket message from websocketServer to be delivered to the client 84 | // returns the string form of the message 85 | // Supported data types are: string, int, bool, bytes and JSON. 86 | func (ms *messageSerializer) serialize(event string, data interface{}) ([]byte, error) { 87 | b := ms.buf.Get() 88 | b.Write(ms.prefix) 89 | b.WriteString(event) 90 | b.WriteByte(messageSeparatorByte) 91 | 92 | switch v := data.(type) { 93 | case string: 94 | b.WriteString(messageTypeString.String()) 95 | b.WriteByte(messageSeparatorByte) 96 | b.WriteString(v) 97 | case int: 98 | b.WriteString(messageTypeInt.String()) 99 | b.WriteByte(messageSeparatorByte) 100 | binary.Write(b, binary.LittleEndian, v) 101 | case bool: 102 | b.WriteString(messageTypeBool.String()) 103 | b.WriteByte(messageSeparatorByte) 104 | if v { 105 | b.Write(boolTrueB) 106 | } else { 107 | b.Write(boolFalseB) 108 | } 109 | case []byte: 110 | b.WriteString(messageTypeBytes.String()) 111 | b.WriteByte(messageSeparatorByte) 112 | b.Write(v) 113 | default: 114 | //we suppose is json 115 | res, err := json.Marshal(data) 116 | if err != nil { 117 | return nil, err 118 | } 119 | b.WriteString(messageTypeJSON.String()) 120 | b.WriteByte(messageSeparatorByte) 121 | b.Write(res) 122 | } 123 | 124 | message := b.Bytes() 125 | ms.buf.Put(b) 126 | 127 | return message, nil 128 | } 129 | 130 | // deserialize deserializes a custom websocket message from the client 131 | // ex: go-websocket-message;chat;4;themarshaledstringfromajsonstruct will return 'hello' as string 132 | // Supported data types are: string, int, bool, bytes and JSON. 133 | func (ms *messageSerializer) deserialize(event []byte, websocketMessage []byte) (interface{}, error) { 134 | dataStartIdx := ms.prefixAndSepIdx + len(event) + 3 135 | if len(websocketMessage) <= dataStartIdx { 136 | return nil, fmt.Errorf("websocket invalid message: %s", string(websocketMessage)) 137 | } 138 | 139 | typ, err := strconv.Atoi(string(websocketMessage[ms.prefixAndSepIdx+len(event)+1 : ms.prefixAndSepIdx+len(event)+2])) // in order to go-websocket-message;user;-> 4 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | data := websocketMessage[dataStartIdx:] // in order to go-websocket-message;user;4; -> themarshaledstringfromajsonstruct 145 | 146 | switch messageType(typ) { 147 | case messageTypeString: 148 | return string(data), nil 149 | case messageTypeInt: 150 | msg, err := strconv.Atoi(string(data)) 151 | if err != nil { 152 | return nil, err 153 | } 154 | return msg, nil 155 | case messageTypeBool: 156 | if bytes.Equal(data, boolTrueB) { 157 | return true, nil 158 | } 159 | return false, nil 160 | case messageTypeBytes: 161 | return data, nil 162 | case messageTypeJSON: 163 | var msg interface{} 164 | err := json.Unmarshal(data, &msg) 165 | return msg, err 166 | default: 167 | return nil, fmt.Errorf("websocket error: invalid message type: %s for message: %s", messageType(typ).Name(), string(websocketMessage)) 168 | } 169 | } 170 | 171 | // getWebsocketCustomEvent return empty string when the websocketMessage is native message 172 | func (ms *messageSerializer) getWebsocketCustomEvent(websocketMessage []byte) []byte { 173 | if len(websocketMessage) < ms.prefixAndSepIdx { 174 | return nil 175 | } 176 | s := websocketMessage[ms.prefixAndSepIdx:] 177 | evt := s[:bytes.IndexByte(s, messageSeparatorByte)] 178 | return evt 179 | } 180 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/sha1" 10 | "encoding/base64" 11 | "io" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 17 | 18 | func computeAcceptKey(challengeKey string) string { 19 | h := sha1.New() 20 | h.Write([]byte(challengeKey)) 21 | h.Write(keyGUID) 22 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 23 | } 24 | 25 | func generateChallengeKey() (string, error) { 26 | p := make([]byte, 16) 27 | if _, err := io.ReadFull(rand.Reader, p); err != nil { 28 | return "", err 29 | } 30 | return base64.StdEncoding.EncodeToString(p), nil 31 | } 32 | 33 | // Octet types from RFC 2616. 34 | var octetTypes [256]byte 35 | 36 | const ( 37 | isTokenOctet = 1 << iota 38 | isSpaceOctet 39 | ) 40 | 41 | func init() { 42 | // From RFC 2616 43 | // 44 | // OCTET = 45 | // CHAR = 46 | // CTL = 47 | // CR = 48 | // LF = 49 | // SP = 50 | // HT = 51 | // <"> = 52 | // CRLF = CR LF 53 | // LWS = [CRLF] 1*( SP | HT ) 54 | // TEXT = 55 | // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> 56 | // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT 57 | // token = 1* 58 | // qdtext = > 59 | 60 | for c := 0; c < 256; c++ { 61 | var t byte 62 | isCtl := c <= 31 || c == 127 63 | isChar := 0 <= c && c <= 127 64 | isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 65 | if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { 66 | t |= isSpaceOctet 67 | } 68 | if isChar && !isCtl && !isSeparator { 69 | t |= isTokenOctet 70 | } 71 | octetTypes[c] = t 72 | } 73 | } 74 | 75 | func skipSpace(s string) (rest string) { 76 | i := 0 77 | for ; i < len(s); i++ { 78 | if octetTypes[s[i]]&isSpaceOctet == 0 { 79 | break 80 | } 81 | } 82 | return s[i:] 83 | } 84 | 85 | func nextToken(s string) (token, rest string) { 86 | i := 0 87 | for ; i < len(s); i++ { 88 | if octetTypes[s[i]]&isTokenOctet == 0 { 89 | break 90 | } 91 | } 92 | return s[:i], s[i:] 93 | } 94 | 95 | func nextTokenOrQuoted(s string) (value string, rest string) { 96 | if !strings.HasPrefix(s, "\"") { 97 | return nextToken(s) 98 | } 99 | s = s[1:] 100 | for i := 0; i < len(s); i++ { 101 | switch s[i] { 102 | case '"': 103 | return s[:i], s[i+1:] 104 | case '\\': 105 | p := make([]byte, len(s)-1) 106 | j := copy(p, s[:i]) 107 | escape := true 108 | for i = i + 1; i < len(s); i++ { 109 | b := s[i] 110 | switch { 111 | case escape: 112 | escape = false 113 | p[j] = b 114 | j += 1 115 | case b == '\\': 116 | escape = true 117 | case b == '"': 118 | return string(p[:j]), s[i+1:] 119 | default: 120 | p[j] = b 121 | j += 1 122 | } 123 | } 124 | return "", "" 125 | } 126 | } 127 | return "", "" 128 | } 129 | 130 | // tokenListContainsValue returns true if the 1#token header with the given 131 | // name contains token. 132 | func tokenListContainsValue(header http.Header, name string, value string) bool { 133 | headers: 134 | for _, s := range header[name] { 135 | for { 136 | var t string 137 | t, s = nextToken(skipSpace(s)) 138 | if t == "" { 139 | continue headers 140 | } 141 | s = skipSpace(s) 142 | if s != "" && s[0] != ',' { 143 | continue headers 144 | } 145 | if strings.EqualFold(t, value) { 146 | return true 147 | } 148 | if s == "" { 149 | continue headers 150 | } 151 | s = s[1:] 152 | } 153 | } 154 | return false 155 | } 156 | 157 | // parseExtensiosn parses WebSocket extensions from a header. 158 | func parseExtensions(header http.Header) []map[string]string { 159 | 160 | // From RFC 6455: 161 | // 162 | // Sec-WebSocket-Extensions = extension-list 163 | // extension-list = 1#extension 164 | // extension = extension-token *( ";" extension-param ) 165 | // extension-token = registered-token 166 | // registered-token = token 167 | // extension-param = token [ "=" (token | quoted-string) ] 168 | // ;When using the quoted-string syntax variant, the value 169 | // ;after quoted-string unescaping MUST conform to the 170 | // ;'token' ABNF. 171 | 172 | var result []map[string]string 173 | headers: 174 | for _, s := range header["Sec-Websocket-Extensions"] { 175 | for { 176 | var t string 177 | t, s = nextToken(skipSpace(s)) 178 | if t == "" { 179 | continue headers 180 | } 181 | ext := map[string]string{"": t} 182 | for { 183 | s = skipSpace(s) 184 | if !strings.HasPrefix(s, ";") { 185 | break 186 | } 187 | var k string 188 | k, s = nextToken(skipSpace(s[1:])) 189 | if k == "" { 190 | continue headers 191 | } 192 | s = skipSpace(s) 193 | var v string 194 | if strings.HasPrefix(s, "=") { 195 | v, s = nextTokenOrQuoted(skipSpace(s[1:])) 196 | s = skipSpace(s) 197 | } 198 | if s != "" && s[0] != ',' && s[0] != ';' { 199 | continue headers 200 | } 201 | ext[k] = v 202 | } 203 | if s != "" && s[0] != ',' { 204 | continue headers 205 | } 206 | result = append(result, ext) 207 | if s == "" { 208 | continue headers 209 | } 210 | s = s[1:] 211 | } 212 | } 213 | return result 214 | } 215 | -------------------------------------------------------------------------------- /vendor/github.com/iris-contrib/go.uuid/codec.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "bytes" 26 | "encoding/hex" 27 | "fmt" 28 | ) 29 | 30 | // FromBytes returns UUID converted from raw byte slice input. 31 | // It will return error if the slice isn't 16 bytes long. 32 | func FromBytes(input []byte) (u UUID, err error) { 33 | err = u.UnmarshalBinary(input) 34 | return 35 | } 36 | 37 | // FromBytesOrNil returns UUID converted from raw byte slice input. 38 | // Same behavior as FromBytes, but returns a Nil UUID on error. 39 | func FromBytesOrNil(input []byte) UUID { 40 | uuid, err := FromBytes(input) 41 | if err != nil { 42 | return Nil 43 | } 44 | return uuid 45 | } 46 | 47 | // FromString returns UUID parsed from string input. 48 | // Input is expected in a form accepted by UnmarshalText. 49 | func FromString(input string) (u UUID, err error) { 50 | err = u.UnmarshalText([]byte(input)) 51 | return 52 | } 53 | 54 | // FromStringOrNil returns UUID parsed from string input. 55 | // Same behavior as FromString, but returns a Nil UUID on error. 56 | func FromStringOrNil(input string) UUID { 57 | uuid, err := FromString(input) 58 | if err != nil { 59 | return Nil 60 | } 61 | return uuid 62 | } 63 | 64 | // MarshalText implements the encoding.TextMarshaler interface. 65 | // The encoding is the same as returned by String. 66 | func (u UUID) MarshalText() (text []byte, err error) { 67 | text = []byte(u.String()) 68 | return 69 | } 70 | 71 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 72 | // Following formats are supported: 73 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 74 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 75 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" 76 | // "6ba7b8109dad11d180b400c04fd430c8" 77 | // ABNF for supported UUID text representation follows: 78 | // uuid := canonical | hashlike | braced | urn 79 | // plain := canonical | hashlike 80 | // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct 81 | // hashlike := 12hexoct 82 | // braced := '{' plain '}' 83 | // urn := URN ':' UUID-NID ':' plain 84 | // URN := 'urn' 85 | // UUID-NID := 'uuid' 86 | // 12hexoct := 6hexoct 6hexoct 87 | // 6hexoct := 4hexoct 2hexoct 88 | // 4hexoct := 2hexoct 2hexoct 89 | // 2hexoct := hexoct hexoct 90 | // hexoct := hexdig hexdig 91 | // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 92 | // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 93 | // 'A' | 'B' | 'C' | 'D' | 'E' | 'F' 94 | func (u *UUID) UnmarshalText(text []byte) (err error) { 95 | switch len(text) { 96 | case 32: 97 | return u.decodeHashLike(text) 98 | case 36: 99 | return u.decodeCanonical(text) 100 | case 38: 101 | return u.decodeBraced(text) 102 | case 41: 103 | fallthrough 104 | case 45: 105 | return u.decodeURN(text) 106 | default: 107 | return fmt.Errorf("uuid: incorrect UUID length: %s", text) 108 | } 109 | } 110 | 111 | // decodeCanonical decodes UUID string in format 112 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8". 113 | func (u *UUID) decodeCanonical(t []byte) (err error) { 114 | if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { 115 | return fmt.Errorf("uuid: incorrect UUID format %s", t) 116 | } 117 | 118 | src := t[:] 119 | dst := u[:] 120 | 121 | for i, byteGroup := range byteGroups { 122 | if i > 0 { 123 | src = src[1:] // skip dash 124 | } 125 | _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) 126 | if err != nil { 127 | return 128 | } 129 | src = src[byteGroup:] 130 | dst = dst[byteGroup/2:] 131 | } 132 | 133 | return 134 | } 135 | 136 | // decodeHashLike decodes UUID string in format 137 | // "6ba7b8109dad11d180b400c04fd430c8". 138 | func (u *UUID) decodeHashLike(t []byte) (err error) { 139 | src := t[:] 140 | dst := u[:] 141 | 142 | if _, err = hex.Decode(dst, src); err != nil { 143 | return err 144 | } 145 | return 146 | } 147 | 148 | // decodeBraced decodes UUID string in format 149 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format 150 | // "{6ba7b8109dad11d180b400c04fd430c8}". 151 | func (u *UUID) decodeBraced(t []byte) (err error) { 152 | l := len(t) 153 | 154 | if t[0] != '{' || t[l-1] != '}' { 155 | return fmt.Errorf("uuid: incorrect UUID format %s", t) 156 | } 157 | 158 | return u.decodePlain(t[1 : l-1]) 159 | } 160 | 161 | // decodeURN decodes UUID string in format 162 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format 163 | // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". 164 | func (u *UUID) decodeURN(t []byte) (err error) { 165 | total := len(t) 166 | 167 | urn_uuid_prefix := t[:9] 168 | 169 | if !bytes.Equal(urn_uuid_prefix, urnPrefix) { 170 | return fmt.Errorf("uuid: incorrect UUID format: %s", t) 171 | } 172 | 173 | return u.decodePlain(t[9:total]) 174 | } 175 | 176 | // decodePlain decodes UUID string in canonical format 177 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format 178 | // "6ba7b8109dad11d180b400c04fd430c8". 179 | func (u *UUID) decodePlain(t []byte) (err error) { 180 | switch len(t) { 181 | case 32: 182 | return u.decodeHashLike(t) 183 | case 36: 184 | return u.decodeCanonical(t) 185 | default: 186 | return fmt.Errorf("uuid: incorrrect UUID length: %s", t) 187 | } 188 | } 189 | 190 | // MarshalBinary implements the encoding.BinaryMarshaler interface. 191 | func (u UUID) MarshalBinary() (data []byte, err error) { 192 | data = u.Bytes() 193 | return 194 | } 195 | 196 | // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. 197 | // It will return error if the slice isn't 16 bytes long. 198 | func (u *UUID) UnmarshalBinary(data []byte) (err error) { 199 | if len(data) != Size { 200 | err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) 201 | return 202 | } 203 | copy(u[:], data) 204 | 205 | return 206 | } 207 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "math/rand" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/iris-contrib/go.uuid" // a stable version of go.uuid to avoid the problems we had with go modules. 9 | ) 10 | 11 | const ( 12 | // DefaultWebsocketWriteTimeout 0, no timeout 13 | DefaultWebsocketWriteTimeout = 0 14 | // DefaultWebsocketReadTimeout 0, no timeout 15 | DefaultWebsocketReadTimeout = 0 16 | // DefaultWebsocketPongTimeout 60 * time.Second 17 | DefaultWebsocketPongTimeout = 60 * time.Second 18 | // DefaultWebsocketPingPeriod (DefaultPongTimeout * 9) / 10 19 | DefaultWebsocketPingPeriod = (DefaultWebsocketPongTimeout * 9) / 10 20 | // DefaultWebsocketMaxMessageSize 1024 21 | DefaultWebsocketMaxMessageSize = 1024 22 | // DefaultWebsocketReadBufferSize 4096 23 | DefaultWebsocketReadBufferSize = 4096 24 | // DefaultWebsocketWriterBufferSize 4096 25 | DefaultWebsocketWriterBufferSize = 4096 26 | // DefaultEvtMessageKey is the default prefix of the underline websocket events 27 | // that are being established under the hoods. 28 | // 29 | // Defaults to "go-websocket-message:". 30 | // Last character of the prefix should be ':'. 31 | DefaultEvtMessageKey = "go-websocket-message:" 32 | ) 33 | 34 | var ( 35 | // DefaultIDGenerator returns a random unique for a new connection. 36 | // Used when config.IDGenerator is nil. 37 | DefaultIDGenerator = func(w http.ResponseWriter, r *http.Request) string { 38 | id, err := uuid.NewV4() 39 | if err != nil { 40 | return randomString(64) 41 | } 42 | return id.String() 43 | } 44 | ) 45 | 46 | // Config the websocket server configuration 47 | // all of these are optional. 48 | type Config struct { 49 | // IDGenerator used to create (and later on, set) 50 | // an ID for each incoming websocket connections (clients). 51 | // The request is an input parameter which you can use to generate the ID (from headers for example). 52 | // If empty then the ID is generated by DefaultIDGenerator: uuid or,if failed, a randomString(64) 53 | IDGenerator func(w http.ResponseWriter, r *http.Request) string 54 | // EvtMessagePrefix is the prefix of the underline websocket events that are being established under the hoods. 55 | // This prefix is visible only to the javascript side (code) and it has nothing to do 56 | // with the message that the end-user receives. 57 | // Do not change it unless it is absolutely necessary. 58 | // 59 | // If empty then defaults to []byte("go-websocket-message:"). 60 | EvtMessagePrefix []byte 61 | // Error is the function that will be fired if any client couldn't upgrade the HTTP connection 62 | // to a websocket connection, a handshake error. 63 | Error func(w http.ResponseWriter, r *http.Request, status int, reason error) 64 | // CheckOrigin a function that is called right before the handshake, 65 | // if returns false then that client is not allowed to connect with the websocket server. 66 | CheckOrigin func(r *http.Request) bool 67 | // HandshakeTimeout specifies the duration for the handshake to complete. 68 | HandshakeTimeout time.Duration 69 | // WriteTimeout time allowed to write a message to the connection. 70 | // 0 means no timeout. 71 | // Default value is 0 72 | WriteTimeout time.Duration 73 | // ReadTimeout time allowed to read a message from the connection. 74 | // 0 means no timeout. 75 | // Default value is 0 76 | ReadTimeout time.Duration 77 | // PongTimeout allowed to read the next pong message from the connection. 78 | // Default value is 60 * time.Second 79 | PongTimeout time.Duration 80 | // PingPeriod send ping messages to the connection within this period. Must be less than PongTimeout. 81 | // Default value is 60 *time.Second 82 | PingPeriod time.Duration 83 | // MaxMessageSize max message size allowed from connection. 84 | // Default value is 1024 85 | MaxMessageSize int64 86 | // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text 87 | // compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication. 88 | // Default value is false 89 | BinaryMessages bool 90 | // ReadBufferSize is the buffer size for the connection reader. 91 | // Default value is 4096 92 | ReadBufferSize int 93 | // WriteBufferSize is the buffer size for the connection writer. 94 | // Default value is 4096 95 | WriteBufferSize int 96 | // EnableCompression specify if the server should attempt to negotiate per 97 | // message compression (RFC 7692). Setting this value to true does not 98 | // guarantee that compression will be supported. Currently only "no context 99 | // takeover" modes are supported. 100 | // 101 | // Defaults to false and it should be remain as it is, unless special requirements. 102 | EnableCompression bool 103 | 104 | // Subprotocols specifies the server's supported protocols in order of 105 | // preference. If this field is set, then the Upgrade method negotiates a 106 | // subprotocol by selecting the first match in this list with a protocol 107 | // requested by the client. 108 | Subprotocols []string 109 | } 110 | 111 | // Validate validates the configuration 112 | func (c Config) Validate() Config { 113 | // 0 means no timeout. 114 | if c.WriteTimeout < 0 { 115 | c.WriteTimeout = DefaultWebsocketWriteTimeout 116 | } 117 | 118 | if c.ReadTimeout < 0 { 119 | c.ReadTimeout = DefaultWebsocketReadTimeout 120 | } 121 | 122 | if c.PongTimeout < 0 { 123 | c.PongTimeout = DefaultWebsocketPongTimeout 124 | } 125 | 126 | if c.PingPeriod <= 0 { 127 | c.PingPeriod = DefaultWebsocketPingPeriod 128 | } 129 | 130 | if c.MaxMessageSize <= 0 { 131 | c.MaxMessageSize = DefaultWebsocketMaxMessageSize 132 | } 133 | 134 | if c.ReadBufferSize <= 0 { 135 | c.ReadBufferSize = DefaultWebsocketReadBufferSize 136 | } 137 | 138 | if c.WriteBufferSize <= 0 { 139 | c.WriteBufferSize = DefaultWebsocketWriterBufferSize 140 | } 141 | 142 | if c.Error == nil { 143 | c.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { 144 | //empty 145 | } 146 | } 147 | 148 | if c.CheckOrigin == nil { 149 | c.CheckOrigin = func(r *http.Request) bool { 150 | // allow all connections by default 151 | return true 152 | } 153 | } 154 | 155 | if len(c.EvtMessagePrefix) == 0 { 156 | c.EvtMessagePrefix = []byte(DefaultEvtMessageKey) 157 | } 158 | 159 | if c.IDGenerator == nil { 160 | c.IDGenerator = DefaultIDGenerator 161 | } 162 | 163 | return c 164 | } 165 | 166 | const ( 167 | letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 168 | letterIdxBits = 6 // 6 bits to represent a letter index 169 | letterIdxMask = 1<= 0; { 181 | if remain == 0 { 182 | cache, remain = src.Int63(), letterIdxMax 183 | } 184 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 185 | b[i] = letterBytes[idx] 186 | i-- 187 | } 188 | cache >>= letterIdxBits 189 | remain-- 190 | } 191 | 192 | return b 193 | } 194 | 195 | // randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm 196 | func randomString(n int) string { 197 | return string(random(n)) 198 | } 199 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package websocket implements the WebSocket protocol defined in RFC 6455. 6 | // 7 | // Overview 8 | // 9 | // The Conn type represents a WebSocket connection. A server application uses 10 | // the Upgrade function from an Upgrader object with a HTTP request handler 11 | // to get a pointer to a Conn: 12 | // 13 | // var upgrader = websocket.Upgrader{ 14 | // ReadBufferSize: 1024, 15 | // WriteBufferSize: 1024, 16 | // } 17 | // 18 | // func handler(w http.ResponseWriter, r *http.Request) { 19 | // conn, err := upgrader.Upgrade(w, r, nil) 20 | // if err != nil { 21 | // log.Println(err) 22 | // return 23 | // } 24 | // ... Use conn to send and receive messages. 25 | // } 26 | // 27 | // Call the connection's WriteMessage and ReadMessage methods to send and 28 | // receive messages as a slice of bytes. This snippet of code shows how to echo 29 | // messages using these methods: 30 | // 31 | // for { 32 | // messageType, p, err := conn.ReadMessage() 33 | // if err != nil { 34 | // return 35 | // } 36 | // if err = conn.WriteMessage(messageType, p); err != nil { 37 | // return err 38 | // } 39 | // } 40 | // 41 | // In above snippet of code, p is a []byte and messageType is an int with value 42 | // websocket.BinaryMessage or websocket.TextMessage. 43 | // 44 | // An application can also send and receive messages using the io.WriteCloser 45 | // and io.Reader interfaces. To send a message, call the connection NextWriter 46 | // method to get an io.WriteCloser, write the message to the writer and close 47 | // the writer when done. To receive a message, call the connection NextReader 48 | // method to get an io.Reader and read until io.EOF is returned. This snippet 49 | // shows how to echo messages using the NextWriter and NextReader methods: 50 | // 51 | // for { 52 | // messageType, r, err := conn.NextReader() 53 | // if err != nil { 54 | // return 55 | // } 56 | // w, err := conn.NextWriter(messageType) 57 | // if err != nil { 58 | // return err 59 | // } 60 | // if _, err := io.Copy(w, r); err != nil { 61 | // return err 62 | // } 63 | // if err := w.Close(); err != nil { 64 | // return err 65 | // } 66 | // } 67 | // 68 | // Data Messages 69 | // 70 | // The WebSocket protocol distinguishes between text and binary data messages. 71 | // Text messages are interpreted as UTF-8 encoded text. The interpretation of 72 | // binary messages is left to the application. 73 | // 74 | // This package uses the TextMessage and BinaryMessage integer constants to 75 | // identify the two data message types. The ReadMessage and NextReader methods 76 | // return the type of the received message. The messageType argument to the 77 | // WriteMessage and NextWriter methods specifies the type of a sent message. 78 | // 79 | // It is the application's responsibility to ensure that text messages are 80 | // valid UTF-8 encoded text. 81 | // 82 | // Control Messages 83 | // 84 | // The WebSocket protocol defines three types of control messages: close, ping 85 | // and pong. Call the connection WriteControl, WriteMessage or NextWriter 86 | // methods to send a control message to the peer. 87 | // 88 | // Connections handle received close messages by sending a close message to the 89 | // peer and returning a *CloseError from the the NextReader, ReadMessage or the 90 | // message Read method. 91 | // 92 | // Connections handle received ping and pong messages by invoking callback 93 | // functions set with SetPingHandler and SetPongHandler methods. The callback 94 | // functions are called from the NextReader, ReadMessage and the message Read 95 | // methods. 96 | // 97 | // The default ping handler sends a pong to the peer. The application's reading 98 | // goroutine can block for a short time while the handler writes the pong data 99 | // to the connection. 100 | // 101 | // The application must read the connection to process ping, pong and close 102 | // messages sent from the peer. If the application is not otherwise interested 103 | // in messages from the peer, then the application should start a goroutine to 104 | // read and discard messages from the peer. A simple example is: 105 | // 106 | // func readLoop(c *websocket.Conn) { 107 | // for { 108 | // if _, _, err := c.NextReader(); err != nil { 109 | // c.Close() 110 | // break 111 | // } 112 | // } 113 | // } 114 | // 115 | // Concurrency 116 | // 117 | // Connections support one concurrent reader and one concurrent writer. 118 | // 119 | // Applications are responsible for ensuring that no more than one goroutine 120 | // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, 121 | // WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and 122 | // that no more than one goroutine calls the read methods (NextReader, 123 | // SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) 124 | // concurrently. 125 | // 126 | // The Close and WriteControl methods can be called concurrently with all other 127 | // methods. 128 | // 129 | // Origin Considerations 130 | // 131 | // Web browsers allow Javascript applications to open a WebSocket connection to 132 | // any host. It's up to the server to enforce an origin policy using the Origin 133 | // request header sent by the browser. 134 | // 135 | // The Upgrader calls the function specified in the CheckOrigin field to check 136 | // the origin. If the CheckOrigin function returns false, then the Upgrade 137 | // method fails the WebSocket handshake with HTTP status 403. 138 | // 139 | // If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail 140 | // the handshake if the Origin request header is present and not equal to the 141 | // Host request header. 142 | // 143 | // An application can allow connections from any origin by specifying a 144 | // function that always returns true: 145 | // 146 | // var upgrader = websocket.Upgrader{ 147 | // CheckOrigin: func(r *http.Request) bool { return true }, 148 | // } 149 | // 150 | // The deprecated Upgrade function does not enforce an origin policy. It's the 151 | // application's responsibility to check the Origin header before calling 152 | // Upgrade. 153 | // 154 | // Compression EXPERIMENTAL 155 | // 156 | // Per message compression extensions (RFC 7692) are experimentally supported 157 | // by this package in a limited capacity. Setting the EnableCompression option 158 | // to true in Dialer or Upgrader will attempt to negotiate per message deflate 159 | // support. 160 | // 161 | // var upgrader = websocket.Upgrader{ 162 | // EnableCompression: true, 163 | // } 164 | // 165 | // If compression was successfully negotiated with the connection's peer, any 166 | // message received in compressed form will be automatically decompressed. 167 | // All Read methods will return uncompressed bytes. 168 | // 169 | // Per message compression of messages written to a connection can be enabled 170 | // or disabled by calling the corresponding Conn method: 171 | // 172 | // conn.EnableWriteCompression(false) 173 | // 174 | // Currently this package does not support compression with "context takeover". 175 | // This means that messages must be compressed and decompressed in isolation, 176 | // without retaining sliding window or dictionary state across messages. For 177 | // more details refer to RFC 7692. 178 | // 179 | // Use of compression is experimental and may result in decreased performance. 180 | package websocket 181 | -------------------------------------------------------------------------------- /vendor/github.com/iris-contrib/go.uuid/generator.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "crypto/md5" 26 | "crypto/rand" 27 | "crypto/sha1" 28 | "encoding/binary" 29 | "fmt" 30 | "hash" 31 | "io" 32 | "net" 33 | "os" 34 | "sync" 35 | "time" 36 | ) 37 | 38 | // Difference in 100-nanosecond intervals between 39 | // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). 40 | const epochStart = 122192928000000000 41 | 42 | type epochFunc func() time.Time 43 | type hwAddrFunc func() (net.HardwareAddr, error) 44 | 45 | var ( 46 | global = newRFC4122Generator() 47 | 48 | posixUID = uint32(os.Getuid()) 49 | posixGID = uint32(os.Getgid()) 50 | ) 51 | 52 | // NewV1 returns UUID based on current timestamp and MAC address. 53 | func NewV1() (UUID, error) { 54 | return global.NewV1() 55 | } 56 | 57 | // NewV2 returns DCE Security UUID based on POSIX UID/GID. 58 | func NewV2(domain byte) (UUID, error) { 59 | return global.NewV2(domain) 60 | } 61 | 62 | // NewV3 returns UUID based on MD5 hash of namespace UUID and name. 63 | func NewV3(ns UUID, name string) UUID { 64 | return global.NewV3(ns, name) 65 | } 66 | 67 | // NewV4 returns random generated UUID. 68 | func NewV4() (UUID, error) { 69 | return global.NewV4() 70 | } 71 | 72 | // NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. 73 | func NewV5(ns UUID, name string) UUID { 74 | return global.NewV5(ns, name) 75 | } 76 | 77 | // Generator provides interface for generating UUIDs. 78 | type Generator interface { 79 | NewV1() (UUID, error) 80 | NewV2(domain byte) (UUID, error) 81 | NewV3(ns UUID, name string) UUID 82 | NewV4() (UUID, error) 83 | NewV5(ns UUID, name string) UUID 84 | } 85 | 86 | // Default generator implementation. 87 | type rfc4122Generator struct { 88 | clockSequenceOnce sync.Once 89 | hardwareAddrOnce sync.Once 90 | storageMutex sync.Mutex 91 | 92 | rand io.Reader 93 | 94 | epochFunc epochFunc 95 | hwAddrFunc hwAddrFunc 96 | lastTime uint64 97 | clockSequence uint16 98 | hardwareAddr [6]byte 99 | } 100 | 101 | func newRFC4122Generator() Generator { 102 | return &rfc4122Generator{ 103 | epochFunc: time.Now, 104 | hwAddrFunc: defaultHWAddrFunc, 105 | rand: rand.Reader, 106 | } 107 | } 108 | 109 | // NewV1 returns UUID based on current timestamp and MAC address. 110 | func (g *rfc4122Generator) NewV1() (UUID, error) { 111 | u := UUID{} 112 | 113 | timeNow, clockSeq, err := g.getClockSequence() 114 | if err != nil { 115 | return Nil, err 116 | } 117 | binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) 118 | binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) 119 | binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) 120 | binary.BigEndian.PutUint16(u[8:], clockSeq) 121 | 122 | hardwareAddr, err := g.getHardwareAddr() 123 | if err != nil { 124 | return Nil, err 125 | } 126 | copy(u[10:], hardwareAddr) 127 | 128 | u.SetVersion(V1) 129 | u.SetVariant(VariantRFC4122) 130 | 131 | return u, nil 132 | } 133 | 134 | // NewV2 returns DCE Security UUID based on POSIX UID/GID. 135 | func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { 136 | u, err := g.NewV1() 137 | if err != nil { 138 | return Nil, err 139 | } 140 | 141 | switch domain { 142 | case DomainPerson: 143 | binary.BigEndian.PutUint32(u[:], posixUID) 144 | case DomainGroup: 145 | binary.BigEndian.PutUint32(u[:], posixGID) 146 | } 147 | 148 | u[9] = domain 149 | 150 | u.SetVersion(V2) 151 | u.SetVariant(VariantRFC4122) 152 | 153 | return u, nil 154 | } 155 | 156 | // NewV3 returns UUID based on MD5 hash of namespace UUID and name. 157 | func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { 158 | u := newFromHash(md5.New(), ns, name) 159 | u.SetVersion(V3) 160 | u.SetVariant(VariantRFC4122) 161 | 162 | return u 163 | } 164 | 165 | // NewV4 returns random generated UUID. 166 | func (g *rfc4122Generator) NewV4() (UUID, error) { 167 | u := UUID{} 168 | if _, err := g.rand.Read(u[:]); err != nil { 169 | return Nil, err 170 | } 171 | u.SetVersion(V4) 172 | u.SetVariant(VariantRFC4122) 173 | 174 | return u, nil 175 | } 176 | 177 | // NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. 178 | func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { 179 | u := newFromHash(sha1.New(), ns, name) 180 | u.SetVersion(V5) 181 | u.SetVariant(VariantRFC4122) 182 | 183 | return u 184 | } 185 | 186 | // Returns epoch and clock sequence. 187 | func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { 188 | var err error 189 | g.clockSequenceOnce.Do(func() { 190 | buf := make([]byte, 2) 191 | if _, err = g.rand.Read(buf); err != nil { 192 | return 193 | } 194 | g.clockSequence = binary.BigEndian.Uint16(buf) 195 | }) 196 | if err != nil { 197 | return 0, 0, err 198 | } 199 | 200 | g.storageMutex.Lock() 201 | defer g.storageMutex.Unlock() 202 | 203 | timeNow := g.getEpoch() 204 | // Clock didn't change since last UUID generation. 205 | // Should increase clock sequence. 206 | if timeNow <= g.lastTime { 207 | g.clockSequence++ 208 | } 209 | g.lastTime = timeNow 210 | 211 | return timeNow, g.clockSequence, nil 212 | } 213 | 214 | // Returns hardware address. 215 | func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { 216 | var err error 217 | g.hardwareAddrOnce.Do(func() { 218 | if hwAddr, err := g.hwAddrFunc(); err == nil { 219 | copy(g.hardwareAddr[:], hwAddr) 220 | return 221 | } 222 | 223 | // Initialize hardwareAddr randomly in case 224 | // of real network interfaces absence. 225 | if _, err = g.rand.Read(g.hardwareAddr[:]); err != nil { 226 | return 227 | } 228 | // Set multicast bit as recommended by RFC 4122 229 | g.hardwareAddr[0] |= 0x01 230 | }) 231 | if err != nil { 232 | return []byte{}, err 233 | } 234 | return g.hardwareAddr[:], nil 235 | } 236 | 237 | // Returns difference in 100-nanosecond intervals between 238 | // UUID epoch (October 15, 1582) and current time. 239 | func (g *rfc4122Generator) getEpoch() uint64 { 240 | return epochStart + uint64(g.epochFunc().UnixNano()/100) 241 | } 242 | 243 | // Returns UUID based on hashing of namespace UUID and name. 244 | func newFromHash(h hash.Hash, ns UUID, name string) UUID { 245 | u := UUID{} 246 | h.Write(ns[:]) 247 | h.Write([]byte(name)) 248 | copy(u[:], h.Sum(nil)) 249 | 250 | return u 251 | } 252 | 253 | // Returns hardware address. 254 | func defaultHWAddrFunc() (net.HardwareAddr, error) { 255 | ifaces, err := net.Interfaces() 256 | if err != nil { 257 | return []byte{}, err 258 | } 259 | for _, iface := range ifaces { 260 | if len(iface.HardwareAddr) >= 6 { 261 | return iface.HardwareAddr, nil 262 | } 263 | } 264 | return []byte{}, fmt.Errorf("uuid: no HW address found") 265 | } 266 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | var websocketStringMessageType = 0; 2 | var websocketIntMessageType = 1; 3 | var websocketBoolMessageType = 2; 4 | var websocketJSONMessageType = 4; 5 | var websocketMessagePrefix = "go-websocket-message:"; 6 | var websocketMessageSeparator = ";"; 7 | var websocketMessagePrefixLen = websocketMessagePrefix.length; 8 | var websocketMessageSeparatorLen = websocketMessageSeparator.length; 9 | var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1; 10 | var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1; 11 | var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1; 12 | var Ws = (function () { 13 | function Ws(endpoint, protocols) { 14 | var _this = this; 15 | // events listeners 16 | this.connectListeners = []; 17 | this.disconnectListeners = []; 18 | this.nativeMessageListeners = []; 19 | this.messageListeners = {}; 20 | if (!window["WebSocket"]) { 21 | return; 22 | } 23 | if (endpoint.indexOf("ws") == -1) { 24 | endpoint = "ws://" + endpoint; 25 | } 26 | if (protocols != null && protocols.length > 0) { 27 | this.conn = new WebSocket(endpoint, protocols); 28 | } 29 | else { 30 | this.conn = new WebSocket(endpoint); 31 | } 32 | this.conn.onopen = (function (evt) { 33 | _this.fireConnect(); 34 | _this.isReady = true; 35 | return null; 36 | }); 37 | this.conn.onclose = (function (evt) { 38 | _this.fireDisconnect(); 39 | return null; 40 | }); 41 | this.conn.onmessage = (function (evt) { 42 | _this.messageReceivedFromConn(evt); 43 | }); 44 | } 45 | //utils 46 | Ws.prototype.isNumber = function (obj) { 47 | return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; 48 | }; 49 | Ws.prototype.isString = function (obj) { 50 | return Object.prototype.toString.call(obj) == "[object String]"; 51 | }; 52 | Ws.prototype.isBoolean = function (obj) { 53 | return typeof obj === 'boolean' || 54 | (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); 55 | }; 56 | Ws.prototype.isJSON = function (obj) { 57 | return typeof obj === 'object'; 58 | }; 59 | // 60 | // messages 61 | Ws.prototype._msg = function (event, websocketMessageType, dataMessage) { 62 | return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; 63 | }; 64 | Ws.prototype.encodeMessage = function (event, data) { 65 | var m = ""; 66 | var t = 0; 67 | if (this.isNumber(data)) { 68 | t = websocketIntMessageType; 69 | m = data.toString(); 70 | } 71 | else if (this.isBoolean(data)) { 72 | t = websocketBoolMessageType; 73 | m = data.toString(); 74 | } 75 | else if (this.isString(data)) { 76 | t = websocketStringMessageType; 77 | m = data.toString(); 78 | } 79 | else if (this.isJSON(data)) { 80 | //propably json-object 81 | t = websocketJSONMessageType; 82 | m = JSON.stringify(data); 83 | } 84 | else if (data !== null && typeof(data) !== "undefined" ) { 85 | // if it has a second parameter but it's not a type we know, then fire this: 86 | console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); 87 | } 88 | return this._msg(event, t, m); 89 | }; 90 | Ws.prototype.decodeMessage = function (event, websocketMessage) { 91 | //go-websocket-message;user;4;themarshaledstringfromajsonstruct 92 | var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; 93 | if (websocketMessage.length < skipLen + 1) { 94 | return null; 95 | } 96 | var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); 97 | var theMessage = websocketMessage.substring(skipLen, websocketMessage.length); 98 | if (websocketMessageType == websocketIntMessageType) { 99 | return parseInt(theMessage); 100 | } 101 | else if (websocketMessageType == websocketBoolMessageType) { 102 | return Boolean(theMessage); 103 | } 104 | else if (websocketMessageType == websocketStringMessageType) { 105 | return theMessage; 106 | } 107 | else if (websocketMessageType == websocketJSONMessageType) { 108 | return JSON.parse(theMessage); 109 | } 110 | else { 111 | return null; // invalid 112 | } 113 | }; 114 | Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) { 115 | if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { 116 | return ""; 117 | } 118 | var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); 119 | var evt = s.substring(0, s.indexOf(websocketMessageSeparator)); 120 | return evt; 121 | }; 122 | Ws.prototype.getCustomMessage = function (event, websocketMessage) { 123 | var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); 124 | var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); 125 | return s; 126 | }; 127 | // 128 | // Ws Events 129 | // messageReceivedFromConn this is the func which decides 130 | // if it's a native websocket message or a custom qws message 131 | // if native message then calls the fireNativeMessage 132 | // else calls the fireMessage 133 | // 134 | // remember this library gives you the freedom of native websocket messages if you don't want to use this client side at all. 135 | Ws.prototype.messageReceivedFromConn = function (evt) { 136 | //check if qws message 137 | var message = evt.data; 138 | if (message.indexOf(websocketMessagePrefix) != -1) { 139 | var event_1 = this.getWebsocketCustomEvent(message); 140 | if (event_1 != "") { 141 | // it's a custom message 142 | this.fireMessage(event_1, this.getCustomMessage(event_1, message)); 143 | return; 144 | } 145 | } 146 | // it's a native websocket message 147 | this.fireNativeMessage(message); 148 | }; 149 | Ws.prototype.OnConnect = function (fn) { 150 | if (this.isReady) { 151 | fn(); 152 | } 153 | this.connectListeners.push(fn); 154 | }; 155 | Ws.prototype.fireConnect = function () { 156 | for (var i = 0; i < this.connectListeners.length; i++) { 157 | this.connectListeners[i](); 158 | } 159 | }; 160 | Ws.prototype.OnDisconnect = function (fn) { 161 | this.disconnectListeners.push(fn); 162 | }; 163 | Ws.prototype.fireDisconnect = function () { 164 | for (var i = 0; i < this.disconnectListeners.length; i++) { 165 | this.disconnectListeners[i](); 166 | } 167 | }; 168 | Ws.prototype.OnMessage = function (cb) { 169 | this.nativeMessageListeners.push(cb); 170 | }; 171 | Ws.prototype.fireNativeMessage = function (websocketMessage) { 172 | for (var i = 0; i < this.nativeMessageListeners.length; i++) { 173 | this.nativeMessageListeners[i](websocketMessage); 174 | } 175 | }; 176 | Ws.prototype.On = function (event, cb) { 177 | if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { 178 | this.messageListeners[event] = []; 179 | } 180 | this.messageListeners[event].push(cb); 181 | }; 182 | Ws.prototype.fireMessage = function (event, message) { 183 | for (var key in this.messageListeners) { 184 | if (this.messageListeners.hasOwnProperty(key)) { 185 | if (key == event) { 186 | for (var i = 0; i < this.messageListeners[key].length; i++) { 187 | this.messageListeners[key][i](message); 188 | } 189 | } 190 | } 191 | } 192 | }; 193 | // 194 | // Ws Actions 195 | Ws.prototype.Disconnect = function () { 196 | this.conn.close(); 197 | }; 198 | // EmitMessage sends a native websocket message 199 | Ws.prototype.EmitMessage = function (websocketMessage) { 200 | this.conn.send(websocketMessage); 201 | }; 202 | // Emit sends a custom websocket message 203 | Ws.prototype.Emit = function (event, data) { 204 | var messageStr = this.encodeMessage(event, data); 205 | this.EmitMessage(messageStr); 206 | }; 207 | return Ws; 208 | }()); -------------------------------------------------------------------------------- /client.ts: -------------------------------------------------------------------------------- 1 | // export to client.go:ClientSource []byte 2 | 3 | const websocketStringMessageType = 0; 4 | const websocketIntMessageType = 1; 5 | const websocketBoolMessageType = 2; 6 | // bytes is missing here for reasons I will explain somewhen 7 | const websocketJSONMessageType = 4; 8 | 9 | const websocketMessagePrefix = "go-websocket-message:"; 10 | const websocketMessageSeparator = ";"; 11 | 12 | const websocketMessagePrefixLen = websocketMessagePrefix.length; 13 | var websocketMessageSeparatorLen = websocketMessageSeparator.length; 14 | var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1; 15 | var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1; 16 | var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1; 17 | 18 | type onConnectFunc = () => void; 19 | type onWebsocketDisconnectFunc = () => void; 20 | type onWebsocketNativeMessageFunc = (websocketMessage: string) => void; 21 | type onMessageFunc = (message: any) => void; 22 | 23 | class Ws { 24 | private conn: WebSocket; 25 | private isReady: boolean; 26 | 27 | // events listeners 28 | 29 | private connectListeners: onConnectFunc[] = []; 30 | private disconnectListeners: onWebsocketDisconnectFunc[] = []; 31 | private nativeMessageListeners: onWebsocketNativeMessageFunc[] = []; 32 | private messageListeners: { [event: string]: onMessageFunc[] } = {}; 33 | 34 | // 35 | 36 | constructor(endpoint: string, protocols?: string[]) { 37 | if (!window["WebSocket"]) { 38 | return; 39 | } 40 | 41 | if (endpoint.indexOf("ws") == -1) { 42 | endpoint = "ws://" + endpoint; 43 | } 44 | if (protocols != null && protocols.length > 0) { 45 | this.conn = new WebSocket(endpoint, protocols); 46 | } else { 47 | this.conn = new WebSocket(endpoint); 48 | } 49 | 50 | this.conn.onopen = ((evt: Event): any => { 51 | this.fireConnect(); 52 | this.isReady = true; 53 | return null; 54 | }); 55 | 56 | this.conn.onclose = ((evt: Event): any => { 57 | this.fireDisconnect(); 58 | return null; 59 | }); 60 | 61 | this.conn.onmessage = ((evt: MessageEvent) => { 62 | this.messageReceivedFromConn(evt); 63 | }); 64 | } 65 | 66 | //utils 67 | 68 | private isNumber(obj: any): boolean { 69 | return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; 70 | } 71 | 72 | private isString(obj: any): boolean { 73 | return Object.prototype.toString.call(obj) == "[object String]"; 74 | } 75 | 76 | private isBoolean(obj: any): boolean { 77 | return typeof obj === 'boolean' || 78 | (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); 79 | } 80 | 81 | private isJSON(obj: any): boolean { 82 | return typeof obj === 'object'; 83 | } 84 | 85 | // 86 | 87 | // messages 88 | private _msg(event: string, websocketMessageType: number, dataMessage: string): string { 89 | 90 | return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; 91 | } 92 | 93 | private encodeMessage(event: string, data: any): string { 94 | let m = ""; 95 | let t = 0; 96 | if (this.isNumber(data)) { 97 | t = websocketIntMessageType; 98 | m = data.toString(); 99 | } else if (this.isBoolean(data)) { 100 | t = websocketBoolMessageType; 101 | m = data.toString(); 102 | } else if (this.isString(data)) { 103 | t = websocketStringMessageType; 104 | m = data.toString(); 105 | } else if (this.isJSON(data)) { 106 | //propably json-object 107 | t = websocketJSONMessageType; 108 | m = JSON.stringify(data); 109 | } else if (data !== null && typeof (data) !== "undefined") { 110 | // if it has a second parameter but it's not a type we know, then fire this: 111 | console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); 112 | } 113 | 114 | return this._msg(event, t, m); 115 | } 116 | 117 | private decodeMessage(event: string, websocketMessage: string): T | any { 118 | //go-websocket-message;user;4;themarshaledstringfromajsonstruct 119 | let skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; 120 | if (websocketMessage.length < skipLen + 1) { 121 | return null; 122 | } 123 | let websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); 124 | let theMessage = websocketMessage.substring(skipLen, websocketMessage.length); 125 | if (websocketMessageType == websocketIntMessageType) { 126 | return parseInt(theMessage); 127 | } else if (websocketMessageType == websocketBoolMessageType) { 128 | return Boolean(theMessage); 129 | } else if (websocketMessageType == websocketStringMessageType) { 130 | return theMessage; 131 | } else if (websocketMessageType == websocketJSONMessageType) { 132 | return JSON.parse(theMessage); 133 | } else { 134 | return null; // invalid 135 | } 136 | } 137 | 138 | private getWebsocketCustomEvent(websocketMessage: string): string { 139 | if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { 140 | return ""; 141 | } 142 | let s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); 143 | let evt = s.substring(0, s.indexOf(websocketMessageSeparator)); 144 | 145 | return evt; 146 | } 147 | 148 | private getCustomMessage(event: string, websocketMessage: string): string { 149 | let eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); 150 | let s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); 151 | return s; 152 | } 153 | 154 | // 155 | 156 | // Ws Events 157 | 158 | // messageReceivedFromConn this is the func which decides 159 | // if it's a native websocket message or a custom qws message 160 | // if native message then calls the fireNativeMessage 161 | // else calls the fireMessage 162 | // 163 | // remember this library gives you the freedom of native websocket messages if you don't want to use this client side at all. 164 | private messageReceivedFromConn(evt: MessageEvent): void { 165 | //check if qws message 166 | let message = evt.data; 167 | if (message.indexOf(websocketMessagePrefix) != -1) { 168 | let event = this.getWebsocketCustomEvent(message); 169 | if (event != "") { 170 | // it's a custom message 171 | this.fireMessage(event, this.getCustomMessage(event, message)); 172 | return; 173 | } 174 | } 175 | 176 | // it's a native websocket message 177 | this.fireNativeMessage(message); 178 | } 179 | 180 | OnConnect(fn: onConnectFunc): void { 181 | if (this.isReady) { 182 | fn(); 183 | } 184 | this.connectListeners.push(fn); 185 | } 186 | 187 | fireConnect(): void { 188 | for (let i = 0; i < this.connectListeners.length; i++) { 189 | this.connectListeners[i](); 190 | } 191 | } 192 | 193 | OnDisconnect(fn: onWebsocketDisconnectFunc): void { 194 | this.disconnectListeners.push(fn); 195 | } 196 | 197 | fireDisconnect(): void { 198 | for (let i = 0; i < this.disconnectListeners.length; i++) { 199 | this.disconnectListeners[i](); 200 | } 201 | } 202 | 203 | OnMessage(cb: onWebsocketNativeMessageFunc): void { 204 | this.nativeMessageListeners.push(cb); 205 | } 206 | 207 | fireNativeMessage(websocketMessage: string): void { 208 | for (let i = 0; i < this.nativeMessageListeners.length; i++) { 209 | this.nativeMessageListeners[i](websocketMessage); 210 | } 211 | } 212 | 213 | On(event: string, cb: onMessageFunc): void { 214 | if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { 215 | this.messageListeners[event] = []; 216 | } 217 | this.messageListeners[event].push(cb); 218 | } 219 | 220 | fireMessage(event: string, message: any): void { 221 | for (let key in this.messageListeners) { 222 | if (this.messageListeners.hasOwnProperty(key)) { 223 | if (key == event) { 224 | for (let i = 0; i < this.messageListeners[key].length; i++) { 225 | this.messageListeners[key][i](message); 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | 233 | // 234 | 235 | // Ws Actions 236 | 237 | Disconnect(): void { 238 | this.conn.close(); 239 | } 240 | 241 | // EmitMessage sends a native websocket message 242 | EmitMessage(websocketMessage: string): void { 243 | this.conn.send(websocketMessage); 244 | } 245 | 246 | // Emit sends a custom websocket message 247 | Emit(event: string, data: any): void { 248 | let messageStr = this.encodeMessage(event, data); 249 | this.EmitMessage(messageStr); 250 | } 251 | 252 | // 253 | 254 | } 255 | 256 | // node-modules export {Ws}; 257 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // ClientHandler is the handler which serves the javascript client-side. 8 | // It can be used as an alternative of a custom http route to register the client-side javascript code, 9 | // Use the Server's `ClientSource` field instead to serve the custom code if you changed the default prefix for custom websocket events. 10 | func ClientHandler(w http.ResponseWriter, r *http.Request) { 11 | w.Header().Set("Content-Type", "application/javascript") 12 | _, err := w.Write(ClientSource) 13 | if err != nil { 14 | http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 15 | w.WriteHeader(http.StatusInternalServerError) 16 | } 17 | } 18 | 19 | // ClientSource the client-side javascript raw source code. 20 | var ClientSource = []byte(`var websocketStringMessageType = 0; 21 | var websocketIntMessageType = 1; 22 | var websocketBoolMessageType = 2; 23 | var websocketJSONMessageType = 4; 24 | var websocketMessagePrefix = "` + DefaultEvtMessageKey + `"; 25 | var websocketMessageSeparator = ";"; 26 | var websocketMessagePrefixLen = websocketMessagePrefix.length; 27 | var websocketMessageSeparatorLen = websocketMessageSeparator.length; 28 | var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1; 29 | var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1; 30 | var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1; 31 | var Ws = (function () { 32 | // 33 | function Ws(endpoint, protocols) { 34 | var _this = this; 35 | // events listeners 36 | this.connectListeners = []; 37 | this.disconnectListeners = []; 38 | this.nativeMessageListeners = []; 39 | this.messageListeners = {}; 40 | if (!window["WebSocket"]) { 41 | return; 42 | } 43 | if (endpoint.indexOf("ws") == -1) { 44 | endpoint = "ws://" + endpoint; 45 | } 46 | if (protocols != null && protocols.length > 0) { 47 | this.conn = new WebSocket(endpoint, protocols); 48 | } 49 | else { 50 | this.conn = new WebSocket(endpoint); 51 | } 52 | this.conn.onopen = (function (evt) { 53 | _this.fireConnect(); 54 | _this.isReady = true; 55 | return null; 56 | }); 57 | this.conn.onclose = (function (evt) { 58 | _this.fireDisconnect(); 59 | return null; 60 | }); 61 | this.conn.onmessage = (function (evt) { 62 | _this.messageReceivedFromConn(evt); 63 | }); 64 | } 65 | //utils 66 | Ws.prototype.isNumber = function (obj) { 67 | return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; 68 | }; 69 | Ws.prototype.isString = function (obj) { 70 | return Object.prototype.toString.call(obj) == "[object String]"; 71 | }; 72 | Ws.prototype.isBoolean = function (obj) { 73 | return typeof obj === 'boolean' || 74 | (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); 75 | }; 76 | Ws.prototype.isJSON = function (obj) { 77 | return typeof obj === 'object'; 78 | }; 79 | // 80 | // messages 81 | Ws.prototype._msg = function (event, websocketMessageType, dataMessage) { 82 | return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; 83 | }; 84 | Ws.prototype.encodeMessage = function (event, data) { 85 | var m = ""; 86 | var t = 0; 87 | if (this.isNumber(data)) { 88 | t = websocketIntMessageType; 89 | m = data.toString(); 90 | } 91 | else if (this.isBoolean(data)) { 92 | t = websocketBoolMessageType; 93 | m = data.toString(); 94 | } 95 | else if (this.isString(data)) { 96 | t = websocketStringMessageType; 97 | m = data.toString(); 98 | } 99 | else if (this.isJSON(data)) { 100 | //propably json-object 101 | t = websocketJSONMessageType; 102 | m = JSON.stringify(data); 103 | } 104 | else if (data !== null && typeof(data) !== "undefined" ) { 105 | // if it has a second parameter but it's not a type we know, then fire this: 106 | console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); 107 | } 108 | return this._msg(event, t, m); 109 | }; 110 | Ws.prototype.decodeMessage = function (event, websocketMessage) { 111 | //go-websocket-message;user;4;themarshaledstringfromajsonstruct 112 | var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; 113 | if (websocketMessage.length < skipLen + 1) { 114 | return null; 115 | } 116 | var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); 117 | var theMessage = websocketMessage.substring(skipLen, websocketMessage.length); 118 | if (websocketMessageType == websocketIntMessageType) { 119 | return parseInt(theMessage); 120 | } 121 | else if (websocketMessageType == websocketBoolMessageType) { 122 | return Boolean(theMessage); 123 | } 124 | else if (websocketMessageType == websocketStringMessageType) { 125 | return theMessage; 126 | } 127 | else if (websocketMessageType == websocketJSONMessageType) { 128 | return JSON.parse(theMessage); 129 | } 130 | else { 131 | return null; // invalid 132 | } 133 | }; 134 | Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) { 135 | if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { 136 | return ""; 137 | } 138 | var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); 139 | var evt = s.substring(0, s.indexOf(websocketMessageSeparator)); 140 | return evt; 141 | }; 142 | Ws.prototype.getCustomMessage = function (event, websocketMessage) { 143 | var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); 144 | var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); 145 | return s; 146 | }; 147 | // 148 | // Ws Events 149 | // messageReceivedFromConn this is the func which decides 150 | // if it's a native websocket message or a custom qws message 151 | // if native message then calls the fireNativeMessage 152 | // else calls the fireMessage 153 | // 154 | // remember this library gives you the freedom of native websocket messages if you don't want to use this client side at all. 155 | Ws.prototype.messageReceivedFromConn = function (evt) { 156 | //check if qws message 157 | var message = evt.data; 158 | if (message.indexOf(websocketMessagePrefix) != -1) { 159 | var event_1 = this.getWebsocketCustomEvent(message); 160 | if (event_1 != "") { 161 | // it's a custom message 162 | this.fireMessage(event_1, this.getCustomMessage(event_1, message)); 163 | return; 164 | } 165 | } 166 | // it's a native websocket message 167 | this.fireNativeMessage(message); 168 | }; 169 | Ws.prototype.OnConnect = function (fn) { 170 | if (this.isReady) { 171 | fn(); 172 | } 173 | this.connectListeners.push(fn); 174 | }; 175 | Ws.prototype.fireConnect = function () { 176 | for (var i = 0; i < this.connectListeners.length; i++) { 177 | this.connectListeners[i](); 178 | } 179 | }; 180 | Ws.prototype.OnDisconnect = function (fn) { 181 | this.disconnectListeners.push(fn); 182 | }; 183 | Ws.prototype.fireDisconnect = function () { 184 | for (var i = 0; i < this.disconnectListeners.length; i++) { 185 | this.disconnectListeners[i](); 186 | } 187 | }; 188 | Ws.prototype.OnMessage = function (cb) { 189 | this.nativeMessageListeners.push(cb); 190 | }; 191 | Ws.prototype.fireNativeMessage = function (websocketMessage) { 192 | for (var i = 0; i < this.nativeMessageListeners.length; i++) { 193 | this.nativeMessageListeners[i](websocketMessage); 194 | } 195 | }; 196 | Ws.prototype.On = function (event, cb) { 197 | if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { 198 | this.messageListeners[event] = []; 199 | } 200 | this.messageListeners[event].push(cb); 201 | }; 202 | Ws.prototype.fireMessage = function (event, message) { 203 | for (var key in this.messageListeners) { 204 | if (this.messageListeners.hasOwnProperty(key)) { 205 | if (key == event) { 206 | for (var i = 0; i < this.messageListeners[key].length; i++) { 207 | this.messageListeners[key][i](message); 208 | } 209 | } 210 | } 211 | } 212 | }; 213 | // 214 | // Ws Actions 215 | Ws.prototype.Disconnect = function () { 216 | this.conn.close(); 217 | }; 218 | // EmitMessage sends a native websocket message 219 | Ws.prototype.EmitMessage = function (websocketMessage) { 220 | this.conn.send(websocketMessage); 221 | }; 222 | // Emit sends a custom websocket message 223 | Ws.prototype.Emit = function (event, data) { 224 | var messageStr = this.encodeMessage(event, data); 225 | this.EmitMessage(messageStr); 226 | }; 227 | return Ws; 228 | }()); 229 | `) 230 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Build Status 2 | License 3 | Releases 4 | Read me docs 5 | Build Status 6 | Built with GoLang 7 | Platforms 8 | 9 | 10 | The package go-websocket provides an easy way to setup a rich Websocket server and client side. 11 | 12 | It's already tested on production & used on [Iris](https://github.com/kataras/iris). 13 | 14 | Installation 15 | ------------ 16 | The only requirement is the [Go Programming Language](https://golang.org/dl). 17 | 18 | ```bash 19 | $ go get -u github.com/kataras/go-websocket 20 | ``` 21 | 22 | 23 | Examples 24 | ------------ 25 | 26 | To view working examples please navigate to the [./examples](https://github.com/kataras/go-websocket/tree/master/examples) folder. 27 | 28 | Docs 29 | ------------ 30 | **WebSocket is a protocol providing full-duplex communication channels over a single TCP connection**. 31 | The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011, and the WebSocket API in Web IDL is being standardized by the W3C. 32 | 33 | WebSocket is designed to be implemented in web browsers and web servers, but it can be used by any client or server application. 34 | The WebSocket protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request. 35 | The WebSocket protocol makes more interaction between a browser and a website possible, **facilitating real-time data transfer from and to the server**. 36 | 37 | [Read more about Websockets on Wikipedia](https://en.wikipedia.org/wiki/WebSocket). 38 | 39 | ----- 40 | 41 | **Configuration** 42 | 43 | ```go 44 | // Config the websocket server configuration 45 | type Config struct { 46 | // IDGenerator used to create (and later on, set) 47 | // an ID for each incoming websocket connections (clients). 48 | // The request is an input parameter which you can use to generate the ID (from headers for example). 49 | // If empty then the ID is generated by DefaultIDGenerator: uuid or,if failed, a randomString(64) 50 | IDGenerator func(w http.ResponseWriter, r *http.Request) string 51 | // EvtMessagePrefix is the prefix of the underline websocket events that are being established under the hoods. 52 | // This prefix is visible only to the javascript side (code) and it has nothing to do 53 | // with the message that the end-user receives. 54 | // Do not change it unless it is absolutely necessary. 55 | // 56 | // If empty then defaults to []byte("go-websocket-message:"). 57 | EvtMessagePrefix []byte 58 | // Error is the function that will be fired if any client couldn't upgrade the HTTP connection 59 | // to a websocket connection, a handshake error. 60 | Error func(w http.ResponseWriter, r *http.Request, status int, reason error) 61 | // CheckOrigin a function that is called right before the handshake, 62 | // if returns false then that client is not allowed to connect with the websocket server. 63 | CheckOrigin func(r *http.Request) bool 64 | // HandshakeTimeout specifies the duration for the handshake to complete. 65 | HandshakeTimeout time.Duration 66 | // WriteTimeout time allowed to write a message to the connection. 67 | // 0 means no timeout. 68 | // Default value is 0 69 | WriteTimeout time.Duration 70 | // ReadTimeout time allowed to read a message from the connection. 71 | // 0 means no timeout. 72 | // Default value is 0 73 | ReadTimeout time.Duration 74 | // PongTimeout allowed to read the next pong message from the connection. 75 | // Default value is 60 * time.Second 76 | PongTimeout time.Duration 77 | // PingPeriod send ping messages to the connection within this period. Must be less than PongTimeout. 78 | // Default value is 60 *time.Second 79 | PingPeriod time.Duration 80 | // MaxMessageSize max message size allowed from connection. 81 | // Default value is 1024 82 | MaxMessageSize int64 83 | // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text 84 | // compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication. 85 | // Default value is false 86 | BinaryMessages bool 87 | // ReadBufferSize is the buffer size for the connection reader. 88 | // Default value is 4096 89 | ReadBufferSize int 90 | // WriteBufferSize is the buffer size for the connection writer. 91 | // Default value is 4096 92 | WriteBufferSize int 93 | // EnableCompression specify if the server should attempt to negotiate per 94 | // message compression (RFC 7692). Setting this value to true does not 95 | // guarantee that compression will be supported. Currently only "no context 96 | // takeover" modes are supported. 97 | // 98 | // Defaults to false and it should be remain as it is, unless special requirements. 99 | EnableCompression bool 100 | 101 | // Subprotocols specifies the server's supported protocols in order of 102 | // preference. If this field is set, then the Upgrade method negotiates a 103 | // subprotocol by selecting the first match in this list with a protocol 104 | // requested by the client. 105 | Subprotocols []string 106 | } 107 | 108 | ``` 109 | 110 | **OUTLINE** 111 | 112 | ```go 113 | // ws := websocket.New(websocket.Config...{}) 114 | // ws.websocket.OnConnection(func(c websocket.Connection){}) 115 | // or package-default 116 | websocket.OnConnection(func(c websocket.Connection){}) 117 | ``` 118 | 119 | Connection's methods 120 | ```go 121 | ID() string 122 | 123 | Request() *http.Request 124 | 125 | // Receive from the client 126 | On("anyCustomEvent", func(message string) {}) 127 | On("anyCustomEvent", func(message int){}) 128 | On("anyCustomEvent", func(message bool){}) 129 | On("anyCustomEvent", func(message anyCustomType){}) 130 | On("anyCustomEvent", func(){}) 131 | 132 | // Receive a native websocket message from the client 133 | // compatible without need of import the go-websocket.js to the .html 134 | OnMessage(func(message []byte){}) 135 | 136 | // Send to the client 137 | Emit("anyCustomEvent", string) 138 | Emit("anyCustomEvent", int) 139 | Emit("anyCustomEvent", bool) 140 | Emit("anyCustomEvent", anyCustomType) 141 | 142 | // Send native websocket messages 143 | // with config.BinaryMessages = true 144 | // useful when you use proto or something like this. 145 | // 146 | // compatible without need of import the go-websocket.js to the .html 147 | EmitMessage([]byte("anyMessage")) 148 | 149 | // Send to specific client(s) 150 | To("otherConnectionId").Emit/EmitMessage... 151 | To("anyCustomRoom").Emit/EmitMessage... 152 | 153 | // Send to all opened connections/clients 154 | To(websocket.All).Emit/EmitMessage... 155 | 156 | // Send to all opened connections/clients EXCEPT this client 157 | To(websocket.Broadcast).Emit/EmitMessage... 158 | 159 | // Rooms, group of connections/clients 160 | Join("anyCustomRoom") 161 | Leave("anyCustomRoom") 162 | 163 | 164 | // Fired when the connection is closed 165 | OnDisconnect(func(){}) 166 | 167 | // Force-disconnect the client from the server-side 168 | Disconnect() error 169 | ``` 170 | 171 | 172 | FAQ 173 | ------------ 174 | 175 | Explore [these questions](https://github.com/kataras/go-websocket/issues?go-websocket=label%3Aquestion) or navigate to the [community chat][Chat]. 176 | 177 | Versioning 178 | ------------ 179 | 180 | Current: **v0.1.0** 181 | 182 | 183 | People 184 | ------------ 185 | The author of go-websocket is [@kataras](https://github.com/kataras). 186 | 187 | If you're **willing to donate**, feel free to send **any** amount through paypal 188 | 189 | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) 190 | 191 | 192 | Contributing 193 | ------------ 194 | If you are interested in contributing to the go-websocket project, please make a PR. 195 | 196 | License 197 | ------------ 198 | 199 | This project is licensed under the MIT License. 200 | 201 | License can be found [here](LICENSE). 202 | 203 | [Travis Widget]: https://img.shields.io/travis/kataras/go-websocket.svg?style=flat-square 204 | [Travis]: http://travis-ci.org/kataras/go-websocket 205 | [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square 206 | [License]: https://github.com/kataras/go-websocket/blob/master/LICENSE 207 | [Release Widget]: https://img.shields.io/badge/release-v0.2.0-blue.svg?style=flat-square 208 | [Release]: https://github.com/kataras/go-websocket/releases 209 | [Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square 210 | [Chat]: https://kataras.rocket.chat/channel/go-websocket 211 | [ChatMain]: https://kataras.rocket.chat/channel/go-websocket 212 | [ChatAlternative]: https://gitter.im/kataras/go-websocket 213 | [Report Widget]: https://img.shields.io/badge/report%20card-A%2B-F44336.svg?style=flat-square 214 | [Report]: http://goreportcard.com/report/kataras/go-websocket 215 | [Documentation Widget]: https://img.shields.io/badge/documentation-reference-5272B4.svg?style=flat-square 216 | [Documentation]: https://www.gitbook.com/book/kataras/go-websocket/details 217 | [Language Widget]: https://img.shields.io/badge/powered_by-Go-3362c2.svg?style=flat-square 218 | [Language]: http://golang.org 219 | [Platform Widget]: https://img.shields.io/badge/platform-Any--OS-gray.svg?style=flat-square 220 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "errors" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // HandshakeError describes an error with the handshake from the peer. 18 | type HandshakeError struct { 19 | message string 20 | } 21 | 22 | func (e HandshakeError) Error() string { return e.message } 23 | 24 | // Upgrader specifies parameters for upgrading an HTTP connection to a 25 | // WebSocket connection. 26 | type Upgrader struct { 27 | // HandshakeTimeout specifies the duration for the handshake to complete. 28 | HandshakeTimeout time.Duration 29 | 30 | // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer 31 | // size is zero, then buffers allocated by the HTTP server are used. The 32 | // I/O buffer sizes do not limit the size of the messages that can be sent 33 | // or received. 34 | ReadBufferSize, WriteBufferSize int 35 | 36 | // Subprotocols specifies the server's supported protocols in order of 37 | // preference. If this field is set, then the Upgrade method negotiates a 38 | // subprotocol by selecting the first match in this list with a protocol 39 | // requested by the client. 40 | Subprotocols []string 41 | 42 | // Error specifies the function for generating HTTP error responses. If Error 43 | // is nil, then http.Error is used to generate the HTTP response. 44 | Error func(w http.ResponseWriter, r *http.Request, status int, reason error) 45 | 46 | // CheckOrigin returns true if the request Origin header is acceptable. If 47 | // CheckOrigin is nil, the host in the Origin header must not be set or 48 | // must match the host of the request. 49 | CheckOrigin func(r *http.Request) bool 50 | 51 | // EnableCompression specify if the server should attempt to negotiate per 52 | // message compression (RFC 7692). Setting this value to true does not 53 | // guarantee that compression will be supported. Currently only "no context 54 | // takeover" modes are supported. 55 | EnableCompression bool 56 | } 57 | 58 | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { 59 | err := HandshakeError{reason} 60 | if u.Error != nil { 61 | u.Error(w, r, status, err) 62 | } else { 63 | w.Header().Set("Sec-Websocket-Version", "13") 64 | http.Error(w, http.StatusText(status), status) 65 | } 66 | return nil, err 67 | } 68 | 69 | // checkSameOrigin returns true if the origin is not set or is equal to the request host. 70 | func checkSameOrigin(r *http.Request) bool { 71 | origin := r.Header["Origin"] 72 | if len(origin) == 0 { 73 | return true 74 | } 75 | u, err := url.Parse(origin[0]) 76 | if err != nil { 77 | return false 78 | } 79 | return u.Host == r.Host 80 | } 81 | 82 | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { 83 | if u.Subprotocols != nil { 84 | clientProtocols := Subprotocols(r) 85 | for _, serverProtocol := range u.Subprotocols { 86 | for _, clientProtocol := range clientProtocols { 87 | if clientProtocol == serverProtocol { 88 | return clientProtocol 89 | } 90 | } 91 | } 92 | } else if responseHeader != nil { 93 | return responseHeader.Get("Sec-Websocket-Protocol") 94 | } 95 | return "" 96 | } 97 | 98 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. 99 | // 100 | // The responseHeader is included in the response to the client's upgrade 101 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the 102 | // application negotiated subprotocol (Sec-Websocket-Protocol). 103 | // 104 | // If the upgrade fails, then Upgrade replies to the client with an HTTP error 105 | // response. 106 | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { 107 | if r.Method != "GET" { 108 | return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET") 109 | } 110 | 111 | if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { 112 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") 113 | } 114 | 115 | if !tokenListContainsValue(r.Header, "Connection", "upgrade") { 116 | return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") 117 | } 118 | 119 | if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { 120 | return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") 121 | } 122 | 123 | if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { 124 | return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") 125 | } 126 | 127 | checkOrigin := u.CheckOrigin 128 | if checkOrigin == nil { 129 | checkOrigin = checkSameOrigin 130 | } 131 | if !checkOrigin(r) { 132 | return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") 133 | } 134 | 135 | challengeKey := r.Header.Get("Sec-Websocket-Key") 136 | if challengeKey == "" { 137 | return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") 138 | } 139 | 140 | subprotocol := u.selectSubprotocol(r, responseHeader) 141 | 142 | // Negotiate PMCE 143 | var compress bool 144 | if u.EnableCompression { 145 | for _, ext := range parseExtensions(r.Header) { 146 | if ext[""] != "permessage-deflate" { 147 | continue 148 | } 149 | compress = true 150 | break 151 | } 152 | } 153 | 154 | var ( 155 | netConn net.Conn 156 | err error 157 | ) 158 | 159 | h, ok := w.(http.Hijacker) 160 | if !ok { 161 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") 162 | } 163 | var brw *bufio.ReadWriter 164 | netConn, brw, err = h.Hijack() 165 | if err != nil { 166 | return u.returnError(w, r, http.StatusInternalServerError, err.Error()) 167 | } 168 | 169 | if brw.Reader.Buffered() > 0 { 170 | netConn.Close() 171 | return nil, errors.New("websocket: client sent data before handshake is complete") 172 | } 173 | 174 | c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw) 175 | c.subprotocol = subprotocol 176 | 177 | if compress { 178 | c.newCompressionWriter = compressNoContextTakeover 179 | c.newDecompressionReader = decompressNoContextTakeover 180 | } 181 | 182 | p := c.writeBuf[:0] 183 | p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) 184 | p = append(p, computeAcceptKey(challengeKey)...) 185 | p = append(p, "\r\n"...) 186 | if c.subprotocol != "" { 187 | p = append(p, "Sec-Websocket-Protocol: "...) 188 | p = append(p, c.subprotocol...) 189 | p = append(p, "\r\n"...) 190 | } 191 | if compress { 192 | p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) 193 | } 194 | for k, vs := range responseHeader { 195 | if k == "Sec-Websocket-Protocol" { 196 | continue 197 | } 198 | for _, v := range vs { 199 | p = append(p, k...) 200 | p = append(p, ": "...) 201 | for i := 0; i < len(v); i++ { 202 | b := v[i] 203 | if b <= 31 { 204 | // prevent response splitting. 205 | b = ' ' 206 | } 207 | p = append(p, b) 208 | } 209 | p = append(p, "\r\n"...) 210 | } 211 | } 212 | p = append(p, "\r\n"...) 213 | 214 | // Clear deadlines set by HTTP server. 215 | netConn.SetDeadline(time.Time{}) 216 | 217 | if u.HandshakeTimeout > 0 { 218 | netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) 219 | } 220 | if _, err = netConn.Write(p); err != nil { 221 | netConn.Close() 222 | return nil, err 223 | } 224 | if u.HandshakeTimeout > 0 { 225 | netConn.SetWriteDeadline(time.Time{}) 226 | } 227 | 228 | return c, nil 229 | } 230 | 231 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. 232 | // 233 | // This function is deprecated, use websocket.Upgrader instead. 234 | // 235 | // The application is responsible for checking the request origin before 236 | // calling Upgrade. An example implementation of the same origin policy is: 237 | // 238 | // if req.Header.Get("Origin") != "http://"+req.Host { 239 | // http.Error(w, "Origin not allowed", 403) 240 | // return 241 | // } 242 | // 243 | // If the endpoint supports subprotocols, then the application is responsible 244 | // for negotiating the protocol used on the connection. Use the Subprotocols() 245 | // function to get the subprotocols requested by the client. Use the 246 | // Sec-Websocket-Protocol response header to specify the subprotocol selected 247 | // by the application. 248 | // 249 | // The responseHeader is included in the response to the client's upgrade 250 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the 251 | // negotiated subprotocol (Sec-Websocket-Protocol). 252 | // 253 | // The connection buffers IO to the underlying network connection. The 254 | // readBufSize and writeBufSize parameters specify the size of the buffers to 255 | // use. Messages can be larger than the buffers. 256 | // 257 | // If the request is not a valid WebSocket handshake, then Upgrade returns an 258 | // error of type HandshakeError. Applications should handle this error by 259 | // replying to the client with an HTTP error response. 260 | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { 261 | u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} 262 | u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { 263 | // don't return errors to maintain backwards compatibility 264 | } 265 | u.CheckOrigin = func(r *http.Request) bool { 266 | // allow all connections by default 267 | return true 268 | } 269 | return u.Upgrade(w, r, responseHeader) 270 | } 271 | 272 | // Subprotocols returns the subprotocols requested by the client in the 273 | // Sec-Websocket-Protocol header. 274 | func Subprotocols(r *http.Request) []string { 275 | h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) 276 | if h == "" { 277 | return nil 278 | } 279 | protocols := strings.Split(h, ",") 280 | for i := range protocols { 281 | protocols[i] = strings.TrimSpace(protocols[i]) 282 | } 283 | return protocols 284 | } 285 | 286 | // IsWebSocketUpgrade returns true if the client requested upgrade to the 287 | // WebSocket protocol. 288 | func IsWebSocketUpgrade(r *http.Request) bool { 289 | return tokenListContainsValue(r.Header, "Connection", "upgrade") && 290 | tokenListContainsValue(r.Header, "Upgrade", "websocket") 291 | } 292 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "crypto/tls" 11 | "encoding/base64" 12 | "errors" 13 | "io" 14 | "io/ioutil" 15 | "net" 16 | "net/http" 17 | "net/url" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | // ErrBadHandshake is returned when the server response to opening handshake is 23 | // invalid. 24 | var ErrBadHandshake = errors.New("websocket: bad handshake") 25 | 26 | var errInvalidCompression = errors.New("websocket: invalid compression negotiation") 27 | 28 | // NewClient creates a new client connection using the given net connection. 29 | // The URL u specifies the host and request URI. Use requestHeader to specify 30 | // the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies 31 | // (Cookie). Use the response.Header to get the selected subprotocol 32 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 33 | // 34 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 35 | // non-nil *http.Response so that callers can handle redirects, authentication, 36 | // etc. 37 | // 38 | // Deprecated: Use Dialer instead. 39 | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { 40 | d := Dialer{ 41 | ReadBufferSize: readBufSize, 42 | WriteBufferSize: writeBufSize, 43 | NetDial: func(net, addr string) (net.Conn, error) { 44 | return netConn, nil 45 | }, 46 | } 47 | return d.Dial(u.String(), requestHeader) 48 | } 49 | 50 | // A Dialer contains options for connecting to WebSocket server. 51 | type Dialer struct { 52 | // NetDial specifies the dial function for creating TCP connections. If 53 | // NetDial is nil, net.Dial is used. 54 | NetDial func(network, addr string) (net.Conn, error) 55 | 56 | // Proxy specifies a function to return a proxy for a given 57 | // Request. If the function returns a non-nil error, the 58 | // request is aborted with the provided error. 59 | // If Proxy is nil or returns a nil *URL, no proxy is used. 60 | Proxy func(*http.Request) (*url.URL, error) 61 | 62 | // TLSClientConfig specifies the TLS configuration to use with tls.Client. 63 | // If nil, the default configuration is used. 64 | TLSClientConfig *tls.Config 65 | 66 | // HandshakeTimeout specifies the duration for the handshake to complete. 67 | HandshakeTimeout time.Duration 68 | 69 | // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer 70 | // size is zero, then a useful default size is used. The I/O buffer sizes 71 | // do not limit the size of the messages that can be sent or received. 72 | ReadBufferSize, WriteBufferSize int 73 | 74 | // Subprotocols specifies the client's requested subprotocols. 75 | Subprotocols []string 76 | 77 | // EnableCompression specifies if the client should attempt to negotiate 78 | // per message compression (RFC 7692). Setting this value to true does not 79 | // guarantee that compression will be supported. Currently only "no context 80 | // takeover" modes are supported. 81 | EnableCompression bool 82 | 83 | // Jar specifies the cookie jar. 84 | // If Jar is nil, cookies are not sent in requests and ignored 85 | // in responses. 86 | Jar http.CookieJar 87 | } 88 | 89 | var errMalformedURL = errors.New("malformed ws or wss URL") 90 | 91 | // parseURL parses the URL. 92 | // 93 | // This function is a replacement for the standard library url.Parse function. 94 | // In Go 1.4 and earlier, url.Parse loses information from the path. 95 | func parseURL(s string) (*url.URL, error) { 96 | // From the RFC: 97 | // 98 | // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] 99 | // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] 100 | var u url.URL 101 | switch { 102 | case strings.HasPrefix(s, "ws://"): 103 | u.Scheme = "ws" 104 | s = s[len("ws://"):] 105 | case strings.HasPrefix(s, "wss://"): 106 | u.Scheme = "wss" 107 | s = s[len("wss://"):] 108 | default: 109 | return nil, errMalformedURL 110 | } 111 | 112 | if i := strings.Index(s, "?"); i >= 0 { 113 | u.RawQuery = s[i+1:] 114 | s = s[:i] 115 | } 116 | 117 | if i := strings.Index(s, "/"); i >= 0 { 118 | u.Opaque = s[i:] 119 | s = s[:i] 120 | } else { 121 | u.Opaque = "/" 122 | } 123 | 124 | u.Host = s 125 | 126 | if strings.Contains(u.Host, "@") { 127 | // Don't bother parsing user information because user information is 128 | // not allowed in websocket URIs. 129 | return nil, errMalformedURL 130 | } 131 | 132 | return &u, nil 133 | } 134 | 135 | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { 136 | hostPort = u.Host 137 | hostNoPort = u.Host 138 | if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { 139 | hostNoPort = hostNoPort[:i] 140 | } else { 141 | switch u.Scheme { 142 | case "wss": 143 | hostPort += ":443" 144 | case "https": 145 | hostPort += ":443" 146 | default: 147 | hostPort += ":80" 148 | } 149 | } 150 | return hostPort, hostNoPort 151 | } 152 | 153 | // DefaultDialer is a dialer with all fields set to the default zero values. 154 | var DefaultDialer = &Dialer{ 155 | Proxy: http.ProxyFromEnvironment, 156 | } 157 | 158 | // Dial creates a new client connection. Use requestHeader to specify the 159 | // origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). 160 | // Use the response.Header to get the selected subprotocol 161 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 162 | // 163 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 164 | // non-nil *http.Response so that callers can handle redirects, authentication, 165 | // etcetera. The response body may not contain the entire response and does not 166 | // need to be closed by the application. 167 | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { 168 | 169 | if d == nil { 170 | d = &Dialer{ 171 | Proxy: http.ProxyFromEnvironment, 172 | } 173 | } 174 | 175 | challengeKey, err := generateChallengeKey() 176 | if err != nil { 177 | return nil, nil, err 178 | } 179 | 180 | u, err := parseURL(urlStr) 181 | if err != nil { 182 | return nil, nil, err 183 | } 184 | 185 | switch u.Scheme { 186 | case "ws": 187 | u.Scheme = "http" 188 | case "wss": 189 | u.Scheme = "https" 190 | default: 191 | return nil, nil, errMalformedURL 192 | } 193 | 194 | if u.User != nil { 195 | // User name and password are not allowed in websocket URIs. 196 | return nil, nil, errMalformedURL 197 | } 198 | 199 | req := &http.Request{ 200 | Method: "GET", 201 | URL: u, 202 | Proto: "HTTP/1.1", 203 | ProtoMajor: 1, 204 | ProtoMinor: 1, 205 | Header: make(http.Header), 206 | Host: u.Host, 207 | } 208 | 209 | // Set the cookies present in the cookie jar of the dialer 210 | if d.Jar != nil { 211 | for _, cookie := range d.Jar.Cookies(u) { 212 | req.AddCookie(cookie) 213 | } 214 | } 215 | 216 | // Set the request headers using the capitalization for names and values in 217 | // RFC examples. Although the capitalization shouldn't matter, there are 218 | // servers that depend on it. The Header.Set method is not used because the 219 | // method canonicalizes the header names. 220 | req.Header["Upgrade"] = []string{"websocket"} 221 | req.Header["Connection"] = []string{"Upgrade"} 222 | req.Header["Sec-WebSocket-Key"] = []string{challengeKey} 223 | req.Header["Sec-WebSocket-Version"] = []string{"13"} 224 | if len(d.Subprotocols) > 0 { 225 | req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} 226 | } 227 | for k, vs := range requestHeader { 228 | switch { 229 | case k == "Host": 230 | if len(vs) > 0 { 231 | req.Host = vs[0] 232 | } 233 | case k == "Upgrade" || 234 | k == "Connection" || 235 | k == "Sec-Websocket-Key" || 236 | k == "Sec-Websocket-Version" || 237 | k == "Sec-Websocket-Extensions" || 238 | (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): 239 | return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) 240 | default: 241 | req.Header[k] = vs 242 | } 243 | } 244 | 245 | if d.EnableCompression { 246 | req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") 247 | } 248 | 249 | hostPort, hostNoPort := hostPortNoPort(u) 250 | 251 | var proxyURL *url.URL 252 | // Check wether the proxy method has been configured 253 | if d.Proxy != nil { 254 | proxyURL, err = d.Proxy(req) 255 | } 256 | if err != nil { 257 | return nil, nil, err 258 | } 259 | 260 | var targetHostPort string 261 | if proxyURL != nil { 262 | targetHostPort, _ = hostPortNoPort(proxyURL) 263 | } else { 264 | targetHostPort = hostPort 265 | } 266 | 267 | var deadline time.Time 268 | if d.HandshakeTimeout != 0 { 269 | deadline = time.Now().Add(d.HandshakeTimeout) 270 | } 271 | 272 | netDial := d.NetDial 273 | if netDial == nil { 274 | netDialer := &net.Dialer{Deadline: deadline} 275 | netDial = netDialer.Dial 276 | } 277 | 278 | netConn, err := netDial("tcp", targetHostPort) 279 | if err != nil { 280 | return nil, nil, err 281 | } 282 | 283 | defer func() { 284 | if netConn != nil { 285 | netConn.Close() 286 | } 287 | }() 288 | 289 | if err := netConn.SetDeadline(deadline); err != nil { 290 | return nil, nil, err 291 | } 292 | 293 | if proxyURL != nil { 294 | connectHeader := make(http.Header) 295 | if user := proxyURL.User; user != nil { 296 | proxyUser := user.Username() 297 | if proxyPassword, passwordSet := user.Password(); passwordSet { 298 | credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) 299 | connectHeader.Set("Proxy-Authorization", "Basic "+credential) 300 | } 301 | } 302 | connectReq := &http.Request{ 303 | Method: "CONNECT", 304 | URL: &url.URL{Opaque: hostPort}, 305 | Host: hostPort, 306 | Header: connectHeader, 307 | } 308 | 309 | connectReq.Write(netConn) 310 | 311 | // Read response. 312 | // Okay to use and discard buffered reader here, because 313 | // TLS server will not speak until spoken to. 314 | br := bufio.NewReader(netConn) 315 | resp, err := http.ReadResponse(br, connectReq) 316 | if err != nil { 317 | return nil, nil, err 318 | } 319 | if resp.StatusCode != 200 { 320 | f := strings.SplitN(resp.Status, " ", 2) 321 | return nil, nil, errors.New(f[1]) 322 | } 323 | } 324 | 325 | if u.Scheme == "https" { 326 | cfg := cloneTLSConfig(d.TLSClientConfig) 327 | if cfg.ServerName == "" { 328 | cfg.ServerName = hostNoPort 329 | } 330 | tlsConn := tls.Client(netConn, cfg) 331 | netConn = tlsConn 332 | if err := tlsConn.Handshake(); err != nil { 333 | return nil, nil, err 334 | } 335 | if !cfg.InsecureSkipVerify { 336 | if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { 337 | return nil, nil, err 338 | } 339 | } 340 | } 341 | 342 | conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) 343 | 344 | if err := req.Write(netConn); err != nil { 345 | return nil, nil, err 346 | } 347 | 348 | resp, err := http.ReadResponse(conn.br, req) 349 | if err != nil { 350 | return nil, nil, err 351 | } 352 | 353 | if d.Jar != nil { 354 | if rc := resp.Cookies(); len(rc) > 0 { 355 | d.Jar.SetCookies(u, rc) 356 | } 357 | } 358 | 359 | if resp.StatusCode != 101 || 360 | !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || 361 | !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || 362 | resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { 363 | // Before closing the network connection on return from this 364 | // function, slurp up some of the response to aid application 365 | // debugging. 366 | buf := make([]byte, 1024) 367 | n, _ := io.ReadFull(resp.Body, buf) 368 | resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) 369 | return nil, resp, ErrBadHandshake 370 | } 371 | 372 | for _, ext := range parseExtensions(resp.Header) { 373 | if ext[""] != "permessage-deflate" { 374 | continue 375 | } 376 | _, snct := ext["server_no_context_takeover"] 377 | _, cnct := ext["client_no_context_takeover"] 378 | if !snct || !cnct { 379 | return nil, resp, errInvalidCompression 380 | } 381 | conn.newCompressionWriter = compressNoContextTakeover 382 | conn.newDecompressionReader = decompressNoContextTakeover 383 | break 384 | } 385 | 386 | resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) 387 | conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") 388 | 389 | netConn.SetDeadline(time.Time{}) 390 | netConn = nil // to avoid close in defer. 391 | return conn, resp, nil 392 | } 393 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "sync" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | type ( 13 | // ConnectionFunc is the callback which fires when a client/connection is connected to the Server. 14 | // Receives one parameter which is the Connection 15 | ConnectionFunc func(Connection) 16 | 17 | // websocketRoomPayload is used as payload from the connection to the Server 18 | websocketRoomPayload struct { 19 | roomName string 20 | connectionID string 21 | } 22 | 23 | // payloads, connection -> Server 24 | websocketMessagePayload struct { 25 | from string 26 | to string 27 | data []byte 28 | } 29 | 30 | // Server is the websocket Server's implementation. 31 | // 32 | // It listens for websocket clients (either from the javascript client-side or from any websocket implementation). 33 | // See `OnConnection` , to register a single event which will handle all incoming connections and 34 | // the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint. 35 | // 36 | // To serve the built'n javascript client-side library look the `websocket.ClientHandler`. 37 | Server struct { 38 | config Config 39 | // ClientSource contains the javascript side code 40 | // for the websocket communication 41 | // based on the configuration's `EvtMessagePrefix`. 42 | // 43 | // Use a route to serve this file on a specific path, i.e 44 | // mux.HandleFUNC("/go-websocket.js", func(w http.ResponseWriter, _ *http.Request) { w.Write(mywebsocketServer.ClientSource) }) 45 | ClientSource []byte 46 | messageSerializer *messageSerializer 47 | connections sync.Map // key = the Connection ID. 48 | rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name 49 | mu sync.RWMutex // for rooms. 50 | onConnectionListeners []ConnectionFunc 51 | //connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed. 52 | upgrader websocket.Upgrader 53 | } 54 | ) 55 | 56 | // New returns a new websocket Server based on a configuration. 57 | // See `OnConnection` , to register a single event which will handle all incoming connections and 58 | // the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint. 59 | // 60 | // To serve the built'n javascript client-side library look the `websocket.ClientHandler`. 61 | func New(cfg Config) *Server { 62 | cfg = cfg.Validate() 63 | return &Server{ 64 | config: cfg, 65 | ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1), 66 | messageSerializer: newMessageSerializer(cfg.EvtMessagePrefix), 67 | connections: sync.Map{}, // ready-to-use, this is not necessary. 68 | rooms: make(map[string][]string), 69 | onConnectionListeners: make([]ConnectionFunc, 0), 70 | upgrader: websocket.Upgrader{ 71 | HandshakeTimeout: cfg.HandshakeTimeout, 72 | ReadBufferSize: cfg.ReadBufferSize, 73 | WriteBufferSize: cfg.WriteBufferSize, 74 | Error: cfg.Error, 75 | CheckOrigin: cfg.CheckOrigin, 76 | Subprotocols: cfg.Subprotocols, 77 | EnableCompression: cfg.EnableCompression, 78 | }, 79 | } 80 | } 81 | 82 | // Handler is the websocket handler which registers the endpoint's handler 83 | // and fires the events to the registered `OnConnection` event. 84 | // It should be called once per Server, its result should be passed 85 | // as a route handler to register the websocket's endpoint. 86 | // 87 | // Endpoint is the path which the websocket Server will listen for clients/connections. 88 | // 89 | // To serve the built'n javascript client-side library look the `websocket.ClientHandler`. 90 | // This handles any websocket error, use the `Upgrade` for custom error reporting and connection handling. 91 | func (s *Server) Handler(w http.ResponseWriter, r *http.Request) { 92 | c := s.Upgrade(w, r) 93 | if c.Err() != nil { 94 | http.Error(w, fmt.Sprintf("websocket error: %v\n", c.Err()), http.StatusServiceUnavailable) 95 | return 96 | } 97 | // NOTE TO ME: fire these first BEFORE startReader and startPinger 98 | // in order to set the events and any messages to send 99 | // the startPinger will send the OK to the client and only 100 | // then the client is able to send and receive from Server 101 | // when all things are ready and only then. DO NOT change this order. 102 | 103 | // fire the on connection event callbacks, if any 104 | for i := range s.onConnectionListeners { 105 | s.onConnectionListeners[i](c) 106 | } 107 | 108 | // start the ping and the messages reader 109 | c.Wait() 110 | } 111 | 112 | // Upgrade upgrades the HTTP Server connection to the WebSocket protocol. 113 | // 114 | // The responseHeader is included in the response to the client's upgrade 115 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the 116 | // application negotiated subprotocol (Sec--Protocol). 117 | // 118 | // If the upgrade fails, then Upgrade replies to the client with an HTTP error 119 | // response and the return `Connection.Err()` is filled with that error. 120 | // 121 | // For a more high-level function use the `Handler()` and `OnConnecton` events. 122 | // This one does not starts the connection's writer and reader, so after your `On/OnMessage` events registration 123 | // the caller has to call the `Connection#Wait` function, otherwise the connection will be not handled. 124 | // 125 | // Caller should handle the error based on the connection's Err(). 126 | func (s *Server) Upgrade(w http.ResponseWriter, r *http.Request) Connection { 127 | conn, err := s.upgrader.Upgrade(w, r, w.Header()) 128 | if err != nil { 129 | // caller should handle the error: http.Error(w, fmt.Sprintf("websocket error: %v\n", err), http.StatusServiceUnavailable) 130 | return &connection{err: err} 131 | } 132 | 133 | return s.handleConnection(w, r, conn) 134 | } 135 | 136 | func (s *Server) addConnection(c *connection) { 137 | s.connections.Store(c.id, c) 138 | } 139 | 140 | func (s *Server) getConnection(connID string) (*connection, bool) { 141 | if cValue, ok := s.connections.Load(connID); ok { 142 | // this cast is not necessary, 143 | // we know that we always save a connection, but for good or worse let it be here. 144 | if conn, ok := cValue.(*connection); ok { 145 | return conn, ok 146 | } 147 | } 148 | 149 | return nil, false 150 | } 151 | 152 | // wrapConnection wraps an underline connection to a *connection. 153 | // It does NOT starts its writer, reader and event mux, the caller is responsible for that. 154 | func (s *Server) handleConnection(w http.ResponseWriter, r *http.Request, websocketConn UnderlineConnection) *connection { 155 | // use the config's id generator (or the default) to create a websocket client/connection id 156 | cid := s.config.IDGenerator(w, r) 157 | // create the new connection 158 | c := newConnection(r, s, websocketConn, cid) 159 | // add the connection to the Server's list 160 | s.addConnection(c) 161 | 162 | // join to itself 163 | s.Join(c.id, c.id) 164 | 165 | return c 166 | } 167 | 168 | /* Notes: 169 | We use the id as the signature of the connection because with the custom IDGenerator 170 | the developer can share this ID with a database field, so we want to give the oportunnity to handle 171 | his/her websocket connections without even use the connection itself. 172 | 173 | Another question may be: 174 | Q: Why you use Server as the main actioner for all of the connection actions? 175 | For example the Server.Disconnect(connID) manages the connection internal fields, is this code-style correct? 176 | A: It's the correct code-style for these type of applications and libraries, Server manages all, the connnection's functions 177 | should just do some internal checks (if needed) and push the action to its parent, which is the Server, the Server is able to 178 | remove a connection, the rooms of its connected and all these things, so in order to not split the logic, we have the main logic 179 | here, in the Server, and let the connection with some exported functions whose exists for the per-connection action user's code-style. 180 | 181 | Ok my english are s** I can feel it, but these comments are mostly for me. 182 | */ 183 | 184 | /* 185 | connection actions, same as the connection's method, 186 | but these methods accept the connection ID, 187 | which is useful when the developer maps 188 | this id with a database field (using config.IDGenerator). 189 | */ 190 | 191 | // OnConnection is the main event you, as developer, will work with each of the websocket connections. 192 | func (s *Server) OnConnection(cb ConnectionFunc) { 193 | s.onConnectionListeners = append(s.onConnectionListeners, cb) 194 | } 195 | 196 | // IsConnected returns true if the connection with that ID is connected to the Server 197 | // useful when you have defined a custom connection id generator (based on a database) 198 | // and you want to check if that connection is already connected (on multiple tabs) 199 | func (s *Server) IsConnected(connID string) bool { 200 | _, found := s.getConnection(connID) 201 | return found 202 | } 203 | 204 | // Join joins a websocket client to a room, 205 | // first parameter is the room name and the second the connection.ID() 206 | // 207 | // You can use connection.Join("room name") instead. 208 | func (s *Server) Join(roomName string, connID string) { 209 | s.mu.Lock() 210 | s.join(roomName, connID) 211 | s.mu.Unlock() 212 | } 213 | 214 | // join used internally, no locks used. 215 | func (s *Server) join(roomName string, connID string) { 216 | if s.rooms[roomName] == nil { 217 | s.rooms[roomName] = make([]string, 0) 218 | } 219 | s.rooms[roomName] = append(s.rooms[roomName], connID) 220 | } 221 | 222 | // IsJoined reports if a specific room has a specific connection into its values. 223 | // First parameter is the room name, second is the connection's id. 224 | // 225 | // It returns true when the "connID" is joined to the "roomName". 226 | func (s *Server) IsJoined(roomName string, connID string) bool { 227 | s.mu.RLock() 228 | room := s.rooms[roomName] 229 | s.mu.RUnlock() 230 | 231 | if room == nil { 232 | return false 233 | } 234 | 235 | for _, connid := range room { 236 | if connID == connid { 237 | return true 238 | } 239 | } 240 | 241 | return false 242 | } 243 | 244 | // LeaveAll kicks out a connection from ALL of its joined rooms 245 | func (s *Server) LeaveAll(connID string) { 246 | s.mu.Lock() 247 | for name := range s.rooms { 248 | s.leave(name, connID) 249 | } 250 | s.mu.Unlock() 251 | } 252 | 253 | // Leave leaves a websocket client from a room, 254 | // first parameter is the room name and the second the connection.ID() 255 | // 256 | // You can use connection.Leave("room name") instead. 257 | // Returns true if the connection has actually left from the particular room. 258 | func (s *Server) Leave(roomName string, connID string) bool { 259 | s.mu.Lock() 260 | left := s.leave(roomName, connID) 261 | s.mu.Unlock() 262 | return left 263 | } 264 | 265 | // leave used internally, no locks used. 266 | func (s *Server) leave(roomName string, connID string) (left bool) { 267 | ///THINK: we could add locks to its room but we still use the lock for the whole rooms or we can just do what we do with connections 268 | // I will think about it on the next revision, so far we use the locks only for rooms so we are ok... 269 | if s.rooms[roomName] != nil { 270 | for i := range s.rooms[roomName] { 271 | if s.rooms[roomName][i] == connID { 272 | s.rooms[roomName] = append(s.rooms[roomName][:i], s.rooms[roomName][i+1:]...) 273 | left = true 274 | break 275 | } 276 | } 277 | if len(s.rooms[roomName]) == 0 { // if room is empty then delete it 278 | delete(s.rooms, roomName) 279 | } 280 | } 281 | 282 | if left { 283 | // fire the on room leave connection's listeners, 284 | // the existence check is not necessary here. 285 | if c, ok := s.getConnection(connID); ok { 286 | c.fireOnLeave(roomName) 287 | } 288 | } 289 | return 290 | } 291 | 292 | // GetTotalConnections returns the number of total connections 293 | func (s *Server) GetTotalConnections() (n int) { 294 | s.connections.Range(func(k, v interface{}) bool { 295 | n++ 296 | return true 297 | }) 298 | 299 | return n 300 | } 301 | 302 | // GetConnections returns all connections 303 | func (s *Server) GetConnections() []Connection { 304 | // first call of Range to get the total length, we don't want to use append or manually grow the list here for many reasons. 305 | length := s.GetTotalConnections() 306 | conns := make([]Connection, length, length) 307 | i := 0 308 | // second call of Range. 309 | s.connections.Range(func(k, v interface{}) bool { 310 | conn, ok := v.(*connection) 311 | if !ok { 312 | // if for some reason (should never happen), the value is not stored as *connection 313 | // then stop the iteration and don't continue insertion of the result connections 314 | // in order to avoid any issues while end-dev will try to iterate a nil entry. 315 | return false 316 | } 317 | conns[i] = conn 318 | i++ 319 | return true 320 | }) 321 | 322 | return conns 323 | } 324 | 325 | // GetConnection returns single connection 326 | func (s *Server) GetConnection(connID string) Connection { 327 | conn, ok := s.getConnection(connID) 328 | if !ok { 329 | return nil 330 | } 331 | 332 | return conn 333 | } 334 | 335 | // GetConnectionsByRoom returns a list of Connection 336 | // which are joined to this room. 337 | func (s *Server) GetConnectionsByRoom(roomName string) []Connection { 338 | var conns []Connection 339 | s.mu.RLock() 340 | if connIDs, found := s.rooms[roomName]; found { 341 | for _, connID := range connIDs { 342 | // existence check is not necessary here. 343 | if cValue, ok := s.connections.Load(connID); ok { 344 | if conn, ok := cValue.(*connection); ok { 345 | conns = append(conns, conn) 346 | } 347 | } 348 | } 349 | } 350 | 351 | s.mu.RUnlock() 352 | 353 | return conns 354 | } 355 | 356 | // emitMessage is the main 'router' of the messages coming from the connection 357 | // this is the main function which writes the RAW websocket messages to the client. 358 | // It sends them(messages) to the correct room (self, broadcast or to specific client) 359 | // 360 | // You don't have to use this generic method, exists only for extreme 361 | // apps which you have an external goroutine with a list of custom connection list. 362 | // 363 | // You SHOULD use connection.EmitMessage/Emit/To().Emit/EmitMessage instead. 364 | // let's keep it unexported for the best. 365 | func (s *Server) emitMessage(from, to string, data []byte) { 366 | if to != All && to != Broadcast { 367 | s.mu.RLock() 368 | room := s.rooms[to] 369 | s.mu.RUnlock() 370 | if room != nil { 371 | // it suppose to send the message to a specific room/or a user inside its own room 372 | for _, connectionIDInsideRoom := range room { 373 | if c, ok := s.getConnection(connectionIDInsideRoom); ok { 374 | c.writeDefault(data) //send the message to the client(s) 375 | } else { 376 | // the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE: 377 | cid := connectionIDInsideRoom 378 | if c != nil { 379 | cid = c.id 380 | } 381 | s.Leave(cid, to) 382 | } 383 | } 384 | } 385 | } else { 386 | // it suppose to send the message to all opened connections or to all except the sender. 387 | s.connections.Range(func(k, v interface{}) bool { 388 | connID, ok := k.(string) 389 | if !ok { 390 | // should never happen. 391 | return true 392 | } 393 | 394 | if to != All && to != connID { // if it's not suppose to send to all connections (including itself) 395 | if to == Broadcast && from == connID { // if broadcast to other connections except this 396 | // here we do the opossite of previous block, 397 | // just skip this connection when it's suppose to send the message to all connections except the sender. 398 | return true 399 | } 400 | 401 | } 402 | 403 | // not necessary cast. 404 | conn, ok := v.(*connection) 405 | if ok { 406 | // send to the client(s) when the top validators passed 407 | conn.writeDefault(data) 408 | } 409 | 410 | return ok 411 | }) 412 | } 413 | } 414 | 415 | // Disconnect force-disconnects a websocket connection based on its connection.ID() 416 | // What it does? 417 | // 1. remove the connection from the list 418 | // 2. leave from all joined rooms 419 | // 3. fire the disconnect callbacks, if any 420 | // 4. close the underline connection and return its error, if any. 421 | // 422 | // You can use the connection.Disconnect() instead. 423 | func (s *Server) Disconnect(connID string) (err error) { 424 | // leave from all joined rooms before remove the actual connection from the list. 425 | // note: we cannot use that to send data if the client is actually closed. 426 | s.LeaveAll(connID) 427 | 428 | // remove the connection from the list. 429 | if conn, ok := s.getConnection(connID); ok { 430 | conn.disconnected = true 431 | // fire the disconnect callbacks, if any. 432 | conn.fireDisconnect() 433 | // close the underline connection and return its error, if any. 434 | err = conn.underline.Close() 435 | 436 | s.connections.Delete(connID) 437 | } 438 | 439 | return 440 | } 441 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "net" 8 | "net/http" 9 | "strconv" 10 | "sync" 11 | "time" 12 | 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | type ( 17 | connectionValue struct { 18 | key []byte 19 | value interface{} 20 | } 21 | // ConnectionValues is the temporary connection's memory store 22 | ConnectionValues []connectionValue 23 | ) 24 | 25 | // Set sets a value based on the key 26 | func (r *ConnectionValues) Set(key string, value interface{}) { 27 | args := *r 28 | n := len(args) 29 | for i := 0; i < n; i++ { 30 | kv := &args[i] 31 | if string(kv.key) == key { 32 | kv.value = value 33 | return 34 | } 35 | } 36 | 37 | c := cap(args) 38 | if c > n { 39 | args = args[:n+1] 40 | kv := &args[n] 41 | kv.key = append(kv.key[:0], key...) 42 | kv.value = value 43 | *r = args 44 | return 45 | } 46 | 47 | kv := connectionValue{} 48 | kv.key = append(kv.key[:0], key...) 49 | kv.value = value 50 | *r = append(args, kv) 51 | } 52 | 53 | // Get returns a value based on its key 54 | func (r *ConnectionValues) Get(key string) interface{} { 55 | args := *r 56 | n := len(args) 57 | for i := 0; i < n; i++ { 58 | kv := &args[i] 59 | if string(kv.key) == key { 60 | return kv.value 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | // Reset clears the values 67 | func (r *ConnectionValues) Reset() { 68 | *r = (*r)[:0] 69 | } 70 | 71 | // UnderlineConnection is the underline connection, nothing to think about, 72 | // it's used internally mostly but can be used for extreme cases with other libraries. 73 | type UnderlineConnection interface { 74 | // SetWriteDeadline sets the write deadline on the underlying network 75 | // connection. After a write has timed out, the websocket state is corrupt and 76 | // all future writes will return an error. A zero value for t means writes will 77 | // not time out. 78 | SetWriteDeadline(t time.Time) error 79 | // SetReadDeadline sets the read deadline on the underlying network connection. 80 | // After a read has timed out, the websocket connection state is corrupt and 81 | // all future reads will return an error. A zero value for t means reads will 82 | // not time out. 83 | SetReadDeadline(t time.Time) error 84 | // SetReadLimit sets the maximum size for a message read from the peer. If a 85 | // message exceeds the limit, the connection sends a close frame to the peer 86 | // and returns ErrReadLimit to the application. 87 | SetReadLimit(limit int64) 88 | // SetPongHandler sets the handler for pong messages received from the peer. 89 | // The appData argument to h is the PONG frame application data. The default 90 | // pong handler does nothing. 91 | SetPongHandler(h func(appData string) error) 92 | // SetPingHandler sets the handler for ping messages received from the peer. 93 | // The appData argument to h is the PING frame application data. The default 94 | // ping handler sends a pong to the peer. 95 | SetPingHandler(h func(appData string) error) 96 | // WriteControl writes a control message with the given deadline. The allowed 97 | // message types are CloseMessage, PingMessage and PongMessage. 98 | WriteControl(messageType int, data []byte, deadline time.Time) error 99 | // WriteMessage is a helper method for getting a writer using NextWriter, 100 | // writing the message and closing the writer. 101 | WriteMessage(messageType int, data []byte) error 102 | // ReadMessage is a helper method for getting a reader using NextReader and 103 | // reading from that reader to a buffer. 104 | ReadMessage() (messageType int, p []byte, err error) 105 | // NextWriter returns a writer for the next message to send. The writer's Close 106 | // method flushes the complete message to the network. 107 | // 108 | // There can be at most one open writer on a connection. NextWriter closes the 109 | // previous writer if the application has not already done so. 110 | NextWriter(messageType int) (io.WriteCloser, error) 111 | // Close closes the underlying network connection without sending or waiting for a close frame. 112 | Close() error 113 | } 114 | 115 | // ------------------------------------------------------------------------------------- 116 | // ------------------------------------------------------------------------------------- 117 | // -------------------------------Connection implementation----------------------------- 118 | // ------------------------------------------------------------------------------------- 119 | // ------------------------------------------------------------------------------------- 120 | 121 | type ( 122 | // DisconnectFunc is the callback which is fired when a client/connection closed 123 | DisconnectFunc func() 124 | // LeaveRoomFunc is the callback which is fired when a client/connection leaves from any room. 125 | // This is called automatically when client/connection disconnected 126 | // (because websocket server automatically leaves from all joined rooms) 127 | LeaveRoomFunc func(roomName string) 128 | // ErrorFunc is the callback which fires whenever an error occurs 129 | ErrorFunc (func(error)) 130 | // NativeMessageFunc is the callback for native websocket messages, receives one []byte parameter which is the raw client's message 131 | NativeMessageFunc func([]byte) 132 | // MessageFunc is the second argument to the Emitter's Emit functions. 133 | // A callback which should receives one parameter of type string, int, bool or any valid JSON/Go struct 134 | MessageFunc interface{} 135 | // PingFunc is the callback which fires each ping 136 | PingFunc func() 137 | // PongFunc is the callback which fires on pong message received 138 | PongFunc func() 139 | // Connection is the front-end API that you will use to communicate with the client side 140 | Connection interface { 141 | // Emitter implements EmitMessage & Emit 142 | Emitter 143 | // Err is not nil if the upgrader failed to upgrade http to websocket connection. 144 | Err() error 145 | 146 | // ID returns the connection's identifier 147 | ID() string 148 | 149 | // Request returns the http.Request ptr of the original http 150 | // request. 151 | Request() *http.Request 152 | // Server returns the websocket server instance 153 | // which this connection is listening to. 154 | // 155 | // Its connection-relative operations are safe for use. 156 | Server() *Server 157 | 158 | // Write writes a raw websocket message with a specific type to the client 159 | // used by ping messages and any CloseMessage types. 160 | Write(websocketMessageType int, data []byte) error 161 | 162 | // OnDisconnect registers a callback which is fired when this connection is closed by an error or manual 163 | OnDisconnect(DisconnectFunc) 164 | // OnError registers a callback which fires when this connection occurs an error 165 | OnError(ErrorFunc) 166 | // OnPing registers a callback which fires on each ping 167 | OnPing(PingFunc) 168 | // OnPong registers a callback which fires on pong message received 169 | OnPong(PongFunc) 170 | // FireOnError can be used to send a custom error message to the connection 171 | // 172 | // It does nothing more than firing the OnError listeners. It doesn't send anything to the client. 173 | FireOnError(err error) 174 | // To defines on what "room" (see Join) the server should send a message 175 | // returns an Emmiter(`EmitMessage` & `Emit`) to send messages. 176 | To(string) Emitter 177 | // OnMessage registers a callback which fires when native websocket message received 178 | OnMessage(NativeMessageFunc) 179 | // On registers a callback to a particular event which is fired when a message to this event is received 180 | On(string, MessageFunc) 181 | // Join registers this connection to a room, if it doesn't exist then it creates a new. One room can have one or more connections. One connection can be joined to many rooms. All connections are joined to a room specified by their `ID` automatically. 182 | Join(string) 183 | // IsJoined returns true when this connection is joined to the room, otherwise false. 184 | // It Takes the room name as its input parameter. 185 | IsJoined(roomName string) bool 186 | // Leave removes this connection entry from a room 187 | // Returns true if the connection has actually left from the particular room. 188 | Leave(string) bool 189 | // OnLeave registers a callback which fires when this connection left from any joined room. 190 | // This callback is called automatically on Disconnected client, because websocket server automatically 191 | // deletes the disconnected connection from any joined rooms. 192 | // 193 | // Note: the callback(s) called right before the server deletes the connection from the room 194 | // so the connection theoretical can still send messages to its room right before it is being disconnected. 195 | OnLeave(roomLeaveCb LeaveRoomFunc) 196 | // Wait starts the pinger and the messages reader, 197 | // it's named as "Wait" because it should be called LAST, 198 | // after the "On" events IF server's `Upgrade` is used, 199 | // otherise you don't have to call it because the `Handler()` does it automatically. 200 | Wait() 201 | // Disconnect disconnects the client, close the underline websocket conn and removes it from the conn list 202 | // returns the error, if any, from the underline connection 203 | Disconnect() error 204 | // SetValue sets a key-value pair on the connection's mem store. 205 | SetValue(key string, value interface{}) 206 | // GetValue gets a value by its key from the connection's mem store. 207 | GetValue(key string) interface{} 208 | // GetValueArrString gets a value as []string by its key from the connection's mem store. 209 | GetValueArrString(key string) []string 210 | // GetValueString gets a value as string by its key from the connection's mem store. 211 | GetValueString(key string) string 212 | // GetValueInt gets a value as integer by its key from the connection's mem store. 213 | GetValueInt(key string) int 214 | } 215 | 216 | connection struct { 217 | err error 218 | underline UnderlineConnection 219 | id string 220 | messageType int 221 | disconnected bool 222 | onDisconnectListeners []DisconnectFunc 223 | onRoomLeaveListeners []LeaveRoomFunc 224 | onErrorListeners []ErrorFunc 225 | onPingListeners []PingFunc 226 | onPongListeners []PongFunc 227 | onNativeMessageListeners []NativeMessageFunc 228 | onEventListeners map[string][]MessageFunc 229 | started bool 230 | // these were maden for performance only 231 | self Emitter // pre-defined emitter than sends message to its self client 232 | broadcast Emitter // pre-defined emitter that sends message to all except this 233 | all Emitter // pre-defined emitter which sends message to all clients 234 | 235 | values ConnectionValues 236 | request *http.Request 237 | server *Server 238 | // #119 , websocket writers are not protected by locks inside the gorilla's websocket code 239 | // so we must protect them otherwise we're getting concurrent connection error on multi writers in the same time. 240 | writerMu sync.Mutex 241 | // same exists for reader look here: https://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages 242 | // but we only use one reader in one goroutine, so we are safe. 243 | // readerMu sync.Mutex 244 | } 245 | ) 246 | 247 | var _ Connection = &connection{} 248 | 249 | // CloseMessage denotes a close control message. The optional message 250 | // payload contains a numeric code and text. Use the FormatCloseMessage 251 | // function to format a close message payload. 252 | // 253 | // Use the `Connection#Disconnect` instead. 254 | const CloseMessage = websocket.CloseMessage 255 | 256 | func newConnection(r *http.Request, s *Server, underlineConn UnderlineConnection, id string) *connection { 257 | c := &connection{ 258 | underline: underlineConn, 259 | id: id, 260 | messageType: websocket.TextMessage, 261 | onDisconnectListeners: make([]DisconnectFunc, 0), 262 | onRoomLeaveListeners: make([]LeaveRoomFunc, 0), 263 | onErrorListeners: make([]ErrorFunc, 0), 264 | onNativeMessageListeners: make([]NativeMessageFunc, 0), 265 | onEventListeners: make(map[string][]MessageFunc, 0), 266 | onPongListeners: make([]PongFunc, 0), 267 | started: false, 268 | request: r, 269 | server: s, 270 | } 271 | 272 | if s.config.BinaryMessages { 273 | c.messageType = websocket.BinaryMessage 274 | } 275 | 276 | c.self = newEmitter(c, c.id) 277 | c.broadcast = newEmitter(c, Broadcast) 278 | c.all = newEmitter(c, All) 279 | 280 | return c 281 | } 282 | 283 | // Err is not nil if the upgrader failed to upgrade http to websocket connection. 284 | func (c *connection) Err() error { 285 | return c.err 286 | } 287 | 288 | // Write writes a raw websocket message with a specific type to the client 289 | // used by ping messages and any CloseMessage types. 290 | func (c *connection) Write(websocketMessageType int, data []byte) error { 291 | // for any-case the app tries to write from different goroutines, 292 | // we must protect them because they're reporting that as bug... 293 | c.writerMu.Lock() 294 | if writeTimeout := c.server.config.WriteTimeout; writeTimeout > 0 { 295 | // set the write deadline based on the configuration 296 | c.underline.SetWriteDeadline(time.Now().Add(writeTimeout)) 297 | } 298 | 299 | // .WriteMessage same as NextWriter and close (flush) 300 | err := c.underline.WriteMessage(websocketMessageType, data) 301 | c.writerMu.Unlock() 302 | if err != nil { 303 | // if failed then the connection is off, fire the disconnect 304 | c.Disconnect() 305 | } 306 | return err 307 | } 308 | 309 | // writeDefault is the same as write but the message type is the configured by c.messageType 310 | // if BinaryMessages is enabled then it's raw []byte as you expected to work with protobufs 311 | func (c *connection) writeDefault(data []byte) { 312 | c.Write(c.messageType, data) 313 | } 314 | 315 | const ( 316 | // WriteWait is 1 second at the internal implementation, 317 | // same as here but this can be changed at the future* 318 | WriteWait = 1 * time.Second 319 | ) 320 | 321 | func (c *connection) startPinger() { 322 | 323 | // this is the default internal handler, we just change the writeWait because of the actions we must do before 324 | // the server sends the ping-pong. 325 | 326 | pingHandler := func(message string) error { 327 | err := c.underline.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(WriteWait)) 328 | if err == websocket.ErrCloseSent { 329 | return nil 330 | } else if e, ok := err.(net.Error); ok && e.Temporary() { 331 | return nil 332 | } 333 | return err 334 | } 335 | 336 | c.underline.SetPingHandler(pingHandler) 337 | 338 | go func() { 339 | for { 340 | // using sleep avoids the ticker error that causes a memory leak 341 | time.Sleep(c.server.config.PingPeriod) 342 | if c.disconnected { 343 | // verifies if already disconected 344 | break 345 | } 346 | //fire all OnPing methods 347 | c.fireOnPing() 348 | // try to ping the client, if failed then it disconnects 349 | err := c.Write(websocket.PingMessage, []byte{}) 350 | if err != nil { 351 | // must stop to exit the loop and finish the go routine 352 | break 353 | } 354 | } 355 | }() 356 | } 357 | 358 | func (c *connection) fireOnPing() { 359 | // fire the onPingListeners 360 | for i := range c.onPingListeners { 361 | c.onPingListeners[i]() 362 | } 363 | } 364 | 365 | func (c *connection) fireOnPong() { 366 | // fire the onPongListeners 367 | for i := range c.onPongListeners { 368 | c.onPongListeners[i]() 369 | } 370 | } 371 | 372 | func (c *connection) startReader() { 373 | conn := c.underline 374 | hasReadTimeout := c.server.config.ReadTimeout > 0 375 | 376 | conn.SetReadLimit(c.server.config.MaxMessageSize) 377 | conn.SetPongHandler(func(s string) error { 378 | if hasReadTimeout { 379 | conn.SetReadDeadline(time.Now().Add(c.server.config.ReadTimeout)) 380 | } 381 | //fire all OnPong methods 382 | go c.fireOnPong() 383 | 384 | return nil 385 | }) 386 | 387 | defer func() { 388 | c.Disconnect() 389 | }() 390 | 391 | for { 392 | if hasReadTimeout { 393 | // set the read deadline based on the configuration 394 | conn.SetReadDeadline(time.Now().Add(c.server.config.ReadTimeout)) 395 | } 396 | 397 | _, data, err := conn.ReadMessage() 398 | if err != nil { 399 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { 400 | c.FireOnError(err) 401 | } 402 | break 403 | } else { 404 | c.messageReceived(data) 405 | } 406 | 407 | } 408 | 409 | } 410 | 411 | // messageReceived checks the incoming message and fire the nativeMessage listeners or the event listeners (ws custom message) 412 | func (c *connection) messageReceived(data []byte) { 413 | 414 | if bytes.HasPrefix(data, c.server.config.EvtMessagePrefix) { 415 | //it's a custom ws message 416 | receivedEvt := c.server.messageSerializer.getWebsocketCustomEvent(data) 417 | listeners, ok := c.onEventListeners[string(receivedEvt)] 418 | if !ok || len(listeners) == 0 { 419 | return // if not listeners for this event exit from here 420 | } 421 | 422 | customMessage, err := c.server.messageSerializer.deserialize(receivedEvt, data) 423 | if customMessage == nil || err != nil { 424 | return 425 | } 426 | 427 | for i := range listeners { 428 | if fn, ok := listeners[i].(func()); ok { // its a simple func(){} callback 429 | fn() 430 | } else if fnString, ok := listeners[i].(func(string)); ok { 431 | 432 | if msgString, is := customMessage.(string); is { 433 | fnString(msgString) 434 | } else if msgInt, is := customMessage.(int); is { 435 | // here if server side waiting for string but client side sent an int, just convert this int to a string 436 | fnString(strconv.Itoa(msgInt)) 437 | } 438 | 439 | } else if fnInt, ok := listeners[i].(func(int)); ok { 440 | fnInt(customMessage.(int)) 441 | } else if fnBool, ok := listeners[i].(func(bool)); ok { 442 | fnBool(customMessage.(bool)) 443 | } else if fnBytes, ok := listeners[i].(func([]byte)); ok { 444 | fnBytes(customMessage.([]byte)) 445 | } else { 446 | listeners[i].(func(interface{}))(customMessage) 447 | } 448 | 449 | } 450 | } else { 451 | // it's native websocket message 452 | for i := range c.onNativeMessageListeners { 453 | c.onNativeMessageListeners[i](data) 454 | } 455 | } 456 | 457 | } 458 | 459 | func (c *connection) ID() string { 460 | return c.id 461 | } 462 | 463 | func (c *connection) Request() *http.Request { 464 | return c.request 465 | } 466 | 467 | func (c *connection) Server() *Server { 468 | return c.server 469 | } 470 | 471 | func (c *connection) Values() ConnectionValues { 472 | return c.values 473 | } 474 | 475 | func (c *connection) fireDisconnect() { 476 | for i := range c.onDisconnectListeners { 477 | c.onDisconnectListeners[i]() 478 | } 479 | } 480 | 481 | func (c *connection) OnDisconnect(cb DisconnectFunc) { 482 | c.onDisconnectListeners = append(c.onDisconnectListeners, cb) 483 | } 484 | 485 | func (c *connection) OnError(cb ErrorFunc) { 486 | c.onErrorListeners = append(c.onErrorListeners, cb) 487 | } 488 | 489 | func (c *connection) OnPing(cb PingFunc) { 490 | c.onPingListeners = append(c.onPingListeners, cb) 491 | } 492 | 493 | func (c *connection) OnPong(cb PongFunc) { 494 | c.onPongListeners = append(c.onPongListeners, cb) 495 | } 496 | 497 | func (c *connection) FireOnError(err error) { 498 | for _, cb := range c.onErrorListeners { 499 | cb(err) 500 | } 501 | } 502 | 503 | func (c *connection) To(to string) Emitter { 504 | if to == Broadcast { // if send to all except me, then return the pre-defined emitter, and so on 505 | return c.broadcast 506 | } else if to == All { 507 | return c.all 508 | } else if to == c.id { 509 | return c.self 510 | } 511 | 512 | // is an emitter to another client/connection 513 | return newEmitter(c, to) 514 | } 515 | 516 | func (c *connection) EmitMessage(nativeMessage []byte) error { 517 | return c.self.EmitMessage(nativeMessage) 518 | } 519 | 520 | func (c *connection) Emit(event string, message interface{}) error { 521 | return c.self.Emit(event, message) 522 | } 523 | 524 | func (c *connection) OnMessage(cb NativeMessageFunc) { 525 | c.onNativeMessageListeners = append(c.onNativeMessageListeners, cb) 526 | } 527 | 528 | func (c *connection) On(event string, cb MessageFunc) { 529 | if c.onEventListeners[event] == nil { 530 | c.onEventListeners[event] = make([]MessageFunc, 0) 531 | } 532 | 533 | c.onEventListeners[event] = append(c.onEventListeners[event], cb) 534 | } 535 | 536 | func (c *connection) Join(roomName string) { 537 | c.server.Join(roomName, c.id) 538 | } 539 | 540 | func (c *connection) IsJoined(roomName string) bool { 541 | return c.server.IsJoined(roomName, c.id) 542 | } 543 | 544 | func (c *connection) Leave(roomName string) bool { 545 | return c.server.Leave(roomName, c.id) 546 | } 547 | 548 | func (c *connection) OnLeave(roomLeaveCb LeaveRoomFunc) { 549 | c.onRoomLeaveListeners = append(c.onRoomLeaveListeners, roomLeaveCb) 550 | // note: the callbacks are called from the server on the '.leave' and '.LeaveAll' funcs. 551 | } 552 | 553 | func (c *connection) fireOnLeave(roomName string) { 554 | // check if connection is already closed 555 | if c == nil { 556 | return 557 | } 558 | // fire the onRoomLeaveListeners 559 | for i := range c.onRoomLeaveListeners { 560 | c.onRoomLeaveListeners[i](roomName) 561 | } 562 | } 563 | 564 | // Wait starts the pinger and the messages reader, 565 | // it's named as "Wait" because it should be called LAST, 566 | // after the "On" events IF server's `Upgrade` is used, 567 | // otherise you don't have to call it because the `Handler()` does it automatically. 568 | func (c *connection) Wait() { 569 | if c.started { 570 | return 571 | } 572 | c.started = true 573 | // start the ping 574 | c.startPinger() 575 | 576 | // start the messages reader 577 | c.startReader() 578 | } 579 | 580 | // ErrAlreadyDisconnected can be reported on the `Connection#Disconnect` function whenever the caller tries to close the 581 | // connection when it is already closed by the client or the caller previously. 582 | var ErrAlreadyDisconnected = errors.New("already disconnected") 583 | 584 | func (c *connection) Disconnect() error { 585 | if c == nil || c.disconnected { 586 | return ErrAlreadyDisconnected 587 | } 588 | return c.server.Disconnect(c.ID()) 589 | } 590 | 591 | // mem per-conn store 592 | 593 | func (c *connection) SetValue(key string, value interface{}) { 594 | c.values.Set(key, value) 595 | } 596 | 597 | func (c *connection) GetValue(key string) interface{} { 598 | return c.values.Get(key) 599 | } 600 | 601 | func (c *connection) GetValueArrString(key string) []string { 602 | if v := c.values.Get(key); v != nil { 603 | if arrString, ok := v.([]string); ok { 604 | return arrString 605 | } 606 | } 607 | return nil 608 | } 609 | 610 | func (c *connection) GetValueString(key string) string { 611 | if v := c.values.Get(key); v != nil { 612 | if s, ok := v.(string); ok { 613 | return s 614 | } 615 | } 616 | return "" 617 | } 618 | 619 | func (c *connection) GetValueInt(key string) int { 620 | if v := c.values.Get(key); v != nil { 621 | if i, ok := v.(int); ok { 622 | return i 623 | } else if s, ok := v.(string); ok { 624 | if iv, err := strconv.Atoi(s); err == nil { 625 | return iv 626 | } 627 | } 628 | } 629 | return 0 630 | } 631 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "encoding/binary" 10 | "errors" 11 | "io" 12 | "io/ioutil" 13 | "math/rand" 14 | "net" 15 | "strconv" 16 | "sync" 17 | "time" 18 | "unicode/utf8" 19 | ) 20 | 21 | const ( 22 | // Frame header byte 0 bits from Section 5.2 of RFC 6455 23 | finalBit = 1 << 7 24 | rsv1Bit = 1 << 6 25 | rsv2Bit = 1 << 5 26 | rsv3Bit = 1 << 4 27 | 28 | // Frame header byte 1 bits from Section 5.2 of RFC 6455 29 | maskBit = 1 << 7 30 | 31 | maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask 32 | maxControlFramePayloadSize = 125 33 | 34 | writeWait = time.Second 35 | 36 | defaultReadBufferSize = 4096 37 | defaultWriteBufferSize = 4096 38 | 39 | continuationFrame = 0 40 | noFrame = -1 41 | ) 42 | 43 | // Close codes defined in RFC 6455, section 11.7. 44 | const ( 45 | CloseNormalClosure = 1000 46 | CloseGoingAway = 1001 47 | CloseProtocolError = 1002 48 | CloseUnsupportedData = 1003 49 | CloseNoStatusReceived = 1005 50 | CloseAbnormalClosure = 1006 51 | CloseInvalidFramePayloadData = 1007 52 | ClosePolicyViolation = 1008 53 | CloseMessageTooBig = 1009 54 | CloseMandatoryExtension = 1010 55 | CloseInternalServerErr = 1011 56 | CloseServiceRestart = 1012 57 | CloseTryAgainLater = 1013 58 | CloseTLSHandshake = 1015 59 | ) 60 | 61 | // The message types are defined in RFC 6455, section 11.8. 62 | const ( 63 | // TextMessage denotes a text data message. The text message payload is 64 | // interpreted as UTF-8 encoded text data. 65 | TextMessage = 1 66 | 67 | // BinaryMessage denotes a binary data message. 68 | BinaryMessage = 2 69 | 70 | // CloseMessage denotes a close control message. The optional message 71 | // payload contains a numeric code and text. Use the FormatCloseMessage 72 | // function to format a close message payload. 73 | CloseMessage = 8 74 | 75 | // PingMessage denotes a ping control message. The optional message payload 76 | // is UTF-8 encoded text. 77 | PingMessage = 9 78 | 79 | // PongMessage denotes a ping control message. The optional message payload 80 | // is UTF-8 encoded text. 81 | PongMessage = 10 82 | ) 83 | 84 | // ErrCloseSent is returned when the application writes a message to the 85 | // connection after sending a close message. 86 | var ErrCloseSent = errors.New("websocket: close sent") 87 | 88 | // ErrReadLimit is returned when reading a message that is larger than the 89 | // read limit set for the connection. 90 | var ErrReadLimit = errors.New("websocket: read limit exceeded") 91 | 92 | // netError satisfies the net Error interface. 93 | type netError struct { 94 | msg string 95 | temporary bool 96 | timeout bool 97 | } 98 | 99 | func (e *netError) Error() string { return e.msg } 100 | func (e *netError) Temporary() bool { return e.temporary } 101 | func (e *netError) Timeout() bool { return e.timeout } 102 | 103 | // CloseError represents close frame. 104 | type CloseError struct { 105 | 106 | // Code is defined in RFC 6455, section 11.7. 107 | Code int 108 | 109 | // Text is the optional text payload. 110 | Text string 111 | } 112 | 113 | func (e *CloseError) Error() string { 114 | s := []byte("websocket: close ") 115 | s = strconv.AppendInt(s, int64(e.Code), 10) 116 | switch e.Code { 117 | case CloseNormalClosure: 118 | s = append(s, " (normal)"...) 119 | case CloseGoingAway: 120 | s = append(s, " (going away)"...) 121 | case CloseProtocolError: 122 | s = append(s, " (protocol error)"...) 123 | case CloseUnsupportedData: 124 | s = append(s, " (unsupported data)"...) 125 | case CloseNoStatusReceived: 126 | s = append(s, " (no status)"...) 127 | case CloseAbnormalClosure: 128 | s = append(s, " (abnormal closure)"...) 129 | case CloseInvalidFramePayloadData: 130 | s = append(s, " (invalid payload data)"...) 131 | case ClosePolicyViolation: 132 | s = append(s, " (policy violation)"...) 133 | case CloseMessageTooBig: 134 | s = append(s, " (message too big)"...) 135 | case CloseMandatoryExtension: 136 | s = append(s, " (mandatory extension missing)"...) 137 | case CloseInternalServerErr: 138 | s = append(s, " (internal server error)"...) 139 | case CloseTLSHandshake: 140 | s = append(s, " (TLS handshake error)"...) 141 | } 142 | if e.Text != "" { 143 | s = append(s, ": "...) 144 | s = append(s, e.Text...) 145 | } 146 | return string(s) 147 | } 148 | 149 | // IsCloseError returns boolean indicating whether the error is a *CloseError 150 | // with one of the specified codes. 151 | func IsCloseError(err error, codes ...int) bool { 152 | if e, ok := err.(*CloseError); ok { 153 | for _, code := range codes { 154 | if e.Code == code { 155 | return true 156 | } 157 | } 158 | } 159 | return false 160 | } 161 | 162 | // IsUnexpectedCloseError returns boolean indicating whether the error is a 163 | // *CloseError with a code not in the list of expected codes. 164 | func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { 165 | if e, ok := err.(*CloseError); ok { 166 | for _, code := range expectedCodes { 167 | if e.Code == code { 168 | return false 169 | } 170 | } 171 | return true 172 | } 173 | return false 174 | } 175 | 176 | var ( 177 | errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true} 178 | errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()} 179 | errBadWriteOpCode = errors.New("websocket: bad write message type") 180 | errWriteClosed = errors.New("websocket: write closed") 181 | errInvalidControlFrame = errors.New("websocket: invalid control frame") 182 | ) 183 | 184 | func newMaskKey() [4]byte { 185 | n := rand.Uint32() 186 | return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} 187 | } 188 | 189 | func hideTempErr(err error) error { 190 | if e, ok := err.(net.Error); ok && e.Temporary() { 191 | err = &netError{msg: e.Error(), timeout: e.Timeout()} 192 | } 193 | return err 194 | } 195 | 196 | func isControl(frameType int) bool { 197 | return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage 198 | } 199 | 200 | func isData(frameType int) bool { 201 | return frameType == TextMessage || frameType == BinaryMessage 202 | } 203 | 204 | var validReceivedCloseCodes = map[int]bool{ 205 | // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number 206 | 207 | CloseNormalClosure: true, 208 | CloseGoingAway: true, 209 | CloseProtocolError: true, 210 | CloseUnsupportedData: true, 211 | CloseNoStatusReceived: false, 212 | CloseAbnormalClosure: false, 213 | CloseInvalidFramePayloadData: true, 214 | ClosePolicyViolation: true, 215 | CloseMessageTooBig: true, 216 | CloseMandatoryExtension: true, 217 | CloseInternalServerErr: true, 218 | CloseServiceRestart: true, 219 | CloseTryAgainLater: true, 220 | CloseTLSHandshake: false, 221 | } 222 | 223 | func isValidReceivedCloseCode(code int) bool { 224 | return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) 225 | } 226 | 227 | // The Conn type represents a WebSocket connection. 228 | type Conn struct { 229 | conn net.Conn 230 | isServer bool 231 | subprotocol string 232 | 233 | // Write fields 234 | mu chan bool // used as mutex to protect write to conn 235 | writeBuf []byte // frame is constructed in this buffer. 236 | writeDeadline time.Time 237 | writer io.WriteCloser // the current writer returned to the application 238 | isWriting bool // for best-effort concurrent write detection 239 | 240 | writeErrMu sync.Mutex 241 | writeErr error 242 | 243 | enableWriteCompression bool 244 | compressionLevel int 245 | newCompressionWriter func(io.WriteCloser, int) io.WriteCloser 246 | 247 | // Read fields 248 | reader io.ReadCloser // the current reader returned to the application 249 | readErr error 250 | br *bufio.Reader 251 | readRemaining int64 // bytes remaining in current frame. 252 | readFinal bool // true the current message has more frames. 253 | readLength int64 // Message size. 254 | readLimit int64 // Maximum message size. 255 | readMaskPos int 256 | readMaskKey [4]byte 257 | handlePong func(string) error 258 | handlePing func(string) error 259 | handleClose func(int, string) error 260 | readErrCount int 261 | messageReader *messageReader // the current low-level reader 262 | 263 | readDecompress bool // whether last read frame had RSV1 set 264 | newDecompressionReader func(io.Reader) io.ReadCloser 265 | } 266 | 267 | func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { 268 | return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) 269 | } 270 | 271 | type writeHook struct { 272 | p []byte 273 | } 274 | 275 | func (wh *writeHook) Write(p []byte) (int, error) { 276 | wh.p = p 277 | return len(p), nil 278 | } 279 | 280 | func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { 281 | mu := make(chan bool, 1) 282 | mu <- true 283 | 284 | var br *bufio.Reader 285 | if readBufferSize == 0 && brw != nil && brw.Reader != nil { 286 | // Reuse the supplied bufio.Reader if the buffer has a useful size. 287 | // This code assumes that peek on a reader returns 288 | // bufio.Reader.buf[:0]. 289 | brw.Reader.Reset(conn) 290 | if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { 291 | br = brw.Reader 292 | } 293 | } 294 | if br == nil { 295 | if readBufferSize == 0 { 296 | readBufferSize = defaultReadBufferSize 297 | } 298 | if readBufferSize < maxControlFramePayloadSize { 299 | readBufferSize = maxControlFramePayloadSize 300 | } 301 | br = bufio.NewReaderSize(conn, readBufferSize) 302 | } 303 | 304 | var writeBuf []byte 305 | if writeBufferSize == 0 && brw != nil && brw.Writer != nil { 306 | // Use the bufio.Writer's buffer if the buffer has a useful size. This 307 | // code assumes that bufio.Writer.buf[:1] is passed to the 308 | // bufio.Writer's underlying writer. 309 | var wh writeHook 310 | brw.Writer.Reset(&wh) 311 | brw.Writer.WriteByte(0) 312 | brw.Flush() 313 | if cap(wh.p) >= maxFrameHeaderSize+256 { 314 | writeBuf = wh.p[:cap(wh.p)] 315 | } 316 | } 317 | 318 | if writeBuf == nil { 319 | if writeBufferSize == 0 { 320 | writeBufferSize = defaultWriteBufferSize 321 | } 322 | writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) 323 | } 324 | 325 | c := &Conn{ 326 | isServer: isServer, 327 | br: br, 328 | conn: conn, 329 | mu: mu, 330 | readFinal: true, 331 | writeBuf: writeBuf, 332 | enableWriteCompression: true, 333 | compressionLevel: defaultCompressionLevel, 334 | } 335 | c.SetCloseHandler(nil) 336 | c.SetPingHandler(nil) 337 | c.SetPongHandler(nil) 338 | return c 339 | } 340 | 341 | // Subprotocol returns the negotiated protocol for the connection. 342 | func (c *Conn) Subprotocol() string { 343 | return c.subprotocol 344 | } 345 | 346 | // Close closes the underlying network connection without sending or waiting for a close frame. 347 | func (c *Conn) Close() error { 348 | return c.conn.Close() 349 | } 350 | 351 | // LocalAddr returns the local network address. 352 | func (c *Conn) LocalAddr() net.Addr { 353 | return c.conn.LocalAddr() 354 | } 355 | 356 | // RemoteAddr returns the remote network address. 357 | func (c *Conn) RemoteAddr() net.Addr { 358 | return c.conn.RemoteAddr() 359 | } 360 | 361 | // Write methods 362 | 363 | func (c *Conn) writeFatal(err error) error { 364 | err = hideTempErr(err) 365 | c.writeErrMu.Lock() 366 | if c.writeErr == nil { 367 | c.writeErr = err 368 | } 369 | c.writeErrMu.Unlock() 370 | return err 371 | } 372 | 373 | func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { 374 | <-c.mu 375 | defer func() { c.mu <- true }() 376 | 377 | c.writeErrMu.Lock() 378 | err := c.writeErr 379 | c.writeErrMu.Unlock() 380 | if err != nil { 381 | return err 382 | } 383 | 384 | c.conn.SetWriteDeadline(deadline) 385 | for _, buf := range bufs { 386 | if len(buf) > 0 { 387 | _, err := c.conn.Write(buf) 388 | if err != nil { 389 | return c.writeFatal(err) 390 | } 391 | } 392 | } 393 | 394 | if frameType == CloseMessage { 395 | c.writeFatal(ErrCloseSent) 396 | } 397 | return nil 398 | } 399 | 400 | // WriteControl writes a control message with the given deadline. The allowed 401 | // message types are CloseMessage, PingMessage and PongMessage. 402 | func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { 403 | if !isControl(messageType) { 404 | return errBadWriteOpCode 405 | } 406 | if len(data) > maxControlFramePayloadSize { 407 | return errInvalidControlFrame 408 | } 409 | 410 | b0 := byte(messageType) | finalBit 411 | b1 := byte(len(data)) 412 | if !c.isServer { 413 | b1 |= maskBit 414 | } 415 | 416 | buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) 417 | buf = append(buf, b0, b1) 418 | 419 | if c.isServer { 420 | buf = append(buf, data...) 421 | } else { 422 | key := newMaskKey() 423 | buf = append(buf, key[:]...) 424 | buf = append(buf, data...) 425 | maskBytes(key, 0, buf[6:]) 426 | } 427 | 428 | d := time.Hour * 1000 429 | if !deadline.IsZero() { 430 | d = deadline.Sub(time.Now()) 431 | if d < 0 { 432 | return errWriteTimeout 433 | } 434 | } 435 | 436 | timer := time.NewTimer(d) 437 | select { 438 | case <-c.mu: 439 | timer.Stop() 440 | case <-timer.C: 441 | return errWriteTimeout 442 | } 443 | defer func() { c.mu <- true }() 444 | 445 | c.writeErrMu.Lock() 446 | err := c.writeErr 447 | c.writeErrMu.Unlock() 448 | if err != nil { 449 | return err 450 | } 451 | 452 | c.conn.SetWriteDeadline(deadline) 453 | _, err = c.conn.Write(buf) 454 | if err != nil { 455 | return c.writeFatal(err) 456 | } 457 | if messageType == CloseMessage { 458 | c.writeFatal(ErrCloseSent) 459 | } 460 | return err 461 | } 462 | 463 | func (c *Conn) prepWrite(messageType int) error { 464 | // Close previous writer if not already closed by the application. It's 465 | // probably better to return an error in this situation, but we cannot 466 | // change this without breaking existing applications. 467 | if c.writer != nil { 468 | c.writer.Close() 469 | c.writer = nil 470 | } 471 | 472 | if !isControl(messageType) && !isData(messageType) { 473 | return errBadWriteOpCode 474 | } 475 | 476 | c.writeErrMu.Lock() 477 | err := c.writeErr 478 | c.writeErrMu.Unlock() 479 | return err 480 | } 481 | 482 | // NextWriter returns a writer for the next message to send. The writer's Close 483 | // method flushes the complete message to the network. 484 | // 485 | // There can be at most one open writer on a connection. NextWriter closes the 486 | // previous writer if the application has not already done so. 487 | func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { 488 | if err := c.prepWrite(messageType); err != nil { 489 | return nil, err 490 | } 491 | 492 | mw := &messageWriter{ 493 | c: c, 494 | frameType: messageType, 495 | pos: maxFrameHeaderSize, 496 | } 497 | c.writer = mw 498 | if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { 499 | w := c.newCompressionWriter(c.writer, c.compressionLevel) 500 | mw.compress = true 501 | c.writer = w 502 | } 503 | return c.writer, nil 504 | } 505 | 506 | type messageWriter struct { 507 | c *Conn 508 | compress bool // whether next call to flushFrame should set RSV1 509 | pos int // end of data in writeBuf. 510 | frameType int // type of the current frame. 511 | err error 512 | } 513 | 514 | func (w *messageWriter) fatal(err error) error { 515 | if w.err != nil { 516 | w.err = err 517 | w.c.writer = nil 518 | } 519 | return err 520 | } 521 | 522 | // flushFrame writes buffered data and extra as a frame to the network. The 523 | // final argument indicates that this is the last frame in the message. 524 | func (w *messageWriter) flushFrame(final bool, extra []byte) error { 525 | c := w.c 526 | length := w.pos - maxFrameHeaderSize + len(extra) 527 | 528 | // Check for invalid control frames. 529 | if isControl(w.frameType) && 530 | (!final || length > maxControlFramePayloadSize) { 531 | return w.fatal(errInvalidControlFrame) 532 | } 533 | 534 | b0 := byte(w.frameType) 535 | if final { 536 | b0 |= finalBit 537 | } 538 | if w.compress { 539 | b0 |= rsv1Bit 540 | } 541 | w.compress = false 542 | 543 | b1 := byte(0) 544 | if !c.isServer { 545 | b1 |= maskBit 546 | } 547 | 548 | // Assume that the frame starts at beginning of c.writeBuf. 549 | framePos := 0 550 | if c.isServer { 551 | // Adjust up if mask not included in the header. 552 | framePos = 4 553 | } 554 | 555 | switch { 556 | case length >= 65536: 557 | c.writeBuf[framePos] = b0 558 | c.writeBuf[framePos+1] = b1 | 127 559 | binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) 560 | case length > 125: 561 | framePos += 6 562 | c.writeBuf[framePos] = b0 563 | c.writeBuf[framePos+1] = b1 | 126 564 | binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) 565 | default: 566 | framePos += 8 567 | c.writeBuf[framePos] = b0 568 | c.writeBuf[framePos+1] = b1 | byte(length) 569 | } 570 | 571 | if !c.isServer { 572 | key := newMaskKey() 573 | copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) 574 | maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) 575 | if len(extra) > 0 { 576 | return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) 577 | } 578 | } 579 | 580 | // Write the buffers to the connection with best-effort detection of 581 | // concurrent writes. See the concurrency section in the package 582 | // documentation for more info. 583 | 584 | if c.isWriting { 585 | panic("concurrent write to websocket connection") 586 | } 587 | c.isWriting = true 588 | 589 | err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra) 590 | 591 | if !c.isWriting { 592 | panic("concurrent write to websocket connection") 593 | } 594 | c.isWriting = false 595 | 596 | if err != nil { 597 | return w.fatal(err) 598 | } 599 | 600 | if final { 601 | c.writer = nil 602 | return nil 603 | } 604 | 605 | // Setup for next frame. 606 | w.pos = maxFrameHeaderSize 607 | w.frameType = continuationFrame 608 | return nil 609 | } 610 | 611 | func (w *messageWriter) ncopy(max int) (int, error) { 612 | n := len(w.c.writeBuf) - w.pos 613 | if n <= 0 { 614 | if err := w.flushFrame(false, nil); err != nil { 615 | return 0, err 616 | } 617 | n = len(w.c.writeBuf) - w.pos 618 | } 619 | if n > max { 620 | n = max 621 | } 622 | return n, nil 623 | } 624 | 625 | func (w *messageWriter) Write(p []byte) (int, error) { 626 | if w.err != nil { 627 | return 0, w.err 628 | } 629 | 630 | if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { 631 | // Don't buffer large messages. 632 | err := w.flushFrame(false, p) 633 | if err != nil { 634 | return 0, err 635 | } 636 | return len(p), nil 637 | } 638 | 639 | nn := len(p) 640 | for len(p) > 0 { 641 | n, err := w.ncopy(len(p)) 642 | if err != nil { 643 | return 0, err 644 | } 645 | copy(w.c.writeBuf[w.pos:], p[:n]) 646 | w.pos += n 647 | p = p[n:] 648 | } 649 | return nn, nil 650 | } 651 | 652 | func (w *messageWriter) WriteString(p string) (int, error) { 653 | if w.err != nil { 654 | return 0, w.err 655 | } 656 | 657 | nn := len(p) 658 | for len(p) > 0 { 659 | n, err := w.ncopy(len(p)) 660 | if err != nil { 661 | return 0, err 662 | } 663 | copy(w.c.writeBuf[w.pos:], p[:n]) 664 | w.pos += n 665 | p = p[n:] 666 | } 667 | return nn, nil 668 | } 669 | 670 | func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { 671 | if w.err != nil { 672 | return 0, w.err 673 | } 674 | for { 675 | if w.pos == len(w.c.writeBuf) { 676 | err = w.flushFrame(false, nil) 677 | if err != nil { 678 | break 679 | } 680 | } 681 | var n int 682 | n, err = r.Read(w.c.writeBuf[w.pos:]) 683 | w.pos += n 684 | nn += int64(n) 685 | if err != nil { 686 | if err == io.EOF { 687 | err = nil 688 | } 689 | break 690 | } 691 | } 692 | return nn, err 693 | } 694 | 695 | func (w *messageWriter) Close() error { 696 | if w.err != nil { 697 | return w.err 698 | } 699 | if err := w.flushFrame(true, nil); err != nil { 700 | return err 701 | } 702 | w.err = errWriteClosed 703 | return nil 704 | } 705 | 706 | // WritePreparedMessage writes prepared message into connection. 707 | func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error { 708 | frameType, frameData, err := pm.frame(prepareKey{ 709 | isServer: c.isServer, 710 | compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType), 711 | compressionLevel: c.compressionLevel, 712 | }) 713 | if err != nil { 714 | return err 715 | } 716 | if c.isWriting { 717 | panic("concurrent write to websocket connection") 718 | } 719 | c.isWriting = true 720 | err = c.write(frameType, c.writeDeadline, frameData, nil) 721 | if !c.isWriting { 722 | panic("concurrent write to websocket connection") 723 | } 724 | c.isWriting = false 725 | return err 726 | } 727 | 728 | // WriteMessage is a helper method for getting a writer using NextWriter, 729 | // writing the message and closing the writer. 730 | func (c *Conn) WriteMessage(messageType int, data []byte) error { 731 | 732 | if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { 733 | // Fast path with no allocations and single frame. 734 | 735 | if err := c.prepWrite(messageType); err != nil { 736 | return err 737 | } 738 | mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize} 739 | n := copy(c.writeBuf[mw.pos:], data) 740 | mw.pos += n 741 | data = data[n:] 742 | return mw.flushFrame(true, data) 743 | } 744 | 745 | w, err := c.NextWriter(messageType) 746 | if err != nil { 747 | return err 748 | } 749 | if _, err = w.Write(data); err != nil { 750 | return err 751 | } 752 | return w.Close() 753 | } 754 | 755 | // SetWriteDeadline sets the write deadline on the underlying network 756 | // connection. After a write has timed out, the websocket state is corrupt and 757 | // all future writes will return an error. A zero value for t means writes will 758 | // not time out. 759 | func (c *Conn) SetWriteDeadline(t time.Time) error { 760 | c.writeDeadline = t 761 | return nil 762 | } 763 | 764 | // Read methods 765 | 766 | func (c *Conn) advanceFrame() (int, error) { 767 | 768 | // 1. Skip remainder of previous frame. 769 | 770 | if c.readRemaining > 0 { 771 | if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { 772 | return noFrame, err 773 | } 774 | } 775 | 776 | // 2. Read and parse first two bytes of frame header. 777 | 778 | p, err := c.read(2) 779 | if err != nil { 780 | return noFrame, err 781 | } 782 | 783 | final := p[0]&finalBit != 0 784 | frameType := int(p[0] & 0xf) 785 | mask := p[1]&maskBit != 0 786 | c.readRemaining = int64(p[1] & 0x7f) 787 | 788 | c.readDecompress = false 789 | if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 { 790 | c.readDecompress = true 791 | p[0] &^= rsv1Bit 792 | } 793 | 794 | if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 { 795 | return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16)) 796 | } 797 | 798 | switch frameType { 799 | case CloseMessage, PingMessage, PongMessage: 800 | if c.readRemaining > maxControlFramePayloadSize { 801 | return noFrame, c.handleProtocolError("control frame length > 125") 802 | } 803 | if !final { 804 | return noFrame, c.handleProtocolError("control frame not final") 805 | } 806 | case TextMessage, BinaryMessage: 807 | if !c.readFinal { 808 | return noFrame, c.handleProtocolError("message start before final message frame") 809 | } 810 | c.readFinal = final 811 | case continuationFrame: 812 | if c.readFinal { 813 | return noFrame, c.handleProtocolError("continuation after final message frame") 814 | } 815 | c.readFinal = final 816 | default: 817 | return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) 818 | } 819 | 820 | // 3. Read and parse frame length. 821 | 822 | switch c.readRemaining { 823 | case 126: 824 | p, err := c.read(2) 825 | if err != nil { 826 | return noFrame, err 827 | } 828 | c.readRemaining = int64(binary.BigEndian.Uint16(p)) 829 | case 127: 830 | p, err := c.read(8) 831 | if err != nil { 832 | return noFrame, err 833 | } 834 | c.readRemaining = int64(binary.BigEndian.Uint64(p)) 835 | } 836 | 837 | // 4. Handle frame masking. 838 | 839 | if mask != c.isServer { 840 | return noFrame, c.handleProtocolError("incorrect mask flag") 841 | } 842 | 843 | if mask { 844 | c.readMaskPos = 0 845 | p, err := c.read(len(c.readMaskKey)) 846 | if err != nil { 847 | return noFrame, err 848 | } 849 | copy(c.readMaskKey[:], p) 850 | } 851 | 852 | // 5. For text and binary messages, enforce read limit and return. 853 | 854 | if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { 855 | 856 | c.readLength += c.readRemaining 857 | if c.readLimit > 0 && c.readLength > c.readLimit { 858 | c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) 859 | return noFrame, ErrReadLimit 860 | } 861 | 862 | return frameType, nil 863 | } 864 | 865 | // 6. Read control frame payload. 866 | 867 | var payload []byte 868 | if c.readRemaining > 0 { 869 | payload, err = c.read(int(c.readRemaining)) 870 | c.readRemaining = 0 871 | if err != nil { 872 | return noFrame, err 873 | } 874 | if c.isServer { 875 | maskBytes(c.readMaskKey, 0, payload) 876 | } 877 | } 878 | 879 | // 7. Process control frame payload. 880 | 881 | switch frameType { 882 | case PongMessage: 883 | if err := c.handlePong(string(payload)); err != nil { 884 | return noFrame, err 885 | } 886 | case PingMessage: 887 | if err := c.handlePing(string(payload)); err != nil { 888 | return noFrame, err 889 | } 890 | case CloseMessage: 891 | closeCode := CloseNoStatusReceived 892 | closeText := "" 893 | if len(payload) >= 2 { 894 | closeCode = int(binary.BigEndian.Uint16(payload)) 895 | if !isValidReceivedCloseCode(closeCode) { 896 | return noFrame, c.handleProtocolError("invalid close code") 897 | } 898 | closeText = string(payload[2:]) 899 | if !utf8.ValidString(closeText) { 900 | return noFrame, c.handleProtocolError("invalid utf8 payload in close frame") 901 | } 902 | } 903 | if err := c.handleClose(closeCode, closeText); err != nil { 904 | return noFrame, err 905 | } 906 | return noFrame, &CloseError{Code: closeCode, Text: closeText} 907 | } 908 | 909 | return frameType, nil 910 | } 911 | 912 | func (c *Conn) handleProtocolError(message string) error { 913 | c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) 914 | return errors.New("websocket: " + message) 915 | } 916 | 917 | // NextReader returns the next data message received from the peer. The 918 | // returned messageType is either TextMessage or BinaryMessage. 919 | // 920 | // There can be at most one open reader on a connection. NextReader discards 921 | // the previous message if the application has not already consumed it. 922 | // 923 | // Applications must break out of the application's read loop when this method 924 | // returns a non-nil error value. Errors returned from this method are 925 | // permanent. Once this method returns a non-nil error, all subsequent calls to 926 | // this method return the same error. 927 | func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { 928 | // Close previous reader, only relevant for decompression. 929 | if c.reader != nil { 930 | c.reader.Close() 931 | c.reader = nil 932 | } 933 | 934 | c.messageReader = nil 935 | c.readLength = 0 936 | 937 | for c.readErr == nil { 938 | frameType, err := c.advanceFrame() 939 | if err != nil { 940 | c.readErr = hideTempErr(err) 941 | break 942 | } 943 | if frameType == TextMessage || frameType == BinaryMessage { 944 | c.messageReader = &messageReader{c} 945 | c.reader = c.messageReader 946 | if c.readDecompress { 947 | c.reader = c.newDecompressionReader(c.reader) 948 | } 949 | return frameType, c.reader, nil 950 | } 951 | } 952 | 953 | // Applications that do handle the error returned from this method spin in 954 | // tight loop on connection failure. To help application developers detect 955 | // this error, panic on repeated reads to the failed connection. 956 | c.readErrCount++ 957 | if c.readErrCount >= 1000 { 958 | panic("repeated read on failed websocket connection") 959 | } 960 | 961 | return noFrame, nil, c.readErr 962 | } 963 | 964 | type messageReader struct{ c *Conn } 965 | 966 | func (r *messageReader) Read(b []byte) (int, error) { 967 | c := r.c 968 | if c.messageReader != r { 969 | return 0, io.EOF 970 | } 971 | 972 | for c.readErr == nil { 973 | 974 | if c.readRemaining > 0 { 975 | if int64(len(b)) > c.readRemaining { 976 | b = b[:c.readRemaining] 977 | } 978 | n, err := c.br.Read(b) 979 | c.readErr = hideTempErr(err) 980 | if c.isServer { 981 | c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) 982 | } 983 | c.readRemaining -= int64(n) 984 | if c.readRemaining > 0 && c.readErr == io.EOF { 985 | c.readErr = errUnexpectedEOF 986 | } 987 | return n, c.readErr 988 | } 989 | 990 | if c.readFinal { 991 | c.messageReader = nil 992 | return 0, io.EOF 993 | } 994 | 995 | frameType, err := c.advanceFrame() 996 | switch { 997 | case err != nil: 998 | c.readErr = hideTempErr(err) 999 | case frameType == TextMessage || frameType == BinaryMessage: 1000 | c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") 1001 | } 1002 | } 1003 | 1004 | err := c.readErr 1005 | if err == io.EOF && c.messageReader == r { 1006 | err = errUnexpectedEOF 1007 | } 1008 | return 0, err 1009 | } 1010 | 1011 | func (r *messageReader) Close() error { 1012 | return nil 1013 | } 1014 | 1015 | // ReadMessage is a helper method for getting a reader using NextReader and 1016 | // reading from that reader to a buffer. 1017 | func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { 1018 | var r io.Reader 1019 | messageType, r, err = c.NextReader() 1020 | if err != nil { 1021 | return messageType, nil, err 1022 | } 1023 | p, err = ioutil.ReadAll(r) 1024 | return messageType, p, err 1025 | } 1026 | 1027 | // SetReadDeadline sets the read deadline on the underlying network connection. 1028 | // After a read has timed out, the websocket connection state is corrupt and 1029 | // all future reads will return an error. A zero value for t means reads will 1030 | // not time out. 1031 | func (c *Conn) SetReadDeadline(t time.Time) error { 1032 | return c.conn.SetReadDeadline(t) 1033 | } 1034 | 1035 | // SetReadLimit sets the maximum size for a message read from the peer. If a 1036 | // message exceeds the limit, the connection sends a close frame to the peer 1037 | // and returns ErrReadLimit to the application. 1038 | func (c *Conn) SetReadLimit(limit int64) { 1039 | c.readLimit = limit 1040 | } 1041 | 1042 | // CloseHandler returns the current close handler 1043 | func (c *Conn) CloseHandler() func(code int, text string) error { 1044 | return c.handleClose 1045 | } 1046 | 1047 | // SetCloseHandler sets the handler for close messages received from the peer. 1048 | // The code argument to h is the received close code or CloseNoStatusReceived 1049 | // if the close message is empty. The default close handler sends a close frame 1050 | // back to the peer. 1051 | // 1052 | // The application must read the connection to process close messages as 1053 | // described in the section on Control Frames above. 1054 | // 1055 | // The connection read methods return a CloseError when a close frame is 1056 | // received. Most applications should handle close messages as part of their 1057 | // normal error handling. Applications should only set a close handler when the 1058 | // application must perform some action before sending a close frame back to 1059 | // the peer. 1060 | func (c *Conn) SetCloseHandler(h func(code int, text string) error) { 1061 | if h == nil { 1062 | h = func(code int, text string) error { 1063 | message := []byte{} 1064 | if code != CloseNoStatusReceived { 1065 | message = FormatCloseMessage(code, "") 1066 | } 1067 | c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) 1068 | return nil 1069 | } 1070 | } 1071 | c.handleClose = h 1072 | } 1073 | 1074 | // PingHandler returns the current ping handler 1075 | func (c *Conn) PingHandler() func(appData string) error { 1076 | return c.handlePing 1077 | } 1078 | 1079 | // SetPingHandler sets the handler for ping messages received from the peer. 1080 | // The appData argument to h is the PING frame application data. The default 1081 | // ping handler sends a pong to the peer. 1082 | // 1083 | // The application must read the connection to process ping messages as 1084 | // described in the section on Control Frames above. 1085 | func (c *Conn) SetPingHandler(h func(appData string) error) { 1086 | if h == nil { 1087 | h = func(message string) error { 1088 | err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) 1089 | if err == ErrCloseSent { 1090 | return nil 1091 | } else if e, ok := err.(net.Error); ok && e.Temporary() { 1092 | return nil 1093 | } 1094 | return err 1095 | } 1096 | } 1097 | c.handlePing = h 1098 | } 1099 | 1100 | // PongHandler returns the current pong handler 1101 | func (c *Conn) PongHandler() func(appData string) error { 1102 | return c.handlePong 1103 | } 1104 | 1105 | // SetPongHandler sets the handler for pong messages received from the peer. 1106 | // The appData argument to h is the PONG frame application data. The default 1107 | // pong handler does nothing. 1108 | // 1109 | // The application must read the connection to process ping messages as 1110 | // described in the section on Control Frames above. 1111 | func (c *Conn) SetPongHandler(h func(appData string) error) { 1112 | if h == nil { 1113 | h = func(string) error { return nil } 1114 | } 1115 | c.handlePong = h 1116 | } 1117 | 1118 | // UnderlyingConn returns the internal net.Conn. This can be used to further 1119 | // modifications to connection specific flags. 1120 | func (c *Conn) UnderlyingConn() net.Conn { 1121 | return c.conn 1122 | } 1123 | 1124 | // EnableWriteCompression enables and disables write compression of 1125 | // subsequent text and binary messages. This function is a noop if 1126 | // compression was not negotiated with the peer. 1127 | func (c *Conn) EnableWriteCompression(enable bool) { 1128 | c.enableWriteCompression = enable 1129 | } 1130 | 1131 | // SetCompressionLevel sets the flate compression level for subsequent text and 1132 | // binary messages. This function is a noop if compression was not negotiated 1133 | // with the peer. See the compress/flate package for a description of 1134 | // compression levels. 1135 | func (c *Conn) SetCompressionLevel(level int) error { 1136 | if !isValidCompressionLevel(level) { 1137 | return errors.New("websocket: invalid compression level") 1138 | } 1139 | c.compressionLevel = level 1140 | return nil 1141 | } 1142 | 1143 | // FormatCloseMessage formats closeCode and text as a WebSocket close message. 1144 | func FormatCloseMessage(closeCode int, text string) []byte { 1145 | buf := make([]byte, 2+len(text)) 1146 | binary.BigEndian.PutUint16(buf, uint16(closeCode)) 1147 | copy(buf[2:], text) 1148 | return buf 1149 | } 1150 | --------------------------------------------------------------------------------