Add the new websocket package (which is just a helper for kataras/neffos) and an example for go server, client, browser client and nodejs client. Add a .fossa.yml and the generated NOTICE file for 3rd-party libs. Update go.mod, go.sum. Update the vendor folder for pongo2 to its latest master as well

Former-commit-id: 89c05079415977d65e7328a1eb8a1c602d76f78a
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-06-02 17:49:45 +03:00
parent 8d388fb1c6
commit 04bc21dd3b
35 changed files with 661 additions and 2897 deletions

11
.fossa.yml Normal file
View File

@ -0,0 +1,11 @@
version: 2
cli:
server: https://app.fossa.com
fetcher: custom
project: https://github.com/kataras/iris.git
analyze:
modules:
- name: iris
type: go
target: .
path: .

98
NOTICE Normal file
View File

@ -0,0 +1,98 @@
================================================================================
Third-Party Software for iris
================================================================================
The following 3rd-party software components may be used by or distributed with iris. This document was automatically generated by FOSSA on 2019-6-2; any information relevant to third-party vendors listed below are collected using common, reasonable means.
Revision ID: 1e956950f72efdd080b904c952d4162fc7f309e9
================================================================================
Direct Dependencies
================================================================================
----------------- ----------------- ------------------------------------------
Library Version Website
----------------- ----------------- ------------------------------------------
amber cdade1c073850f4 https://github.com/eknkc/amber
ffc70a829e31235
ea6892853b
blackfriday 48b3da6a6f3865c https://github.com/iris-contrib/
7eb1eba96d74cf0 blackfriday
a16f63faca
bluemonday 89802068f71166e https://github.com/microcosm-cc/
95c92040512bf2e bluemonday
11767721ed
columnize 9e6335e58db3b4c https://github.com/ryanuber/columnize
fe3c3c5c881f51f
fbc1091b34
formBinder fbd5963f41e18ae https://github.com/iris-contrib/
1f1423ba0462350 formBinder
94b0721ea1
go e369490fb7db5f2 https://github.com/golang/go
d42bb0e8ee19b48
378dee0ebf
go-version 192140e6f3e645d https://github.com/hashicorp/go-version
971b134d4e35b51
91adb9dfd3
go.uuid 36e9d2ebbde5e3f https://github.com/iris-contrib/go.uuid
13ab2e25625fd45
3271d6522e
golog 03be101463868ed https://github.com/kataras/golog
c5a81f094fc68a5
f6c1b5503a
goreferrer ec9c9a553398739 https://github.com/Shopify/goreferrer
f0dcf817e0ad5e0
1c4e7dcd08
httpexpect ebe99fcebbcedf6 https://github.com/iris-contrib/
e7916320cce24c3 httpexpect
e1832766ac
i18n 987a633949d087b https://github.com/iris-contrib/i18n
a52207b587792e8
c67d65780b
jade 9ffefa50b5f3141 https://github.com/Joker/jade
6ac643e9d9ad611
6f4688705f
json-iterator 08047c174c6c03e https://github.com/json-iterator/go
8ec963a411bde1b
6d1ee67b26
neffos 38e9cc9b65c6ae0 https://github.com/kataras/neffos
2998cc1a2df8767
ecd5951e52
pongo2 8914e1cf9164420 https://github.com/flosch/pongo2
c91423cdefc7d97
8a76c38213
raymond b565731e1464263 https://github.com/aymerick/raymond
de0bda75f2e45d9
7b54b60110
structs 878a968ab225483 https://github.com/fatih/structs
62a09bdb3322f98
b00f470d46
toml 3012a1dbe2e4bd1 https://github.com/BurntSushi/toml
391d42b32f0577c
b7bbc7f005
yaml.v2 51d6538a90f86fe https://gopkg.in/yaml.v2
93ac480b35f37b2
be17fef232
================================================================================
Deep Dependencies
================================================================================
badger e9447c910efd3c6 https://github.com/dgraph-io/badger
7c5453ea1f65d2f
355544dd82
bbolt 2eb7227adea1d5c https://github.com/etcd-io/bbolt
f85f0bc2a82b705
9b13c2fa68
redigo 39e2c31b7ca38b5 https://github.com/gomodule/redigo
21ceb836620a269
e62c895dc9

View File

@ -74,7 +74,7 @@ import (
## Quick start
```sh
# assume the following codes in example.go file
# assume the following code in example.go file
$ cat example.go
```

View File

@ -0,0 +1,93 @@
<!-- the message's input -->
<input id="input" type="text" />
<!-- when clicked then a websocket event will be sent to the server, at this example we registered the 'chat' -->
<button id="sendBtn" disabled>Send</button>
<!-- the messages will be shown here -->
<pre id="output"></pre>
<!-- import the iris client-side library for browser from a CDN or locally.
However, `neffos.(min.)js` is a NPM package too so alternatively,
you can use it as dependency on your package.json and all nodejs-npm tooling become available:
see the "browserify" example for more-->
<script src="https://cdn.jsdelivr.net/npm/neffos.js@0.1.8/dist/neffos.min.js"></script>
<script>
// `neffos` global variable is available now.
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
var port = document.location.port ? ":" + document.location.port : "";
var wsURL = scheme + "://" + document.location.hostname + port + "/echo";
var outputTxt = document.getElementById("output");
function addMessage(msg) {
outputTxt.innerHTML += msg + "\n";
}
function handleError(reason) {
console.log(reason);
window.alert(reason);
}
function handleNamespaceConnectedConn(nsConn) {
let inputTxt = document.getElementById("input");
let sendBtn = document.getElementById("sendBtn");
sendBtn.disabled = false;
sendBtn.onclick = function () {
const input = inputTxt.value;
inputTxt.value = "";
nsConn.emit("chat", input);
addMessage("Me: " + input);
};
}
async function runExample() {
// You can omit the "default" and simply define only Events, the namespace will be an empty string"",
// however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
try {
const conn = await neffos.dial(wsURL, {
default: { // "default" namespace.
_OnNamespaceConnected: function (nsConn, msg) {
addMessage("connected to namespace: " + msg.Namespace);
handleNamespaceConnectedConn(nsConn)
},
_OnNamespaceDisconnect: function (nsConn, msg) {
addMessage("disconnected from namespace: " + msg.Namespace);
},
chat: function (nsConn, msg) { // "chat" event.
addMessage(msg.Body);
}
}
});
// You can either wait to conenct or just conn.connect("connect")
// and put the `handleNamespaceConnectedConn` inside `_OnNamespaceConnected` callback instead.
// const nsConn = await conn.connect("default");
// handleNamespaceConnectedConn(nsConn);
conn.connect("default");
} catch (err) {
handleError(err);
}
}
runExample();
// If "await" and "async" are available, use them instead^, all modern browsers support those,
// all of the javascript examples will be written using async/await method instead of promise then/catch callbacks.
// A usage example of promise then/catch follows:
// neffos.dial(wsURL, {
// default: { // "default" namespace.
// _OnNamespaceConnected: function (ns, msg) {
// addMessage("connected to namespace: " + msg.Namespace);
// },
// _OnNamespaceDisconnect: function (ns, msg) {
// addMessage("disconnected from namespace: " + msg.Namespace);
// },
// chat: function (ns, msg) { // "chat" event.
// addMessage(msg.Body);
// }
// }
// }).then(function (conn) {
// conn.connect("default").then(handleNamespaceConnectedConn).catch(handleError);
// }).catch(handleError);
</script>

View File

@ -0,0 +1,11 @@
# Browserify example
```sh
$ npm install --only=dev # install browserify from the devDependencies.
$ npm run-script build # browserify and minify the `app.js` into `bundle.js`.
$ cd ../ && go run server.go # start the neffos server.
```
> make sure that you have [golang](https://golang.org/dl) installed to run and edit the neffos (server-side).
That's all, now navigate to <http://localhost:8080/browserify>.

View File

@ -0,0 +1,61 @@
const neffos = require('neffos.js');
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
var port = document.location.port ? ":" + document.location.port : "";
var wsURL = scheme + "://" + document.location.hostname + port + "/echo";
var outputTxt = document.getElementById("output");
function addMessage(msg) {
outputTxt.innerHTML += msg + "\n";
}
function handleError(reason) {
console.log(reason);
window.alert(reason);
}
function handleNamespaceConnectedConn(nsConn) {
const inputTxt = document.getElementById("input");
const sendBtn = document.getElementById("sendBtn");
sendBtn.disabled = false;
sendBtn.onclick = function () {
const input = inputTxt.value;
inputTxt.value = "";
nsConn.emit("chat", input);
addMessage("Me: " + input);
};
}
async function runExample() {
try {
const conn = await neffos.dial(wsURL, {
default: { // "default" namespace.
_OnNamespaceConnected: function (nsConn, msg) {
addMessage("connected to namespace: " + msg.Namespace);
handleNamespaceConnectedConn(nsConn);
},
_OnNamespaceDisconnect: function (nsConn, msg) {
addMessage("disconnected from namespace: " + msg.Namespace);
},
chat: function (nsConn, msg) { // "chat" event.
addMessage(msg.Body);
}
}
});
// You can either wait to conenct or just conn.connect("connect")
// and put the `handleNamespaceConnectedConn` inside `_OnNamespaceConnected` callback instead.
// const nsConn = await conn.connect("default");
// handleNamespaceConnectedConn(nsConn);
conn.connect("default");
} catch (err) {
handleError(err);
}
}
runExample();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
<!-- the message's input -->
<input id="input" type="text" />
<!-- when clicked then a websocket event will be sent to the server, at this example we registered the 'chat' -->
<button id="sendBtn" disabled>Send</button>
<!-- the messages will be shown here -->
<pre id="output"></pre>
<script src="./bundle.js"></script>

View File

@ -0,0 +1,16 @@
{
"name": "neffos.js.example.browserify",
"version": "0.0.1",
"scripts": {
"browserify": "browserify ./app.js -o ./bundle.js",
"minifyES6": "minify ./bundle.js --outFile ./bundle.js",
"build": "npm run-script browserify && npm run-script minifyES6"
},
"dependencies": {
"neffos.js": "latest"
},
"devDependencies": {
"browserify": "^16.2.3",
"babel-minify": "^0.5.0"
}
}

View File

@ -0,0 +1,85 @@
package main
import (
"bufio"
"bytes"
"context"
"fmt"
"log"
"os"
"time"
"github.com/kataras/iris/websocket"
)
const (
endpoint = "ws://localhost:8080/echo"
namespace = "default"
dialAndConnectTimeout = 5 * time.Second
)
// this can be shared with the server.go's.
// `NSConn.Conn` has the `IsClient() bool` method which can be used to
// check if that's is a client or a server-side callback.
var clientEvents = websocket.Namespaces{
namespace: websocket.Events{
websocket.OnNamespaceConnected: func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] connected to namespace [%s]", c, msg.Namespace)
return nil
},
websocket.OnNamespaceDisconnect: func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] disconnected from namespace [%s]", c, msg.Namespace)
return nil
},
"chat": func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] sent: %s", c.Conn.ID(), string(msg.Body))
// Write message back to the client message owner with:
// c.Emit("chat", msg)
// Write message to all except this client with:
c.Conn.Server().Broadcast(c, msg)
return nil
},
},
}
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(dialAndConnectTimeout))
defer cancel()
client, err := websocket.Dial(ctx, websocket.GorillaDialer, endpoint, clientEvents)
if err != nil {
panic(err)
}
defer client.Close()
c, err := client.Connect(ctx, namespace)
if err != nil {
panic(err)
}
fmt.Fprint(os.Stdout, ">> ")
scanner := bufio.NewScanner(os.Stdin)
for {
if !scanner.Scan() {
log.Printf("ERROR: %v", scanner.Err())
return
}
text := scanner.Bytes()
if bytes.Equal(text, []byte("exit")) {
if err := c.Disconnect(nil); err != nil {
log.Printf("reply from server: %v", err)
}
break
}
ok := c.Emit("chat", text)
if !ok {
break
}
fmt.Fprint(os.Stdout, ">> ")
}
} // try running this program twice or/and run the server's http://localhost:8080 to check the browser client as well.

View File

@ -0,0 +1,53 @@
package main
import (
"log"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
)
const namespace = "default"
// if namespace is empty then simply websocket.Events{...} can be used instead.
var serverEvents = websocket.Namespaces{
namespace: websocket.Events{
websocket.OnNamespaceConnected: func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] connected to namespace [%s]", c, msg.Namespace)
return nil
},
websocket.OnNamespaceDisconnect: func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] disconnected from namespace [%s]", c, msg.Namespace)
return nil
},
"chat": func(c *websocket.NSConn, msg websocket.Message) error {
log.Printf("[%s] sent: %s", c.Conn.ID(), string(msg.Body))
// Write message back to the client message owner with:
// c.Emit("chat", msg)
// Write message to all except this client with:
c.Conn.Server().Broadcast(c, msg)
return nil
},
},
}
func main() {
app := iris.New()
websocketServer := websocket.New(
websocket.DefaultGorillaUpgrader, /*DefaultGobwasUpgrader can be used as well*/
serverEvents)
// serves the endpoint of ws://localhost:8080/echo
app.Get("/echo", websocket.Handler(websocketServer))
// serves the browser-based websocket client.
app.Get("/", func(ctx iris.Context) {
ctx.ServeFile("./browser/index.html", false)
})
// serves the npm browser websocket client usage example.
app.StaticWeb("/browserify", "./browserify")
app.Run(iris.Addr(":8080"))
}

View File

@ -1,60 +0,0 @@
package main
import (
"fmt"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.ServeFile("websockets.html", false) // second parameter: enable gzip?
})
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{
// These are low-level optionally fields,
// user/client can't see those values.
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// only javascript client-side code has the same rule,
// which you serve using the ws.ClientSource (see below).
EvtMessagePrefix: []byte("my-custom-prefix:"),
})
ws.OnConnection(handleConnection)
// register the server on an 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 builtin client-side library,
// see websockets.html script tags, this path is used.
app.Any("/iris-ws.js", func(ctx iris.Context) {
ctx.Write(ws.ClientSource)
})
}
func handleConnection(c websocket.Connection) {
// Read events from browser
c.On("chat", func(msg string) {
// Print the message to the console, c.Context() is the iris's http context.
fmt.Printf("[%s <%s>] %s\n", c.ID(), c.Context().RemoteAddr(), msg)
// Write message back to the client message owner with:
// c.Emit("chat", msg)
// Write message to all except this client with:
c.To(websocket.Broadcast).Emit("chat", msg)
})
}

View File

@ -1,45 +0,0 @@
<!-- the message's input -->
<input id="input" type="text" />
<!-- when clicked then an iris websocket event will be sent to the server, at this example we registered the 'chat' -->
<button onclick="send()">Send</button>
<!-- the messages will be shown here -->
<pre id="output"></pre>
<!-- import the iris client-side library for browser-->
<script src="/iris-ws.js"></script>
<script>
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
// see app.Get("/echo", ws.Handler()) on main.go
var wsURL = scheme + "://" + document.location.hostname + port+"/echo";
var input = document.getElementById("input");
var output = document.getElementById("output");
// Ws comes from the auto-served '/iris-ws.js'
var socket = new Ws(wsURL)
socket.OnConnect(function () {
output.innerHTML += "Status: Connected\n";
});
socket.OnDisconnect(function () {
output.innerHTML += "Status: Disconnected\n";
});
// read events from the server
socket.On("chat", function (msg) {
addMessage(msg);
});
function send() {
addMessage("Me: " + input.value); // write ourselves
socket.Emit("chat", input.value);// send chat event data to the websocket server
input.value = ""; // clear the input
}
function addMessage(msg) {
output.innerHTML += msg + "\n";
}
</script>

View File

@ -1,96 +0,0 @@
package main
import (
"fmt"
"sync"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
)
type clientPage struct {
Title string
Host string
}
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./templates", ".html")) // select the html engine to serve templates
ws := websocket.New(websocket.Config{})
// register the server on an endpoint.
// see the inline javascript code i the websockets.html, this endpoint is used to connect to the server.
app.Get("/my_endpoint", ws.Handler())
// serve the javascript builtin client-side library,
// see websockets.html script tags, this path is used.
app.Any("/iris-ws.js", func(ctx iris.Context) {
ctx.Write(websocket.ClientSource)
})
app.StaticWeb("/js", "./static/js") // serve our custom javascript code
app.Get("/", func(ctx iris.Context) {
ctx.ViewData("", clientPage{"Client Page", "localhost:8080"})
ctx.View("client.html")
})
Conn := make(map[websocket.Connection]bool)
var myChatRoom = "room1"
var mutex = new(sync.Mutex)
ws.OnConnection(func(c websocket.Connection) {
c.Join(myChatRoom)
mutex.Lock()
Conn[c] = true
mutex.Unlock()
c.On("chat", func(message string) {
if message == "leave" {
c.Leave(myChatRoom)
c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
return
}
})
c.OnDisconnect(func() {
mutex.Lock()
delete(Conn, c)
mutex.Unlock()
fmt.Printf("\nConnection with ID: %s has been disconnected!\n", c.ID())
})
})
var delay = 1 * time.Second
go func() {
i := 0
for {
mutex.Lock()
broadcast(Conn, fmt.Sprintf("aaaa %d\n", i))
mutex.Unlock()
time.Sleep(delay)
i++
}
}()
go func() {
i := 0
for range time.Tick(1 * time.Second) { //another way to get clock signal
mutex.Lock()
broadcast(Conn, fmt.Sprintf("aaaa2 %d\n", i))
mutex.Unlock()
time.Sleep(delay)
i++
}
}()
app.Run(iris.Addr(":8080"))
}
func broadcast(Conn map[websocket.Connection]bool, message string) {
for k := range Conn {
k.To("room1").Emit("chat", message)
}
}

View File

@ -1,38 +0,0 @@
var messageTxt;
var messages;
$(function () {
messageTxt = $("#messageTxt");
messages = $("#messages");
w = new Ws("ws://" + HOST + "/my_endpoint");
w.OnConnect(function () {
console.log("Websocket connection established");
});
w.OnDisconnect(function () {
appendMessage($("<div><center><h3>Disconnected</h3></center></div>"));
});
w.On("chat", function (message) {
appendMessage($("<div>" + message + "</div>"));
});
$("#sendBtn").click(function () {
w.Emit("chat", messageTxt.val().toString());
messageTxt.val("");
});
})
function appendMessage(messageDiv) {
var theDiv = messages[0];
var doScroll = theDiv.scrollTop == theDiv.scrollHeight - theDiv.clientHeight;
messageDiv.appendTo(messages);
if (doScroll) {
theDiv.scrollTop = theDiv.scrollHeight - theDiv.clientHeight;
}
}

View File

@ -1,24 +0,0 @@
<html>
<head>
<title>{{ .Title}}</title>
</head>
<body>
<div id="messages"
style="border-width: 1px; border-style: solid; height: 400px; width: 375px;">
</div>
<input type="text" id="messageTxt" />
<button type="button" id="sendBtn">Send</button>
<script type="text/javascript">
var HOST = {{.Host}}
</script>
<script src="js/vendor/jquery-2.2.3.min.js" type="text/javascript"></script>
<!-- This is auto-serving by the iris, you don't need to have this file in your disk-->
<script src="/iris-ws.js" type="text/javascript"></script>
<!-- -->
<script src="js/chat.js" type="text/javascript"></script>
</body>
</html>

View File

@ -1,179 +0,0 @@
package main
// Run first `go run main.go server`
// and `go run main.go client` as many times as you want.
// Originally written by: github.com/antlaw to describe an old issue.
import (
"fmt"
"os"
"strings"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
xwebsocket "golang.org/x/net/websocket"
)
// WS is the current websocket connection
var WS *xwebsocket.Conn
// $ go run main.go server
// $ go run main.go client
func main() {
if len(os.Args) == 2 && strings.ToLower(os.Args[1]) == "server" {
ServerLoop()
} else if len(os.Args) == 2 && strings.ToLower(os.Args[1]) == "client" {
ClientLoop()
} else {
fmt.Println("wsserver [server|client]")
}
}
/////////////////////////////////////////////////////////////////////////
// client side
func sendUntilErr(sendInterval int) {
i := 1
for {
time.Sleep(time.Duration(sendInterval) * time.Second)
err := SendMessage("2", "all", "objectupdate", "2.UsrSchedule_v1_1")
if err != nil {
fmt.Println("failed to send join message", err.Error())
return
}
fmt.Println("objectupdate", i)
i++
}
}
func recvUntilErr() {
var msg = make([]byte, 2048)
var n int
var err error
i := 1
for {
if n, err = WS.Read(msg); err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("%v Received: %s.%v\n", time.Now(), string(msg[:n]), i)
i++
}
}
//ConnectWebSocket connect a websocket to host
func ConnectWebSocket() error {
var origin = "http://localhost/"
var url = "ws://localhost:8080/socket"
var err error
WS, err = xwebsocket.Dial(url, "", origin)
return err
}
// CloseWebSocket closes the current websocket connection
func CloseWebSocket() error {
if WS != nil {
return WS.Close()
}
return nil
}
// SendMessage broadcast a message to server
func SendMessage(serverID, to, method, message string) error {
buffer := []byte(message)
return SendtBytes(serverID, to, method, buffer)
}
// SendtBytes broadcast a message to server
func SendtBytes(serverID, to, method string, message []byte) error {
// look https://github.com/kataras/iris/blob/master/websocket/message.go , client.js.go and client.js
// to understand the buffer line:
buffer := []byte(fmt.Sprintf("%s%v;0;%v;%v;", websocket.DefaultEvtMessageKey, method, serverID, to))
buffer = append(buffer, message...)
_, err := WS.Write(buffer)
if err != nil {
fmt.Println(err)
return err
}
return nil
}
// ClientLoop connects to websocket server, the keep send and recv dataS
func ClientLoop() {
for {
time.Sleep(time.Second)
err := ConnectWebSocket()
if err != nil {
fmt.Println("failed to connect websocket", err.Error())
continue
}
// time.Sleep(time.Second)
err = SendMessage("2", "all", "join", "dummy2")
go sendUntilErr(1)
recvUntilErr()
err = CloseWebSocket()
if err != nil {
fmt.Println("failed to close websocket", err.Error())
}
}
}
/////////////////////////////////////////////////////////////////////////
// server side
// OnConnect handles incoming websocket connection
func OnConnect(c websocket.Connection) {
fmt.Println("socket.OnConnect()")
c.On("join", func(message string) { OnJoin(message, c) })
c.On("objectupdate", func(message string) { OnObjectUpdated(message, c) })
// ok works too c.EmitMessage([]byte("dsadsa"))
c.OnDisconnect(func() { OnDisconnect(c) })
}
// ServerLoop listen and serve websocket requests
func ServerLoop() {
app := iris.New()
ws := websocket.New(websocket.Config{})
// register the server on an endpoint.
// see the inline javascript code i the websockets.html, this endpoint is used to connect to the server.
app.Get("/socket", ws.Handler())
ws.OnConnection(OnConnect)
app.Run(iris.Addr(":8080"))
}
// OnJoin handles Join broadcast group request
func OnJoin(message string, c websocket.Connection) {
t := time.Now()
c.Join("server2")
fmt.Println("OnJoin() time taken:", time.Since(t))
}
// OnObjectUpdated broadcasts to all client an incoming message
func OnObjectUpdated(message string, c websocket.Connection) {
t := time.Now()
s := strings.Split(message, ";")
if len(s) != 3 {
fmt.Println("OnObjectUpdated() invalid message format:" + message)
return
}
serverID, _, objectID := s[0], s[1], s[2]
err := c.To("server"+serverID).Emit("objectupdate", objectID)
if err != nil {
fmt.Println(err, "failed to broacast object")
return
}
fmt.Println(fmt.Sprintf("OnObjectUpdated() message:%v, time taken: %v", message, time.Since(t)))
}
// OnDisconnect clean up things when a client is disconnected
func OnDisconnect(c websocket.Connection) {
c.Leave("server2")
fmt.Println("OnDisconnect(): client disconnected!")
}

View File

@ -1,4 +0,0 @@
@echo off
REM run.bat 30
start go run main.go server
for /L %%n in (1,1,%1) do start go run main.go client

View File

@ -1,58 +0,0 @@
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() {
c, err := websocket.Dial(nil, url, websocket.ConnectionConfig{})
if err != nil {
panic(err)
}
c.OnError(func(err error) {
fmt.Printf("error: %v", err)
})
c.OnDisconnect(func() {
fmt.Println("Server was force-closed[see ../server/main.go#L17] this connection after 20 seconds, therefore I am disconnected.")
os.Exit(0)
})
c.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
}
c.Emit("chat", msgToSend)
}
fmt.Println("Terminated.")
}

View File

@ -1,32 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
)
func main() {
app := iris.New()
ws := websocket.New(websocket.Config{})
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.Get("/socket", ws.Handler())
app.Run(iris.Addr(":8080"))
}

54
go.mod
View File

@ -1,57 +1,29 @@
module github.com/kataras/iris
go 1.12
require (
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/Joker/jade v1.0.0
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f // indirect
github.com/aymerick/raymond v2.0.2+incompatible
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.5.4
github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
github.com/etcd-io/bbolt v1.3.0
github.com/fatih/structs v1.1.0
github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0
github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/go-querystring v1.0.0 // indirect
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/go-version v1.0.0
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164
github.com/iris-contrib/blackfriday v2.0.0+incompatible
github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1
github.com/iris-contrib/go.uuid v2.0.0+incompatible
github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0
github.com/json-iterator/go v1.1.5
github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea // indirect
github.com/json-iterator/go v1.1.6
github.com/kataras/golog v0.0.0-20180321173939-03be10146386
github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
github.com/klauspost/compress v1.4.1
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/moul/http2curl v1.0.0 // indirect
github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.2
github.com/ryanuber/columnize v2.1.0+incompatible
github.com/sergi/go-diff v1.0.0 // indirect
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
github.com/stretchr/testify v1.2.2 // indirect
github.com/valyala/bytebufferpool v1.0.0
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/ini.v1 v1.39.0 // indirect
gopkg.in/yaml.v2 v2.2.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190602112858-2de7f9bf822c // indirect
gopkg.in/yaml.v2 v2.2.2
)

93
go.sum
View File

@ -1,4 +1,3 @@
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Joker/hpp v0.0.0-20180418125244-6893e659854a/go.mod h1:MzD2WMdSxvbHw5fM/OXOFily/lipJWRc9C1px0Mt0ZE=
@ -6,77 +5,63 @@ github.com/Joker/jade v1.0.0 h1:lOCEPvTAtWfLpSZYMOv/g44MGQFAolbKh2khHHGu0Kc=
github.com/Joker/jade v1.0.0/go.mod h1:efZIdO0py/LtcJRSa/j2WEklMSAw84WV0zZVMxNToB8=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/etcd-io/bbolt v1.3.0/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 h1:ZHx2BEERvWkuwuE7qWN9TuRxucHDH2JrsvneZjVJfo0=
github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg=
github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164 h1:/HMcOGZC5Bi8JPgfbwz13ELWn/91+vY59HXS3z0qY5w=
github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164/go.mod h1:tbAXHifHQWNSpWbiJHpJTZH5fi3XHhDMdP//vuz9WS4=
github.com/go-check/check v1.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.0 h1:1WdyfgUcImUfVBvYbsW2krIsnko+1QU2t45soaF8v1M=
github.com/gobwas/ws v1.0.0/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1 h1:7GsNnSLoVceNylMpwcfy5aFNz/S5/TV25crb34I5PEo=
github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w=
github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce/go.mod h1:VER17o2JZqquOx41avolD/wMGQSFEFBKWmhag9/RQRY=
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea h1:g2k+8WR7cHch4g0tBDhfiEvAp7fXxTNBiD1oC1Oxj3E=
github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA=
github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M=
github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6 h1:Kt26efzwR6OeuQ9IO8ufl6MjoJRvl0P6/fSnzHrW638=
github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6/go.mod h1:q/Hkityxm91OTjAXtQDTgaNhIrAe7JcDVDkvqSP+YGE=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190602112858-2de7f9bf822c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,4 +0,0 @@
# This is the official list of Iris Websocket authors for copyright
# purposes.
Gerasimos Maropoulos <kataras2006@hotmail.com>

View File

@ -1,27 +0,0 @@
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
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Iris nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,208 +0,0 @@
var websocketStringMessageType = 0;
var websocketIntMessageType = 1;
var websocketBoolMessageType = 2;
var websocketJSONMessageType = 4;
var websocketMessagePrefix = "iris-websocket-message:";
var websocketMessageSeparator = ";";
var websocketMessagePrefixLen = websocketMessagePrefix.length;
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
var Ws = (function () {
function Ws(endpoint, protocols) {
var _this = this;
// events listeners
this.connectListeners = [];
this.disconnectListeners = [];
this.nativeMessageListeners = [];
this.messageListeners = {};
if (!window["WebSocket"]) {
return;
}
if (endpoint.indexOf("ws") == -1) {
endpoint = "ws://" + endpoint;
}
if (protocols != null && protocols.length > 0) {
this.conn = new WebSocket(endpoint, protocols);
}
else {
this.conn = new WebSocket(endpoint);
}
this.conn.onopen = (function (evt) {
_this.fireConnect();
_this.isReady = true;
return null;
});
this.conn.onclose = (function (evt) {
_this.fireDisconnect();
return null;
});
this.conn.onmessage = (function (evt) {
_this.messageReceivedFromConn(evt);
});
}
//utils
Ws.prototype.isNumber = function (obj) {
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
};
Ws.prototype.isString = function (obj) {
return Object.prototype.toString.call(obj) == "[object String]";
};
Ws.prototype.isBoolean = function (obj) {
return typeof obj === 'boolean' ||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
};
Ws.prototype.isJSON = function (obj) {
return typeof obj === 'object';
};
//
// messages
Ws.prototype._msg = function (event, websocketMessageType, dataMessage) {
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
};
Ws.prototype.encodeMessage = function (event, data) {
var m = "";
var t = 0;
if (this.isNumber(data)) {
t = websocketIntMessageType;
m = data.toString();
}
else if (this.isBoolean(data)) {
t = websocketBoolMessageType;
m = data.toString();
}
else if (this.isString(data)) {
t = websocketStringMessageType;
m = data.toString();
}
else if (this.isJSON(data)) {
//propably json-object
t = websocketJSONMessageType;
m = JSON.stringify(data);
}
else if (data !== null && typeof(data) !== "undefined" ) {
// if it has a second parameter but it's not a type we know, then fire this:
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
}
return this._msg(event, t, m);
};
Ws.prototype.decodeMessage = function (event, websocketMessage) {
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
if (websocketMessage.length < skipLen + 1) {
return null;
}
var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
if (websocketMessageType == websocketIntMessageType) {
return parseInt(theMessage);
}
else if (websocketMessageType == websocketBoolMessageType) {
return Boolean(theMessage);
}
else if (websocketMessageType == websocketStringMessageType) {
return theMessage;
}
else if (websocketMessageType == websocketJSONMessageType) {
return JSON.parse(theMessage);
}
else {
return null; // invalid
}
};
Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) {
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
return "";
}
var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
var evt = s.substring(0, s.indexOf(websocketMessageSeparator));
return evt;
};
Ws.prototype.getCustomMessage = function (event, websocketMessage) {
var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
return s;
};
//
// Ws Events
// messageReceivedFromConn this is the func which decides
// if it's a native websocket message or a custom qws message
// if native message then calls the fireNativeMessage
// else calls the fireMessage
//
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
Ws.prototype.messageReceivedFromConn = function (evt) {
//check if qws message
var message = evt.data;
if (message.indexOf(websocketMessagePrefix) != -1) {
var event_1 = this.getWebsocketCustomEvent(message);
if (event_1 != "") {
// it's a custom message
this.fireMessage(event_1, this.getCustomMessage(event_1, message));
return;
}
}
// it's a native websocket message
this.fireNativeMessage(message);
};
Ws.prototype.OnConnect = function (fn) {
if (this.isReady) {
fn();
}
this.connectListeners.push(fn);
};
Ws.prototype.fireConnect = function () {
for (var i = 0; i < this.connectListeners.length; i++) {
this.connectListeners[i]();
}
};
Ws.prototype.OnDisconnect = function (fn) {
this.disconnectListeners.push(fn);
};
Ws.prototype.fireDisconnect = function () {
for (var i = 0; i < this.disconnectListeners.length; i++) {
this.disconnectListeners[i]();
}
};
Ws.prototype.OnMessage = function (cb) {
this.nativeMessageListeners.push(cb);
};
Ws.prototype.fireNativeMessage = function (websocketMessage) {
for (var i = 0; i < this.nativeMessageListeners.length; i++) {
this.nativeMessageListeners[i](websocketMessage);
}
};
Ws.prototype.On = function (event, cb) {
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
this.messageListeners[event] = [];
}
this.messageListeners[event].push(cb);
};
Ws.prototype.fireMessage = function (event, message) {
for (var key in this.messageListeners) {
if (this.messageListeners.hasOwnProperty(key)) {
if (key == event) {
for (var i = 0; i < this.messageListeners[key].length; i++) {
this.messageListeners[key][i](message);
}
}
}
}
};
//
// Ws Actions
Ws.prototype.Disconnect = function () {
this.conn.close();
};
// EmitMessage sends a native websocket message
Ws.prototype.EmitMessage = function (websocketMessage) {
this.conn.send(websocketMessage);
};
// Emit sends an iris-custom websocket message
Ws.prototype.Emit = function (event, data) {
var messageStr = this.encodeMessage(event, data);
this.EmitMessage(messageStr);
};
return Ws;
}());

View File

@ -1,233 +0,0 @@
package websocket
import (
"time"
"github.com/kataras/iris/context"
)
// ClientHandler is the handler which serves the javascript client-side
// library. It uses a small cache based on the iris/context.WriteWithExpiration.
func ClientHandler() context.Handler {
modNow := time.Now()
return func(ctx context.Context) {
ctx.ContentType("application/javascript")
if _, err := ctx.WriteWithExpiration(ClientSource, modNow); err != nil {
ctx.StatusCode(500)
ctx.StopExecution()
// ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error())
}
}
}
// ClientSource the client-side javascript raw source code.
var ClientSource = []byte(`var websocketStringMessageType = 0;
var websocketIntMessageType = 1;
var websocketBoolMessageType = 2;
var websocketJSONMessageType = 4;
var websocketMessagePrefix = "` + DefaultEvtMessageKey + `";
var websocketMessageSeparator = ";";
var websocketMessagePrefixLen = websocketMessagePrefix.length;
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
var Ws = (function () {
//
function Ws(endpoint, protocols) {
var _this = this;
// events listeners
this.connectListeners = [];
this.disconnectListeners = [];
this.nativeMessageListeners = [];
this.messageListeners = {};
if (!window["WebSocket"]) {
return;
}
if (endpoint.indexOf("ws") == -1) {
endpoint = "ws://" + endpoint;
}
if (protocols != null && protocols.length > 0) {
this.conn = new WebSocket(endpoint, protocols);
}
else {
this.conn = new WebSocket(endpoint);
}
this.conn.onopen = (function (evt) {
_this.fireConnect();
_this.isReady = true;
return null;
});
this.conn.onclose = (function (evt) {
_this.fireDisconnect();
return null;
});
this.conn.onmessage = (function (evt) {
_this.messageReceivedFromConn(evt);
});
}
//utils
Ws.prototype.isNumber = function (obj) {
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
};
Ws.prototype.isString = function (obj) {
return Object.prototype.toString.call(obj) == "[object String]";
};
Ws.prototype.isBoolean = function (obj) {
return typeof obj === 'boolean' ||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
};
Ws.prototype.isJSON = function (obj) {
return typeof obj === 'object';
};
//
// messages
Ws.prototype._msg = function (event, websocketMessageType, dataMessage) {
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
};
Ws.prototype.encodeMessage = function (event, data) {
var m = "";
var t = 0;
if (this.isNumber(data)) {
t = websocketIntMessageType;
m = data.toString();
}
else if (this.isBoolean(data)) {
t = websocketBoolMessageType;
m = data.toString();
}
else if (this.isString(data)) {
t = websocketStringMessageType;
m = data.toString();
}
else if (this.isJSON(data)) {
//propably json-object
t = websocketJSONMessageType;
m = JSON.stringify(data);
}
else if (data !== null && typeof(data) !== "undefined" ) {
// if it has a second parameter but it's not a type we know, then fire this:
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
}
return this._msg(event, t, m);
};
Ws.prototype.decodeMessage = function (event, websocketMessage) {
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
if (websocketMessage.length < skipLen + 1) {
return null;
}
var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
if (websocketMessageType == websocketIntMessageType) {
return parseInt(theMessage);
}
else if (websocketMessageType == websocketBoolMessageType) {
return Boolean(theMessage);
}
else if (websocketMessageType == websocketStringMessageType) {
return theMessage;
}
else if (websocketMessageType == websocketJSONMessageType) {
return JSON.parse(theMessage);
}
else {
return null; // invalid
}
};
Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) {
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
return "";
}
var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
var evt = s.substring(0, s.indexOf(websocketMessageSeparator));
return evt;
};
Ws.prototype.getCustomMessage = function (event, websocketMessage) {
var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
return s;
};
//
// Ws Events
// messageReceivedFromConn this is the func which decides
// if it's a native websocket message or a custom qws message
// if native message then calls the fireNativeMessage
// else calls the fireMessage
//
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
Ws.prototype.messageReceivedFromConn = function (evt) {
//check if qws message
var message = evt.data;
if (message.indexOf(websocketMessagePrefix) != -1) {
var event_1 = this.getWebsocketCustomEvent(message);
if (event_1 != "") {
// it's a custom message
this.fireMessage(event_1, this.getCustomMessage(event_1, message));
return;
}
}
// it's a native websocket message
this.fireNativeMessage(message);
};
Ws.prototype.OnConnect = function (fn) {
if (this.isReady) {
fn();
}
this.connectListeners.push(fn);
};
Ws.prototype.fireConnect = function () {
for (var i = 0; i < this.connectListeners.length; i++) {
this.connectListeners[i]();
}
};
Ws.prototype.OnDisconnect = function (fn) {
this.disconnectListeners.push(fn);
};
Ws.prototype.fireDisconnect = function () {
for (var i = 0; i < this.disconnectListeners.length; i++) {
this.disconnectListeners[i]();
}
};
Ws.prototype.OnMessage = function (cb) {
this.nativeMessageListeners.push(cb);
};
Ws.prototype.fireNativeMessage = function (websocketMessage) {
for (var i = 0; i < this.nativeMessageListeners.length; i++) {
this.nativeMessageListeners[i](websocketMessage);
}
};
Ws.prototype.On = function (event, cb) {
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
this.messageListeners[event] = [];
}
this.messageListeners[event].push(cb);
};
Ws.prototype.fireMessage = function (event, message) {
for (var key in this.messageListeners) {
if (this.messageListeners.hasOwnProperty(key)) {
if (key == event) {
for (var i = 0; i < this.messageListeners[key].length; i++) {
this.messageListeners[key][i](message);
}
}
}
}
};
//
// Ws Actions
Ws.prototype.Disconnect = function () {
this.conn.close();
};
// EmitMessage sends a native websocket message
Ws.prototype.EmitMessage = function (websocketMessage) {
this.conn.send(websocketMessage);
};
// Emit sends an iris-custom websocket message
Ws.prototype.Emit = function (event, data) {
var messageStr = this.encodeMessage(event, data);
this.EmitMessage(messageStr);
};
return Ws;
}());
`)

View File

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

View File

@ -1,256 +0,0 @@
// export to client.js.go:ClientSource []byte
const websocketStringMessageType = 0;
const websocketIntMessageType = 1;
const websocketBoolMessageType = 2;
// bytes is missing here for reasons I will explain somewhen
const websocketJSONMessageType = 4;
const websocketMessagePrefix = "iris-websocket-message:";
const websocketMessageSeparator = ";";
const websocketMessagePrefixLen = websocketMessagePrefix.length;
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
type onConnectFunc = () => void;
type onWebsocketDisconnectFunc = () => void;
type onWebsocketNativeMessageFunc = (websocketMessage: string) => void;
type onMessageFunc = (message: any) => void;
class Ws {
private conn: WebSocket;
private isReady: boolean;
// events listeners
private connectListeners: onConnectFunc[] = [];
private disconnectListeners: onWebsocketDisconnectFunc[] = [];
private nativeMessageListeners: onWebsocketNativeMessageFunc[] = [];
private messageListeners: { [event: string]: onMessageFunc[] } = {};
//
constructor(endpoint: string, protocols?: string[]) {
if (!window["WebSocket"]) {
return;
}
if (endpoint.indexOf("ws") == -1) {
endpoint = "ws://" + endpoint;
}
if (protocols != null && protocols.length > 0) {
this.conn = new WebSocket(endpoint, protocols);
} else {
this.conn = new WebSocket(endpoint);
}
this.conn.onopen = ((evt: Event): any => {
this.fireConnect();
this.isReady = true;
return null;
});
this.conn.onclose = ((evt: Event): any => {
this.fireDisconnect();
return null;
});
this.conn.onmessage = ((evt: MessageEvent) => {
this.messageReceivedFromConn(evt);
});
}
//utils
private isNumber(obj: any): boolean {
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
}
private isString(obj: any): boolean {
return Object.prototype.toString.call(obj) == "[object String]";
}
private isBoolean(obj: any): boolean {
return typeof obj === 'boolean' ||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
}
private isJSON(obj: any): boolean {
return typeof obj === 'object';
}
//
// messages
private _msg(event: string, websocketMessageType: number, dataMessage: string): string {
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
}
private encodeMessage(event: string, data: any): string {
let m = "";
let t = 0;
if (this.isNumber(data)) {
t = websocketIntMessageType;
m = data.toString();
} else if (this.isBoolean(data)) {
t = websocketBoolMessageType;
m = data.toString();
} else if (this.isString(data)) {
t = websocketStringMessageType;
m = data.toString();
} else if (this.isJSON(data)) {
//propably json-object
t = websocketJSONMessageType;
m = JSON.stringify(data);
} else if (data !== null && typeof (data) !== "undefined") {
// if it has a second parameter but it's not a type we know, then fire this:
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
}
return this._msg(event, t, m);
}
private decodeMessage<T>(event: string, websocketMessage: string): T | any {
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
let skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
if (websocketMessage.length < skipLen + 1) {
return null;
}
let websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
let theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
if (websocketMessageType == websocketIntMessageType) {
return parseInt(theMessage);
} else if (websocketMessageType == websocketBoolMessageType) {
return Boolean(theMessage);
} else if (websocketMessageType == websocketStringMessageType) {
return theMessage;
} else if (websocketMessageType == websocketJSONMessageType) {
return JSON.parse(theMessage);
} else {
return null; // invalid
}
}
private getWebsocketCustomEvent(websocketMessage: string): string {
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
return "";
}
let s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
let evt = s.substring(0, s.indexOf(websocketMessageSeparator));
return evt;
}
private getCustomMessage(event: string, websocketMessage: string): string {
let eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
let s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
return s;
}
//
// Ws Events
// messageReceivedFromConn this is the func which decides
// if it's a native websocket message or a custom qws message
// if native message then calls the fireNativeMessage
// else calls the fireMessage
//
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
private messageReceivedFromConn(evt: MessageEvent): void {
//check if qws message
let message = <string>evt.data;
if (message.indexOf(websocketMessagePrefix) != -1) {
let event = this.getWebsocketCustomEvent(message);
if (event != "") {
// it's a custom message
this.fireMessage(event, this.getCustomMessage(event, message));
return;
}
}
// it's a native websocket message
this.fireNativeMessage(message);
}
OnConnect(fn: onConnectFunc): void {
if (this.isReady) {
fn();
}
this.connectListeners.push(fn);
}
fireConnect(): void {
for (let i = 0; i < this.connectListeners.length; i++) {
this.connectListeners[i]();
}
}
OnDisconnect(fn: onWebsocketDisconnectFunc): void {
this.disconnectListeners.push(fn);
}
fireDisconnect(): void {
for (let i = 0; i < this.disconnectListeners.length; i++) {
this.disconnectListeners[i]();
}
}
OnMessage(cb: onWebsocketNativeMessageFunc): void {
this.nativeMessageListeners.push(cb);
}
fireNativeMessage(websocketMessage: string): void {
for (let i = 0; i < this.nativeMessageListeners.length; i++) {
this.nativeMessageListeners[i](websocketMessage);
}
}
On(event: string, cb: onMessageFunc): void {
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
this.messageListeners[event] = [];
}
this.messageListeners[event].push(cb);
}
fireMessage(event: string, message: any): void {
for (let key in this.messageListeners) {
if (this.messageListeners.hasOwnProperty(key)) {
if (key == event) {
for (let i = 0; i < this.messageListeners[key].length; i++) {
this.messageListeners[key][i](message);
}
}
}
}
}
//
// Ws Actions
Disconnect(): void {
this.conn.close();
}
// EmitMessage sends a native websocket message
EmitMessage(websocketMessage: string): void {
this.conn.send(websocketMessage);
}
// Emit sends an iris-custom websocket message
Emit(event: string, data: any): void {
let messageStr = this.encodeMessage(event, data);
this.EmitMessage(messageStr);
}
//
}
// node-modules export {Ws};

View File

@ -1,159 +0,0 @@
package websocket
import (
"net/http"
"strconv"
"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
// DefaultWebsocketMaxMessageSize 0
DefaultWebsocketMaxMessageSize = 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 string for a new connection.
// Used when config.IDGenerator is nil.
DefaultIDGenerator = func(context.Context) string {
id, err := uuid.NewV4()
if err != nil {
return strconv.FormatInt(time.Now().Unix(), 10)
}
return id.String()
}
)
// 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).
// 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
// MaxMessageSize max message size allowed from connection.
// Default value is 1024
MaxMessageSize int64
// 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.MaxMessageSize <= 0 {
c.MaxMessageSize = DefaultWebsocketMaxMessageSize
}
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
}

View File

@ -1,689 +0,0 @@
package websocket
import (
"bytes"
stdContext "context"
"errors"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/kataras/iris/context"
)
const (
// TextMessage denotes a text data message. The text message payload is
// interpreted as UTF-8 encoded text data.
TextMessage = websocket.TextMessage
// BinaryMessage denotes a binary data message.
BinaryMessage = websocket.BinaryMessage
// CloseMessage denotes a close control message. The optional message
// payload contains a numeric code and text. Use the FormatCloseMessage
// function to format a close message payload.
CloseMessage = websocket.CloseMessage
// PingMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PingMessage = websocket.PingMessage
// PongMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PongMessage = websocket.PongMessage
)
type (
connectionValue struct {
key []byte
value interface{}
}
)
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------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 Emitter(`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)
}
// 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 int, 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,
// otherwise you don't have to call it because the `Handler()` does it automatically.
Wait()
// UnderlyingConn returns the underline gorilla websocket connection.
UnderlyingConn() *websocket.Conn
}
connection struct {
err error
underline *websocket.Conn
config ConnectionConfig
defaultMessageType int
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
server *Server
// #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(underlineConn *websocket.Conn, cfg ConnectionConfig) Connection {
return newConnection(underlineConn, cfg)
}
func newConnection(underlineConn *websocket.Conn, cfg ConnectionConfig) *connection {
cfg = cfg.Validate()
c := &connection{
underline: underlineConn,
config: cfg,
serializer: newMessageSerializer(cfg.EvtMessagePrefix),
defaultMessageType: websocket.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 = websocket.BinaryMessage
}
return c
}
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,
ReadTimeout: s.config.ReadTimeout,
PingPeriod: s.config.PingPeriod,
MaxMessageSize: s.config.MaxMessageSize,
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
}
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
}
// 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 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()
if writeTimeout := c.config.WriteTimeout; writeTimeout > 0 {
// set the write deadline based on the configuration
c.underline.SetWriteDeadline(time.Now().Add(writeTimeout))
}
// .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
}
// 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)
}
const (
// WriteWait is 1 second at the internal implementation,
// same as here but this can be changed at the future*
WriteWait = 1 * time.Second
)
func (c *connection) startPinger() {
// this is the default internal handler, we just change the writeWait because of the actions we must do before
// the server sends the ping-pong.
pingHandler := func(message string) error {
err := c.underline.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(WriteWait))
if err == websocket.ErrCloseSent {
return nil
} else if e, ok := err.(net.Error); ok && e.Temporary() {
return nil
}
return err
}
c.underline.SetPingHandler(pingHandler)
if c.config.PingPeriod > 0 {
go func() {
for {
time.Sleep(c.config.PingPeriod)
if c == nil || atomic.LoadUint32(&c.disconnected) > 0 {
// verifies if already disconnected.
return
}
//fire all OnPing methods
c.fireOnPing()
// try to ping the client, if failed then it disconnects.
err := c.Write(websocket.PingMessage, []byte{})
if err != nil {
// must stop to exit the loop and exit from the routine.
return
}
}
}()
}
}
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) startReader() {
conn := c.underline
hasReadTimeout := c.config.ReadTimeout > 0
conn.SetReadLimit(c.config.MaxMessageSize)
conn.SetPongHandler(func(s string) error {
if hasReadTimeout {
conn.SetReadDeadline(time.Now().Add(c.config.ReadTimeout))
}
//fire all OnPong methods
go c.fireOnPong()
return nil
})
defer func() {
c.Disconnect()
}()
for {
if hasReadTimeout {
// set the read deadline based on the configuration
conn.SetReadDeadline(time.Now().Add(c.config.ReadTimeout))
}
_, data, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNormalClosure) {
c.FireOnError(err)
}
return
}
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) 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,
// otherwise you don't have to call it because the `Handler()` does it automatically.
func (c *connection) Wait() {
// if c.server != nil && c.server.config.MaxConcurrentConnections > 0 {
// defer func() {
// go func() {
// c.server.threads <- struct{}{}
// }()
// }()
// }
if c.started {
return
}
c.started = true
// start the ping
c.startPinger()
// start the messages reader
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.underline.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
err = c.underline.Close()
}
if err == nil {
c.fireDisconnect()
}
return err
}
// 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
// MaxMessageSize max message size allowed from connection.
// Default value is 0. Unlimited but it is recommended to be 1024 for medium to large messages.
MaxMessageSize int64
// 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.MaxMessageSize <= 0 {
c.MaxMessageSize = DefaultWebsocketMaxMessageSize
}
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 = websocket.ErrBadHandshake
// Dial creates a new client connection.
//
// The context will be used in the request and in the Dialer.
//
// If the WebSocket handshake fails, `ErrBadHandshake` 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) {
if ctx == nil {
ctx = stdContext.Background()
}
if !strings.HasPrefix(url, "ws://") && !strings.HasPrefix(url, "wss://") {
url = "ws://" + url
}
conn, _, err := websocket.DefaultDialer.DialContext(ctx, url, nil)
if err != nil {
return nil, err
}
clientConn := WrapConnection(conn, cfg)
go clientConn.Wait()
return clientConn, nil
}

View File

@ -1,43 +0,0 @@
package websocket
const (
// All is the string which the Emitter use to send a message to all.
All = ""
// Broadcast is the string which the Emitter use to send a message to all except this connection.
Broadcast = ";to;all;except;me;"
)
type (
// Emitter is the message/or/event manager
Emitter interface {
// EmitMessage sends a native websocket message
EmitMessage([]byte) error
// Emit sends a message on a particular event
Emit(string, interface{}) error
}
emitter struct {
conn *connection
to string
}
)
var _ Emitter = &emitter{}
func newEmitter(c *connection, to string) *emitter {
return &emitter{conn: c, to: to}
}
func (e *emitter) EmitMessage(nativeMessage []byte) error {
e.conn.server.emitMessage(e.conn.id, e.to, nativeMessage)
return nil
}
func (e *emitter) Emit(event string, data interface{}) error {
message, err := e.conn.serializer.serialize(event, data)
if err != nil {
return err
}
e.EmitMessage(message)
return nil
}

View File

@ -1,182 +0,0 @@
package websocket
import (
"bytes"
"encoding/binary"
"encoding/json"
"strconv"
"github.com/kataras/iris/core/errors"
"github.com/valyala/bytebufferpool"
)
type (
messageType uint8
)
func (m messageType) String() string {
return strconv.Itoa(int(m))
}
func (m messageType) Name() string {
switch m {
case messageTypeString:
return "string"
case messageTypeInt:
return "int"
case messageTypeBool:
return "bool"
case messageTypeBytes:
return "[]byte"
case messageTypeJSON:
return "json"
default:
return "Invalid(" + m.String() + ")"
}
}
// The same values are exists on client side too.
const (
messageTypeString messageType = iota
messageTypeInt
messageTypeBool
messageTypeBytes
messageTypeJSON
)
const (
messageSeparator = ";"
)
var messageSeparatorByte = messageSeparator[0]
type messageSerializer struct {
prefix []byte
prefixLen int
separatorLen int
prefixAndSepIdx int
prefixIdx int
separatorIdx int
buf *bytebufferpool.Pool
}
func newMessageSerializer(messagePrefix []byte) *messageSerializer {
return &messageSerializer{
prefix: messagePrefix,
prefixLen: len(messagePrefix),
separatorLen: len(messageSeparator),
prefixAndSepIdx: len(messagePrefix) + len(messageSeparator) - 1,
prefixIdx: len(messagePrefix) - 1,
separatorIdx: len(messageSeparator) - 1,
buf: new(bytebufferpool.Pool),
}
}
var (
boolTrueB = []byte("true")
boolFalseB = []byte("false")
)
// websocketMessageSerialize serializes a custom websocket message from websocketServer to be delivered to the client
// returns the string form of the message
// Supported data types are: string, int, bool, bytes and JSON.
func (ms *messageSerializer) serialize(event string, data interface{}) ([]byte, error) {
b := ms.buf.Get()
b.Write(ms.prefix)
b.WriteString(event)
b.WriteByte(messageSeparatorByte)
switch v := data.(type) {
case string:
b.WriteString(messageTypeString.String())
b.WriteByte(messageSeparatorByte)
b.WriteString(v)
case int:
b.WriteString(messageTypeInt.String())
b.WriteByte(messageSeparatorByte)
binary.Write(b, binary.LittleEndian, v)
case bool:
b.WriteString(messageTypeBool.String())
b.WriteByte(messageSeparatorByte)
if v {
b.Write(boolTrueB)
} else {
b.Write(boolFalseB)
}
case []byte:
b.WriteString(messageTypeBytes.String())
b.WriteByte(messageSeparatorByte)
b.Write(v)
default:
//we suppose is json
res, err := json.Marshal(data)
if err != nil {
ms.buf.Put(b)
return nil, err
}
b.WriteString(messageTypeJSON.String())
b.WriteByte(messageSeparatorByte)
b.Write(res)
}
message := b.Bytes()
ms.buf.Put(b)
return message, nil
}
var errInvalidTypeMessage = errors.New("Type %s is invalid for message: %s")
// deserialize deserializes a custom websocket message from the client
// ex: iris-websocket-message;chat;4;themarshaledstringfromajsonstruct will return 'hello' as string
// Supported data types are: string, int, bool, bytes and JSON.
func (ms *messageSerializer) deserialize(event []byte, websocketMessage []byte) (interface{}, error) {
dataStartIdx := ms.prefixAndSepIdx + len(event) + 3
if len(websocketMessage) <= dataStartIdx {
return nil, errors.New("websocket invalid message: " + string(websocketMessage))
}
typ, err := strconv.Atoi(string(websocketMessage[ms.prefixAndSepIdx+len(event)+1 : ms.prefixAndSepIdx+len(event)+2])) // in order to iris-websocket-message;user;-> 4
if err != nil {
return nil, err
}
data := websocketMessage[dataStartIdx:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
switch messageType(typ) {
case messageTypeString:
return string(data), nil
case messageTypeInt:
msg, err := strconv.Atoi(string(data))
if err != nil {
return nil, err
}
return msg, nil
case messageTypeBool:
if bytes.Equal(data, boolTrueB) {
return true, nil
}
return false, nil
case messageTypeBytes:
return data, nil
case messageTypeJSON:
var msg interface{}
err := json.Unmarshal(data, &msg)
return msg, err
default:
return nil, errInvalidTypeMessage.Format(messageType(typ).Name(), websocketMessage)
}
}
// getWebsocketCustomEvent return empty string when the websocketMessage is native message
func (ms *messageSerializer) getWebsocketCustomEvent(websocketMessage []byte) []byte {
if len(websocketMessage) < ms.prefixAndSepIdx {
return nil
}
s := websocketMessage[ms.prefixAndSepIdx:]
evt := s[:bytes.IndexByte(s, messageSeparatorByte)]
return evt
}

View File

@ -1,395 +0,0 @@
package websocket
import (
"bytes"
"sync"
"sync/atomic"
"github.com/kataras/iris/context"
"github.com/gorilla/websocket"
)
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)
// 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 builtin 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.
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
}
)
// 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 builtin javascript client-side library look the `websocket.ClientHandler`.
func New(cfg Config) *Server {
cfg = cfg.Validate()
s := &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),
upgrader: websocket.Upgrader{
HandshakeTimeout: cfg.HandshakeTimeout,
ReadBufferSize: cfg.ReadBufferSize,
WriteBufferSize: cfg.WriteBufferSize,
Error: cfg.Error,
CheckOrigin: cfg.CheckOrigin,
Subprotocols: cfg.Subprotocols,
EnableCompression: cfg.EnableCompression,
},
}
return s
}
// 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 builtin 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 `OnConnection` 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.upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), ctx.ResponseWriter().Header())
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) 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, 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
c := newServerConnection(ctx, s, websocketConn, cid)
// add the connection to the Server's list
s.addConnection(c)
// join to itself
s.Join(c.id, c.id)
return c
}
// 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 n
}
// 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) (conns []Connection) {
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)
}
}
}
}
return
}
// 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()
// close the underline connection and return its error, if any.
err = conn.underline.Close()
s.connections.Delete(connID)
}
return
}

View File

@ -1,69 +1,107 @@
/*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 builtin 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
import (
"github.com/kataras/iris/context"
"github.com/kataras/neffos"
"github.com/kataras/neffos/gobwas"
"github.com/kataras/neffos/gorilla"
)
var (
// GorillaUpgrader is an upgrader type for the gorilla/websocket subprotocol implementation.
// Should be used on `New` to construct the websocket server.
GorillaUpgrader = gorilla.Upgrader
// GobwasUpgrader is an upgrader type for the gobwas/ws subprotocol implementation.
// Should be used on `New` to construct the websocket server.
GobwasUpgrader = gobwas.Upgrader
// DefaultGorillaUpgrader is a gorilla/websocket Upgrader with all fields set to the default values.
DefaultGorillaUpgrader = gorilla.DefaultUpgrader
// DefaultGobwasUpgrader is a gobwas/ws Upgrader with all fields set to the default values.
DefaultGobwasUpgrader = gobwas.DefaultUpgrader
// New constructs and returns a new websocket server.
// Listens to incoming connections automatically, no further action is required from the caller.
// The second parameter is the "connHandler", it can be
// filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the client-side as well,
// Use the `Conn#IsClient` on any event callback to determinate if it's a client-side connection or a server-side one.
//
// See examples for more.
New = neffos.New
// GorillaDialer is a gorilla/websocket dialer with all fields set to the default values.
GorillaDialer = gorilla.DefaultDialer
// GobwasDialer is a gobwas/ws dialer with all fields set to the default values.
GobwasDialer = gobwas.DefaultDialer
// Dial establishes a new websocket client connection.
// Context "ctx" is used for handshake timeout.
// Dialer "dial" can be either `GorillaDialer` or `GobwasDialer`,
// custom dialers can be used as well when complete the `Socket` and `Dialer` interfaces for valid client.
// URL "url" is the endpoint of the websocket server, i.e "ws://localhost:8080/echo".
// The last parameter, and the most important one is the "connHandler", it can be
// filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the server-side as well.
//
// See examples for more.
Dial = neffos.Dial
// OnNamespaceConnect is the event name which its callback is fired right before namespace connect,
// if non-nil error then the remote connection's `Conn.Connect` will fail and send that error text.
// Connection is not ready to emit data to the namespace.
OnNamespaceConnect = neffos.OnNamespaceConnect
// OnNamespaceConnected is the event name which its callback is fired after namespace successfully connected.
// Connection is ready to emit data back to the namespace.
OnNamespaceConnected = neffos.OnNamespaceConnected
// OnNamespaceDisconnect is the event name which its callback is fired when
// remote namespace disconnection or local namespace disconnection is happening.
// For server-side connections the reply matters, so if error returned then the client-side cannot disconnect yet,
// for client-side the return value does not matter.
OnNamespaceDisconnect = neffos.OnNamespaceDisconnect // if allowed to connect then it's allowed to disconnect as well.
// OnRoomJoin is the event name which its callback is fired right before room join.
OnRoomJoin = neffos.OnRoomJoin // able to check if allowed to join.
// OnRoomJoined is the event name which its callback is fired after the connection has successfully joined to a room.
OnRoomJoined = neffos.OnRoomJoined // able to broadcast messages to room.
// OnRoomLeave is the event name which its callback is fired right before room leave.
OnRoomLeave = neffos.OnRoomLeave // able to broadcast bye-bye messages to room.
// OnRoomLeft is the event name which its callback is fired after the connection has successfully left from a room.
OnRoomLeft = neffos.OnRoomLeft // if allowed to join to a room, then its allowed to leave from it.
// OnAnyEvent is the event name which its callback is fired when incoming message's event is not declared to the ConnHandler(`Events` or `Namespaces`).
OnAnyEvent = neffos.OnAnyEvent // when event no match.
// OnNativeMessage is fired on incoming native/raw websocket messages.
// If this event defined then an incoming message can pass the check (it's an invalid message format)
// with just the Message's Body filled, the Event is "OnNativeMessage" and IsNative always true.
// This event should be defined under an empty namespace in order this to work.
OnNativeMessage = neffos.OnNativeMessage
// IsSystemEvent reports whether the "event" is a system event,
// OnNamespaceConnect, OnNamespaceConnected, OnNamespaceDisconnect,
// OnRoomJoin, OnRoomJoined, OnRoomLeave and OnRoomLeft.
IsSystemEvent = neffos.IsSystemEvent
// Reply is a special type of custom error which sends a message back to the other side
// with the exact same incoming Message's Namespace (and Room if specified)
// except its body which would be the given "body".
Reply = neffos.Reply
)
// Handler returns an Iris handler to be served in a route of an Iris application.
func Handler(s *neffos.Server) context.Handler {
return func(ctx context.Context) {
s.Upgrade(ctx.ResponseWriter(), ctx.Request(), func(socket neffos.Socket) neffos.Socket {
return &socketWrapper{
Socket: socket,
ctx: ctx,
}
})
}
}
type socketWrapper struct {
neffos.Socket
ctx context.Context
}
// GetContext returns the Iris Context from a websocket connection.
func GetContext(c *neffos.Conn) context.Context {
if sw, ok := c.Socket().(*socketWrapper); ok {
return sw.ctx
}
return nil
}

View File

@ -0,0 +1,63 @@
// +build go1.9
package websocket
import (
"github.com/kataras/neffos"
)
type (
// Conn describes the main websocket connection's functionality.
// Its `Connection` will return a new `NSConn` instance.
// Each connection can connect to one or more declared namespaces.
// Each `NSConn` can join to multiple rooms.
Conn = neffos.Conn
// NSConn describes a connected connection to a specific namespace,
// it emits with the `Message.Namespace` filled and it can join to multiple rooms.
// A single `Conn` can be connected to one or more namespaces,
// each connected namespace is described by this structure.
NSConn = neffos.NSConn
// Room describes a connected connection to a room,
// emits messages with the `Message.Room` filled to the specific room
// and `Message.Namespace` to the underline `NSConn`'s namespace.
Room = neffos.Room
// CloseError can be used to send and close a remote connection in the event callback's return statement.
CloseError = neffos.CloseError
// MessageHandlerFunc is the definition type of the events' callback.
// Its error can be written to the other side on specific events,
// i.e on `OnNamespaceConnect` it will abort a remote namespace connection.
// See examples for more.
MessageHandlerFunc = neffos.MessageHandlerFunc
// Events completes the `ConnHandler` interface.
// It is a map which its key is the event name
// and its value the event's callback.
//
// Events type completes the `ConnHandler` itself therefore,
// can be used as standalone value on the `New` and `Dial` functions
// to register events on empty namespace as well.
//
// See `Namespaces`, `New` and `Dial` too.
Events = neffos.Events
// Namespaces completes the `ConnHandler` interface.
// Can be used to register one or more namespaces on the `New` and `Dial` functions.
// The key is the namespace literal and the value is the `Events`,
// a map with event names and their callbacks.
//
// See `WithTimeout`, `New` and `Dial` too.
Namespaces = neffos.Namespaces
// WithTimeout completes the `ConnHandler` interface.
// Can be used to register namespaces and events or just events on an empty namespace
// with Read and Write timeouts.
//
// See `New` and `Dial`.
WithTimeout = neffos.WithTimeout
// The Message is the structure which describes the incoming and outcoming data.
// Emitter's "body" argument is the `Message.Body` field.
// Emitter's return non-nil error is the `Message.Err` field.
// If native message sent then the `Message.Body` is filled with the body and
// when incoming native message then the `Message.Event` is the `OnNativeMessage`,
// native messages are allowed only when an empty namespace("") and its `OnNativeMessage` callback are present.
Message = neffos.Message
)