mirror of
https://github.com/kataras/iris.git
synced 2025-03-15 12:26:29 +01:00
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:
parent
8d388fb1c6
commit
04bc21dd3b
11
.fossa.yml
Normal file
11
.fossa.yml
Normal 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
98
NOTICE
Normal 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
|
|
@ -74,7 +74,7 @@ import (
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# assume the following codes in example.go file
|
# assume the following code in example.go file
|
||||||
$ cat example.go
|
$ cat example.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
93
_examples/websocket/basic/browser/index.html
Normal file
93
_examples/websocket/basic/browser/index.html
Normal 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>
|
11
_examples/websocket/basic/browserify/README.md
Normal file
11
_examples/websocket/basic/browserify/README.md
Normal 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>.
|
61
_examples/websocket/basic/browserify/app.js
Normal file
61
_examples/websocket/basic/browserify/app.js
Normal 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();
|
||||||
|
|
1
_examples/websocket/basic/browserify/bundle.js
Normal file
1
_examples/websocket/basic/browserify/bundle.js
Normal file
File diff suppressed because one or more lines are too long
10
_examples/websocket/basic/browserify/index.html
Normal file
10
_examples/websocket/basic/browserify/index.html
Normal 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>
|
16
_examples/websocket/basic/browserify/package.json
Normal file
16
_examples/websocket/basic/browserify/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
85
_examples/websocket/basic/go-client/client.go
Normal file
85
_examples/websocket/basic/go-client/client.go
Normal 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.
|
53
_examples/websocket/basic/server.go
Normal file
53
_examples/websocket/basic/server.go
Normal 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"))
|
||||||
|
}
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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!")
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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.")
|
|
||||||
}
|
|
|
@ -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
54
go.mod
|
@ -1,57 +1,29 @@
|
||||||
module github.com/kataras/iris
|
module github.com/kataras/iris
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect
|
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/Joker/jade v1.0.0
|
github.com/Joker/jade v1.0.0
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398
|
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/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/eknkc/amber v0.0.0-20171010120322-cdade1c07385
|
||||||
github.com/etcd-io/bbolt v1.3.0
|
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0
|
github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164
|
||||||
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/iris-contrib/blackfriday v2.0.0+incompatible
|
github.com/iris-contrib/blackfriday v2.0.0+incompatible
|
||||||
github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1
|
github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1
|
||||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible
|
github.com/iris-contrib/go.uuid v2.0.0+incompatible
|
||||||
github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce
|
github.com/json-iterator/go v1.1.6
|
||||||
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/kataras/golog v0.0.0-20180321173939-03be10146386
|
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/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
|
||||||
github.com/klauspost/compress v1.4.1
|
github.com/microcosm-cc/bluemonday v1.0.2
|
||||||
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/ryanuber/columnize v2.1.0+incompatible
|
github.com/ryanuber/columnize v2.1.0+incompatible
|
||||||
github.com/sergi/go-diff v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
|
||||||
github.com/stretchr/testify v1.2.2 // indirect
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0
|
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
golang.org/x/tools v0.0.0-20190602112858-2de7f9bf822c // indirect
|
||||||
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 // indirect
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
93
go.sum
93
go.sum
|
@ -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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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=
|
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/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 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
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 h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
|
||||||
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
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 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
|
||||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
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 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
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-20190505152737-8914e1cf9164 h1:/HMcOGZC5Bi8JPgfbwz13ELWn/91+vY59HXS3z0qY5w=
|
||||||
github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg=
|
github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164/go.mod h1:tbAXHifHQWNSpWbiJHpJTZH5fi3XHhDMdP//vuz9WS4=
|
||||||
github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA=
|
github.com/go-check/check v1.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
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 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
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 h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4=
|
||||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
|
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 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/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 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/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/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
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/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA=
|
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/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 h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao=
|
||||||
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
|
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/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
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/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
|
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/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 v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
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=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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=
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# This is the official list of Iris Websocket authors for copyright
|
|
||||||
# purposes.
|
|
||||||
|
|
||||||
Gerasimos Maropoulos <kataras2006@hotmail.com>
|
|
|
@ -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.
|
|
|
@ -1,208 +0,0 @@
|
||||||
var websocketStringMessageType = 0;
|
|
||||||
var websocketIntMessageType = 1;
|
|
||||||
var websocketBoolMessageType = 2;
|
|
||||||
var websocketJSONMessageType = 4;
|
|
||||||
var websocketMessagePrefix = "iris-websocket-message:";
|
|
||||||
var websocketMessageSeparator = ";";
|
|
||||||
var websocketMessagePrefixLen = websocketMessagePrefix.length;
|
|
||||||
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
|
|
||||||
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
|
|
||||||
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
|
|
||||||
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
|
|
||||||
var Ws = (function () {
|
|
||||||
function Ws(endpoint, protocols) {
|
|
||||||
var _this = this;
|
|
||||||
// events listeners
|
|
||||||
this.connectListeners = [];
|
|
||||||
this.disconnectListeners = [];
|
|
||||||
this.nativeMessageListeners = [];
|
|
||||||
this.messageListeners = {};
|
|
||||||
if (!window["WebSocket"]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (endpoint.indexOf("ws") == -1) {
|
|
||||||
endpoint = "ws://" + endpoint;
|
|
||||||
}
|
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
|
||||||
}
|
|
||||||
this.conn.onopen = (function (evt) {
|
|
||||||
_this.fireConnect();
|
|
||||||
_this.isReady = true;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onclose = (function (evt) {
|
|
||||||
_this.fireDisconnect();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onmessage = (function (evt) {
|
|
||||||
_this.messageReceivedFromConn(evt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//utils
|
|
||||||
Ws.prototype.isNumber = function (obj) {
|
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
|
||||||
};
|
|
||||||
Ws.prototype.isString = function (obj) {
|
|
||||||
return Object.prototype.toString.call(obj) == "[object String]";
|
|
||||||
};
|
|
||||||
Ws.prototype.isBoolean = function (obj) {
|
|
||||||
return typeof obj === 'boolean' ||
|
|
||||||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
|
|
||||||
};
|
|
||||||
Ws.prototype.isJSON = function (obj) {
|
|
||||||
return typeof obj === 'object';
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// messages
|
|
||||||
Ws.prototype._msg = function (event, websocketMessageType, dataMessage) {
|
|
||||||
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
|
|
||||||
};
|
|
||||||
Ws.prototype.encodeMessage = function (event, data) {
|
|
||||||
var m = "";
|
|
||||||
var t = 0;
|
|
||||||
if (this.isNumber(data)) {
|
|
||||||
t = websocketIntMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isBoolean(data)) {
|
|
||||||
t = websocketBoolMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isString(data)) {
|
|
||||||
t = websocketStringMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isJSON(data)) {
|
|
||||||
//propably json-object
|
|
||||||
t = websocketJSONMessageType;
|
|
||||||
m = JSON.stringify(data);
|
|
||||||
}
|
|
||||||
else if (data !== null && typeof(data) !== "undefined" ) {
|
|
||||||
// if it has a second parameter but it's not a type we know, then fire this:
|
|
||||||
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
|
|
||||||
}
|
|
||||||
return this._msg(event, t, m);
|
|
||||||
};
|
|
||||||
Ws.prototype.decodeMessage = function (event, websocketMessage) {
|
|
||||||
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
|
|
||||||
var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
|
|
||||||
if (websocketMessage.length < skipLen + 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
|
|
||||||
var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
|
|
||||||
if (websocketMessageType == websocketIntMessageType) {
|
|
||||||
return parseInt(theMessage);
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketBoolMessageType) {
|
|
||||||
return Boolean(theMessage);
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketStringMessageType) {
|
|
||||||
return theMessage;
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketJSONMessageType) {
|
|
||||||
return JSON.parse(theMessage);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null; // invalid
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) {
|
|
||||||
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
|
|
||||||
var evt = s.substring(0, s.indexOf(websocketMessageSeparator));
|
|
||||||
return evt;
|
|
||||||
};
|
|
||||||
Ws.prototype.getCustomMessage = function (event, websocketMessage) {
|
|
||||||
var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
|
|
||||||
var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Events
|
|
||||||
// messageReceivedFromConn this is the func which decides
|
|
||||||
// if it's a native websocket message or a custom qws message
|
|
||||||
// if native message then calls the fireNativeMessage
|
|
||||||
// else calls the fireMessage
|
|
||||||
//
|
|
||||||
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
|
|
||||||
Ws.prototype.messageReceivedFromConn = function (evt) {
|
|
||||||
//check if qws message
|
|
||||||
var message = evt.data;
|
|
||||||
if (message.indexOf(websocketMessagePrefix) != -1) {
|
|
||||||
var event_1 = this.getWebsocketCustomEvent(message);
|
|
||||||
if (event_1 != "") {
|
|
||||||
// it's a custom message
|
|
||||||
this.fireMessage(event_1, this.getCustomMessage(event_1, message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// it's a native websocket message
|
|
||||||
this.fireNativeMessage(message);
|
|
||||||
};
|
|
||||||
Ws.prototype.OnConnect = function (fn) {
|
|
||||||
if (this.isReady) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
this.connectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireConnect = function () {
|
|
||||||
for (var i = 0; i < this.connectListeners.length; i++) {
|
|
||||||
this.connectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnDisconnect = function (fn) {
|
|
||||||
this.disconnectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireDisconnect = function () {
|
|
||||||
for (var i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnMessage = function (cb) {
|
|
||||||
this.nativeMessageListeners.push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireNativeMessage = function (websocketMessage) {
|
|
||||||
for (var i = 0; i < this.nativeMessageListeners.length; i++) {
|
|
||||||
this.nativeMessageListeners[i](websocketMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.On = function (event, cb) {
|
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
|
||||||
this.messageListeners[event] = [];
|
|
||||||
}
|
|
||||||
this.messageListeners[event].push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireMessage = function (event, message) {
|
|
||||||
for (var key in this.messageListeners) {
|
|
||||||
if (this.messageListeners.hasOwnProperty(key)) {
|
|
||||||
if (key == event) {
|
|
||||||
for (var i = 0; i < this.messageListeners[key].length; i++) {
|
|
||||||
this.messageListeners[key][i](message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Actions
|
|
||||||
Ws.prototype.Disconnect = function () {
|
|
||||||
this.conn.close();
|
|
||||||
};
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
Ws.prototype.EmitMessage = function (websocketMessage) {
|
|
||||||
this.conn.send(websocketMessage);
|
|
||||||
};
|
|
||||||
// Emit sends an iris-custom websocket message
|
|
||||||
Ws.prototype.Emit = function (event, data) {
|
|
||||||
var messageStr = this.encodeMessage(event, data);
|
|
||||||
this.EmitMessage(messageStr);
|
|
||||||
};
|
|
||||||
return Ws;
|
|
||||||
}());
|
|
|
@ -1,233 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientHandler is the handler which serves the javascript client-side
|
|
||||||
// library. It uses a small cache based on the iris/context.WriteWithExpiration.
|
|
||||||
func ClientHandler() context.Handler {
|
|
||||||
modNow := time.Now()
|
|
||||||
return func(ctx context.Context) {
|
|
||||||
ctx.ContentType("application/javascript")
|
|
||||||
if _, err := ctx.WriteWithExpiration(ClientSource, modNow); err != nil {
|
|
||||||
ctx.StatusCode(500)
|
|
||||||
ctx.StopExecution()
|
|
||||||
// ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientSource the client-side javascript raw source code.
|
|
||||||
var ClientSource = []byte(`var websocketStringMessageType = 0;
|
|
||||||
var websocketIntMessageType = 1;
|
|
||||||
var websocketBoolMessageType = 2;
|
|
||||||
var websocketJSONMessageType = 4;
|
|
||||||
var websocketMessagePrefix = "` + DefaultEvtMessageKey + `";
|
|
||||||
var websocketMessageSeparator = ";";
|
|
||||||
var websocketMessagePrefixLen = websocketMessagePrefix.length;
|
|
||||||
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
|
|
||||||
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
|
|
||||||
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
|
|
||||||
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
|
|
||||||
var Ws = (function () {
|
|
||||||
//
|
|
||||||
function Ws(endpoint, protocols) {
|
|
||||||
var _this = this;
|
|
||||||
// events listeners
|
|
||||||
this.connectListeners = [];
|
|
||||||
this.disconnectListeners = [];
|
|
||||||
this.nativeMessageListeners = [];
|
|
||||||
this.messageListeners = {};
|
|
||||||
if (!window["WebSocket"]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (endpoint.indexOf("ws") == -1) {
|
|
||||||
endpoint = "ws://" + endpoint;
|
|
||||||
}
|
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
|
||||||
}
|
|
||||||
this.conn.onopen = (function (evt) {
|
|
||||||
_this.fireConnect();
|
|
||||||
_this.isReady = true;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onclose = (function (evt) {
|
|
||||||
_this.fireDisconnect();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onmessage = (function (evt) {
|
|
||||||
_this.messageReceivedFromConn(evt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//utils
|
|
||||||
Ws.prototype.isNumber = function (obj) {
|
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
|
||||||
};
|
|
||||||
Ws.prototype.isString = function (obj) {
|
|
||||||
return Object.prototype.toString.call(obj) == "[object String]";
|
|
||||||
};
|
|
||||||
Ws.prototype.isBoolean = function (obj) {
|
|
||||||
return typeof obj === 'boolean' ||
|
|
||||||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
|
|
||||||
};
|
|
||||||
Ws.prototype.isJSON = function (obj) {
|
|
||||||
return typeof obj === 'object';
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// messages
|
|
||||||
Ws.prototype._msg = function (event, websocketMessageType, dataMessage) {
|
|
||||||
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
|
|
||||||
};
|
|
||||||
Ws.prototype.encodeMessage = function (event, data) {
|
|
||||||
var m = "";
|
|
||||||
var t = 0;
|
|
||||||
if (this.isNumber(data)) {
|
|
||||||
t = websocketIntMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isBoolean(data)) {
|
|
||||||
t = websocketBoolMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isString(data)) {
|
|
||||||
t = websocketStringMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isJSON(data)) {
|
|
||||||
//propably json-object
|
|
||||||
t = websocketJSONMessageType;
|
|
||||||
m = JSON.stringify(data);
|
|
||||||
}
|
|
||||||
else if (data !== null && typeof(data) !== "undefined" ) {
|
|
||||||
// if it has a second parameter but it's not a type we know, then fire this:
|
|
||||||
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
|
|
||||||
}
|
|
||||||
return this._msg(event, t, m);
|
|
||||||
};
|
|
||||||
Ws.prototype.decodeMessage = function (event, websocketMessage) {
|
|
||||||
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
|
|
||||||
var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
|
|
||||||
if (websocketMessage.length < skipLen + 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
|
|
||||||
var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
|
|
||||||
if (websocketMessageType == websocketIntMessageType) {
|
|
||||||
return parseInt(theMessage);
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketBoolMessageType) {
|
|
||||||
return Boolean(theMessage);
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketStringMessageType) {
|
|
||||||
return theMessage;
|
|
||||||
}
|
|
||||||
else if (websocketMessageType == websocketJSONMessageType) {
|
|
||||||
return JSON.parse(theMessage);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null; // invalid
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) {
|
|
||||||
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
|
|
||||||
var evt = s.substring(0, s.indexOf(websocketMessageSeparator));
|
|
||||||
return evt;
|
|
||||||
};
|
|
||||||
Ws.prototype.getCustomMessage = function (event, websocketMessage) {
|
|
||||||
var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
|
|
||||||
var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Events
|
|
||||||
// messageReceivedFromConn this is the func which decides
|
|
||||||
// if it's a native websocket message or a custom qws message
|
|
||||||
// if native message then calls the fireNativeMessage
|
|
||||||
// else calls the fireMessage
|
|
||||||
//
|
|
||||||
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
|
|
||||||
Ws.prototype.messageReceivedFromConn = function (evt) {
|
|
||||||
//check if qws message
|
|
||||||
var message = evt.data;
|
|
||||||
if (message.indexOf(websocketMessagePrefix) != -1) {
|
|
||||||
var event_1 = this.getWebsocketCustomEvent(message);
|
|
||||||
if (event_1 != "") {
|
|
||||||
// it's a custom message
|
|
||||||
this.fireMessage(event_1, this.getCustomMessage(event_1, message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// it's a native websocket message
|
|
||||||
this.fireNativeMessage(message);
|
|
||||||
};
|
|
||||||
Ws.prototype.OnConnect = function (fn) {
|
|
||||||
if (this.isReady) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
this.connectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireConnect = function () {
|
|
||||||
for (var i = 0; i < this.connectListeners.length; i++) {
|
|
||||||
this.connectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnDisconnect = function (fn) {
|
|
||||||
this.disconnectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireDisconnect = function () {
|
|
||||||
for (var i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnMessage = function (cb) {
|
|
||||||
this.nativeMessageListeners.push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireNativeMessage = function (websocketMessage) {
|
|
||||||
for (var i = 0; i < this.nativeMessageListeners.length; i++) {
|
|
||||||
this.nativeMessageListeners[i](websocketMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.On = function (event, cb) {
|
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
|
||||||
this.messageListeners[event] = [];
|
|
||||||
}
|
|
||||||
this.messageListeners[event].push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireMessage = function (event, message) {
|
|
||||||
for (var key in this.messageListeners) {
|
|
||||||
if (this.messageListeners.hasOwnProperty(key)) {
|
|
||||||
if (key == event) {
|
|
||||||
for (var i = 0; i < this.messageListeners[key].length; i++) {
|
|
||||||
this.messageListeners[key][i](message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Actions
|
|
||||||
Ws.prototype.Disconnect = function () {
|
|
||||||
this.conn.close();
|
|
||||||
};
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
Ws.prototype.EmitMessage = function (websocketMessage) {
|
|
||||||
this.conn.send(websocketMessage);
|
|
||||||
};
|
|
||||||
// Emit sends an iris-custom websocket message
|
|
||||||
Ws.prototype.Emit = function (event, data) {
|
|
||||||
var messageStr = this.encodeMessage(event, data);
|
|
||||||
this.EmitMessage(messageStr);
|
|
||||||
};
|
|
||||||
return Ws;
|
|
||||||
}());
|
|
||||||
`)
|
|
1
websocket/client.min.js
vendored
1
websocket/client.min.js
vendored
|
@ -1 +0,0 @@
|
||||||
var websocketStringMessageType=0,websocketIntMessageType=1,websocketBoolMessageType=2,websocketJSONMessageType=4,websocketMessagePrefix="iris-websocket-message:",websocketMessageSeparator=";",websocketMessagePrefixLen=websocketMessagePrefix.length,websocketMessageSeparatorLen=websocketMessageSeparator.length,websocketMessagePrefixAndSepIdx=websocketMessagePrefixLen+websocketMessageSeparatorLen-1,websocketMessagePrefixIdx=websocketMessagePrefixLen-1,websocketMessageSeparatorIdx=websocketMessageSeparatorLen-1,Ws=function(){function e(e,s){var t=this;this.connectListeners=[],this.disconnectListeners=[],this.nativeMessageListeners=[],this.messageListeners={},window.WebSocket&&(-1==e.indexOf("ws")&&(e="ws://"+e),null!=s&&0<s.length?this.conn=new WebSocket(e,s):this.conn=new WebSocket(e),this.conn.onopen=function(e){return t.fireConnect(),t.isReady=!0,null},this.conn.onclose=function(e){return t.fireDisconnect(),null},this.conn.onmessage=function(e){t.messageReceivedFromConn(e)})}return e.prototype.isNumber=function(e){return!isNaN(e-0)&&null!==e&&""!==e&&!1!==e},e.prototype.isString=function(e){return"[object String]"==Object.prototype.toString.call(e)},e.prototype.isBoolean=function(e){return"boolean"==typeof e||"object"==typeof e&&"boolean"==typeof e.valueOf()},e.prototype.isJSON=function(e){return"object"==typeof e},e.prototype._msg=function(e,s,t){return websocketMessagePrefix+e+websocketMessageSeparator+String(s)+websocketMessageSeparator+t},e.prototype.encodeMessage=function(e,s){var t="",n=0;return this.isNumber(s)?(n=websocketIntMessageType,t=s.toString()):this.isBoolean(s)?(n=websocketBoolMessageType,t=s.toString()):this.isString(s)?(n=websocketStringMessageType,t=s.toString()):this.isJSON(s)?(n=websocketJSONMessageType,t=JSON.stringify(s)):null!=s&&console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'"),this._msg(e,n,t)},e.prototype.decodeMessage=function(e,s){var t=websocketMessagePrefixLen+websocketMessageSeparatorLen+e.length+2;if(s.length<t+1)return null;var n=parseInt(s.charAt(t-2)),o=s.substring(t,s.length);return n==websocketIntMessageType?parseInt(o):n==websocketBoolMessageType?Boolean(o):n==websocketStringMessageType?o:n==websocketJSONMessageType?JSON.parse(o):null},e.prototype.getWebsocketCustomEvent=function(e){if(e.length<websocketMessagePrefixAndSepIdx)return"";var s=e.substring(websocketMessagePrefixAndSepIdx,e.length);return s.substring(0,s.indexOf(websocketMessageSeparator))},e.prototype.getCustomMessage=function(e,s){var t=s.indexOf(e+websocketMessageSeparator);return s.substring(t+e.length+websocketMessageSeparator.length+2,s.length)},e.prototype.messageReceivedFromConn=function(e){var s=e.data;if(-1!=s.indexOf(websocketMessagePrefix)){var t=this.getWebsocketCustomEvent(s);if(""!=t)return void this.fireMessage(t,this.getCustomMessage(t,s))}this.fireNativeMessage(s)},e.prototype.OnConnect=function(e){this.isReady&&e(),this.connectListeners.push(e)},e.prototype.fireConnect=function(){for(var e=0;e<this.connectListeners.length;e++)this.connectListeners[e]()},e.prototype.OnDisconnect=function(e){this.disconnectListeners.push(e)},e.prototype.fireDisconnect=function(){for(var e=0;e<this.disconnectListeners.length;e++)this.disconnectListeners[e]()},e.prototype.OnMessage=function(e){this.nativeMessageListeners.push(e)},e.prototype.fireNativeMessage=function(e){for(var s=0;s<this.nativeMessageListeners.length;s++)this.nativeMessageListeners[s](e)},e.prototype.On=function(e,s){null!=this.messageListeners[e]&&null!=this.messageListeners[e]||(this.messageListeners[e]=[]),this.messageListeners[e].push(s)},e.prototype.fireMessage=function(e,s){for(var t in this.messageListeners)if(this.messageListeners.hasOwnProperty(t)&&t==e)for(var n=0;n<this.messageListeners[t].length;n++)this.messageListeners[t][n](s)},e.prototype.Disconnect=function(){this.conn.close()},e.prototype.EmitMessage=function(e){this.conn.send(e)},e.prototype.Emit=function(e,s){var t=this.encodeMessage(e,s);this.EmitMessage(t)},e}();
|
|
|
@ -1,256 +0,0 @@
|
||||||
// export to client.js.go:ClientSource []byte
|
|
||||||
|
|
||||||
const websocketStringMessageType = 0;
|
|
||||||
const websocketIntMessageType = 1;
|
|
||||||
const websocketBoolMessageType = 2;
|
|
||||||
// bytes is missing here for reasons I will explain somewhen
|
|
||||||
const websocketJSONMessageType = 4;
|
|
||||||
|
|
||||||
const websocketMessagePrefix = "iris-websocket-message:";
|
|
||||||
const websocketMessageSeparator = ";";
|
|
||||||
|
|
||||||
const websocketMessagePrefixLen = websocketMessagePrefix.length;
|
|
||||||
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
|
|
||||||
var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
|
|
||||||
var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
|
|
||||||
var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
|
|
||||||
|
|
||||||
type onConnectFunc = () => void;
|
|
||||||
type onWebsocketDisconnectFunc = () => void;
|
|
||||||
type onWebsocketNativeMessageFunc = (websocketMessage: string) => void;
|
|
||||||
type onMessageFunc = (message: any) => void;
|
|
||||||
|
|
||||||
class Ws {
|
|
||||||
private conn: WebSocket;
|
|
||||||
private isReady: boolean;
|
|
||||||
|
|
||||||
// events listeners
|
|
||||||
|
|
||||||
private connectListeners: onConnectFunc[] = [];
|
|
||||||
private disconnectListeners: onWebsocketDisconnectFunc[] = [];
|
|
||||||
private nativeMessageListeners: onWebsocketNativeMessageFunc[] = [];
|
|
||||||
private messageListeners: { [event: string]: onMessageFunc[] } = {};
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
constructor(endpoint: string, protocols?: string[]) {
|
|
||||||
if (!window["WebSocket"]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endpoint.indexOf("ws") == -1) {
|
|
||||||
endpoint = "ws://" + endpoint;
|
|
||||||
}
|
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
|
||||||
} else {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conn.onopen = ((evt: Event): any => {
|
|
||||||
this.fireConnect();
|
|
||||||
this.isReady = true;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conn.onclose = ((evt: Event): any => {
|
|
||||||
this.fireDisconnect();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conn.onmessage = ((evt: MessageEvent) => {
|
|
||||||
this.messageReceivedFromConn(evt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//utils
|
|
||||||
|
|
||||||
private isNumber(obj: any): boolean {
|
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isString(obj: any): boolean {
|
|
||||||
return Object.prototype.toString.call(obj) == "[object String]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private isBoolean(obj: any): boolean {
|
|
||||||
return typeof obj === 'boolean' ||
|
|
||||||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
|
|
||||||
}
|
|
||||||
|
|
||||||
private isJSON(obj: any): boolean {
|
|
||||||
return typeof obj === 'object';
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// messages
|
|
||||||
private _msg(event: string, websocketMessageType: number, dataMessage: string): string {
|
|
||||||
|
|
||||||
return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private encodeMessage(event: string, data: any): string {
|
|
||||||
let m = "";
|
|
||||||
let t = 0;
|
|
||||||
if (this.isNumber(data)) {
|
|
||||||
t = websocketIntMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isBoolean(data)) {
|
|
||||||
t = websocketBoolMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isString(data)) {
|
|
||||||
t = websocketStringMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isJSON(data)) {
|
|
||||||
//propably json-object
|
|
||||||
t = websocketJSONMessageType;
|
|
||||||
m = JSON.stringify(data);
|
|
||||||
} else if (data !== null && typeof (data) !== "undefined") {
|
|
||||||
// if it has a second parameter but it's not a type we know, then fire this:
|
|
||||||
console.log("unsupported type of input argument passed, try to not include this argument to the 'Emit'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._msg(event, t, m);
|
|
||||||
}
|
|
||||||
|
|
||||||
private decodeMessage<T>(event: string, websocketMessage: string): T | any {
|
|
||||||
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
|
|
||||||
let skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
|
|
||||||
if (websocketMessage.length < skipLen + 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
|
|
||||||
let theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
|
|
||||||
if (websocketMessageType == websocketIntMessageType) {
|
|
||||||
return parseInt(theMessage);
|
|
||||||
} else if (websocketMessageType == websocketBoolMessageType) {
|
|
||||||
return Boolean(theMessage);
|
|
||||||
} else if (websocketMessageType == websocketStringMessageType) {
|
|
||||||
return theMessage;
|
|
||||||
} else if (websocketMessageType == websocketJSONMessageType) {
|
|
||||||
return JSON.parse(theMessage);
|
|
||||||
} else {
|
|
||||||
return null; // invalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getWebsocketCustomEvent(websocketMessage: string): string {
|
|
||||||
if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
let s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
|
|
||||||
let evt = s.substring(0, s.indexOf(websocketMessageSeparator));
|
|
||||||
|
|
||||||
return evt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCustomMessage(event: string, websocketMessage: string): string {
|
|
||||||
let eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
|
|
||||||
let s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Ws Events
|
|
||||||
|
|
||||||
// messageReceivedFromConn this is the func which decides
|
|
||||||
// if it's a native websocket message or a custom qws message
|
|
||||||
// if native message then calls the fireNativeMessage
|
|
||||||
// else calls the fireMessage
|
|
||||||
//
|
|
||||||
// remember iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
|
|
||||||
private messageReceivedFromConn(evt: MessageEvent): void {
|
|
||||||
//check if qws message
|
|
||||||
let message = <string>evt.data;
|
|
||||||
if (message.indexOf(websocketMessagePrefix) != -1) {
|
|
||||||
let event = this.getWebsocketCustomEvent(message);
|
|
||||||
if (event != "") {
|
|
||||||
// it's a custom message
|
|
||||||
this.fireMessage(event, this.getCustomMessage(event, message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's a native websocket message
|
|
||||||
this.fireNativeMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnConnect(fn: onConnectFunc): void {
|
|
||||||
if (this.isReady) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
this.connectListeners.push(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireConnect(): void {
|
|
||||||
for (let i = 0; i < this.connectListeners.length; i++) {
|
|
||||||
this.connectListeners[i]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnDisconnect(fn: onWebsocketDisconnectFunc): void {
|
|
||||||
this.disconnectListeners.push(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireDisconnect(): void {
|
|
||||||
for (let i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnMessage(cb: onWebsocketNativeMessageFunc): void {
|
|
||||||
this.nativeMessageListeners.push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireNativeMessage(websocketMessage: string): void {
|
|
||||||
for (let i = 0; i < this.nativeMessageListeners.length; i++) {
|
|
||||||
this.nativeMessageListeners[i](websocketMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
On(event: string, cb: onMessageFunc): void {
|
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
|
||||||
this.messageListeners[event] = [];
|
|
||||||
}
|
|
||||||
this.messageListeners[event].push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireMessage(event: string, message: any): void {
|
|
||||||
for (let key in this.messageListeners) {
|
|
||||||
if (this.messageListeners.hasOwnProperty(key)) {
|
|
||||||
if (key == event) {
|
|
||||||
for (let i = 0; i < this.messageListeners[key].length; i++) {
|
|
||||||
this.messageListeners[key][i](message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Ws Actions
|
|
||||||
|
|
||||||
Disconnect(): void {
|
|
||||||
this.conn.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
EmitMessage(websocketMessage: string): void {
|
|
||||||
this.conn.send(websocketMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit sends an iris-custom websocket message
|
|
||||||
Emit(event: string, data: any): void {
|
|
||||||
let messageStr = this.encodeMessage(event, data);
|
|
||||||
this.EmitMessage(messageStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// node-modules export {Ws};
|
|
|
@ -1,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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
const (
|
|
||||||
// All is the string which the Emitter use to send a message to all.
|
|
||||||
All = ""
|
|
||||||
// Broadcast is the string which the Emitter use to send a message to all except this connection.
|
|
||||||
Broadcast = ";to;all;except;me;"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Emitter is the message/or/event manager
|
|
||||||
Emitter interface {
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
EmitMessage([]byte) error
|
|
||||||
// Emit sends a message on a particular event
|
|
||||||
Emit(string, interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
emitter struct {
|
|
||||||
conn *connection
|
|
||||||
to string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Emitter = &emitter{}
|
|
||||||
|
|
||||||
func newEmitter(c *connection, to string) *emitter {
|
|
||||||
return &emitter{conn: c, to: to}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emitter) EmitMessage(nativeMessage []byte) error {
|
|
||||||
e.conn.server.emitMessage(e.conn.id, e.to, nativeMessage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emitter) Emit(event string, data interface{}) error {
|
|
||||||
message, err := e.conn.serializer.serialize(event, data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.EmitMessage(message)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/core/errors"
|
|
||||||
"github.com/valyala/bytebufferpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
messageType uint8
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m messageType) String() string {
|
|
||||||
return strconv.Itoa(int(m))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m messageType) Name() string {
|
|
||||||
switch m {
|
|
||||||
case messageTypeString:
|
|
||||||
return "string"
|
|
||||||
case messageTypeInt:
|
|
||||||
return "int"
|
|
||||||
case messageTypeBool:
|
|
||||||
return "bool"
|
|
||||||
case messageTypeBytes:
|
|
||||||
return "[]byte"
|
|
||||||
case messageTypeJSON:
|
|
||||||
return "json"
|
|
||||||
default:
|
|
||||||
return "Invalid(" + m.String() + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The same values are exists on client side too.
|
|
||||||
const (
|
|
||||||
messageTypeString messageType = iota
|
|
||||||
messageTypeInt
|
|
||||||
messageTypeBool
|
|
||||||
messageTypeBytes
|
|
||||||
messageTypeJSON
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
messageSeparator = ";"
|
|
||||||
)
|
|
||||||
|
|
||||||
var messageSeparatorByte = messageSeparator[0]
|
|
||||||
|
|
||||||
type messageSerializer struct {
|
|
||||||
prefix []byte
|
|
||||||
|
|
||||||
prefixLen int
|
|
||||||
separatorLen int
|
|
||||||
prefixAndSepIdx int
|
|
||||||
prefixIdx int
|
|
||||||
separatorIdx int
|
|
||||||
|
|
||||||
buf *bytebufferpool.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMessageSerializer(messagePrefix []byte) *messageSerializer {
|
|
||||||
return &messageSerializer{
|
|
||||||
prefix: messagePrefix,
|
|
||||||
prefixLen: len(messagePrefix),
|
|
||||||
separatorLen: len(messageSeparator),
|
|
||||||
prefixAndSepIdx: len(messagePrefix) + len(messageSeparator) - 1,
|
|
||||||
prefixIdx: len(messagePrefix) - 1,
|
|
||||||
separatorIdx: len(messageSeparator) - 1,
|
|
||||||
|
|
||||||
buf: new(bytebufferpool.Pool),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
boolTrueB = []byte("true")
|
|
||||||
boolFalseB = []byte("false")
|
|
||||||
)
|
|
||||||
|
|
||||||
// websocketMessageSerialize serializes a custom websocket message from websocketServer to be delivered to the client
|
|
||||||
// returns the string form of the message
|
|
||||||
// Supported data types are: string, int, bool, bytes and JSON.
|
|
||||||
func (ms *messageSerializer) serialize(event string, data interface{}) ([]byte, error) {
|
|
||||||
b := ms.buf.Get()
|
|
||||||
b.Write(ms.prefix)
|
|
||||||
b.WriteString(event)
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
|
|
||||||
switch v := data.(type) {
|
|
||||||
case string:
|
|
||||||
b.WriteString(messageTypeString.String())
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
b.WriteString(v)
|
|
||||||
case int:
|
|
||||||
b.WriteString(messageTypeInt.String())
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
binary.Write(b, binary.LittleEndian, v)
|
|
||||||
case bool:
|
|
||||||
b.WriteString(messageTypeBool.String())
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
if v {
|
|
||||||
b.Write(boolTrueB)
|
|
||||||
} else {
|
|
||||||
b.Write(boolFalseB)
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
b.WriteString(messageTypeBytes.String())
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
b.Write(v)
|
|
||||||
default:
|
|
||||||
//we suppose is json
|
|
||||||
res, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
ms.buf.Put(b)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.WriteString(messageTypeJSON.String())
|
|
||||||
b.WriteByte(messageSeparatorByte)
|
|
||||||
b.Write(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
message := b.Bytes()
|
|
||||||
ms.buf.Put(b)
|
|
||||||
|
|
||||||
return message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidTypeMessage = errors.New("Type %s is invalid for message: %s")
|
|
||||||
|
|
||||||
// deserialize deserializes a custom websocket message from the client
|
|
||||||
// ex: iris-websocket-message;chat;4;themarshaledstringfromajsonstruct will return 'hello' as string
|
|
||||||
// Supported data types are: string, int, bool, bytes and JSON.
|
|
||||||
func (ms *messageSerializer) deserialize(event []byte, websocketMessage []byte) (interface{}, error) {
|
|
||||||
dataStartIdx := ms.prefixAndSepIdx + len(event) + 3
|
|
||||||
if len(websocketMessage) <= dataStartIdx {
|
|
||||||
return nil, errors.New("websocket invalid message: " + string(websocketMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
typ, err := strconv.Atoi(string(websocketMessage[ms.prefixAndSepIdx+len(event)+1 : ms.prefixAndSepIdx+len(event)+2])) // in order to iris-websocket-message;user;-> 4
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := websocketMessage[dataStartIdx:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
|
|
||||||
|
|
||||||
switch messageType(typ) {
|
|
||||||
case messageTypeString:
|
|
||||||
return string(data), nil
|
|
||||||
case messageTypeInt:
|
|
||||||
msg, err := strconv.Atoi(string(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
case messageTypeBool:
|
|
||||||
if bytes.Equal(data, boolTrueB) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
case messageTypeBytes:
|
|
||||||
return data, nil
|
|
||||||
case messageTypeJSON:
|
|
||||||
var msg interface{}
|
|
||||||
err := json.Unmarshal(data, &msg)
|
|
||||||
return msg, err
|
|
||||||
default:
|
|
||||||
return nil, errInvalidTypeMessage.Format(messageType(typ).Name(), websocketMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWebsocketCustomEvent return empty string when the websocketMessage is native message
|
|
||||||
func (ms *messageSerializer) getWebsocketCustomEvent(websocketMessage []byte) []byte {
|
|
||||||
if len(websocketMessage) < ms.prefixAndSepIdx {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s := websocketMessage[ms.prefixAndSepIdx:]
|
|
||||||
evt := s[:bytes.IndexByte(s, messageSeparatorByte)]
|
|
||||||
return evt
|
|
||||||
}
|
|
|
@ -1,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
|
|
||||||
}
|
|
|
@ -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
|
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
|
||||||
|
}
|
||||||
|
|
63
websocket/websocket_go19.go
Normal file
63
websocket/websocket_go19.go
Normal 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
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user