mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add iris websocket client side for Go and a simple chat example
Former-commit-id: af1c555b6b092a3d0484fee2e200fd8767d7239e
This commit is contained in:
parent
d30f17eb3f
commit
280872fd59
|
@ -478,6 +478,9 @@ iris websocket library lives on its own [package](https://github.com/kataras/iri
|
||||||
The package is designed to work with raw websockets although its API is similar to the famous [socket.io](https://socket.io). I have read an article recently and I felt very contented about my decision to design a **fast** websocket-**only** package for Iris and not a backwards socket.io-like package. You can read that article by following this link: https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd.
|
The package is designed to work with raw websockets although its API is similar to the famous [socket.io](https://socket.io). I have read an article recently and I felt very contented about my decision to design a **fast** websocket-**only** package for Iris and not a backwards socket.io-like package. You can read that article by following this link: https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd.
|
||||||
|
|
||||||
- [Chat](websocket/chat/main.go)
|
- [Chat](websocket/chat/main.go)
|
||||||
|
- [Chat with Iris Go Client Side](websocket/go-client) **NEW**
|
||||||
|
* [Server](websocket/go-client/server/main.go)
|
||||||
|
* [Client](websocket/go-client/client/main.go)
|
||||||
- [Native Messages](websocket/native-messages/main.go)
|
- [Native Messages](websocket/native-messages/main.go)
|
||||||
- [Connection List](websocket/connectionlist/main.go)
|
- [Connection List](websocket/connectionlist/main.go)
|
||||||
- [TLS Enabled](websocket/secure/main.go)
|
- [TLS Enabled](websocket/secure/main.go)
|
||||||
|
|
|
@ -431,6 +431,9 @@ iris websocket库依赖于它自己的[包](https://github.com/kataras/iris/tree
|
||||||
决定给iris设计一个**快速的**websocket**限定**包并且不是一个向后传递类socket.io的包。你可以阅读这个链接里的文章https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd。
|
决定给iris设计一个**快速的**websocket**限定**包并且不是一个向后传递类socket.io的包。你可以阅读这个链接里的文章https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd。
|
||||||
|
|
||||||
- [聊天](websocket/chat/main.go)
|
- [聊天](websocket/chat/main.go)
|
||||||
|
- [Chat with Iris Go Client Side](websocket/go-client) **NEW**
|
||||||
|
* [Server](websocket/go-client/server/main.go)
|
||||||
|
* [Client](websocket/go-client/client/main.go)
|
||||||
- [原生消息](websocket/native-messages/main.go)
|
- [原生消息](websocket/native-messages/main.go)
|
||||||
- [连接列表](websocket/connectionlist/main.go)
|
- [连接列表](websocket/connectionlist/main.go)
|
||||||
- [TLS支持](websocket/secure/main.go)
|
- [TLS支持](websocket/secure/main.go)
|
||||||
|
|
|
@ -87,7 +87,7 @@ func SendMessage(serverID, to, method, message string) error {
|
||||||
|
|
||||||
// SendtBytes broadcast a message to server
|
// SendtBytes broadcast a message to server
|
||||||
func SendtBytes(serverID, to, method string, message []byte) error {
|
func SendtBytes(serverID, to, method string, message []byte) error {
|
||||||
// look https://github.com/kataras/iris/blob/master/websocket/message.go , client.go and client.js
|
// look https://github.com/kataras/iris/blob/master/websocket/message.go , client.js.go and client.js
|
||||||
// to understand the buffer line:
|
// to understand the buffer line:
|
||||||
buffer := []byte(fmt.Sprintf("%s%v;0;%v;%v;", websocket.DefaultEvtMessageKey, method, serverID, to))
|
buffer := []byte(fmt.Sprintf("%s%v;0;%v;%v;", websocket.DefaultEvtMessageKey, method, serverID, to))
|
||||||
buffer = append(buffer, message...)
|
buffer = append(buffer, message...)
|
||||||
|
|
58
_examples/websocket/go-client/client/main.go
Normal file
58
_examples/websocket/go-client/client/main.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
url = "ws://localhost:8080/socket"
|
||||||
|
prompt = ">> "
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
How to run:
|
||||||
|
Start the server, if it is not already started by executing `go run ../server/main.go`
|
||||||
|
And open two or more terminal windows and start the clients:
|
||||||
|
$ go run main.go
|
||||||
|
>> hi!
|
||||||
|
*/
|
||||||
|
func main() {
|
||||||
|
conn, err := websocket.Dial(url, websocket.DefaultEvtMessageKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.OnError(func(err error) {
|
||||||
|
fmt.Printf("error: %v", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.OnDisconnect(func() {
|
||||||
|
fmt.Println("Server was force-closed[see ../server/main.go#L19] this connection after 20 seconds, therefore I am disconnected.")
|
||||||
|
os.Exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.On("chat", func(message string) {
|
||||||
|
fmt.Printf("\n%s\n", message)
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("Start by typing a message to send")
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Print(prompt)
|
||||||
|
if !scanner.Scan() || scanner.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
msgToSend := scanner.Text()
|
||||||
|
if msgToSend == "exit" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Emit("chat", msgToSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Terminated.")
|
||||||
|
}
|
32
_examples/websocket/go-client/server/main.go
Normal file
32
_examples/websocket/go-client/server/main.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
ws := websocket.New(websocket.Config{})
|
||||||
|
app.Get("/socket", ws.Handler())
|
||||||
|
|
||||||
|
ws.OnConnection(func(c websocket.Connection) {
|
||||||
|
go func() {
|
||||||
|
<-time.After(20 * time.Second)
|
||||||
|
c.Disconnect()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.On("chat", func(message string) {
|
||||||
|
c.To(websocket.Broadcast).Emit("chat", c.ID()+": "+message)
|
||||||
|
})
|
||||||
|
|
||||||
|
c.OnDisconnect(func() {
|
||||||
|
fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Run(iris.Addr(":8080"))
|
||||||
|
}
|
|
@ -1,233 +1,214 @@
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientHandler is the handler which serves the javascript client-side
|
// Dial opens a new client connection to a WebSocket.
|
||||||
// library. It uses a small cache based on the iris/context.WriteWithExpiration.
|
func Dial(url, evtMessagePrefix string) (ws *ClientConn, err error) {
|
||||||
func ClientHandler() context.Handler {
|
if !strings.HasPrefix(url, "ws://") {
|
||||||
modNow := time.Now()
|
url = "ws://" + url
|
||||||
return func(ctx context.Context) {
|
}
|
||||||
ctx.ContentType("application/javascript")
|
|
||||||
if _, err := ctx.WriteWithExpiration(ClientSource, modNow); err != nil {
|
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||||
ctx.StatusCode(500)
|
if err != nil {
|
||||||
ctx.StopExecution()
|
return nil, err
|
||||||
// ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error())
|
}
|
||||||
|
|
||||||
|
return NewClientConn(conn, evtMessagePrefix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConn struct {
|
||||||
|
underline UnderlineConnection // TODO make it using gorilla's one, because the 'startReader' will not know when to stop otherwise, we have a fixed length currently...
|
||||||
|
messageType int
|
||||||
|
serializer *messageSerializer
|
||||||
|
|
||||||
|
onErrorListeners []ErrorFunc
|
||||||
|
onDisconnectListeners []DisconnectFunc
|
||||||
|
onNativeMessageListeners []NativeMessageFunc
|
||||||
|
onEventListeners map[string][]MessageFunc
|
||||||
|
|
||||||
|
writerMu sync.Mutex
|
||||||
|
|
||||||
|
disconnected uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientConn(conn UnderlineConnection, evtMessagePrefix string) *ClientConn {
|
||||||
|
if evtMessagePrefix == "" {
|
||||||
|
evtMessagePrefix = DefaultEvtMessageKey
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &ClientConn{
|
||||||
|
underline: conn,
|
||||||
|
serializer: newMessageSerializer([]byte(evtMessagePrefix)),
|
||||||
|
|
||||||
|
onErrorListeners: make([]ErrorFunc, 0),
|
||||||
|
onDisconnectListeners: make([]DisconnectFunc, 0),
|
||||||
|
onNativeMessageListeners: make([]NativeMessageFunc, 0),
|
||||||
|
onEventListeners: make(map[string][]MessageFunc, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetBinaryMessages(false)
|
||||||
|
|
||||||
|
go c.startReader()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) SetBinaryMessages(binaryMessages bool) {
|
||||||
|
if binaryMessages {
|
||||||
|
c.messageType = websocket.BinaryMessage
|
||||||
|
} else {
|
||||||
|
c.messageType = websocket.TextMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) startReader() {
|
||||||
|
defer c.Disconnect()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, data, err := c.underline.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
c.FireOnError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
c.messageReceived(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientSource the client-side javascript raw source code.
|
func (c *ClientConn) messageReceived(data []byte) error {
|
||||||
var ClientSource = []byte(`var websocketStringMessageType = 0;
|
if bytes.HasPrefix(data, c.serializer.prefix) {
|
||||||
var websocketIntMessageType = 1;
|
// is a custom iris message.
|
||||||
var websocketBoolMessageType = 2;
|
receivedEvt := c.serializer.getWebsocketCustomEvent(data)
|
||||||
var websocketJSONMessageType = 4;
|
listeners, ok := c.onEventListeners[string(receivedEvt)]
|
||||||
var websocketMessagePrefix = "` + DefaultEvtMessageKey + `";
|
if !ok || len(listeners) == 0 {
|
||||||
var websocketMessageSeparator = ";";
|
return nil // if not listeners for this event exit from here
|
||||||
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;
|
customMessage, err := c.serializer.deserialize(receivedEvt, data)
|
||||||
|
if customMessage == nil || err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
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 {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
} 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)
|
||||||
}
|
}
|
||||||
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
|
} else {
|
||||||
Ws.prototype.isNumber = function (obj) {
|
// it's native websocket message
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
for i := range c.onNativeMessageListeners {
|
||||||
};
|
c.onNativeMessageListeners[i](data)
|
||||||
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);
|
return nil
|
||||||
};
|
}
|
||||||
Ws.prototype.OnConnect = function (fn) {
|
|
||||||
if (this.isReady) {
|
func (c *ClientConn) OnMessage(cb NativeMessageFunc) {
|
||||||
fn();
|
c.onNativeMessageListeners = append(c.onNativeMessageListeners, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) On(event string, cb MessageFunc) {
|
||||||
|
if c.onEventListeners[event] == nil {
|
||||||
|
c.onEventListeners[event] = make([]MessageFunc, 0)
|
||||||
}
|
}
|
||||||
this.connectListeners.push(fn);
|
|
||||||
};
|
c.onEventListeners[event] = append(c.onEventListeners[event], cb)
|
||||||
Ws.prototype.fireConnect = function () {
|
}
|
||||||
for (var i = 0; i < this.connectListeners.length; i++) {
|
|
||||||
this.connectListeners[i]();
|
func (c *ClientConn) OnError(cb ErrorFunc) {
|
||||||
|
c.onErrorListeners = append(c.onErrorListeners, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) FireOnError(err error) {
|
||||||
|
for _, cb := range c.onErrorListeners {
|
||||||
|
cb(err)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Ws.prototype.OnDisconnect = function (fn) {
|
|
||||||
this.disconnectListeners.push(fn);
|
func (c *ClientConn) OnDisconnect(cb DisconnectFunc) {
|
||||||
};
|
c.onDisconnectListeners = append(c.onDisconnectListeners, cb)
|
||||||
Ws.prototype.fireDisconnect = function () {
|
}
|
||||||
for (var i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
func (c *ClientConn) Disconnect() error {
|
||||||
|
if c == nil || !atomic.CompareAndSwapUint32(&c.disconnected, 0, 1) {
|
||||||
|
return ErrAlreadyDisconnected
|
||||||
}
|
}
|
||||||
};
|
|
||||||
Ws.prototype.OnMessage = function (cb) {
|
err := c.underline.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
this.nativeMessageListeners.push(cb);
|
if err != nil {
|
||||||
};
|
err = c.underline.Close()
|
||||||
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 err == nil {
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
for i := range c.onDisconnectListeners {
|
||||||
this.messageListeners[event] = [];
|
c.onDisconnectListeners[i]()
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) EmitMessage(nativeMessage []byte) error {
|
||||||
|
return c.writeDefault(nativeMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Emit(event string, data interface{}) error {
|
||||||
|
b, err := c.serializer.serialize(event, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return c.EmitMessage(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a raw websocket message with a specific type to the client
|
||||||
|
// used by ping messages and any CloseMessage types.
|
||||||
|
func (c *ClientConn) Write(websocketMessageType int, data []byte) 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()
|
||||||
|
// .WriteMessage same as NextWriter and close (flush)
|
||||||
|
err := c.underline.WriteMessage(websocketMessageType, data)
|
||||||
|
c.writerMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
// if failed then the connection is off, fire the disconnect
|
||||||
|
c.Disconnect()
|
||||||
}
|
}
|
||||||
};
|
return err
|
||||||
//
|
}
|
||||||
// Ws Actions
|
|
||||||
Ws.prototype.Disconnect = function () {
|
// writeDefault is the same as write but the message type is the configured by c.messageType
|
||||||
this.conn.close();
|
// if BinaryMessages is enabled then it's raw []byte as you expected to work with protobufs
|
||||||
};
|
func (c *ClientConn) writeDefault(data []byte) error {
|
||||||
// EmitMessage sends a native websocket message
|
return c.Write(c.messageType, data)
|
||||||
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;
|
|
||||||
}());
|
|
||||||
`)
|
|
||||||
|
|
233
websocket/client.js.go
Normal file
233
websocket/client.js.go
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
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,4 +1,4 @@
|
||||||
// export to client.go:ClientSource []byte
|
// export to client.js.go:ClientSource []byte
|
||||||
|
|
||||||
const websocketStringMessageType = 0;
|
const websocketStringMessageType = 0;
|
||||||
const websocketIntMessageType = 1;
|
const websocketIntMessageType = 1;
|
||||||
|
|
|
@ -400,7 +400,7 @@ func (c *connection) startReader() {
|
||||||
|
|
||||||
_, data, err := conn.ReadMessage()
|
_, data, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
c.FireOnError(err)
|
c.FireOnError(err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
Loading…
Reference in New Issue
Block a user