2017-02-15 07:40:43 +01:00
|
|
|
package websocket
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
"github.com/kataras/iris/context"
|
2017-02-15 07:40:43 +01:00
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
)
|
2017-02-15 07:40:43 +01:00
|
|
|
|
|
|
|
type connectionKV struct {
|
|
|
|
key string // the connection ID
|
|
|
|
value *connection
|
|
|
|
}
|
|
|
|
|
|
|
|
type connections []connectionKV
|
|
|
|
|
|
|
|
func (cs *connections) add(key string, value *connection) {
|
|
|
|
args := *cs
|
|
|
|
n := len(args)
|
|
|
|
// check if already id/key exist, if yes replace the conn
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
kv := &args[i]
|
|
|
|
if kv.key == key {
|
|
|
|
kv.value = value
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c := cap(args)
|
|
|
|
// make the connections slice bigger and put the conn
|
|
|
|
if c > n {
|
|
|
|
args = args[:n+1]
|
|
|
|
kv := &args[n]
|
|
|
|
kv.key = key
|
|
|
|
kv.value = value
|
|
|
|
*cs = args
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// append to the connections slice and put the conn
|
|
|
|
kv := connectionKV{}
|
|
|
|
kv.key = key
|
|
|
|
kv.value = value
|
|
|
|
*cs = append(args, kv)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *connections) get(key string) *connection {
|
|
|
|
args := *cs
|
|
|
|
n := len(args)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
kv := &args[i]
|
|
|
|
if kv.key == key {
|
|
|
|
return kv.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the connection which removed and a bool value of found or not
|
|
|
|
// the connection is useful to fire the disconnect events, we use that form in order to
|
|
|
|
// make work things faster without the need of get-remove, just -remove should do the job.
|
|
|
|
func (cs *connections) remove(key string) (*connection, bool) {
|
|
|
|
args := *cs
|
|
|
|
n := len(args)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
kv := &args[i]
|
|
|
|
if kv.key == key {
|
|
|
|
conn := kv.value
|
|
|
|
// we found the index,
|
|
|
|
// let's remove the item by appending to the temp and
|
|
|
|
// after set the pointer of the slice to this temp args
|
|
|
|
args = append(args[:i], args[i+1:]...)
|
|
|
|
*cs = args
|
|
|
|
return conn, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
type (
|
2017-07-10 17:32:42 +02:00
|
|
|
// ConnectionFunc is the callback which fires when a client/connection is connected to the Server.
|
2017-02-15 07:40:43 +01:00
|
|
|
// Receives one parameter which is the Connection
|
|
|
|
ConnectionFunc func(Connection)
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// websocketRoomPayload is used as payload from the connection to the Server
|
2017-02-15 07:40:43 +01:00
|
|
|
websocketRoomPayload struct {
|
|
|
|
roomName string
|
|
|
|
connectionID string
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// payloads, connection -> Server
|
2017-02-15 07:40:43 +01:00
|
|
|
websocketMessagePayload struct {
|
|
|
|
from string
|
|
|
|
to string
|
|
|
|
data []byte
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// 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 {
|
2017-02-15 07:40:43 +01:00
|
|
|
config Config
|
|
|
|
connections connections
|
|
|
|
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
|
|
|
|
mu sync.Mutex // for rooms
|
|
|
|
onConnectionListeners []ConnectionFunc
|
2017-07-10 17:32:42 +02:00
|
|
|
//connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
|
|
|
|
handler context.Handler
|
2017-02-15 07:40:43 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// 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 {
|
|
|
|
return &Server{
|
|
|
|
config: cfg.Validate(),
|
|
|
|
rooms: make(map[string][]string, 0),
|
|
|
|
onConnectionListeners: make([]ConnectionFunc, 0),
|
|
|
|
}
|
|
|
|
}
|
2017-02-15 07:40:43 +01:00
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// 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 {
|
2017-02-15 07:40:43 +01:00
|
|
|
// build the upgrader once
|
|
|
|
c := s.config
|
|
|
|
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
upgrader := websocket.Upgrader{
|
2017-07-10 17:32:42 +02:00
|
|
|
HandshakeTimeout: c.HandshakeTimeout,
|
|
|
|
ReadBufferSize: c.ReadBufferSize,
|
|
|
|
WriteBufferSize: c.WriteBufferSize,
|
|
|
|
Error: c.Error,
|
|
|
|
CheckOrigin: c.CheckOrigin,
|
|
|
|
Subprotocols: c.Subprotocols,
|
|
|
|
EnableCompression: c.EnableCompression,
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
}
|
2017-07-10 17:32:42 +02:00
|
|
|
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
return func(ctx context.Context) {
|
2017-07-10 17:32:42 +02:00
|
|
|
// Upgrade upgrades the HTTP Server connection to the WebSocket protocol.
|
2017-02-15 07:40:43 +01:00
|
|
|
//
|
|
|
|
// 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.
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
conn, err := upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), ctx.ResponseWriter().Header())
|
2017-02-15 07:40:43 +01:00
|
|
|
if err != nil {
|
2017-07-10 17:32:42 +02:00
|
|
|
ctx.Application().Logger().Warnf("websocket error: %v\n", err)
|
|
|
|
ctx.StatusCode(503) // Status Service Unavailable
|
2017-02-15 07:40:43 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
s.handleConnection(ctx, conn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleConnection creates & starts to listening to a new connection
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) handleConnection(ctx context.Context, websocketConn UnderlineConnection) {
|
2017-02-15 07:40:43 +01:00
|
|
|
// 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
|
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
|
|
|
c := newConnection(ctx, s, websocketConn, cid)
|
2017-07-10 17:32:42 +02:00
|
|
|
// add the connection to the Server's list
|
2017-02-15 07:40:43 +01:00
|
|
|
s.connections.add(cid, c)
|
|
|
|
|
|
|
|
// join to itself
|
|
|
|
s.Join(c.ID(), c.ID())
|
|
|
|
|
|
|
|
// 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
|
2017-07-10 17:32:42 +02:00
|
|
|
// then the client is able to send and receive from Server
|
2017-02-15 07:40:43 +01:00
|
|
|
// 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
|
|
|
|
c.startPinger()
|
|
|
|
|
|
|
|
// start the messages reader
|
|
|
|
c.startReader()
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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:
|
2017-07-10 17:32:42 +02:00
|
|
|
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
|
2017-02-15 07:40:43 +01:00
|
|
|
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
|
2017-07-10 17:32:42 +02:00
|
|
|
here, in the Server, and let the connection with some exported functions whose exists for the per-connection action user's code-style.
|
2017-02-15 07:40:43 +01:00
|
|
|
|
|
|
|
Ok my english are s** I can feel it, but these comments are mostly for me.
|
|
|
|
*/
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
/*
|
|
|
|
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) {
|
2017-02-15 07:40:43 +01:00
|
|
|
s.onConnectionListeners = append(s.onConnectionListeners, cb)
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// IsConnected returns true if the connection with that ID is connected to the Server
|
2017-02-15 07:40:43 +01:00
|
|
|
// 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)
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) IsConnected(connID string) bool {
|
2017-02-15 07:40:43 +01:00
|
|
|
c := s.connections.get(connID)
|
|
|
|
return c != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) Join(roomName string, connID string) {
|
2017-02-15 07:40:43 +01:00
|
|
|
s.mu.Lock()
|
|
|
|
s.join(roomName, connID)
|
|
|
|
s.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// join used internally, no locks used.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) join(roomName string, connID string) {
|
2017-02-15 07:40:43 +01:00
|
|
|
if s.rooms[roomName] == nil {
|
|
|
|
s.rooms[roomName] = make([]string, 0)
|
|
|
|
}
|
|
|
|
s.rooms[roomName] = append(s.rooms[roomName], connID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LeaveAll kicks out a connection from ALL of its joined rooms
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) LeaveAll(connID string) {
|
2017-02-15 07:40:43 +01:00
|
|
|
s.mu.Lock()
|
|
|
|
for name, connectionIDs := range s.rooms {
|
|
|
|
for i := range connectionIDs {
|
|
|
|
if connectionIDs[i] == connID {
|
2017-02-22 19:32:38 +01:00
|
|
|
// fire the on room leave connection's listeners
|
|
|
|
s.connections.get(connID).fireOnLeave(name)
|
2017-02-15 07:40:43 +01:00
|
|
|
// the connection is inside this room, lets remove it
|
2017-08-09 12:31:56 +02:00
|
|
|
if i < len(s.rooms[name]) {
|
|
|
|
s.rooms[name] = append(s.rooms[name][:i], s.rooms[name][i+1:]...)
|
|
|
|
}
|
2017-02-15 07:40:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
2017-02-22 19:32:38 +01:00
|
|
|
// Returns true if the connection has actually left from the particular room.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) Leave(roomName string, connID string) bool {
|
2017-02-15 07:40:43 +01:00
|
|
|
s.mu.Lock()
|
2017-02-22 19:32:38 +01:00
|
|
|
left := s.leave(roomName, connID)
|
2017-02-15 07:40:43 +01:00
|
|
|
s.mu.Unlock()
|
2017-02-22 19:32:38 +01:00
|
|
|
return left
|
2017-02-15 07:40:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// leave used internally, no locks used.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) leave(roomName string, connID string) (left bool) {
|
2017-02-15 07:40:43 +01:00
|
|
|
///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 {
|
2017-08-09 12:31:56 +02:00
|
|
|
s.rooms[roomName] = append(s.rooms[roomName][:i], s.rooms[roomName][i+1:]...)
|
2017-02-22 19:32:38 +01:00
|
|
|
left = true
|
2017-02-15 07:40:43 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(s.rooms[roomName]) == 0 { // if room is empty then delete it
|
|
|
|
delete(s.rooms, roomName)
|
|
|
|
}
|
|
|
|
}
|
2017-02-22 19:32:38 +01:00
|
|
|
|
|
|
|
if left {
|
|
|
|
// fire the on room leave connection's listeners
|
|
|
|
s.connections.get(connID).fireOnLeave(roomName)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetConnectionsByRoom returns a list of Connection
|
|
|
|
// which are joined to this room.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
|
2017-02-22 19:32:38 +01:00
|
|
|
s.mu.Lock()
|
|
|
|
var conns []Connection
|
|
|
|
if connIDs, found := s.rooms[roomName]; found {
|
|
|
|
for _, connID := range connIDs {
|
|
|
|
conns = append(conns, s.connections.get(connID))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
s.mu.Unlock()
|
|
|
|
return conns
|
2017-02-15 07:40:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) emitMessage(from, to string, data []byte) {
|
2017-02-15 07:40:43 +01:00
|
|
|
if to != All && to != Broadcast && s.rooms[to] != nil {
|
|
|
|
// it suppose to send the message to a specific room/or a user inside its own room
|
|
|
|
for _, connectionIDInsideRoom := range s.rooms[to] {
|
|
|
|
if c := s.connections.get(connectionIDInsideRoom); c != nil {
|
|
|
|
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
|
|
|
|
for _, cKV := range s.connections {
|
|
|
|
connID := cKV.key
|
|
|
|
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
|
|
|
|
continue //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
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
// send to the client(s) when the top validators passed
|
|
|
|
cKV.value.writeDefault(data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2017-07-10 17:32:42 +02:00
|
|
|
func (s *Server) Disconnect(connID string) (err error) {
|
2017-02-22 19:32:38 +01:00
|
|
|
// 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)
|
|
|
|
|
2017-02-15 07:40:43 +01:00
|
|
|
// remove the connection from the list
|
|
|
|
if c, ok := s.connections.remove(connID); ok {
|
|
|
|
if !c.disconnected {
|
|
|
|
c.disconnected = true
|
2017-02-22 19:32:38 +01:00
|
|
|
|
2017-02-15 07:40:43 +01:00
|
|
|
// fire the disconnect callbacks, if any
|
|
|
|
c.fireDisconnect()
|
|
|
|
// close the underline connection and return its error, if any.
|
|
|
|
err = c.underline.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|