From bda36145e54ecff8e57db6b26d5eae680e7b3d82 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 22 Feb 2019 21:24:10 +0200 Subject: [PATCH] some cleanup, and remove the test 'testwebocket2' package at all; A lower-level fast websocket impl based on gobwas/ws will be published on a different repo, it is a WIP Former-commit-id: b680974c593196ce20865ed12778929ced6afea1 --- LICENSE | 2 +- .../go-client-stress-test/client/main.go | 22 +- .../go-client-stress-test/server/main.go | 37 +- cache/LICENSE | 2 +- doc.go | 2 +- hero/LICENSE | 2 +- macro/LICENSE | 2 +- mvc/LICENSE | 2 +- sessions/LICENSE | 2 +- typescript/LICENSE | 2 +- versioning/LICENSE | 2 +- websocket/LICENSE | 2 +- websocket/config.go | 43 +- websocket/connection.go | 58 +- websocket/server.go | 126 +-- websocket2/client.js | 208 ---- websocket2/client.js.go | 233 ----- websocket2/client.min.js | 1 - websocket2/client.ts | 256 ----- websocket2/config.go | 185 ---- websocket2/connection.go | 936 ------------------ websocket2/emitter.go | 43 - websocket2/message.go | 182 ---- websocket2/server.go | 460 --------- websocket2/websocket.go | 69 -- 25 files changed, 110 insertions(+), 2769 deletions(-) delete mode 100644 websocket2/client.js delete mode 100644 websocket2/client.js.go delete mode 100644 websocket2/client.min.js delete mode 100644 websocket2/client.ts delete mode 100644 websocket2/config.go delete mode 100644 websocket2/connection.go delete mode 100644 websocket2/emitter.go delete mode 100644 websocket2/message.go delete mode 100644 websocket2/server.go delete mode 100644 websocket2/websocket.go diff --git a/LICENSE b/LICENSE index c9eada50..a56c9e1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Authors. All rights reserved. +Copyright (c) 2017-2019 The Iris Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/_examples/websocket/go-client-stress-test/client/main.go b/_examples/websocket/go-client-stress-test/client/main.go index 0be23e13..fb5d9806 100644 --- a/_examples/websocket/go-client-stress-test/client/main.go +++ b/_examples/websocket/go-client-stress-test/client/main.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "time" - "github.com/kataras/iris/websocket2" + "github.com/kataras/iris/websocket" ) var ( @@ -19,6 +19,7 @@ var ( ) const totalClients = 16000 // max depends on the OS. +const verbose = true var connectionFailures uint64 @@ -42,7 +43,7 @@ func collectError(op string, err error) { } func main() { - log.Println("--------======Running tests...==========--------------") + log.Println("--Running...") var err error f, err = os.Open("./test.data") if err != nil { @@ -85,11 +86,11 @@ func main() { log.Println() if connectionFailures > 0 { - log.Printf("Finished with %d/%d connection failures. Please close the server-side manually.\n", connectionFailures, totalClients) + log.Printf("Finished with %d/%d connection failures.", connectionFailures, totalClients) } if n := len(connectErrors); n > 0 { - log.Printf("Finished with %d connect errors:\n", n) + log.Printf("Finished with %d connect errors: ", n) var lastErr error var sameC int @@ -123,7 +124,7 @@ func main() { if n := len(disconnectErrors); n > 0 { log.Printf("Finished with %d disconnect errors\n", n) for i, err := range disconnectErrors { - if err == websocket.ErrAlreadyDisconnected { + if err == websocket.ErrAlreadyDisconnected && i > 0 { continue } @@ -135,7 +136,7 @@ func main() { log.Println("ALL OK.") } - log.Println("--------================--------------") + log.Println("--Finished.") } func connect(wg *sync.WaitGroup, alive time.Duration) { @@ -153,12 +154,17 @@ func connect(wg *sync.WaitGroup, alive time.Duration) { disconnected := false c.OnDisconnect(func() { - // log.Printf("I am disconnected after [%s].\n", alive) + if verbose { + log.Printf("I am disconnected after [%s].\n", alive) + } + disconnected = true }) c.On("chat", func(message string) { - // log.Printf("\n%s\n", message) + if verbose { + log.Printf("\n%s\n", message) + } }) go func() { diff --git a/_examples/websocket/go-client-stress-test/server/main.go b/_examples/websocket/go-client-stress-test/server/main.go index a0cf6668..37fe7383 100644 --- a/_examples/websocket/go-client-stress-test/server/main.go +++ b/_examples/websocket/go-client-stress-test/server/main.go @@ -8,11 +8,11 @@ import ( "time" "github.com/kataras/iris" - "github.com/kataras/iris/websocket2" + "github.com/kataras/iris/websocket" ) const totalClients = 16000 // max depends on the OS. -const http = true +const verbose = true func main() { @@ -62,32 +62,9 @@ func main() { } }() - if http { - app := iris.New() - app.Get("/", ws.Handler()) - app.Run(iris.Addr(":8080")) - return - } - - // ln, err := net.Listen("tcp", ":8080") - // if err != nil { - // panic(err) - // } - - // defer ln.Close() - // for { - // conn, err := ln.Accept() - // if err != nil { - // panic(err) - // } - - // go func() { - // err = ws.HandleConn(conn) - // if err != nil { - // panic(err) - // } - // }() - // } + app := iris.New() + app.Get("/", ws.Handler()) + app.Run(iris.Addr(":8080")) } @@ -103,7 +80,9 @@ var count uint64 func handleDisconnect(c websocket.Connection) { atomic.AddUint64(&count, 1) - // log.Printf("client [%s] disconnected!\n", c.ID()) + if verbose { + log.Printf("client [%s] disconnected!\n", c.ID()) + } } func handleErr(c websocket.Connection, err error) { diff --git a/cache/LICENSE b/cache/LICENSE index 6beda715..568450b0 100644 --- a/cache/LICENSE +++ b/cache/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Cache Authors. All rights reserved. +Copyright (c) 2017-2019 The Iris Cache Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/doc.go b/doc.go index 84b0b61f..b40c8aa3 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018 The Iris Authors. All rights reserved. +// Copyright (c) 2017-2019 The Iris Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/hero/LICENSE b/hero/LICENSE index a0b2d92f..970e41a7 100644 --- a/hero/LICENSE +++ b/hero/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Gerasimos Maropoulos. All rights reserved. +Copyright (c) 2018-2019 Gerasimos Maropoulos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/macro/LICENSE b/macro/LICENSE index c73df4ce..8f0865b2 100644 --- a/macro/LICENSE +++ b/macro/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Macro and Route path interpreter. All rights reserved. +Copyright (c) 2017-2019 The Iris Macro and Route path interpreter. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/mvc/LICENSE b/mvc/LICENSE index 469fb44d..fb9b3b8a 100644 --- a/mvc/LICENSE +++ b/mvc/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Gerasimos Maropoulos. All rights reserved. +Copyright (c) 2018-2019 Gerasimos Maropoulos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/sessions/LICENSE b/sessions/LICENSE index 6a39906b..ca7456f2 100644 --- a/sessions/LICENSE +++ b/sessions/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Sessions Authors. All rights reserved. +Copyright (c) 2017-2019 The Iris Sessions Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/typescript/LICENSE b/typescript/LICENSE index ec401599..50c59d41 100644 --- a/typescript/LICENSE +++ b/typescript/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Typescript Authors. All rights reserved. +Copyright (c) 2017-2019 The Iris Typescript Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/versioning/LICENSE b/versioning/LICENSE index 469fb44d..fb9b3b8a 100644 --- a/versioning/LICENSE +++ b/versioning/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Gerasimos Maropoulos. All rights reserved. +Copyright (c) 2018-2019 Gerasimos Maropoulos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/websocket/LICENSE b/websocket/LICENSE index 47aea48d..1ea6d9b5 100644 --- a/websocket/LICENSE +++ b/websocket/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2018 The Iris Websocket Authors. All rights reserved. +Copyright (c) 2017-2019 The Iris Websocket Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/websocket/config.go b/websocket/config.go index 145ea2a6..0636ae53 100644 --- a/websocket/config.go +++ b/websocket/config.go @@ -1,8 +1,8 @@ package websocket import ( - "math/rand" "net/http" + "strconv" "time" "github.com/kataras/iris/context" @@ -33,19 +33,18 @@ const ( ) var ( - // DefaultIDGenerator returns a random unique for a new connection. + // DefaultIDGenerator returns a random unique string for a new connection. // Used when config.IDGenerator is nil. DefaultIDGenerator = func(context.Context) string { id, err := uuid.NewV4() if err != nil { - return randomString(64) + return strconv.FormatInt(time.Now().Unix(), 10) } return id.String() } ) -// Config the websocket server configuration -// all of these are optional. +// Config contains the websocket server's configuration, optional. type Config struct { // IDGenerator used to create (and later on, set) // an ID for each incoming websocket connections (clients). @@ -158,37 +157,3 @@ func (c Config) Validate() Config { return c } - -const ( - letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = src.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - - return b -} - -// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm -func randomString(n int) string { - return string(random(n)) -} diff --git a/websocket/connection.go b/websocket/connection.go index 8987ec3e..2203e399 100644 --- a/websocket/connection.go +++ b/websocket/connection.go @@ -4,7 +4,6 @@ import ( "bytes" stdContext "context" "errors" - "io" "net" "strconv" "strings" @@ -93,50 +92,6 @@ func (r *ConnectionValues) Reset() { *r = (*r)[:0] } -// UnderlineConnection is the underline connection, nothing to think about, -// it's used internally mostly but can be used for extreme cases with other libraries. -type UnderlineConnection interface { - // SetWriteDeadline sets the write deadline on the underlying network - // connection. After a write has timed out, the websocket state is corrupt and - // all future writes will return an error. A zero value for t means writes will - // not time out. - SetWriteDeadline(t time.Time) error - // SetReadDeadline sets the read deadline on the underlying network connection. - // After a read has timed out, the websocket connection state is corrupt and - // all future reads will return an error. A zero value for t means reads will - // not time out. - SetReadDeadline(t time.Time) error - // SetReadLimit sets the maximum size for a message read from the peer. If a - // message exceeds the limit, the connection sends a close frame to the peer - // and returns ErrReadLimit to the application. - SetReadLimit(limit int64) - // SetPongHandler sets the handler for pong messages received from the peer. - // The appData argument to h is the PONG frame application data. The default - // pong handler does nothing. - SetPongHandler(h func(appData string) error) - // SetPingHandler sets the handler for ping messages received from the peer. - // The appData argument to h is the PING frame application data. The default - // ping handler sends a pong to the peer. - SetPingHandler(h func(appData string) error) - // WriteControl writes a control message with the given deadline. The allowed - // message types are CloseMessage, PingMessage and PongMessage. - WriteControl(messageType int, data []byte, deadline time.Time) error - // WriteMessage is a helper method for getting a writer using NextWriter, - // writing the message and closing the writer. - WriteMessage(messageType int, data []byte) error - // ReadMessage is a helper method for getting a reader using NextReader and - // reading from that reader to a buffer. - ReadMessage() (messageType int, p []byte, err error) - // NextWriter returns a writer for the next message to send. The writer's Close - // method flushes the complete message to the network. - // - // There can be at most one open writer on a connection. NextWriter closes the - // previous writer if the application has not already done so. - NextWriter(messageType int) (io.WriteCloser, error) - // Close closes the underlying network connection without sending or waiting for a close frame. - Close() error -} - // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // -------------------------------Connection implementation----------------------------- @@ -239,11 +194,12 @@ type ( // after the "On" events IF server's `Upgrade` is used, // otherise you don't have to call it because the `Handler()` does it automatically. Wait() + UnderlyingConn() *websocket.Conn } connection struct { err error - underline UnderlineConnection + underline *websocket.Conn config ConnectionConfig defaultMessageType int serializer *messageSerializer @@ -281,11 +237,11 @@ var _ Connection = &connection{} // WrapConnection wraps the underline websocket connection into a new iris websocket connection. // The caller should call the `connection#Wait` (which blocks) to enable its read and write functionality. -func WrapConnection(underlineConn UnderlineConnection, cfg ConnectionConfig) Connection { +func WrapConnection(underlineConn *websocket.Conn, cfg ConnectionConfig) Connection { return newConnection(underlineConn, cfg) } -func newConnection(underlineConn UnderlineConnection, cfg ConnectionConfig) *connection { +func newConnection(underlineConn *websocket.Conn, cfg ConnectionConfig) *connection { cfg = cfg.Validate() c := &connection{ underline: underlineConn, @@ -308,7 +264,7 @@ func newConnection(underlineConn UnderlineConnection, cfg ConnectionConfig) *con return c } -func newServerConnection(ctx context.Context, s *Server, underlineConn UnderlineConnection, id string) *connection { +func newServerConnection(ctx context.Context, s *Server, underlineConn *websocket.Conn, id string) *connection { c := newConnection(underlineConn, ConnectionConfig{ EvtMessagePrefix: s.config.EvtMessagePrefix, WriteTimeout: s.config.WriteTimeout, @@ -334,6 +290,10 @@ func newServerConnection(ctx context.Context, s *Server, underlineConn Underline return c } +func (c *connection) UnderlyingConn() *websocket.Conn { + return c.underline +} + // Err is not nil if the upgrader failed to upgrade http to websocket connection. func (c *connection) Err() error { return c.err diff --git a/websocket/server.go b/websocket/server.go index 6b9b6130..da747e3e 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -15,19 +15,6 @@ type ( // Receives one parameter which is the Connection ConnectionFunc func(Connection) - // websocketRoomPayload is used as payload from the connection to the Server - websocketRoomPayload struct { - roomName string - connectionID string - } - - // payloads, connection -> Server - websocketMessagePayload struct { - from string - to string - data []byte - } - // Server is the websocket Server's implementation. // // It listens for websocket clients (either from the javascript client-side or from any websocket implementation). @@ -44,9 +31,9 @@ type ( // Use a route to serve this file on a specific path, i.e // app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) }) ClientSource []byte - connections map[string]*connection // key = the Connection ID. - rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name - mu sync.RWMutex // for rooms and connections. + connections sync.Map // key = the Connection ID. + rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name + mu sync.RWMutex // for rooms. onConnectionListeners []ConnectionFunc //connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed. upgrader websocket.Upgrader @@ -63,7 +50,7 @@ func New(cfg Config) *Server { return &Server{ config: cfg, ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1), - connections: make(map[string]*connection), + connections: sync.Map{}, // ready-to-use, this is not necessary. rooms: make(map[string][]string), onConnectionListeners: make([]ConnectionFunc, 0), upgrader: websocket.Upgrader{ @@ -132,19 +119,24 @@ func (s *Server) Upgrade(ctx context.Context) Connection { } func (s *Server) addConnection(c *connection) { - s.mu.Lock() - s.connections[c.id] = c - s.mu.Unlock() + s.connections.Store(c.id, c) } func (s *Server) getConnection(connID string) (*connection, bool) { - c, ok := s.connections[connID] - return c, ok + if cValue, ok := s.connections.Load(connID); ok { + // this cast is not necessary, + // we know that we always save a connection, but for good or worse let it be here. + if conn, ok := cValue.(*connection); ok { + return conn, ok + } + } + + return nil, false } // wrapConnection wraps an underline connection to an iris websocket connection. // It does NOT starts its writer, reader and event mux, the caller is responsible for that. -func (s *Server) handleConnection(ctx context.Context, websocketConn UnderlineConnection) *connection { +func (s *Server) handleConnection(ctx context.Context, websocketConn *websocket.Conn) *connection { // use the config's id generator (or the default) to create a websocket client/connection id cid := s.config.IDGenerator(ctx) // create the new connection @@ -282,27 +274,31 @@ func (s *Server) leave(roomName string, connID string) (left bool) { return } -// GetTotalConnections returns the number of total connections +// GetTotalConnections returns the number of total connections. func (s *Server) GetTotalConnections() (n int) { - s.mu.RLock() - n = len(s.connections) - s.mu.RUnlock() + s.connections.Range(func(k, v interface{}) bool { + n++ + return true + }) - return + return n } -// GetConnections returns all connections -func (s *Server) GetConnections() []Connection { - s.mu.RLock() - conns := make([]Connection, len(s.connections)) - i := 0 - for _, c := range s.connections { - conns[i] = c - i++ - } +// GetConnections returns all connections. +func (s *Server) GetConnections() (conns []Connection) { + s.connections.Range(func(k, v interface{}) bool { + conn, ok := v.(*connection) + if !ok { + // if for some reason (should never happen), the value is not stored as *connection + // then stop the iteration and don't continue insertion of the result connections + // in order to avoid any issues while end-dev will try to iterate a nil entry. + return false + } + conns = append(conns, conn) + return true + }) - s.mu.RUnlock() - return conns + return } // GetConnection returns single connection @@ -317,21 +313,19 @@ func (s *Server) GetConnection(connID string) Connection { // GetConnectionsByRoom returns a list of Connection // which are joined to this room. -func (s *Server) GetConnectionsByRoom(roomName string) []Connection { - var conns []Connection - s.mu.RLock() +func (s *Server) GetConnectionsByRoom(roomName string) (conns []Connection) { if connIDs, found := s.rooms[roomName]; found { for _, connID := range connIDs { // existence check is not necessary here. - if conn, ok := s.connections[connID]; ok { - conns = append(conns, conn) + if cValue, ok := s.connections.Load(connID); ok { + if conn, ok := cValue.(*connection); ok { + conns = append(conns, conn) + } } } } - s.mu.RUnlock() - - return conns + return } // emitMessage is the main 'router' of the messages coming from the connection @@ -364,20 +358,32 @@ func (s *Server) emitMessage(from, to string, data []byte) { } } } else { - s.mu.RLock() // it suppose to send the message to all opened connections or to all except the sender. - for _, conn := range s.connections { - if to != All && to != conn.id { // if it's not suppose to send to all connections (including itself) - if to == Broadcast && from == conn.id { // if broadcast to other connections except this - // here we do the opossite of previous block, - // just skip this connection when it's suppose to send the message to all connections except the sender. - continue - } + s.connections.Range(func(k, v interface{}) bool { + connID, ok := k.(string) + if !ok { + // should never happen. + return true } - conn.writeDefault(data) - } - s.mu.RUnlock() + if to != All && to != connID { // if it's not suppose to send to all connections (including itself) + if to == Broadcast && from == connID { // if broadcast to other connections except this + // here we do the opossite of previous block, + // just skip this connection when it's suppose to send the message to all connections except the sender. + return true + } + + } + + // not necessary cast. + conn, ok := v.(*connection) + if ok { + // send to the client(s) when the top validators passed + conn.writeDefault(data) + } + + return ok + }) } } @@ -402,9 +408,7 @@ func (s *Server) Disconnect(connID string) (err error) { // close the underline connection and return its error, if any. err = conn.underline.Close() - s.mu.Lock() - delete(s.connections, conn.id) - s.mu.Unlock() + s.connections.Delete(connID) } return diff --git a/websocket2/client.js b/websocket2/client.js deleted file mode 100644 index ff323057..00000000 --- a/websocket2/client.js +++ /dev/null @@ -1,208 +0,0 @@ -var websocketStringMessageType = 0; -var websocketIntMessageType = 1; -var websocketBoolMessageType = 2; -var websocketJSONMessageType = 4; -var websocketMessagePrefix = "iris-websocket-message:"; -var websocketMessageSeparator = ";"; -var websocketMessagePrefixLen = websocketMessagePrefix.length; -var websocketMessageSeparatorLen = websocketMessageSeparator.length; -var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1; -var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1; -var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1; -var Ws = (function () { - function Ws(endpoint, protocols) { - var _this = this; - // events listeners - this.connectListeners = []; - this.disconnectListeners = []; - this.nativeMessageListeners = []; - this.messageListeners = {}; - if (!window["WebSocket"]) { - return; - } - if (endpoint.indexOf("ws") == -1) { - endpoint = "ws://" + endpoint; - } - if (protocols != null && protocols.length > 0) { - this.conn = new WebSocket(endpoint, protocols); - } - else { - this.conn = new WebSocket(endpoint); - } - this.conn.onopen = (function (evt) { - _this.fireConnect(); - _this.isReady = true; - return null; - }); - this.conn.onclose = (function (evt) { - _this.fireDisconnect(); - return null; - }); - this.conn.onmessage = (function (evt) { - _this.messageReceivedFromConn(evt); - }); - } - //utils - Ws.prototype.isNumber = function (obj) { - return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; - }; - Ws.prototype.isString = function (obj) { - return Object.prototype.toString.call(obj) == "[object String]"; - }; - Ws.prototype.isBoolean = function (obj) { - return typeof obj === 'boolean' || - (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); - }; - Ws.prototype.isJSON = function (obj) { - return typeof obj === 'object'; - }; - // - // messages - Ws.prototype._msg = function (event, websocketMessageType, dataMessage) { - return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; - }; - Ws.prototype.encodeMessage = function (event, data) { - var m = ""; - var t = 0; - if (this.isNumber(data)) { - t = websocketIntMessageType; - m = data.toString(); - } - else if (this.isBoolean(data)) { - t = websocketBoolMessageType; - m = data.toString(); - } - else if (this.isString(data)) { - t = websocketStringMessageType; - m = data.toString(); - } - else if (this.isJSON(data)) { - //propably json-object - t = websocketJSONMessageType; - m = JSON.stringify(data); - } - else if (data !== null && typeof(data) !== "undefined" ) { - // if it has a second parameter but it's not a type we know, then fire this: - console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); - } - return this._msg(event, t, m); - }; - Ws.prototype.decodeMessage = function (event, websocketMessage) { - //iris-websocket-message;user;4;themarshaledstringfromajsonstruct - var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; - if (websocketMessage.length < skipLen + 1) { - return null; - } - var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); - var theMessage = websocketMessage.substring(skipLen, websocketMessage.length); - if (websocketMessageType == websocketIntMessageType) { - return parseInt(theMessage); - } - else if (websocketMessageType == websocketBoolMessageType) { - return Boolean(theMessage); - } - else if (websocketMessageType == websocketStringMessageType) { - return theMessage; - } - else if (websocketMessageType == websocketJSONMessageType) { - return JSON.parse(theMessage); - } - else { - return null; // invalid - } - }; - Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) { - if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { - return ""; - } - var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); - var evt = s.substring(0, s.indexOf(websocketMessageSeparator)); - return evt; - }; - Ws.prototype.getCustomMessage = function (event, websocketMessage) { - var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); - var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); - return s; - }; - // - // Ws Events - // messageReceivedFromConn this is the func which decides - // if it's a native websocket message or a custom qws message - // if native message then calls the fireNativeMessage - // else calls the fireMessage - // - // remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all. - Ws.prototype.messageReceivedFromConn = function (evt) { - //check if qws message - var message = evt.data; - if (message.indexOf(websocketMessagePrefix) != -1) { - var event_1 = this.getWebsocketCustomEvent(message); - if (event_1 != "") { - // it's a custom message - this.fireMessage(event_1, this.getCustomMessage(event_1, message)); - return; - } - } - // it's a native websocket message - this.fireNativeMessage(message); - }; - Ws.prototype.OnConnect = function (fn) { - if (this.isReady) { - fn(); - } - this.connectListeners.push(fn); - }; - Ws.prototype.fireConnect = function () { - for (var i = 0; i < this.connectListeners.length; i++) { - this.connectListeners[i](); - } - }; - Ws.prototype.OnDisconnect = function (fn) { - this.disconnectListeners.push(fn); - }; - Ws.prototype.fireDisconnect = function () { - for (var i = 0; i < this.disconnectListeners.length; i++) { - this.disconnectListeners[i](); - } - }; - Ws.prototype.OnMessage = function (cb) { - this.nativeMessageListeners.push(cb); - }; - Ws.prototype.fireNativeMessage = function (websocketMessage) { - for (var i = 0; i < this.nativeMessageListeners.length; i++) { - this.nativeMessageListeners[i](websocketMessage); - } - }; - Ws.prototype.On = function (event, cb) { - if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { - this.messageListeners[event] = []; - } - this.messageListeners[event].push(cb); - }; - Ws.prototype.fireMessage = function (event, message) { - for (var key in this.messageListeners) { - if (this.messageListeners.hasOwnProperty(key)) { - if (key == event) { - for (var i = 0; i < this.messageListeners[key].length; i++) { - this.messageListeners[key][i](message); - } - } - } - } - }; - // - // Ws Actions - Ws.prototype.Disconnect = function () { - this.conn.close(); - }; - // EmitMessage sends a native websocket message - Ws.prototype.EmitMessage = function (websocketMessage) { - this.conn.send(websocketMessage); - }; - // Emit sends an iris-custom websocket message - Ws.prototype.Emit = function (event, data) { - var messageStr = this.encodeMessage(event, data); - this.EmitMessage(messageStr); - }; - return Ws; -}()); \ No newline at end of file diff --git a/websocket2/client.js.go b/websocket2/client.js.go deleted file mode 100644 index 2144411a..00000000 --- a/websocket2/client.js.go +++ /dev/null @@ -1,233 +0,0 @@ -package websocket - -import ( - "time" - - "github.com/kataras/iris/context" -) - -// ClientHandler is the handler which serves the javascript client-side -// library. It uses a small cache based on the iris/context.WriteWithExpiration. -func ClientHandler() context.Handler { - modNow := time.Now() - return func(ctx context.Context) { - ctx.ContentType("application/javascript") - if _, err := ctx.WriteWithExpiration(ClientSource, modNow); err != nil { - ctx.StatusCode(500) - ctx.StopExecution() - // ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error()) - } - } -} - -// ClientSource the client-side javascript raw source code. -var ClientSource = []byte(`var websocketStringMessageType = 0; -var websocketIntMessageType = 1; -var websocketBoolMessageType = 2; -var websocketJSONMessageType = 4; -var websocketMessagePrefix = "` + DefaultEvtMessageKey + `"; -var websocketMessageSeparator = ";"; -var websocketMessagePrefixLen = websocketMessagePrefix.length; -var websocketMessageSeparatorLen = websocketMessageSeparator.length; -var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1; -var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1; -var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1; -var Ws = (function () { - // - function Ws(endpoint, protocols) { - var _this = this; - // events listeners - this.connectListeners = []; - this.disconnectListeners = []; - this.nativeMessageListeners = []; - this.messageListeners = {}; - if (!window["WebSocket"]) { - return; - } - if (endpoint.indexOf("ws") == -1) { - endpoint = "ws://" + endpoint; - } - if (protocols != null && protocols.length > 0) { - this.conn = new WebSocket(endpoint, protocols); - } - else { - this.conn = new WebSocket(endpoint); - } - this.conn.onopen = (function (evt) { - _this.fireConnect(); - _this.isReady = true; - return null; - }); - this.conn.onclose = (function (evt) { - _this.fireDisconnect(); - return null; - }); - this.conn.onmessage = (function (evt) { - _this.messageReceivedFromConn(evt); - }); - } - //utils - Ws.prototype.isNumber = function (obj) { - return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; - }; - Ws.prototype.isString = function (obj) { - return Object.prototype.toString.call(obj) == "[object String]"; - }; - Ws.prototype.isBoolean = function (obj) { - return typeof obj === 'boolean' || - (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); - }; - Ws.prototype.isJSON = function (obj) { - return typeof obj === 'object'; - }; - // - // messages - Ws.prototype._msg = function (event, websocketMessageType, dataMessage) { - return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; - }; - Ws.prototype.encodeMessage = function (event, data) { - var m = ""; - var t = 0; - if (this.isNumber(data)) { - t = websocketIntMessageType; - m = data.toString(); - } - else if (this.isBoolean(data)) { - t = websocketBoolMessageType; - m = data.toString(); - } - else if (this.isString(data)) { - t = websocketStringMessageType; - m = data.toString(); - } - else if (this.isJSON(data)) { - //propably json-object - t = websocketJSONMessageType; - m = JSON.stringify(data); - } - else if (data !== null && typeof(data) !== "undefined" ) { - // if it has a second parameter but it's not a type we know, then fire this: - console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); - } - return this._msg(event, t, m); - }; - Ws.prototype.decodeMessage = function (event, websocketMessage) { - //iris-websocket-message;user;4;themarshaledstringfromajsonstruct - var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; - if (websocketMessage.length < skipLen + 1) { - return null; - } - var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); - var theMessage = websocketMessage.substring(skipLen, websocketMessage.length); - if (websocketMessageType == websocketIntMessageType) { - return parseInt(theMessage); - } - else if (websocketMessageType == websocketBoolMessageType) { - return Boolean(theMessage); - } - else if (websocketMessageType == websocketStringMessageType) { - return theMessage; - } - else if (websocketMessageType == websocketJSONMessageType) { - return JSON.parse(theMessage); - } - else { - return null; // invalid - } - }; - Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) { - if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { - return ""; - } - var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); - var evt = s.substring(0, s.indexOf(websocketMessageSeparator)); - return evt; - }; - Ws.prototype.getCustomMessage = function (event, websocketMessage) { - var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); - var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); - return s; - }; - // - // Ws Events - // messageReceivedFromConn this is the func which decides - // if it's a native websocket message or a custom qws message - // if native message then calls the fireNativeMessage - // else calls the fireMessage - // - // remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all. - Ws.prototype.messageReceivedFromConn = function (evt) { - //check if qws message - var message = evt.data; - if (message.indexOf(websocketMessagePrefix) != -1) { - var event_1 = this.getWebsocketCustomEvent(message); - if (event_1 != "") { - // it's a custom message - this.fireMessage(event_1, this.getCustomMessage(event_1, message)); - return; - } - } - // it's a native websocket message - this.fireNativeMessage(message); - }; - Ws.prototype.OnConnect = function (fn) { - if (this.isReady) { - fn(); - } - this.connectListeners.push(fn); - }; - Ws.prototype.fireConnect = function () { - for (var i = 0; i < this.connectListeners.length; i++) { - this.connectListeners[i](); - } - }; - Ws.prototype.OnDisconnect = function (fn) { - this.disconnectListeners.push(fn); - }; - Ws.prototype.fireDisconnect = function () { - for (var i = 0; i < this.disconnectListeners.length; i++) { - this.disconnectListeners[i](); - } - }; - Ws.prototype.OnMessage = function (cb) { - this.nativeMessageListeners.push(cb); - }; - Ws.prototype.fireNativeMessage = function (websocketMessage) { - for (var i = 0; i < this.nativeMessageListeners.length; i++) { - this.nativeMessageListeners[i](websocketMessage); - } - }; - Ws.prototype.On = function (event, cb) { - if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { - this.messageListeners[event] = []; - } - this.messageListeners[event].push(cb); - }; - Ws.prototype.fireMessage = function (event, message) { - for (var key in this.messageListeners) { - if (this.messageListeners.hasOwnProperty(key)) { - if (key == event) { - for (var i = 0; i < this.messageListeners[key].length; i++) { - this.messageListeners[key][i](message); - } - } - } - } - }; - // - // Ws Actions - Ws.prototype.Disconnect = function () { - this.conn.close(); - }; - // EmitMessage sends a native websocket message - Ws.prototype.EmitMessage = function (websocketMessage) { - this.conn.send(websocketMessage); - }; - // Emit sends an iris-custom websocket message - Ws.prototype.Emit = function (event, data) { - var messageStr = this.encodeMessage(event, data); - this.EmitMessage(messageStr); - }; - return Ws; -}()); -`) diff --git a/websocket2/client.min.js b/websocket2/client.min.js deleted file mode 100644 index 3d930f50..00000000 --- a/websocket2/client.min.js +++ /dev/null @@ -1 +0,0 @@ -var websocketStringMessageType=0,websocketIntMessageType=1,websocketBoolMessageType=2,websocketJSONMessageType=4,websocketMessagePrefix="iris-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 void; -type onWebsocketDisconnectFunc = () => void; -type onWebsocketNativeMessageFunc = (websocketMessage: string) => void; -type onMessageFunc = (message: any) => void; - -class Ws { - private conn: WebSocket; - private isReady: boolean; - - // events listeners - - private connectListeners: onConnectFunc[] = []; - private disconnectListeners: onWebsocketDisconnectFunc[] = []; - private nativeMessageListeners: onWebsocketNativeMessageFunc[] = []; - private messageListeners: { [event: string]: onMessageFunc[] } = {}; - - // - - constructor(endpoint: string, protocols?: string[]) { - if (!window["WebSocket"]) { - return; - } - - if (endpoint.indexOf("ws") == -1) { - endpoint = "ws://" + endpoint; - } - if (protocols != null && protocols.length > 0) { - this.conn = new WebSocket(endpoint, protocols); - } else { - this.conn = new WebSocket(endpoint); - } - - this.conn.onopen = ((evt: Event): any => { - this.fireConnect(); - this.isReady = true; - return null; - }); - - this.conn.onclose = ((evt: Event): any => { - this.fireDisconnect(); - return null; - }); - - this.conn.onmessage = ((evt: MessageEvent) => { - this.messageReceivedFromConn(evt); - }); - } - - //utils - - private isNumber(obj: any): boolean { - return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false; - } - - private isString(obj: any): boolean { - return Object.prototype.toString.call(obj) == "[object String]"; - } - - private isBoolean(obj: any): boolean { - return typeof obj === 'boolean' || - (typeof obj === 'object' && typeof obj.valueOf() === 'boolean'); - } - - private isJSON(obj: any): boolean { - return typeof obj === 'object'; - } - - // - - // messages - private _msg(event: string, websocketMessageType: number, dataMessage: string): string { - - return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage; - } - - private encodeMessage(event: string, data: any): string { - let m = ""; - let t = 0; - if (this.isNumber(data)) { - t = websocketIntMessageType; - m = data.toString(); - } else if (this.isBoolean(data)) { - t = websocketBoolMessageType; - m = data.toString(); - } else if (this.isString(data)) { - t = websocketStringMessageType; - m = data.toString(); - } else if (this.isJSON(data)) { - //propably json-object - t = websocketJSONMessageType; - m = JSON.stringify(data); - } else if (data !== null && typeof (data) !== "undefined") { - // if it has a second parameter but it's not a type we know, then fire this: - console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"); - } - - return this._msg(event, t, m); - } - - private decodeMessage(event: string, websocketMessage: string): T | any { - //iris-websocket-message;user;4;themarshaledstringfromajsonstruct - let skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2; - if (websocketMessage.length < skipLen + 1) { - return null; - } - let websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2)); - let theMessage = websocketMessage.substring(skipLen, websocketMessage.length); - if (websocketMessageType == websocketIntMessageType) { - return parseInt(theMessage); - } else if (websocketMessageType == websocketBoolMessageType) { - return Boolean(theMessage); - } else if (websocketMessageType == websocketStringMessageType) { - return theMessage; - } else if (websocketMessageType == websocketJSONMessageType) { - return JSON.parse(theMessage); - } else { - return null; // invalid - } - } - - private getWebsocketCustomEvent(websocketMessage: string): string { - if (websocketMessage.length < websocketMessagePrefixAndSepIdx) { - return ""; - } - let s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length); - let evt = s.substring(0, s.indexOf(websocketMessageSeparator)); - - return evt; - } - - private getCustomMessage(event: string, websocketMessage: string): string { - let eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator); - let s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length); - return s; - } - - // - - // Ws Events - - // messageReceivedFromConn this is the func which decides - // if it's a native websocket message or a custom qws message - // if native message then calls the fireNativeMessage - // else calls the fireMessage - // - // remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all. - private messageReceivedFromConn(evt: MessageEvent): void { - //check if qws message - let message = evt.data; - if (message.indexOf(websocketMessagePrefix) != -1) { - let event = this.getWebsocketCustomEvent(message); - if (event != "") { - // it's a custom message - this.fireMessage(event, this.getCustomMessage(event, message)); - return; - } - } - - // it's a native websocket message - this.fireNativeMessage(message); - } - - OnConnect(fn: onConnectFunc): void { - if (this.isReady) { - fn(); - } - this.connectListeners.push(fn); - } - - fireConnect(): void { - for (let i = 0; i < this.connectListeners.length; i++) { - this.connectListeners[i](); - } - } - - OnDisconnect(fn: onWebsocketDisconnectFunc): void { - this.disconnectListeners.push(fn); - } - - fireDisconnect(): void { - for (let i = 0; i < this.disconnectListeners.length; i++) { - this.disconnectListeners[i](); - } - } - - OnMessage(cb: onWebsocketNativeMessageFunc): void { - this.nativeMessageListeners.push(cb); - } - - fireNativeMessage(websocketMessage: string): void { - for (let i = 0; i < this.nativeMessageListeners.length; i++) { - this.nativeMessageListeners[i](websocketMessage); - } - } - - On(event: string, cb: onMessageFunc): void { - if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) { - this.messageListeners[event] = []; - } - this.messageListeners[event].push(cb); - } - - fireMessage(event: string, message: any): void { - for (let key in this.messageListeners) { - if (this.messageListeners.hasOwnProperty(key)) { - if (key == event) { - for (let i = 0; i < this.messageListeners[key].length; i++) { - this.messageListeners[key][i](message); - } - } - } - } - } - - - // - - // Ws Actions - - Disconnect(): void { - this.conn.close(); - } - - // EmitMessage sends a native websocket message - EmitMessage(websocketMessage: string): void { - this.conn.send(websocketMessage); - } - - // Emit sends an iris-custom websocket message - Emit(event: string, data: any): void { - let messageStr = this.encodeMessage(event, data); - this.EmitMessage(messageStr); - } - - // - -} - -// node-modules export {Ws}; diff --git a/websocket2/config.go b/websocket2/config.go deleted file mode 100644 index 6453230d..00000000 --- a/websocket2/config.go +++ /dev/null @@ -1,185 +0,0 @@ -package websocket - -import ( - "math/rand" - "net/http" - "time" - - "github.com/kataras/iris/context" - - "github.com/iris-contrib/go.uuid" -) - -const ( - // DefaultWebsocketWriteTimeout 0, no timeout - DefaultWebsocketWriteTimeout = 0 - // DefaultWebsocketReadTimeout 0, no timeout - DefaultWebsocketReadTimeout = 0 - // DefaultWebsocketPingPeriod is 0 but - // could be 10 * time.Second. - DefaultWebsocketPingPeriod = 0 - // DefaultWebsocketReadBufferSize 0 - DefaultWebsocketReadBufferSize = 0 - // DefaultWebsocketWriterBufferSize 0 - DefaultWebsocketWriterBufferSize = 0 - // DefaultEvtMessageKey is the default prefix of the underline websocket events - // that are being established under the hoods. - // - // Defaults to "iris-websocket-message:". - // Last character of the prefix should be ':'. - DefaultEvtMessageKey = "iris-websocket-message:" -) - -var ( - // DefaultIDGenerator returns a random unique for a new connection. - // Used when config.IDGenerator is nil. - DefaultIDGenerator = func(context.Context) string { - id, err := uuid.NewV4() - if err != nil { - return randomString(64) - } - return id.String() - } -) - -// Config the websocket server configuration -// all of these are optional. -type Config struct { - // IDGenerator used to create (and later on, set) - // an ID for each incoming websocket connections (clients). - // The request is an input parameter which you can use to generate the ID (from headers for example). - // If empty then the ID is generated by DefaultIDGenerator: randomString(64) - IDGenerator func(ctx context.Context) string - // EvtMessagePrefix is the prefix of the underline websocket events that are being established under the hoods. - // This prefix is visible only to the javascript side (code) and it has nothing to do - // with the message that the end-user receives. - // Do not change it unless it is absolutely necessary. - // - // If empty then defaults to []byte("iris-websocket-message:"). - EvtMessagePrefix []byte - // Error is the function that will be fired if any client couldn't upgrade the HTTP connection - // to a websocket connection, a handshake error. - Error func(w http.ResponseWriter, r *http.Request, status int, reason error) - // CheckOrigin a function that is called right before the handshake, - // if returns false then that client is not allowed to connect with the websocket server. - CheckOrigin func(r *http.Request) bool - // HandshakeTimeout specifies the duration for the handshake to complete. - HandshakeTimeout time.Duration - // WriteTimeout time allowed to write a message to the connection. - // 0 means no timeout. - // Default value is 0 - WriteTimeout time.Duration - // ReadTimeout time allowed to read a message from the connection. - // 0 means no timeout. - // Default value is 0 - ReadTimeout time.Duration - // PingPeriod send ping messages to the connection repeatedly after this period. - // The value should be close to the ReadTimeout to avoid issues. - // Default value is 0. - PingPeriod time.Duration - // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text - // compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication. - // Default value is false - BinaryMessages bool - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer - // size is zero, then buffers allocated by the HTTP server are used. The - // I/O buffer sizes do not limit the size of the messages that can be sent - // or received. - // - // Default value is 0. - ReadBufferSize, WriteBufferSize int - // EnableCompression specify if the server should attempt to negotiate per - // message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - // - // Defaults to false and it should be remain as it is, unless special requirements. - EnableCompression bool - - // Subprotocols specifies the server's supported protocols in order of - // preference. If this field is set, then the Upgrade method negotiates a - // subprotocol by selecting the first match in this list with a protocol - // requested by the client. - Subprotocols []string -} - -// Validate validates the configuration -func (c Config) Validate() Config { - // 0 means no timeout. - if c.WriteTimeout < 0 { - c.WriteTimeout = DefaultWebsocketWriteTimeout - } - - if c.ReadTimeout < 0 { - c.ReadTimeout = DefaultWebsocketReadTimeout - } - - if c.PingPeriod <= 0 { - c.PingPeriod = DefaultWebsocketPingPeriod - } - - if c.ReadBufferSize <= 0 { - c.ReadBufferSize = DefaultWebsocketReadBufferSize - } - - if c.WriteBufferSize <= 0 { - c.WriteBufferSize = DefaultWebsocketWriterBufferSize - } - - if c.Error == nil { - c.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { - //empty - } - } - - if c.CheckOrigin == nil { - c.CheckOrigin = func(r *http.Request) bool { - // allow all connections by default - return true - } - } - - if len(c.EvtMessagePrefix) == 0 { - c.EvtMessagePrefix = []byte(DefaultEvtMessageKey) - } - - if c.IDGenerator == nil { - c.IDGenerator = DefaultIDGenerator - } - - return c -} - -const ( - letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = src.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - - return b -} - -// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm -func randomString(n int) string { - return string(random(n)) -} diff --git a/websocket2/connection.go b/websocket2/connection.go deleted file mode 100644 index 2547cf5a..00000000 --- a/websocket2/connection.go +++ /dev/null @@ -1,936 +0,0 @@ -package websocket - -import ( - "bytes" - stdContext "context" - "errors" - "io" - "io/ioutil" - "net" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/kataras/iris/context" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -// Operation codes defined by specification. -// See https://tools.ietf.org/html/rfc6455#section-5.2 -const ( - // TextMessage denotes a text data message. The text message payload is - // interpreted as UTF-8 encoded text data. - TextMessage ws.OpCode = ws.OpText - // BinaryMessage denotes a binary data message. - BinaryMessage ws.OpCode = ws.OpBinary - // CloseMessage denotes a close control message. - CloseMessage ws.OpCode = ws.OpClose - - // PingMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PingMessage ws.OpCode = ws.OpPing - // PongMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PongMessage ws.OpCode = ws.OpPong -) - -type ( - connectionValue struct { - key []byte - value interface{} - } - // ConnectionValues is the temporary connection's memory store - ConnectionValues []connectionValue -) - -// Set sets a value based on the key -func (r *ConnectionValues) Set(key string, value interface{}) { - args := *r - n := len(args) - for i := 0; i < n; i++ { - kv := &args[i] - if string(kv.key) == key { - kv.value = value - return - } - } - - c := cap(args) - if c > n { - args = args[:n+1] - kv := &args[n] - kv.key = append(kv.key[:0], key...) - kv.value = value - *r = args - return - } - - kv := connectionValue{} - kv.key = append(kv.key[:0], key...) - kv.value = value - *r = append(args, kv) -} - -// Get returns a value based on its key -func (r *ConnectionValues) Get(key string) interface{} { - args := *r - n := len(args) - for i := 0; i < n; i++ { - kv := &args[i] - if string(kv.key) == key { - return kv.value - } - } - return nil -} - -// Reset clears the values -func (r *ConnectionValues) Reset() { - *r = (*r)[:0] -} - -// UnderlineConnection is the underline connection, nothing to think about, -// it's used internally mostly but can be used for extreme cases with other libraries. -type UnderlineConnection interface { - // SetWriteDeadline sets the write deadline on the underlying network - // connection. After a write has timed out, the websocket state is corrupt and - // all future writes will return an error. A zero value for t means writes will - // not time out. - SetWriteDeadline(t time.Time) error - // SetReadDeadline sets the read deadline on the underlying network connection. - // After a read has timed out, the websocket connection state is corrupt and - // all future reads will return an error. A zero value for t means reads will - // not time out. - SetReadDeadline(t time.Time) error - // SetReadLimit sets the maximum size for a message read from the peer. If a - // message exceeds the limit, the connection sends a close frame to the peer - // and returns ErrReadLimit to the application. - SetReadLimit(limit int64) - // SetPongHandler sets the handler for pong messages received from the peer. - // The appData argument to h is the PONG frame application data. The default - // pong handler does nothing. - SetPongHandler(h func(appData string) error) - // SetPingHandler sets the handler for ping messages received from the peer. - // The appData argument to h is the PING frame application data. The default - // ping handler sends a pong to the peer. - SetPingHandler(h func(appData string) error) - // WriteControl writes a control message with the given deadline. The allowed - // message types are CloseMessage, PingMessage and PongMessage. - WriteControl(messageType int, data []byte, deadline time.Time) error - // WriteMessage is a helper method for getting a writer using NextWriter, - // writing the message and closing the writer. - WriteMessage(messageType int, data []byte) error - // ReadMessage is a helper method for getting a reader using NextReader and - // reading from that reader to a buffer. - ReadMessage() (messageType int, p []byte, err error) - // NextWriter returns a writer for the next message to send. The writer's Close - // method flushes the complete message to the network. - // - // There can be at most one open writer on a connection. NextWriter closes the - // previous writer if the application has not already done so. - NextWriter(messageType int) (io.WriteCloser, error) - // Close closes the underlying network connection without sending or waiting for a close frame. - Close() error -} - -// ------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------- -// -------------------------------Connection implementation----------------------------- -// ------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------- - -type ( - // DisconnectFunc is the callback which is fired when a client/connection closed - DisconnectFunc func() - // LeaveRoomFunc is the callback which is fired when a client/connection leaves from any room. - // This is called automatically when client/connection disconnected - // (because websocket server automatically leaves from all joined rooms) - LeaveRoomFunc func(roomName string) - // ErrorFunc is the callback which fires whenever an error occurs - ErrorFunc (func(error)) - // NativeMessageFunc is the callback for native websocket messages, receives one []byte parameter which is the raw client's message - NativeMessageFunc func([]byte) - // MessageFunc is the second argument to the Emitter's Emit functions. - // A callback which should receives one parameter of type string, int, bool or any valid JSON/Go struct - MessageFunc interface{} - // PingFunc is the callback which fires each ping - PingFunc func() - // PongFunc is the callback which fires on pong message received - PongFunc func() - // Connection is the front-end API that you will use to communicate with the client side, - // it is the server-side connection. - Connection interface { - ClientConnection - // Err is not nil if the upgrader failed to upgrade http to websocket connection. - Err() error - // ID returns the connection's identifier - ID() string - // Server returns the websocket server instance - // which this connection is listening to. - // - // Its connection-relative operations are safe for use. - Server() *Server - // Context returns the (upgraded) context.Context of this connection - // avoid using it, you normally don't need it, - // websocket has everything you need to authenticate the user BUT if it's necessary - // then you use it to receive user information, for example: from headers - Context() context.Context - // To defines on what "room" (see Join) the server should send a message - // returns an Emmiter(`EmitMessage` & `Emit`) to send messages. - To(string) Emitter - // 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. - Join(string) - // IsJoined returns true when this connection is joined to the room, otherwise false. - // It Takes the room name as its input parameter. - IsJoined(roomName string) bool - // Leave removes this connection entry from a room - // Returns true if the connection has actually left from the particular room. - Leave(string) bool - // OnLeave registers a callback which fires when this connection left from any joined room. - // This callback is called automatically on Disconnected client, because websocket server automatically - // deletes the disconnected connection from any joined rooms. - // - // Note: the callback(s) called right before the server deletes the connection from the room - // so the connection theoretical can still send messages to its room right before it is being disconnected. - OnLeave(roomLeaveCb LeaveRoomFunc) - - // SetValue sets a key-value pair on the connection's mem store. - SetValue(key string, value interface{}) - // GetValue gets a value by its key from the connection's mem store. - GetValue(key string) interface{} - // GetValueArrString gets a value as []string by its key from the connection's mem store. - GetValueArrString(key string) []string - // GetValueString gets a value as string by its key from the connection's mem store. - GetValueString(key string) string - // GetValueInt gets a value as integer by its key from the connection's mem store. - GetValueInt(key string) int - } - - // ClientConnection is the client-side connection interface. Server shares some of its methods but the underline actions differs. - ClientConnection interface { - Emitter - // Write writes a raw websocket message with a specific type to the client - // used by ping messages and any CloseMessage types. - Write(websocketMessageType ws.OpCode, data []byte) error - // OnMessage registers a callback which fires when native websocket message received - OnMessage(NativeMessageFunc) - // On registers a callback to a particular event which is fired when a message to this event is received - On(string, MessageFunc) - // OnError registers a callback which fires when this connection occurs an error - OnError(ErrorFunc) - // OnPing registers a callback which fires on each ping - OnPing(PingFunc) - // OnPong registers a callback which fires on pong message received - OnPong(PongFunc) - // FireOnError can be used to send a custom error message to the connection - // - // It does nothing more than firing the OnError listeners. It doesn't send anything to the client. - FireOnError(err error) - // OnDisconnect registers a callback which is fired when this connection is closed by an error or manual - OnDisconnect(DisconnectFunc) - // Disconnect disconnects the client, close the underline websocket conn and removes it from the conn list - // returns the error, if any, from the underline connection - Disconnect() error - // Wait starts the pinger and the messages reader, - // it's named as "Wait" because it should be called LAST, - // after the "On" events IF server's `Upgrade` is used, - // otherise you don't have to call it because the `Handler()` does it automatically. - Wait() error - } - - connection struct { - err error - underline net.Conn - config ConnectionConfig - defaultMessageType ws.OpCode - serializer *messageSerializer - id string - - onErrorListeners []ErrorFunc - onPingListeners []PingFunc - onPongListeners []PongFunc - onNativeMessageListeners []NativeMessageFunc - onEventListeners map[string][]MessageFunc - onRoomLeaveListeners []LeaveRoomFunc - onDisconnectListeners []DisconnectFunc - disconnected uint32 - - started bool - // these were maden for performance only - self Emitter // pre-defined emitter than sends message to its self client - broadcast Emitter // pre-defined emitter that sends message to all except this - all Emitter // pre-defined emitter which sends message to all clients - - // access to the Context, use with caution, you can't use response writer as you imagine. - ctx context.Context - values ConnectionValues - server *Server - - writer *wsutil.Writer - - // #119 , websocket writers are not protected by locks inside the gorilla's websocket code - // so we must protect them otherwise we're getting concurrent connection error on multi writers in the same time. - writerMu sync.Mutex - // same exists for reader look here: https://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages - // but we only use one reader in one goroutine, so we are safe. - // readerMu sync.Mutex - } -) - -var _ Connection = &connection{} - -// WrapConnection wraps the underline websocket connection into a new iris websocket connection. -// The caller should call the `connection#Wait` (which blocks) to enable its read and write functionality. -func WrapConnection(conn net.Conn, cfg ConnectionConfig) Connection { - return newConnection(conn, cfg) -} - -func newConnection(conn net.Conn, cfg ConnectionConfig) *connection { - cfg = cfg.Validate() - c := &connection{ - underline: conn, - config: cfg, - serializer: newMessageSerializer(cfg.EvtMessagePrefix), - defaultMessageType: TextMessage, - onErrorListeners: make([]ErrorFunc, 0), - onPingListeners: make([]PingFunc, 0), - onPongListeners: make([]PongFunc, 0), - onNativeMessageListeners: make([]NativeMessageFunc, 0), - onEventListeners: make(map[string][]MessageFunc, 0), - onDisconnectListeners: make([]DisconnectFunc, 0), - disconnected: 0, - } - - if cfg.BinaryMessages { - c.defaultMessageType = BinaryMessage - } - - // c.writer = wsutil.NewWriter(conn, c.getState(), c.defaultMessageType) - - return c -} - -func newServerConnection(ctx context.Context, s *Server, conn net.Conn, id string) *connection { - c := newConnection(conn, ConnectionConfig{ - EvtMessagePrefix: s.config.EvtMessagePrefix, - WriteTimeout: s.config.WriteTimeout, - ReadTimeout: s.config.ReadTimeout, - PingPeriod: s.config.PingPeriod, - BinaryMessages: s.config.BinaryMessages, - ReadBufferSize: s.config.ReadBufferSize, - WriteBufferSize: s.config.WriteBufferSize, - EnableCompression: s.config.EnableCompression, - }) - - c.id = id - c.server = s - c.ctx = ctx - c.onRoomLeaveListeners = make([]LeaveRoomFunc, 0) - c.started = false - - c.self = newEmitter(c, c.id) - c.broadcast = newEmitter(c, Broadcast) - c.all = newEmitter(c, All) - - return c -} - -// Err is not nil if the upgrader failed to upgrade http to websocket connection. -func (c *connection) Err() error { - return c.err -} - -// IsClient returns true if that connection is from client. -func (c *connection) getState() ws.State { - if c.server != nil { - // server-side. - return ws.StateServerSide - } - - // else return client-side. - return ws.StateClientSide -} - -// Write writes a raw websocket message with a specific type to the client -// used by ping messages and any CloseMessage types. -func (c *connection) Write(websocketMessageType ws.OpCode, data []byte) (err error) { - // for any-case the app tries to write from different goroutines, - // we must protect them because they're reporting that as bug... - c.writerMu.Lock() - defer c.writerMu.Unlock() - if writeTimeout := c.config.WriteTimeout; writeTimeout > 0 { - // set the write deadline based on the configuration - c.underline.SetWriteDeadline(time.Now().Add(writeTimeout)) - } - - // 2. - // if websocketMessageType != c.defaultMessageType { - // err = wsutil.WriteMessage(c.underline, c.getState(), websocketMessageType, data) - // } else { - // _, err = c.writer.Write(data) - // c.writer.Flush() - // } - - err = wsutil.WriteMessage(c.underline, c.getState(), websocketMessageType, data) - - if err != nil { - // if failed then the connection is off, fire the disconnect - c.Disconnect() - } - return err -} - -// writeDefault is the same as write but the message type is the configured by c.messageType -// if BinaryMessages is enabled then it's raw []byte as you expected to work with protobufs -func (c *connection) writeDefault(data []byte) error { - return c.Write(c.defaultMessageType, data) -} - -func (c *connection) startPinger() { - if c.config.PingPeriod > 0 { - go func() { - for { - time.Sleep(c.config.PingPeriod) - if c == nil || atomic.LoadUint32(&c.disconnected) > 0 { - // verifies if already disconected. - return - } - - // try to ping the client, if failed then it disconnects. - err := c.Write(PingMessage, []byte{}) - if err != nil && !c.isErrClosed(err) { - c.FireOnError(err) - // must stop to exit the loop and exit from the routine. - return - } - - //fire all OnPing methods - c.fireOnPing() - - } - }() - } -} - -func (c *connection) fireOnPing() { - // fire the onPingListeners - for i := range c.onPingListeners { - c.onPingListeners[i]() - } -} - -func (c *connection) fireOnPong() { - // fire the onPongListeners - for i := range c.onPongListeners { - c.onPongListeners[i]() - } -} - -func (c *connection) isErrClosed(err error) bool { - if err == nil { - return false - } - - _, is := err.(wsutil.ClosedError) - if is { - return true - } - - if opErr, is := err.(*net.OpError); is { - if opErr.Err == io.EOF { - return false - } - - if atomic.LoadUint32(&c.disconnected) == 0 { - c.Disconnect() - } - - return true - } - - return err != io.EOF -} - -func (c *connection) startReader() error { - defer c.Disconnect() - - hasReadTimeout := c.config.ReadTimeout > 0 - - controlHandler := wsutil.ControlFrameHandler(c.underline, c.getState()) - rd := wsutil.Reader{ - Source: c.underline, - State: c.getState(), - CheckUTF8: false, - SkipHeaderCheck: false, - OnIntermediate: controlHandler, - } - - for { - if hasReadTimeout { - // set the read deadline based on the configuration - c.underline.SetReadDeadline(time.Now().Add(c.config.ReadTimeout)) - } - - hdr, err := rd.NextFrame() - if err != nil { - return err - } - if hdr.OpCode.IsControl() { - if err := controlHandler(hdr, &rd); err != nil { - return err - } - continue - } - - if hdr.OpCode&TextMessage == 0 && hdr.OpCode&BinaryMessage == 0 { - if err := rd.Discard(); err != nil { - return err - } - continue - } - - data, err := ioutil.ReadAll(&rd) - if err != nil { - return err - } - - c.messageReceived(data) - - // 4. - // var buf bytes.Buffer - // data, code, err := wsutil.ReadData(struct { - // io.Reader - // io.Writer - // }{c.underline, &buf}, c.getState()) - // if err != nil { - // if _, closed := err.(*net.OpError); closed && code == 0 { - // c.Disconnect() - // return - // } else if _, closed = err.(wsutil.ClosedError); closed { - // c.Disconnect() - // return - // // > 1200 conns but I don't know why yet: - // } else if err == ws.ErrProtocolOpCodeReserved || err == ws.ErrProtocolNonZeroRsv { - // c.Disconnect() - // return - // } else if err == io.EOF || err == io.ErrUnexpectedEOF { - // c.Disconnect() - // return - // } - - // c.FireOnError(err) - // } - - // c.messageReceived(data) - - // 2. - // header, err := reader.NextFrame() - // if err != nil { - // println("next frame err: " + err.Error()) - // return - // } - - // if header.OpCode == ws.OpClose { // io.EOF. - // return - // } - // payload := make([]byte, header.Length) - // _, err = io.ReadFull(reader, payload) - // if err != nil { - // return - // } - - // if header.Masked { - // ws.Cipher(payload, header.Mask, 0) - // } - - // c.messageReceived(payload) - - // data, code, err := wsutil.ReadData(c.underline, c.getState()) - // // if code == CloseMessage || c.isErrClosed(err) { - // // c.Disconnect() - // // return - // // } - - // if err != nil { - // if _, closed := err.(*net.OpError); closed && code == 0 { - // c.Disconnect() - // return - // } else if _, closed = err.(wsutil.ClosedError); closed { - // c.Disconnect() - // return - // // > 1200 conns but I don't know why yet: - // } else if err == ws.ErrProtocolOpCodeReserved || err == ws.ErrProtocolNonZeroRsv { - // c.Disconnect() - // return - // } else if err == io.EOF || err == io.ErrUnexpectedEOF { - // c.Disconnect() - // return - // } - - // c.FireOnError(err) - // } - - // c.messageReceived(data) - } -} - -// messageReceived checks the incoming message and fire the nativeMessage listeners or the event listeners (ws custom message) -func (c *connection) messageReceived(data []byte) { - - if bytes.HasPrefix(data, c.config.EvtMessagePrefix) { - //it's a custom ws message - receivedEvt := c.serializer.getWebsocketCustomEvent(data) - listeners, ok := c.onEventListeners[string(receivedEvt)] - if !ok || len(listeners) == 0 { - return // if not listeners for this event exit from here - } - - customMessage, err := c.serializer.deserialize(receivedEvt, data) - if customMessage == nil || err != nil { - return - } - - for i := range listeners { - if fn, ok := listeners[i].(func()); ok { // its a simple func(){} callback - fn() - } else if fnString, ok := listeners[i].(func(string)); ok { - - if msgString, is := customMessage.(string); is { - fnString(msgString) - } else if msgInt, is := customMessage.(int); is { - // here if server side waiting for string but client side sent an int, just convert this int to a string - fnString(strconv.Itoa(msgInt)) - } - - } else if fnInt, ok := listeners[i].(func(int)); ok { - fnInt(customMessage.(int)) - } else if fnBool, ok := listeners[i].(func(bool)); ok { - fnBool(customMessage.(bool)) - } else if fnBytes, ok := listeners[i].(func([]byte)); ok { - fnBytes(customMessage.([]byte)) - } else { - listeners[i].(func(interface{}))(customMessage) - } - - } - } else { - // it's native websocket message - for i := range c.onNativeMessageListeners { - c.onNativeMessageListeners[i](data) - } - } - -} - -func (c *connection) ID() string { - return c.id -} - -func (c *connection) Server() *Server { - return c.server -} - -func (c *connection) Context() context.Context { - return c.ctx -} - -func (c *connection) Values() ConnectionValues { - return c.values -} - -func (c *connection) fireDisconnect() { - for i := range c.onDisconnectListeners { - c.onDisconnectListeners[i]() - } -} - -func (c *connection) OnDisconnect(cb DisconnectFunc) { - c.onDisconnectListeners = append(c.onDisconnectListeners, cb) -} - -func (c *connection) OnError(cb ErrorFunc) { - c.onErrorListeners = append(c.onErrorListeners, cb) -} - -func (c *connection) OnPing(cb PingFunc) { - c.onPingListeners = append(c.onPingListeners, cb) -} - -func (c *connection) OnPong(cb PongFunc) { - c.onPongListeners = append(c.onPongListeners, cb) -} - -func (c *connection) FireOnError(err error) { - for _, cb := range c.onErrorListeners { - cb(err) - } -} - -func (c *connection) To(to string) Emitter { - if to == Broadcast { // if send to all except me, then return the pre-defined emitter, and so on - return c.broadcast - } else if to == All { - return c.all - } else if to == c.id { - return c.self - } - - // is an emitter to another client/connection - return newEmitter(c, to) -} - -func (c *connection) EmitMessage(nativeMessage []byte) error { - if c.server != nil { - return c.self.EmitMessage(nativeMessage) - } - return c.writeDefault(nativeMessage) -} - -func (c *connection) Emit(event string, message interface{}) error { - if c.server != nil { - return c.self.Emit(event, message) - } - - b, err := c.serializer.serialize(event, message) - if err != nil { - return err - } - - return c.EmitMessage(b) -} - -func (c *connection) OnMessage(cb NativeMessageFunc) { - c.onNativeMessageListeners = append(c.onNativeMessageListeners, cb) -} - -func (c *connection) On(event string, cb MessageFunc) { - if c.onEventListeners[event] == nil { - c.onEventListeners[event] = make([]MessageFunc, 0) - } - - c.onEventListeners[event] = append(c.onEventListeners[event], cb) -} - -func (c *connection) Join(roomName string) { - c.server.Join(roomName, c.id) -} - -func (c *connection) IsJoined(roomName string) bool { - return c.server.IsJoined(roomName, c.id) -} - -func (c *connection) Leave(roomName string) bool { - return c.server.Leave(roomName, c.id) -} - -func (c *connection) OnLeave(roomLeaveCb LeaveRoomFunc) { - c.onRoomLeaveListeners = append(c.onRoomLeaveListeners, roomLeaveCb) - // note: the callbacks are called from the server on the '.leave' and '.LeaveAll' funcs. -} - -func (c *connection) fireOnLeave(roomName string) { - // check if connection is already closed - if c == nil { - return - } - // fire the onRoomLeaveListeners - for i := range c.onRoomLeaveListeners { - c.onRoomLeaveListeners[i](roomName) - } -} - -// Wait starts the pinger and the messages reader, -// it's named as "Wait" because it should be called LAST, -// after the "On" events IF server's `Upgrade` is used, -// otherise you don't have to call it because the `Handler()` does it automatically. -func (c *connection) Wait() error { - if c.started { - return nil - } - c.started = true - // start the ping - c.startPinger() - - // start the messages reader - return c.startReader() -} - -// ErrAlreadyDisconnected can be reported on the `Connection#Disconnect` function whenever the caller tries to close the -// connection when it is already closed by the client or the caller previously. -var ErrAlreadyDisconnected = errors.New("already disconnected") - -func (c *connection) Disconnect() error { - if c == nil || !atomic.CompareAndSwapUint32(&c.disconnected, 0, 1) { - return ErrAlreadyDisconnected - } - - if c.server != nil { - return c.server.Disconnect(c.ID()) - } - - err := c.Write(CloseMessage, nil) - - if err == nil { - c.fireDisconnect() - } - - c.underline.Close() - - return err -} - -// mem per-conn store - -func (c *connection) SetValue(key string, value interface{}) { - c.values.Set(key, value) -} - -func (c *connection) GetValue(key string) interface{} { - return c.values.Get(key) -} - -func (c *connection) GetValueArrString(key string) []string { - if v := c.values.Get(key); v != nil { - if arrString, ok := v.([]string); ok { - return arrString - } - } - return nil -} - -func (c *connection) GetValueString(key string) string { - if v := c.values.Get(key); v != nil { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - -func (c *connection) GetValueInt(key string) int { - if v := c.values.Get(key); v != nil { - if i, ok := v.(int); ok { - return i - } else if s, ok := v.(string); ok { - if iv, err := strconv.Atoi(s); err == nil { - return iv - } - } - } - return 0 -} - -// ConnectionConfig is the base configuration for both server and client connections. -// Clients must use `ConnectionConfig` in order to `Dial`, server's connection configuration is set by the `Config` structure. -type ConnectionConfig struct { - // EvtMessagePrefix is the prefix of the underline websocket events that are being established under the hoods. - // This prefix is visible only to the javascript side (code) and it has nothing to do - // with the message that the end-user receives. - // Do not change it unless it is absolutely necessary. - // - // If empty then defaults to []byte("iris-websocket-message:"). - // Should match with the server's EvtMessagePrefix. - EvtMessagePrefix []byte - // WriteTimeout time allowed to write a message to the connection. - // 0 means no timeout. - // Default value is 0 - WriteTimeout time.Duration - // ReadTimeout time allowed to read a message from the connection. - // 0 means no timeout. - // Default value is 0 - ReadTimeout time.Duration - // PingPeriod send ping messages to the connection repeatedly after this period. - // The value should be close to the ReadTimeout to avoid issues. - // Default value is 0 - PingPeriod time.Duration - // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text - // compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication. - // Default value is false - BinaryMessages bool - // ReadBufferSize is the buffer size for the connection reader. - // Default value is 4096 - ReadBufferSize int - // WriteBufferSize is the buffer size for the connection writer. - // Default value is 4096 - WriteBufferSize int - // EnableCompression specify if the server should attempt to negotiate per - // message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - // - // Defaults to false and it should be remain as it is, unless special requirements. - EnableCompression bool -} - -// Validate validates the connection configuration. -func (c ConnectionConfig) Validate() ConnectionConfig { - if len(c.EvtMessagePrefix) == 0 { - c.EvtMessagePrefix = []byte(DefaultEvtMessageKey) - } - - // 0 means no timeout. - if c.WriteTimeout < 0 { - c.WriteTimeout = DefaultWebsocketWriteTimeout - } - - if c.ReadTimeout < 0 { - c.ReadTimeout = DefaultWebsocketReadTimeout - } - - if c.PingPeriod <= 0 { - c.PingPeriod = DefaultWebsocketPingPeriod - } - - if c.ReadBufferSize <= 0 { - c.ReadBufferSize = DefaultWebsocketReadBufferSize - } - - if c.WriteBufferSize <= 0 { - c.WriteBufferSize = DefaultWebsocketWriterBufferSize - } - - return c -} - -// ErrBadHandshake is returned when the server response to opening handshake is -// invalid. -var ErrBadHandshake = ws.ErrHandshakeBadConnection - -// Dial creates a new client connection. -// -// The context will be used in the request and in the Dialer. -// -// If the WebSocket handshake fails, `ErrHandshakeBadConnection` is returned. -// -// The "url" input parameter is the url to connect to the server, it should be -// the ws:// (or wss:// if secure) + the host + the endpoint of the -// open socket of the server, i.e ws://localhost:8080/my_websocket_endpoint. -// -// Custom dialers can be used by wrapping the iris websocket connection via `websocket.WrapConnection`. -func Dial(ctx stdContext.Context, url string, cfg ConnectionConfig) (ClientConnection, error) { - return dial(ctx, url, cfg) -} - -func dial(ctx stdContext.Context, url string, cfg ConnectionConfig) (ClientConnection, error) { - if ctx == nil { - ctx = stdContext.Background() - } - - if !strings.HasPrefix(url, "ws://") && !strings.HasPrefix(url, "wss://") { - url = "ws://" + url - } - - conn, _, _, err := ws.DefaultDialer.Dial(ctx, url) - if err != nil { - return nil, err - } - - clientConn := WrapConnection(conn, cfg) - go clientConn.Wait() - - return clientConn, nil -} diff --git a/websocket2/emitter.go b/websocket2/emitter.go deleted file mode 100644 index 84d1fa48..00000000 --- a/websocket2/emitter.go +++ /dev/null @@ -1,43 +0,0 @@ -package websocket - -const ( - // All is the string which the Emitter use to send a message to all. - All = "" - // Broadcast is the string which the Emitter use to send a message to all except this connection. - Broadcast = ";to;all;except;me;" -) - -type ( - // Emitter is the message/or/event manager - Emitter interface { - // EmitMessage sends a native websocket message - EmitMessage([]byte) error - // Emit sends a message on a particular event - Emit(string, interface{}) error - } - - emitter struct { - conn *connection - to string - } -) - -var _ Emitter = &emitter{} - -func newEmitter(c *connection, to string) *emitter { - return &emitter{conn: c, to: to} -} - -func (e *emitter) EmitMessage(nativeMessage []byte) error { - e.conn.server.emitMessage(e.conn.id, e.to, nativeMessage) - return nil -} - -func (e *emitter) Emit(event string, data interface{}) error { - message, err := e.conn.serializer.serialize(event, data) - if err != nil { - return err - } - e.EmitMessage(message) - return nil -} diff --git a/websocket2/message.go b/websocket2/message.go deleted file mode 100644 index 6b27fbee..00000000 --- a/websocket2/message.go +++ /dev/null @@ -1,182 +0,0 @@ -package websocket - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "strconv" - - "github.com/kataras/iris/core/errors" - "github.com/valyala/bytebufferpool" -) - -type ( - messageType uint8 -) - -func (m messageType) String() string { - return strconv.Itoa(int(m)) -} - -func (m messageType) Name() string { - switch m { - case messageTypeString: - return "string" - case messageTypeInt: - return "int" - case messageTypeBool: - return "bool" - case messageTypeBytes: - return "[]byte" - case messageTypeJSON: - return "json" - default: - return "Invalid(" + m.String() + ")" - } -} - -// The same values are exists on client side too. -const ( - messageTypeString messageType = iota - messageTypeInt - messageTypeBool - messageTypeBytes - messageTypeJSON -) - -const ( - messageSeparator = ";" -) - -var messageSeparatorByte = messageSeparator[0] - -type messageSerializer struct { - prefix []byte - - prefixLen int - separatorLen int - prefixAndSepIdx int - prefixIdx int - separatorIdx int - - buf *bytebufferpool.Pool -} - -func newMessageSerializer(messagePrefix []byte) *messageSerializer { - return &messageSerializer{ - prefix: messagePrefix, - prefixLen: len(messagePrefix), - separatorLen: len(messageSeparator), - prefixAndSepIdx: len(messagePrefix) + len(messageSeparator) - 1, - prefixIdx: len(messagePrefix) - 1, - separatorIdx: len(messageSeparator) - 1, - - buf: new(bytebufferpool.Pool), - } -} - -var ( - boolTrueB = []byte("true") - boolFalseB = []byte("false") -) - -// websocketMessageSerialize serializes a custom websocket message from websocketServer to be delivered to the client -// returns the string form of the message -// Supported data types are: string, int, bool, bytes and JSON. -func (ms *messageSerializer) serialize(event string, data interface{}) ([]byte, error) { - b := ms.buf.Get() - b.Write(ms.prefix) - b.WriteString(event) - b.WriteByte(messageSeparatorByte) - - switch v := data.(type) { - case string: - b.WriteString(messageTypeString.String()) - b.WriteByte(messageSeparatorByte) - b.WriteString(v) - case int: - b.WriteString(messageTypeInt.String()) - b.WriteByte(messageSeparatorByte) - binary.Write(b, binary.LittleEndian, v) - case bool: - b.WriteString(messageTypeBool.String()) - b.WriteByte(messageSeparatorByte) - if v { - b.Write(boolTrueB) - } else { - b.Write(boolFalseB) - } - case []byte: - b.WriteString(messageTypeBytes.String()) - b.WriteByte(messageSeparatorByte) - b.Write(v) - default: - //we suppose is json - res, err := json.Marshal(data) - if err != nil { - ms.buf.Put(b) - return nil, err - } - b.WriteString(messageTypeJSON.String()) - b.WriteByte(messageSeparatorByte) - b.Write(res) - } - - message := b.Bytes() - ms.buf.Put(b) - - return message, nil -} - -var errInvalidTypeMessage = errors.New("Type %s is invalid for message: %s") - -// deserialize deserializes a custom websocket message from the client -// ex: iris-websocket-message;chat;4;themarshaledstringfromajsonstruct will return 'hello' as string -// Supported data types are: string, int, bool, bytes and JSON. -func (ms *messageSerializer) deserialize(event []byte, websocketMessage []byte) (interface{}, error) { - dataStartIdx := ms.prefixAndSepIdx + len(event) + 3 - if len(websocketMessage) <= dataStartIdx { - return nil, errors.New("websocket invalid message: " + string(websocketMessage)) - } - - typ, err := strconv.Atoi(string(websocketMessage[ms.prefixAndSepIdx+len(event)+1 : ms.prefixAndSepIdx+len(event)+2])) // in order to iris-websocket-message;user;-> 4 - if err != nil { - return nil, err - } - - data := websocketMessage[dataStartIdx:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct - - switch messageType(typ) { - case messageTypeString: - return string(data), nil - case messageTypeInt: - msg, err := strconv.Atoi(string(data)) - if err != nil { - return nil, err - } - return msg, nil - case messageTypeBool: - if bytes.Equal(data, boolTrueB) { - return true, nil - } - return false, nil - case messageTypeBytes: - return data, nil - case messageTypeJSON: - var msg interface{} - err := json.Unmarshal(data, &msg) - return msg, err - default: - return nil, errInvalidTypeMessage.Format(messageType(typ).Name(), websocketMessage) - } -} - -// getWebsocketCustomEvent return empty string when the websocketMessage is native message -func (ms *messageSerializer) getWebsocketCustomEvent(websocketMessage []byte) []byte { - if len(websocketMessage) < ms.prefixAndSepIdx { - return nil - } - s := websocketMessage[ms.prefixAndSepIdx:] - evt := s[:bytes.IndexByte(s, messageSeparatorByte)] - return evt -} diff --git a/websocket2/server.go b/websocket2/server.go deleted file mode 100644 index 8adfd32e..00000000 --- a/websocket2/server.go +++ /dev/null @@ -1,460 +0,0 @@ -package websocket - -import ( - "bytes" - "net" - "sync" - "sync/atomic" - - "github.com/kataras/iris/context" - - "github.com/gobwas/ws" -) - -type ( - // ConnectionFunc is the callback which fires when a client/connection is connected to the Server. - // Receives one parameter which is the Connection - ConnectionFunc func(Connection) - - // websocketRoomPayload is used as payload from the connection to the Server - websocketRoomPayload struct { - roomName string - connectionID string - } - - // payloads, connection -> Server - websocketMessagePayload struct { - from string - to string - data []byte - } - - // Server is the websocket Server's implementation. - // - // It listens for websocket clients (either from the javascript client-side or from any websocket implementation). - // See `OnConnection` , to register a single event which will handle all incoming connections and - // the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint. - // - // To serve the built'n javascript client-side library look the `websocket.ClientHandler`. - Server struct { - config Config - // ClientSource contains the javascript side code - // for the iris websocket communication - // based on the configuration's `EvtMessagePrefix`. - // - // Use a route to serve this file on a specific path, i.e - // app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) }) - ClientSource []byte - connections sync.Map // key = the Connection ID. // key = the Connection ID. - rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name - mu sync.RWMutex // for rooms. - onConnectionListeners []ConnectionFunc - //connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed. - httpUpgrader ws.HTTPUpgrader - tcpUpgrader ws.Upgrader - } -) - -// New returns a new websocket Server based on a configuration. -// See `OnConnection` , to register a single event which will handle all incoming connections and -// the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint. -// -// To serve the built'n javascript client-side library look the `websocket.ClientHandler`. -func New(cfg Config) *Server { - cfg = cfg.Validate() - return &Server{ - config: cfg, - ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1), - connections: sync.Map{}, // ready-to-use, this is not necessary. - rooms: make(map[string][]string), - onConnectionListeners: make([]ConnectionFunc, 0), - httpUpgrader: ws.DefaultHTTPUpgrader, // ws.DefaultUpgrader, - tcpUpgrader: ws.DefaultUpgrader, - } -} - -// Handler builds the handler based on the configuration and returns it. -// It should be called once per Server, its result should be passed -// as a middleware to an iris route which will be responsible -// to register the websocket's endpoint. -// -// Endpoint is the path which the websocket Server will listen for clients/connections. -// -// To serve the built'n javascript client-side library look the `websocket.ClientHandler`. -func (s *Server) Handler() context.Handler { - return func(ctx context.Context) { - c := s.Upgrade(ctx) - if c.Err() != nil { - return - } - - // NOTE TO ME: fire these first BEFORE startReader and startPinger - // in order to set the events and any messages to send - // the startPinger will send the OK to the client and only - // then the client is able to send and receive from Server - // when all things are ready and only then. DO NOT change this order. - - // fire the on connection event callbacks, if any - for i := range s.onConnectionListeners { - s.onConnectionListeners[i](c) - } - - // start the ping and the messages reader - c.Wait() - } -} - -// Upgrade upgrades the HTTP Server connection to the WebSocket protocol. -// -// The responseHeader is included in the response to the client's upgrade -// request. Use the responseHeader to specify cookies (Set-Cookie) and the -// application negotiated subprotocol (Sec--Protocol). -// -// If the upgrade fails, then Upgrade replies to the client with an HTTP error -// response and the return `Connection.Err()` is filled with that error. -// -// For a more high-level function use the `Handler()` and `OnConnecton` events. -// This one does not starts the connection's writer and reader, so after your `On/OnMessage` events registration -// the caller has to call the `Connection#Wait` function, otherwise the connection will be not handled. -func (s *Server) Upgrade(ctx context.Context) Connection { - conn, _, _, err := s.httpUpgrader.Upgrade(ctx.Request(), ctx.ResponseWriter()) - if err != nil { - ctx.Application().Logger().Warnf("websocket error: %v\n", err) - ctx.StatusCode(503) // Status Service Unavailable - return &connection{err: err} - } - - return s.handleConnection(ctx, conn) -} - -func (s *Server) ZeroUpgrade(conn net.Conn) Connection { - _, err := s.tcpUpgrader.Upgrade(conn) - if err != nil { - return &connection{err: err} - } - - return s.handleConnection(nil, conn) -} - -func (s *Server) HandleConn(conn net.Conn) error { - c := s.ZeroUpgrade(conn) - if c.Err() != nil { - return c.Err() - } - - // NOTE TO ME: fire these first BEFORE startReader and startPinger - // in order to set the events and any messages to send - // the startPinger will send the OK to the client and only - // then the client is able to send and receive from Server - // when all things are ready and only then. DO NOT change this order. - - // fire the on connection event callbacks, if any - for i := range s.onConnectionListeners { - s.onConnectionListeners[i](c) - } - - // start the ping and the messages reader - c.Wait() - return nil -} - -func (s *Server) addConnection(c *connection) { - s.connections.Store(c.id, c) -} - -func (s *Server) getConnection(connID string) (*connection, bool) { - if cValue, ok := s.connections.Load(connID); ok { - // this cast is not necessary, - // we know that we always save a connection, but for good or worse let it be here. - if conn, ok := cValue.(*connection); ok { - return conn, ok - } - } - - return nil, false -} - -// wrapConnection wraps an underline connection to an iris websocket connection. -// It does NOT starts its writer, reader and event mux, the caller is responsible for that. -func (s *Server) handleConnection(ctx context.Context, conn net.Conn) *connection { - // use the config's id generator (or the default) to create a websocket client/connection id - cid := s.config.IDGenerator(ctx) - // create the new connection - c := newServerConnection(ctx, s, conn, cid) - // add the connection to the Server's list - s.addConnection(c) - - // join to itself - s.Join(c.id, c.id) - - return c -} - -/* Notes: - We use the id as the signature of the connection because with the custom IDGenerator - the developer can share this ID with a database field, so we want to give the oportunnity to handle - his/her websocket connections without even use the connection itself. - - Another question may be: - Q: Why you use Server as the main actioner for all of the connection actions? - For example the Server.Disconnect(connID) manages the connection internal fields, is this code-style correct? - A: It's the correct code-style for these type of applications and libraries, Server manages all, the connnection's functions - should just do some internal checks (if needed) and push the action to its parent, which is the Server, the Server is able to - 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 - here, in the Server, and let the connection with some exported functions whose exists for the per-connection action user's code-style. - - Ok my english are s** I can feel it, but these comments are mostly for me. -*/ - -/* - connection actions, same as the connection's method, - but these methods accept the connection ID, - which is useful when the developer maps - this id with a database field (using config.IDGenerator). -*/ - -// OnConnection is the main event you, as developer, will work with each of the websocket connections. -func (s *Server) OnConnection(cb ConnectionFunc) { - s.onConnectionListeners = append(s.onConnectionListeners, cb) -} - -// IsConnected returns true if the connection with that ID is connected to the Server -// useful when you have defined a custom connection id generator (based on a database) -// and you want to check if that connection is already connected (on multiple tabs) -func (s *Server) IsConnected(connID string) bool { - _, found := s.getConnection(connID) - return found -} - -// Join joins a websocket client to a room, -// first parameter is the room name and the second the connection.ID() -// -// You can use connection.Join("room name") instead. -func (s *Server) Join(roomName string, connID string) { - s.mu.Lock() - s.join(roomName, connID) - s.mu.Unlock() -} - -// join used internally, no locks used. -func (s *Server) join(roomName string, connID string) { - if s.rooms[roomName] == nil { - s.rooms[roomName] = make([]string, 0) - } - s.rooms[roomName] = append(s.rooms[roomName], connID) -} - -// IsJoined reports if a specific room has a specific connection into its values. -// First parameter is the room name, second is the connection's id. -// -// It returns true when the "connID" is joined to the "roomName". -func (s *Server) IsJoined(roomName string, connID string) bool { - s.mu.RLock() - room := s.rooms[roomName] - s.mu.RUnlock() - - if room == nil { - return false - } - - for _, connid := range room { - if connID == connid { - return true - } - } - - return false -} - -// LeaveAll kicks out a connection from ALL of its joined rooms -func (s *Server) LeaveAll(connID string) { - s.mu.Lock() - for name := range s.rooms { - s.leave(name, connID) - } - s.mu.Unlock() -} - -// Leave leaves a websocket client from a room, -// first parameter is the room name and the second the connection.ID() -// -// You can use connection.Leave("room name") instead. -// Returns true if the connection has actually left from the particular room. -func (s *Server) Leave(roomName string, connID string) bool { - s.mu.Lock() - left := s.leave(roomName, connID) - s.mu.Unlock() - return left -} - -// leave used internally, no locks used. -func (s *Server) leave(roomName string, connID string) (left bool) { - ///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 - // I will think about it on the next revision, so far we use the locks only for rooms so we are ok... - if s.rooms[roomName] != nil { - for i := range s.rooms[roomName] { - if s.rooms[roomName][i] == connID { - s.rooms[roomName] = append(s.rooms[roomName][:i], s.rooms[roomName][i+1:]...) - left = true - break - } - } - if len(s.rooms[roomName]) == 0 { // if room is empty then delete it - delete(s.rooms, roomName) - } - } - - if left { - // fire the on room leave connection's listeners, - // the existence check is not necessary here. - if c, ok := s.getConnection(connID); ok { - c.fireOnLeave(roomName) - } - } - return -} - -// GetTotalConnections returns the number of total connections -func (s *Server) GetTotalConnections() (n int) { - s.connections.Range(func(k, v interface{}) bool { - n++ - return true - }) - - return -} - -// GetConnections returns all connections -func (s *Server) GetConnections() (conns []Connection) { - s.connections.Range(func(k, v interface{}) bool { - conn, ok := v.(*connection) - if !ok { - // if for some reason (should never happen), the value is not stored as *connection - // then stop the iteration and don't continue insertion of the result connections - // in order to avoid any issues while end-dev will try to iterate a nil entry. - return false - } - conns = append(conns, conn) - return true - }) - - return -} - -// GetConnection returns single connection -func (s *Server) GetConnection(connID string) Connection { - conn, ok := s.getConnection(connID) - if !ok { - return nil - } - - return conn -} - -// GetConnectionsByRoom returns a list of Connection -// which are joined to this room. -func (s *Server) GetConnectionsByRoom(roomName string) []Connection { - var conns []Connection - s.mu.RLock() - if connIDs, found := s.rooms[roomName]; found { - for _, connID := range connIDs { - // existence check is not necessary here. - if cValue, ok := s.connections.Load(connID); ok { - if conn, ok := cValue.(*connection); ok { - conns = append(conns, conn) - } - } - } - } - - s.mu.RUnlock() - - return conns -} - -// emitMessage is the main 'router' of the messages coming from the connection -// this is the main function which writes the RAW websocket messages to the client. -// It sends them(messages) to the correct room (self, broadcast or to specific client) -// -// You don't have to use this generic method, exists only for extreme -// apps which you have an external goroutine with a list of custom connection list. -// -// You SHOULD use connection.EmitMessage/Emit/To().Emit/EmitMessage instead. -// let's keep it unexported for the best. -func (s *Server) emitMessage(from, to string, data []byte) { - if to != All && to != Broadcast { - s.mu.RLock() - room := s.rooms[to] - s.mu.RUnlock() - if room != nil { - // it suppose to send the message to a specific room/or a user inside its own room - for _, connectionIDInsideRoom := range room { - if c, ok := s.getConnection(connectionIDInsideRoom); ok { - c.writeDefault(data) //send the message to the client(s) - } else { - // the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE: - cid := connectionIDInsideRoom - if c != nil { - cid = c.id - } - s.Leave(cid, to) - } - } - } - } else { - // it suppose to send the message to all opened connections or to all except the sender. - s.connections.Range(func(k, v interface{}) bool { - connID, ok := k.(string) - if !ok { - // should never happen. - return true - } - - if to != All && to != connID { // if it's not suppose to send to all connections (including itself) - if to == Broadcast && from == connID { // if broadcast to other connections except this - // here we do the opossite of previous block, - // just skip this connection when it's suppose to send the message to all connections except the sender. - return true - } - - } - - // not necessary cast. - conn, ok := v.(*connection) - if ok { - // send to the client(s) when the top validators passed - conn.writeDefault(data) - } - - return ok - }) - } -} - -// Disconnect force-disconnects a websocket connection based on its connection.ID() -// What it does? -// 1. remove the connection from the list -// 2. leave from all joined rooms -// 3. fire the disconnect callbacks, if any -// 4. close the underline connection and return its error, if any. -// -// You can use the connection.Disconnect() instead. -func (s *Server) Disconnect(connID string) (err error) { - // leave from all joined rooms before remove the actual connection from the list. - // note: we cannot use that to send data if the client is actually closed. - s.LeaveAll(connID) - - // remove the connection from the list. - if conn, ok := s.getConnection(connID); ok { - atomic.StoreUint32(&conn.disconnected, 1) - - // fire the disconnect callbacks, if any. - conn.fireDisconnect() - - s.connections.Delete(connID) - - err = conn.underline.Close() - } - - return -} diff --git a/websocket2/websocket.go b/websocket2/websocket.go deleted file mode 100644 index 1792e0dc..00000000 --- a/websocket2/websocket.go +++ /dev/null @@ -1,69 +0,0 @@ -/*Package websocket provides rich websocket support for the iris web framework. - -Source code and other details for the project are available at GitHub: - - https://github.com/kataras/iris/tree/master/websocket - -Example code: - - - package main - - import ( - "fmt" - - "github.com/kataras/iris" - "github.com/kataras/iris/context" - - "github.com/kataras/iris/websocket" - ) - - func main() { - app := iris.New() - - app.Get("/", func(ctx context.Context) { - ctx.ServeFile("websockets.html", false) - }) - - setupWebsocket(app) - - // x2 - // http://localhost:8080 - // http://localhost:8080 - // write something, press submit, see the result. - app.Run(iris.Addr(":8080")) - } - - func setupWebsocket(app *iris.Application) { - // create our echo websocket server - ws := websocket.New(websocket.Config{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - }) - ws.OnConnection(handleConnection) - - // register the server's endpoint. - // see the inline javascript code in the websockets.html, - // this endpoint is used to connect to the server. - app.Get("/echo", ws.Handler()) - - // serve the javascript built'n client-side library, - // see websockets.html script tags, this path is used. - app.Any("/iris-ws.js", func(ctx context.Context) { - ctx.Write(websocket.ClientSource) - }) - } - - func handleConnection(c websocket.Connection) { - // Read events from browser - c.On("chat", func(msg string) { - // Print the message to the console - fmt.Printf("%s sent: %s\n", c.Context().RemoteAddr(), msg) - // Write message back to the client message owner: - // c.Emit("chat", msg) - c.To(websocket.Broadcast).Emit("chat", msg) - }) - } - -*/ -package websocket