From 04bc21dd3b701d43404e640254c520b3b134a47f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 2 Jun 2019 17:49:45 +0300 Subject: [PATCH] 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 --- .fossa.yml | 11 + NOTICE | 98 +++ README.md | 2 +- _examples/websocket/basic/browser/index.html | 93 +++ .../websocket/basic/browserify/README.md | 11 + _examples/websocket/basic/browserify/app.js | 61 ++ .../websocket/basic/browserify/bundle.js | 1 + .../websocket/basic/browserify/index.html | 10 + .../websocket/basic/browserify/package.json | 16 + _examples/websocket/basic/go-client/client.go | 85 +++ _examples/websocket/basic/server.go | 53 ++ _examples/websocket/chat/main.go | 60 -- _examples/websocket/chat/websockets.html | 45 -- _examples/websocket/connectionlist/main.go | 96 --- .../connectionlist/static/js/chat.js | 38 - .../connectionlist/templates/client.html | 24 - _examples/websocket/custom-go-client/main.go | 179 ----- _examples/websocket/custom-go-client/run.bat | 4 - _examples/websocket/go-client/client/main.go | 58 -- _examples/websocket/go-client/server/main.go | 32 - go.mod | 54 +- go.sum | 93 +-- websocket/AUTHORS | 4 - websocket/LICENSE | 27 - websocket/client.js | 208 ------ websocket/client.js.go | 233 ------ websocket/client.min.js | 1 - websocket/client.ts | 256 ------- websocket/config.go | 159 ---- websocket/connection.go | 689 ------------------ websocket/emitter.go | 43 -- websocket/message.go | 182 ----- websocket/server.go | 395 ---------- websocket/websocket.go | 174 +++-- websocket/websocket_go19.go | 63 ++ 35 files changed, 661 insertions(+), 2897 deletions(-) create mode 100644 .fossa.yml create mode 100644 NOTICE create mode 100644 _examples/websocket/basic/browser/index.html create mode 100644 _examples/websocket/basic/browserify/README.md create mode 100644 _examples/websocket/basic/browserify/app.js create mode 100644 _examples/websocket/basic/browserify/bundle.js create mode 100644 _examples/websocket/basic/browserify/index.html create mode 100644 _examples/websocket/basic/browserify/package.json create mode 100644 _examples/websocket/basic/go-client/client.go create mode 100644 _examples/websocket/basic/server.go delete mode 100644 _examples/websocket/chat/main.go delete mode 100644 _examples/websocket/chat/websockets.html delete mode 100644 _examples/websocket/connectionlist/main.go delete mode 100644 _examples/websocket/connectionlist/static/js/chat.js delete mode 100644 _examples/websocket/connectionlist/templates/client.html delete mode 100644 _examples/websocket/custom-go-client/main.go delete mode 100644 _examples/websocket/custom-go-client/run.bat delete mode 100644 _examples/websocket/go-client/client/main.go delete mode 100644 _examples/websocket/go-client/server/main.go delete mode 100644 websocket/AUTHORS delete mode 100644 websocket/LICENSE delete mode 100644 websocket/client.js delete mode 100644 websocket/client.js.go delete mode 100644 websocket/client.min.js delete mode 100644 websocket/client.ts delete mode 100644 websocket/config.go delete mode 100644 websocket/connection.go delete mode 100644 websocket/emitter.go delete mode 100644 websocket/message.go delete mode 100644 websocket/server.go create mode 100644 websocket/websocket_go19.go diff --git a/.fossa.yml b/.fossa.yml new file mode 100644 index 00000000..cb3f48ee --- /dev/null +++ b/.fossa.yml @@ -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: . \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..41cb2f41 --- /dev/null +++ b/NOTICE @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index 5e450b55..51d093c9 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ import ( ## Quick start ```sh -# assume the following codes in example.go file +# assume the following code in example.go file $ cat example.go ``` diff --git a/_examples/websocket/basic/browser/index.html b/_examples/websocket/basic/browser/index.html new file mode 100644 index 00000000..2f846405 --- /dev/null +++ b/_examples/websocket/basic/browser/index.html @@ -0,0 +1,93 @@ + + + + + + + +

+
+
+
\ No newline at end of file
diff --git a/_examples/websocket/basic/browserify/README.md b/_examples/websocket/basic/browserify/README.md
new file mode 100644
index 00000000..6d8d4993
--- /dev/null
+++ b/_examples/websocket/basic/browserify/README.md
@@ -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 .
diff --git a/_examples/websocket/basic/browserify/app.js b/_examples/websocket/basic/browserify/app.js
new file mode 100644
index 00000000..967a8480
--- /dev/null
+++ b/_examples/websocket/basic/browserify/app.js
@@ -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();
+
diff --git a/_examples/websocket/basic/browserify/bundle.js b/_examples/websocket/basic/browserify/bundle.js
new file mode 100644
index 00000000..33831280
--- /dev/null
+++ b/_examples/websocket/basic/browserify/bundle.js
@@ -0,0 +1 @@
+(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ci[0]&&c[1]
+
+
+
+
+
+
+

+
+
diff --git a/_examples/websocket/basic/browserify/package.json b/_examples/websocket/basic/browserify/package.json
new file mode 100644
index 00000000..de851367
--- /dev/null
+++ b/_examples/websocket/basic/browserify/package.json
@@ -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"
+    }
+}
diff --git a/_examples/websocket/basic/go-client/client.go b/_examples/websocket/basic/go-client/client.go
new file mode 100644
index 00000000..5437ae9f
--- /dev/null
+++ b/_examples/websocket/basic/go-client/client.go
@@ -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.
diff --git a/_examples/websocket/basic/server.go b/_examples/websocket/basic/server.go
new file mode 100644
index 00000000..9c462dac
--- /dev/null
+++ b/_examples/websocket/basic/server.go
@@ -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"))
+}
diff --git a/_examples/websocket/chat/main.go b/_examples/websocket/chat/main.go
deleted file mode 100644
index 86246418..00000000
--- a/_examples/websocket/chat/main.go
+++ /dev/null
@@ -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)
-	})
-}
diff --git a/_examples/websocket/chat/websockets.html b/_examples/websocket/chat/websockets.html
deleted file mode 100644
index 3604bfd0..00000000
--- a/_examples/websocket/chat/websockets.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-

-
-
-
-
\ No newline at end of file
diff --git a/_examples/websocket/connectionlist/main.go b/_examples/websocket/connectionlist/main.go
deleted file mode 100644
index ef808f8f..00000000
--- a/_examples/websocket/connectionlist/main.go
+++ /dev/null
@@ -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)
-	}
-}
diff --git a/_examples/websocket/connectionlist/static/js/chat.js b/_examples/websocket/connectionlist/static/js/chat.js
deleted file mode 100644
index f9fb8d22..00000000
--- a/_examples/websocket/connectionlist/static/js/chat.js
+++ /dev/null
@@ -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($("

Disconnected

")); - }); - - w.On("chat", function (message) { - appendMessage($("
" + message + "
")); - }); - - $("#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; - } -} diff --git a/_examples/websocket/connectionlist/templates/client.html b/_examples/websocket/connectionlist/templates/client.html deleted file mode 100644 index b957433c..00000000 --- a/_examples/websocket/connectionlist/templates/client.html +++ /dev/null @@ -1,24 +0,0 @@ - - - -{{ .Title}} - - - -
- -
- - - - - - - - - - - diff --git a/_examples/websocket/custom-go-client/main.go b/_examples/websocket/custom-go-client/main.go deleted file mode 100644 index 7dba7e16..00000000 --- a/_examples/websocket/custom-go-client/main.go +++ /dev/null @@ -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!") - -} diff --git a/_examples/websocket/custom-go-client/run.bat b/_examples/websocket/custom-go-client/run.bat deleted file mode 100644 index 3e483188..00000000 --- a/_examples/websocket/custom-go-client/run.bat +++ /dev/null @@ -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 \ No newline at end of file diff --git a/_examples/websocket/go-client/client/main.go b/_examples/websocket/go-client/client/main.go deleted file mode 100644 index 6d3f744d..00000000 --- a/_examples/websocket/go-client/client/main.go +++ /dev/null @@ -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.") -} diff --git a/_examples/websocket/go-client/server/main.go b/_examples/websocket/go-client/server/main.go deleted file mode 100644 index 0c6bed81..00000000 --- a/_examples/websocket/go-client/server/main.go +++ /dev/null @@ -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")) -} diff --git a/go.mod b/go.mod index 3199ff89..dc3ca374 100644 --- a/go.mod +++ b/go.mod @@ -1,57 +1,29 @@ module github.com/kataras/iris +go 1.12 + require ( - github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect github.com/BurntSushi/toml v0.3.1 github.com/Joker/jade v1.0.0 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 - github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f // indirect github.com/aymerick/raymond v2.0.2+incompatible - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgraph-io/badger v1.5.4 - github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 - github.com/etcd-io/bbolt v1.3.0 github.com/fatih/structs v1.1.0 - github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 - github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d // indirect - github.com/golang/protobuf v1.2.0 // indirect - github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/go-querystring v1.0.0 // indirect - github.com/gorilla/websocket v1.4.0 - github.com/hashicorp/go-version v1.0.0 - github.com/imkira/go-interpol v1.1.0 // indirect + github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164 github.com/iris-contrib/blackfriday v2.0.0+incompatible github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1 github.com/iris-contrib/go.uuid v2.0.0+incompatible - github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce - github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 - github.com/json-iterator/go v1.1.5 - github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea // indirect + github.com/json-iterator/go v1.1.6 github.com/kataras/golog v0.0.0-20180321173939-03be10146386 + github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect - github.com/klauspost/compress v1.4.1 - github.com/klauspost/cpuid v1.2.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.1 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/moul/http2curl v1.0.0 // indirect - github.com/pkg/errors v0.8.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/microcosm-cc/bluemonday v1.0.2 github.com/ryanuber/columnize v2.1.0+incompatible - github.com/sergi/go-diff v1.0.0 // indirect - github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect - github.com/stretchr/testify v1.2.2 // indirect - github.com/valyala/bytebufferpool v1.0.0 - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 // indirect - github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect - github.com/yudai/gojsondiff v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 - golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/ini.v1 v1.39.0 // indirect - gopkg.in/yaml.v2 v2.2.1 + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 + golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect + golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20190602112858-2de7f9bf822c // indirect + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 5c1b1605..50cadf86 100644 --- a/go.sum +++ b/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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Joker/hpp v0.0.0-20180418125244-6893e659854a/go.mod h1:MzD2WMdSxvbHw5fM/OXOFily/lipJWRc9C1px0Mt0ZE= @@ -6,77 +5,63 @@ github.com/Joker/jade v1.0.0 h1:lOCEPvTAtWfLpSZYMOv/g44MGQFAolbKh2khHHGu0Kc= github.com/Joker/jade v1.0.0/go.mod h1:efZIdO0py/LtcJRSa/j2WEklMSAw84WV0zZVMxNToB8= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= -github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/etcd-io/bbolt v1.3.0/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 h1:ZHx2BEERvWkuwuE7qWN9TuRxucHDH2JrsvneZjVJfo0= -github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg= -github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164 h1:/HMcOGZC5Bi8JPgfbwz13ELWn/91+vY59HXS3z0qY5w= +github.com/flosch/pongo2 v0.0.0-20190505152737-8914e1cf9164/go.mod h1:tbAXHifHQWNSpWbiJHpJTZH5fi3XHhDMdP//vuz9WS4= +github.com/go-check/check v1.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.0 h1:1WdyfgUcImUfVBvYbsW2krIsnko+1QU2t45soaF8v1M= +github.com/gobwas/ws v1.0.0/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1 h1:7GsNnSLoVceNylMpwcfy5aFNz/S5/TV25crb34I5PEo= github.com/iris-contrib/formBinder v0.0.0-20190104093907-fbd5963f41e1/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w= github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce/go.mod h1:VER17o2JZqquOx41avolD/wMGQSFEFBKWmhag9/RQRY= -github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea h1:g2k+8WR7cHch4g0tBDhfiEvAp7fXxTNBiD1oC1Oxj3E= -github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA= github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M= +github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6 h1:Kt26efzwR6OeuQ9IO8ufl6MjoJRvl0P6/fSnzHrW638= +github.com/kataras/neffos v0.0.0-20190602135205-38e9cc9b65c6/go.mod h1:q/Hkityxm91OTjAXtQDTgaNhIrAe7JcDVDkvqSP+YGE= github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao= github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= -github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= -golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190602112858-2de7f9bf822c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/websocket/AUTHORS b/websocket/AUTHORS deleted file mode 100644 index 7d08458d..00000000 --- a/websocket/AUTHORS +++ /dev/null @@ -1,4 +0,0 @@ -# This is the official list of Iris Websocket authors for copyright -# purposes. - -Gerasimos Maropoulos diff --git a/websocket/LICENSE b/websocket/LICENSE deleted file mode 100644 index 1ea6d9b5..00000000 --- a/websocket/LICENSE +++ /dev/null @@ -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. \ No newline at end of file diff --git a/websocket/client.js b/websocket/client.js deleted file mode 100644 index ff323057..00000000 --- a/websocket/client.js +++ /dev/null @@ -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; -}()); \ No newline at end of file diff --git a/websocket/client.js.go b/websocket/client.js.go deleted file mode 100644 index 2144411a..00000000 --- a/websocket/client.js.go +++ /dev/null @@ -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; -}()); -`) diff --git a/websocket/client.min.js b/websocket/client.min.js deleted file mode 100644 index 3d930f50..00000000 --- a/websocket/client.min.js +++ /dev/null @@ -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 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(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 = 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}; diff --git a/websocket/config.go b/websocket/config.go deleted file mode 100644 index 0636ae53..00000000 --- a/websocket/config.go +++ /dev/null @@ -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 -} diff --git a/websocket/connection.go b/websocket/connection.go deleted file mode 100644 index ef6ef3a0..00000000 --- a/websocket/connection.go +++ /dev/null @@ -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 -} diff --git a/websocket/emitter.go b/websocket/emitter.go deleted file mode 100644 index 84d1fa48..00000000 --- a/websocket/emitter.go +++ /dev/null @@ -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 -} diff --git a/websocket/message.go b/websocket/message.go deleted file mode 100644 index 6b27fbee..00000000 --- a/websocket/message.go +++ /dev/null @@ -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 -} diff --git a/websocket/server.go b/websocket/server.go deleted file mode 100644 index 6aaaccac..00000000 --- a/websocket/server.go +++ /dev/null @@ -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 -} diff --git a/websocket/websocket.go b/websocket/websocket.go index fff59634..88cc0199 100644 --- a/websocket/websocket.go +++ b/websocket/websocket.go @@ -1,69 +1,107 @@ -/*Package websocket provides rich websocket support for the iris web framework. - -Source code and other details for the project are available at GitHub: - - https://github.com/kataras/iris/tree/master/websocket - -Example code: - - - package main - - import ( - "fmt" - - "github.com/kataras/iris" - "github.com/kataras/iris/context" - - "github.com/kataras/iris/websocket" - ) - - func main() { - app := iris.New() - - app.Get("/", func(ctx context.Context) { - ctx.ServeFile("websockets.html", false) - }) - - setupWebsocket(app) - - // x2 - // http://localhost:8080 - // http://localhost:8080 - // write something, press submit, see the result. - app.Run(iris.Addr(":8080")) - } - - func setupWebsocket(app *iris.Application) { - // create our echo websocket server - ws := websocket.New(websocket.Config{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - }) - ws.OnConnection(handleConnection) - - // register the server's endpoint. - // see the inline javascript code in the websockets.html, - // this endpoint is used to connect to the server. - app.Get("/echo", ws.Handler()) - - // serve the javascript builtin client-side library, - // see websockets.html script tags, this path is used. - app.Any("/iris-ws.js", func(ctx context.Context) { - ctx.Write(websocket.ClientSource) - }) - } - - func handleConnection(c websocket.Connection) { - // Read events from browser - c.On("chat", func(msg string) { - // Print the message to the console - fmt.Printf("%s sent: %s\n", c.Context().RemoteAddr(), msg) - // Write message back to the client message owner: - // c.Emit("chat", msg) - c.To(websocket.Broadcast).Emit("chat", msg) - }) - } - -*/ package websocket + +import ( + "github.com/kataras/iris/context" + + "github.com/kataras/neffos" + "github.com/kataras/neffos/gobwas" + "github.com/kataras/neffos/gorilla" +) + +var ( + // GorillaUpgrader is an upgrader type for the gorilla/websocket subprotocol implementation. + // Should be used on `New` to construct the websocket server. + GorillaUpgrader = gorilla.Upgrader + // GobwasUpgrader is an upgrader type for the gobwas/ws subprotocol implementation. + // Should be used on `New` to construct the websocket server. + GobwasUpgrader = gobwas.Upgrader + // DefaultGorillaUpgrader is a gorilla/websocket Upgrader with all fields set to the default values. + DefaultGorillaUpgrader = gorilla.DefaultUpgrader + // DefaultGobwasUpgrader is a gobwas/ws Upgrader with all fields set to the default values. + DefaultGobwasUpgrader = gobwas.DefaultUpgrader + // New constructs and returns a new websocket server. + // Listens to incoming connections automatically, no further action is required from the caller. + // The second parameter is the "connHandler", it can be + // filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the client-side as well, + // Use the `Conn#IsClient` on any event callback to determinate if it's a client-side connection or a server-side one. + // + // See examples for more. + New = neffos.New + + // GorillaDialer is a gorilla/websocket dialer with all fields set to the default values. + GorillaDialer = gorilla.DefaultDialer + // GobwasDialer is a gobwas/ws dialer with all fields set to the default values. + GobwasDialer = gobwas.DefaultDialer + // Dial establishes a new websocket client connection. + // Context "ctx" is used for handshake timeout. + // Dialer "dial" can be either `GorillaDialer` or `GobwasDialer`, + // custom dialers can be used as well when complete the `Socket` and `Dialer` interfaces for valid client. + // URL "url" is the endpoint of the websocket server, i.e "ws://localhost:8080/echo". + // The last parameter, and the most important one is the "connHandler", it can be + // filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the server-side as well. + // + // See examples for more. + Dial = neffos.Dial + + // OnNamespaceConnect is the event name which its callback is fired right before namespace connect, + // if non-nil error then the remote connection's `Conn.Connect` will fail and send that error text. + // Connection is not ready to emit data to the namespace. + OnNamespaceConnect = neffos.OnNamespaceConnect + // OnNamespaceConnected is the event name which its callback is fired after namespace successfully connected. + // Connection is ready to emit data back to the namespace. + OnNamespaceConnected = neffos.OnNamespaceConnected + // OnNamespaceDisconnect is the event name which its callback is fired when + // remote namespace disconnection or local namespace disconnection is happening. + // For server-side connections the reply matters, so if error returned then the client-side cannot disconnect yet, + // for client-side the return value does not matter. + OnNamespaceDisconnect = neffos.OnNamespaceDisconnect // if allowed to connect then it's allowed to disconnect as well. + // OnRoomJoin is the event name which its callback is fired right before room join. + OnRoomJoin = neffos.OnRoomJoin // able to check if allowed to join. + // OnRoomJoined is the event name which its callback is fired after the connection has successfully joined to a room. + OnRoomJoined = neffos.OnRoomJoined // able to broadcast messages to room. + // OnRoomLeave is the event name which its callback is fired right before room leave. + OnRoomLeave = neffos.OnRoomLeave // able to broadcast bye-bye messages to room. + // OnRoomLeft is the event name which its callback is fired after the connection has successfully left from a room. + OnRoomLeft = neffos.OnRoomLeft // if allowed to join to a room, then its allowed to leave from it. + // OnAnyEvent is the event name which its callback is fired when incoming message's event is not declared to the ConnHandler(`Events` or `Namespaces`). + OnAnyEvent = neffos.OnAnyEvent // when event no match. + // OnNativeMessage is fired on incoming native/raw websocket messages. + // If this event defined then an incoming message can pass the check (it's an invalid message format) + // with just the Message's Body filled, the Event is "OnNativeMessage" and IsNative always true. + // This event should be defined under an empty namespace in order this to work. + OnNativeMessage = neffos.OnNativeMessage + + // IsSystemEvent reports whether the "event" is a system event, + // OnNamespaceConnect, OnNamespaceConnected, OnNamespaceDisconnect, + // OnRoomJoin, OnRoomJoined, OnRoomLeave and OnRoomLeft. + IsSystemEvent = neffos.IsSystemEvent + // Reply is a special type of custom error which sends a message back to the other side + // with the exact same incoming Message's Namespace (and Room if specified) + // except its body which would be the given "body". + Reply = neffos.Reply +) + +// Handler returns an Iris handler to be served in a route of an Iris application. +func Handler(s *neffos.Server) context.Handler { + return func(ctx context.Context) { + s.Upgrade(ctx.ResponseWriter(), ctx.Request(), func(socket neffos.Socket) neffos.Socket { + return &socketWrapper{ + Socket: socket, + ctx: ctx, + } + }) + } +} + +type socketWrapper struct { + neffos.Socket + ctx context.Context +} + +// GetContext returns the Iris Context from a websocket connection. +func GetContext(c *neffos.Conn) context.Context { + if sw, ok := c.Socket().(*socketWrapper); ok { + return sw.ctx + } + return nil +} diff --git a/websocket/websocket_go19.go b/websocket/websocket_go19.go new file mode 100644 index 00000000..34d4802c --- /dev/null +++ b/websocket/websocket_go19.go @@ -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 +)