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
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-02-22 21:24:10 +02:00
parent c477251d1f
commit bda36145e5
25 changed files with 110 additions and 2769 deletions

View File

@ -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

View File

@ -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() {

View File

@ -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)
// }
// }()
// }
}
@ -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) {

2
cache/LICENSE vendored
View File

@ -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

2
doc.go
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var src = rand.NewSource(time.Now().UnixNano())
// random takes a parameter (int) and returns random slice of byte
// ex: var randomstrbytes []byte; randomstrbytes = utils.Random(32)
func random(n int) []byte {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 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))
}

View File

@ -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

View File

@ -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.
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 and connections.
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 {
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
}
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)
}
s.mu.RUnlock()
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

View File

@ -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;
}());

View File

@ -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;
}());
`)

View File

@ -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<s.length?this.conn=new WebSocket(e,s):this.conn=new WebSocket(e),this.conn.onopen=function(e){return t.fireConnect(),t.isReady=!0,null},this.conn.onclose=function(e){return t.fireDisconnect(),null},this.conn.onmessage=function(e){t.messageReceivedFromConn(e)})}return e.prototype.isNumber=function(e){return!isNaN(e-0)&&null!==e&&""!==e&&!1!==e},e.prototype.isString=function(e){return"[object String]"==Object.prototype.toString.call(e)},e.prototype.isBoolean=function(e){return"boolean"==typeof e||"object"==typeof e&&"boolean"==typeof e.valueOf()},e.prototype.isJSON=function(e){return"object"==typeof e},e.prototype._msg=function(e,s,t){return websocketMessagePrefix+e+websocketMessageSeparator+String(s)+websocketMessageSeparator+t},e.prototype.encodeMessage=function(e,s){var t="",n=0;return this.isNumber(s)?(n=websocketIntMessageType,t=s.toString()):this.isBoolean(s)?(n=websocketBoolMessageType,t=s.toString()):this.isString(s)?(n=websocketStringMessageType,t=s.toString()):this.isJSON(s)?(n=websocketJSONMessageType,t=JSON.stringify(s)):null!=s&&console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"),this._msg(e,n,t)},e.prototype.decodeMessage=function(e,s){var t=websocketMessagePrefixLen+websocketMessageSeparatorLen+e.length+2;if(s.length<t+1)return null;var n=parseInt(s.charAt(t-2)),o=s.substring(t,s.length);return n==websocketIntMessageType?parseInt(o):n==websocketBoolMessageType?Boolean(o):n==websocketStringMessageType?o:n==websocketJSONMessageType?JSON.parse(o):null},e.prototype.getWebsocketCustomEvent=function(e){if(e.length<websocketMessagePrefixAndSepIdx)return"";var s=e.substring(websocketMessagePrefixAndSepIdx,e.length);return s.substring(0,s.indexOf(websocketMessageSeparator))},e.prototype.getCustomMessage=function(e,s){var t=s.indexOf(e+websocketMessageSeparator);return s.substring(t+e.length+websocketMessageSeparator.length+2,s.length)},e.prototype.messageReceivedFromConn=function(e){var s=e.data;if(-1!=s.indexOf(websocketMessagePrefix)){var t=this.getWebsocketCustomEvent(s);if(""!=t)return void this.fireMessage(t,this.getCustomMessage(t,s))}this.fireNativeMessage(s)},e.prototype.OnConnect=function(e){this.isReady&&e(),this.connectListeners.push(e)},e.prototype.fireConnect=function(){for(var e=0;e<this.connectListeners.length;e++)this.connectListeners[e]()},e.prototype.OnDisconnect=function(e){this.disconnectListeners.push(e)},e.prototype.fireDisconnect=function(){for(var e=0;e<this.disconnectListeners.length;e++)this.disconnectListeners[e]()},e.prototype.OnMessage=function(e){this.nativeMessageListeners.push(e)},e.prototype.fireNativeMessage=function(e){for(var s=0;s<this.nativeMessageListeners.length;s++)this.nativeMessageListeners[s](e)},e.prototype.On=function(e,s){null!=this.messageListeners[e]&&null!=this.messageListeners[e]||(this.messageListeners[e]=[]),this.messageListeners[e].push(s)},e.prototype.fireMessage=function(e,s){for(var t in this.messageListeners)if(this.messageListeners.hasOwnProperty(t)&&t==e)for(var n=0;n<this.messageListeners[t].length;n++)this.messageListeners[t][n](s)},e.prototype.Disconnect=function(){this.conn.close()},e.prototype.EmitMessage=function(e){this.conn.send(e)},e.prototype.Emit=function(e,s){var t=this.encodeMessage(e,s);this.EmitMessage(t)},e}();

View File

@ -1,256 +0,0 @@
// export to client.js.go:ClientSource []byte
const websocketStringMessageType = 0;
const websocketIntMessageType = 1;
const websocketBoolMessageType = 2;
// bytes is missing here for reasons I will explain somewhen
const websocketJSONMessageType = 4;
const websocketMessagePrefix = "iris-websocket-message:";
const websocketMessageSeparator = ";";
const websocketMessagePrefixLen = websocketMessagePrefix.length;
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
type onConnectFunc = () => 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<T>(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 = <string>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};

View File

@ -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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var src = rand.NewSource(time.Now().UnixNano())
// random takes a parameter (int) and returns random slice of byte
// ex: var randomstrbytes []byte; randomstrbytes = utils.Random(32)
func random(n int) []byte {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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