mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
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:
parent
c477251d1f
commit
bda36145e5
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2017-2018 The Iris Authors. All rights reserved.
|
||||
Copyright (c) 2017-2019 The Iris Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/websocket2"
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
|
||||
const totalClients = 16000 // max depends on the OS.
|
||||
const http = true
|
||||
const verbose = true
|
||||
|
||||
func main() {
|
||||
|
||||
|
@ -62,32 +62,9 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
if http {
|
||||
app := iris.New()
|
||||
app.Get("/", ws.Handler())
|
||||
app.Run(iris.Addr(":8080"))
|
||||
return
|
||||
}
|
||||
|
||||
// ln, err := net.Listen("tcp", ":8080")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// defer ln.Close()
|
||||
// for {
|
||||
// conn, err := ln.Accept()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// go func() {
|
||||
// err = ws.HandleConn(conn)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }()
|
||||
// }
|
||||
app := iris.New()
|
||||
app.Get("/", ws.Handler())
|
||||
app.Run(iris.Addr(":8080"))
|
||||
|
||||
}
|
||||
|
||||
|
@ -103,7 +80,9 @@ var count uint64
|
|||
|
||||
func handleDisconnect(c websocket.Connection) {
|
||||
atomic.AddUint64(&count, 1)
|
||||
// log.Printf("client [%s] disconnected!\n", c.ID())
|
||||
if verbose {
|
||||
log.Printf("client [%s] disconnected!\n", c.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func handleErr(c websocket.Connection, err error) {
|
||||
|
|
2
cache/LICENSE
vendored
2
cache/LICENSE
vendored
|
@ -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
2
doc.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017-2018 The Iris Authors. All rights reserved.
|
||||
// Copyright (c) 2017-2019 The Iris Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,19 +15,6 @@ type (
|
|||
// Receives one parameter which is the Connection
|
||||
ConnectionFunc func(Connection)
|
||||
|
||||
// websocketRoomPayload is used as payload from the connection to the Server
|
||||
websocketRoomPayload struct {
|
||||
roomName string
|
||||
connectionID string
|
||||
}
|
||||
|
||||
// payloads, connection -> Server
|
||||
websocketMessagePayload struct {
|
||||
from string
|
||||
to string
|
||||
data []byte
|
||||
}
|
||||
|
||||
// Server is the websocket Server's implementation.
|
||||
//
|
||||
// It listens for websocket clients (either from the javascript client-side or from any websocket implementation).
|
||||
|
@ -44,9 +31,9 @@ type (
|
|||
// Use a route to serve this file on a specific path, i.e
|
||||
// app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) })
|
||||
ClientSource []byte
|
||||
connections map[string]*connection // key = the Connection ID.
|
||||
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
|
||||
mu sync.RWMutex // for rooms and connections.
|
||||
connections sync.Map // key = the Connection ID.
|
||||
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
|
||||
mu sync.RWMutex // for rooms.
|
||||
onConnectionListeners []ConnectionFunc
|
||||
//connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
|
||||
upgrader websocket.Upgrader
|
||||
|
@ -63,7 +50,7 @@ func New(cfg Config) *Server {
|
|||
return &Server{
|
||||
config: cfg,
|
||||
ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1),
|
||||
connections: make(map[string]*connection),
|
||||
connections: sync.Map{}, // ready-to-use, this is not necessary.
|
||||
rooms: make(map[string][]string),
|
||||
onConnectionListeners: make([]ConnectionFunc, 0),
|
||||
upgrader: websocket.Upgrader{
|
||||
|
@ -132,19 +119,24 @@ func (s *Server) Upgrade(ctx context.Context) Connection {
|
|||
}
|
||||
|
||||
func (s *Server) addConnection(c *connection) {
|
||||
s.mu.Lock()
|
||||
s.connections[c.id] = c
|
||||
s.mu.Unlock()
|
||||
s.connections.Store(c.id, c)
|
||||
}
|
||||
|
||||
func (s *Server) getConnection(connID string) (*connection, bool) {
|
||||
c, ok := s.connections[connID]
|
||||
return c, ok
|
||||
if cValue, ok := s.connections.Load(connID); ok {
|
||||
// this cast is not necessary,
|
||||
// we know that we always save a connection, but for good or worse let it be here.
|
||||
if conn, ok := cValue.(*connection); ok {
|
||||
return conn, ok
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// wrapConnection wraps an underline connection to an iris websocket connection.
|
||||
// It does NOT starts its writer, reader and event mux, the caller is responsible for that.
|
||||
func (s *Server) handleConnection(ctx context.Context, websocketConn UnderlineConnection) *connection {
|
||||
func (s *Server) handleConnection(ctx context.Context, websocketConn *websocket.Conn) *connection {
|
||||
// use the config's id generator (or the default) to create a websocket client/connection id
|
||||
cid := s.config.IDGenerator(ctx)
|
||||
// create the new connection
|
||||
|
@ -282,27 +274,31 @@ func (s *Server) leave(roomName string, connID string) (left bool) {
|
|||
return
|
||||
}
|
||||
|
||||
// GetTotalConnections returns the number of total connections
|
||||
// GetTotalConnections returns the number of total connections.
|
||||
func (s *Server) GetTotalConnections() (n int) {
|
||||
s.mu.RLock()
|
||||
n = len(s.connections)
|
||||
s.mu.RUnlock()
|
||||
s.connections.Range(func(k, v interface{}) bool {
|
||||
n++
|
||||
return true
|
||||
})
|
||||
|
||||
return
|
||||
return n
|
||||
}
|
||||
|
||||
// GetConnections returns all connections
|
||||
func (s *Server) GetConnections() []Connection {
|
||||
s.mu.RLock()
|
||||
conns := make([]Connection, len(s.connections))
|
||||
i := 0
|
||||
for _, c := range s.connections {
|
||||
conns[i] = c
|
||||
i++
|
||||
}
|
||||
// GetConnections returns all connections.
|
||||
func (s *Server) GetConnections() (conns []Connection) {
|
||||
s.connections.Range(func(k, v interface{}) bool {
|
||||
conn, ok := v.(*connection)
|
||||
if !ok {
|
||||
// if for some reason (should never happen), the value is not stored as *connection
|
||||
// then stop the iteration and don't continue insertion of the result connections
|
||||
// in order to avoid any issues while end-dev will try to iterate a nil entry.
|
||||
return false
|
||||
}
|
||||
conns = append(conns, conn)
|
||||
return true
|
||||
})
|
||||
|
||||
s.mu.RUnlock()
|
||||
return conns
|
||||
return
|
||||
}
|
||||
|
||||
// GetConnection returns single connection
|
||||
|
@ -317,21 +313,19 @@ func (s *Server) GetConnection(connID string) Connection {
|
|||
|
||||
// GetConnectionsByRoom returns a list of Connection
|
||||
// which are joined to this room.
|
||||
func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
|
||||
var conns []Connection
|
||||
s.mu.RLock()
|
||||
func (s *Server) GetConnectionsByRoom(roomName string) (conns []Connection) {
|
||||
if connIDs, found := s.rooms[roomName]; found {
|
||||
for _, connID := range connIDs {
|
||||
// existence check is not necessary here.
|
||||
if conn, ok := s.connections[connID]; ok {
|
||||
conns = append(conns, conn)
|
||||
if cValue, ok := s.connections.Load(connID); ok {
|
||||
if conn, ok := cValue.(*connection); ok {
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.mu.RUnlock()
|
||||
|
||||
return conns
|
||||
return
|
||||
}
|
||||
|
||||
// emitMessage is the main 'router' of the messages coming from the connection
|
||||
|
@ -364,20 +358,32 @@ func (s *Server) emitMessage(from, to string, data []byte) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
s.mu.RLock()
|
||||
// it suppose to send the message to all opened connections or to all except the sender.
|
||||
for _, conn := range s.connections {
|
||||
if to != All && to != conn.id { // if it's not suppose to send to all connections (including itself)
|
||||
if to == Broadcast && from == conn.id { // if broadcast to other connections except this
|
||||
// here we do the opossite of previous block,
|
||||
// just skip this connection when it's suppose to send the message to all connections except the sender.
|
||||
continue
|
||||
}
|
||||
s.connections.Range(func(k, v interface{}) bool {
|
||||
connID, ok := k.(string)
|
||||
if !ok {
|
||||
// should never happen.
|
||||
return true
|
||||
}
|
||||
|
||||
conn.writeDefault(data)
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
|
||||
if to == Broadcast && from == connID { // if broadcast to other connections except this
|
||||
// here we do the opossite of previous block,
|
||||
// just skip this connection when it's suppose to send the message to all connections except the sender.
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// not necessary cast.
|
||||
conn, ok := v.(*connection)
|
||||
if ok {
|
||||
// send to the client(s) when the top validators passed
|
||||
conn.writeDefault(data)
|
||||
}
|
||||
|
||||
return ok
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,9 +408,7 @@ func (s *Server) Disconnect(connID string) (err error) {
|
|||
// close the underline connection and return its error, if any.
|
||||
err = conn.underline.Close()
|
||||
|
||||
s.mu.Lock()
|
||||
delete(s.connections, conn.id)
|
||||
s.mu.Unlock()
|
||||
s.connections.Delete(connID)
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -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;
|
||||
}());
|
|
@ -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;
|
||||
}());
|
||||
`)
|
1
websocket2/client.min.js
vendored
1
websocket2/client.min.js
vendored
|
@ -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}();
|
|
@ -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};
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user