diff --git a/HISTORY.md b/HISTORY.md
index 37e029f5..51a3a9af 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,13 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+## 4.1.1 -> 4.1.2
+
+Zero front-end changes. No real improvements, developers can ignore this update.
+
+- Replace the main and underline websocket implementation with [go-websocket](https://github.com/kataras/go-websocket). Note that we still need the [ris-contrib/websocket](https://github.com/iris-contrib/websocket) package.
+- Replace the use of iris-contrib/errors with [go-errors](https://github.com/kataras/go-errors), which has more features
+
## 4.0.0 -> 4.1.1
- **NEW FEATURE**: Basic remote control through SSH, example [here](https://github.com/iris-contrib/examples/blob/master/ssh/main.go)
diff --git a/README.md b/README.md
index 323edc92..0f4ea59e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+

@@ -108,6 +108,21 @@ Features
| [Iris control Plugin](https://github.com/iris-contrib/plugin/tree/master/iriscontrol) | Basic (browser-based) control over your Iris station | [example](https://github.com/iris-contrib/examples/blob/master/plugin_iriscontrol/main.go), [book section](https://kataras.gitbooks.io/iris/content/plugin-iriscontrol.html) |
+go-* packages
+------------
+
+| Name | Description
+| ------------------|:---------------------:|
+| [go-errors](https://github.com/kataras/go-errors) | Error handling
+| [go-fs](https://github.com/kataras/go-fs) | FileSystem utils and common net/http static files handlers
+| [go-events](https://github.com/kataras/go-events) | EventEmmiter for Go
+| [go-websocket](https://github.com/kataras/go-errors) | A websocket server and ,optionally, client side lib for Go
+| [go-ssh](https://github.com/kataras/go-ssh) | SSH Server, build ssh interfaces, remote commands and remote cli with ease
+| [go-gzipwriter](https://github.com/kataras/go-gzipwriter) | Write gzip data to a io.Writer
+| [go-mailer](https://github.com/kataras/go-mailer) | E-mail Sender, send rich mails with one call
+| [rizla](https://github.com/kataras/rizla) | Monitor and live-reload of your Go App
+| [Q](https://github.com/kataras/q) | HTTP2 Web Framework, 100% compatible with net/http
+
FAQ
------------
Explore [these questions](https://github.com/kataras/iris/issues?q=label%3Aquestion) or navigate to the [community chat][Chat].
@@ -144,7 +159,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v4.1.1**
+Current: **v4.1.2**
> Iris is an active project
@@ -179,7 +194,7 @@ License can be found [here](LICENSE).
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
-[Release Widget]: https://img.shields.io/badge/release-v4.1.1-blue.svg?style=flat-square
+[Release Widget]: https://img.shields.io/badge/release-v4.1.2-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris
diff --git a/context.go b/context.go
index 4bc33747..1558517c 100644
--- a/context.go
+++ b/context.go
@@ -21,8 +21,8 @@ import (
"sync"
"time"
- "github.com/iris-contrib/errors"
"github.com/iris-contrib/formBinder"
+ "github.com/kataras/go-errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
@@ -49,8 +49,11 @@ const (
contentBinary = "application/octet-stream"
// ContentJSON header value for JSON data.
contentJSON = "application/json"
- // ContentJSONP header value for JSONP data.
+ // ContentJSONP header value for JSONP & Javascript data.
contentJSONP = "application/javascript"
+ // ContentJavascript header value for Javascript/JSONP
+ // conversional
+ contentJavascript = "application/javascript"
// ContentText header value for Text data.
contentText = "text/plain"
// ContentXML header value for XML data.
@@ -402,12 +405,12 @@ func (ctx *Context) ReadForm(formObject interface{}) error {
multipartForm, err := reqCtx.MultipartForm()
if err == nil {
//we have multipart form
- return errReadBody.With(formBinder.Decode(multipartForm.Value, formObject))
+ return errReadBody.Format(formBinder.Decode(multipartForm.Value, formObject))
}
// if no multipart and post arguments ( means normal form)
if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 {
- return errReadBody.With(errNoForm.Return())
+ return errReadBody.Format(errNoForm)
}
form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len())
@@ -435,7 +438,7 @@ func (ctx *Context) ReadForm(formObject interface{}) error {
}
})
- return errReadBody.With(formBinder.Decode(form, formObject))
+ return errReadBody.Format(formBinder.Decode(form, formObject))
}
/* Response */
@@ -651,7 +654,7 @@ func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime
}
_, err := io.Copy(out, content)
- return errServeContent.With(err)
+ return errServeContent.Format(err)
}
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
@@ -902,7 +905,7 @@ func (ctx *Context) GetFlash(key string) (string, error) {
cookieKey, cookieValue := ctx.decodeFlashCookie(key)
if cookieValue == "" {
- return "", errFlashNotFound.Return()
+ return "", errFlashNotFound
}
// store this flash message to the lifetime request's local storage,
// I choose this method because no need to store it if not used at all
diff --git a/glide.lock b/glide.lock
deleted file mode 100644
index e7e1aac1..00000000
--- a/glide.lock
+++ /dev/null
@@ -1,118 +0,0 @@
-hash: 27412419f856542949823407f446947b0d68376a907cc8d3c0071d33fc4d5e99
-updated: 2016-06-30T17:27:11.6436557+03:00
-imports:
-- name: github.com/aymerick/raymond
- version: a2232af10b53ef1ae5a767f5178db3a6c1dab655
- subpackages:
- - github.com\aymerick\raymond
- - github.com\aymerick\raymond\ast
- - github.com\aymerick\raymond\parser
- - github.com\aymerick\raymond\lexer
-- name: github.com/eknkc/amber
- version: 91774f050c1453128146169b626489e60108ec03
- subpackages:
- - github.com\eknkc\amber
- - github.com\eknkc\amber\parser
-- name: github.com/fatih/color
- version: 87d4004f2ab62d0d255e0a38f1680aa534549fe3
- subpackages:
- - github.com\fatih\color
-- name: github.com/flosch/pongo2
- version: 7f5515449ea9bbeb00256d9cbb654c556e7d0146
- subpackages:
- - github.com\flosch\pongo2
-- name: github.com/fsnotify/fsnotify
- version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- subpackages:
- - github.com\fsnotify\fsnotify
-- name: github.com/garyburd/redigo
- version: b8dc90050f24c1a73a52f107f3f575be67b21b7c
- subpackages:
- - github.com\garyburd\redigo\redis
- - github.com\garyburd\redigo\internal
-- name: github.com/imdario/mergo
- version: 50d4dbd4eb0e84778abe37cefef140271d96fade
- subpackages:
- - github.com\imdario\mergo
-- name: github.com/iris-contrib/color
- version: 62c14c567492326ac5a952fb13e69533d2c6a735
- subpackages:
- - github.com\iris-contrib\color
-- name: github.com/iris-contrib/errors
- version: 9d4599565f75fd376a8e4ae5ccc98fc078b93b69
- subpackages:
- - github.com\iris-contrib\errors
-- name: github.com/iris-contrib/formBinder
- version: c311e88d3a2dd059c5d79c7ed3703cf0492088e3
- subpackages:
- - github.com\iris-contrib\formBinder
-- name: github.com/iris-contrib/websocket
- version: a2c30084384c39ce6595169df8b93d7298ac04cd
- subpackages:
- - github.com\iris-contrib\websocket
-- name: github.com/Joker/jade
- version: db4d7e4f68708c5020c392b380e3565df2132840
- subpackages:
- - github.com\Joker\jade
-- name: github.com/kataras/cli
- version: df0553868c71f2a6410b9792b5a7f61710567f41
- subpackages:
- - github.com\kataras\cli
-- name: github.com/kataras/rizla
- version: e525ea809fd22f54c5415ddfee364543d06d95a8
- subpackages:
- - github.com\kataras\rizla\rizla
-- name: github.com/klauspost/compress
- version: 14eb9c4951195779ecfbec34431a976de7335b0a
- subpackages:
- - github.com\klauspost\compress\gzip
- - github.com\klauspost\compress\flate
- - github.com\klauspost\compress\zlib
-- name: github.com/klauspost/cpuid
- version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
- subpackages:
- - github.com\klauspost\cpuid
-- name: github.com/klauspost/crc32
- version: 19b0b332c9e4516a6370a0456e6182c3b5036720
- subpackages:
- - github.com\klauspost\crc32
-- name: github.com/mattn/go-colorable
- version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
- subpackages:
- - github.com\mattn\go-colorable
-- name: github.com/mattn/go-isatty
- version: 56b76bdf51f7708750eac80fa38b952bb9f32639
- subpackages:
- - github.com\mattn\go-isatty
-- name: github.com/microcosm-cc/bluemonday
- version: 4ac6f27528d0a3f2a59e0b0a6f6b3ff0bb89fe20
- subpackages:
- - github.com\microcosm-cc\bluemonday
-- name: github.com/russross/blackfriday
- version: 1d6b8e9301e720b08a8938b8c25c018285885438
- subpackages:
- - github.com\russross\blackfriday
-- name: github.com/shurcooL/sanitized_anchor_name
- version: 10ef21a441db47d8b13ebcc5fd2310f636973c77
- subpackages:
- - github.com\shurcooL\sanitized_anchor_name
-- name: github.com/valyala/bytebufferpool
- version: 21a4b2115581cdabcc9cd97b1624d56785fc407e
- subpackages:
- - github.com\valyala\bytebufferpool
-- name: github.com/valyala/fasthttp
- version: 45697fe30a130ec6a54426a069c82f3abe76b63d
- subpackages:
- - github.com\valyala\fasthttp
- - github.com\valyala\fasthttp\fasthttpadaptor
- - github.com\valyala\fasthttp\fasthttputil
-- name: golang.org/x/net
- version: 8e573f4005aa312856df2ea97c32b9beac70dd89
- subpackages:
- - golang.org\x\net\html
- - golang.org\x\net\html\atom
-- name: golang.org/x/sys
- version: 62bee037599929a6e9146f29d10dd5208c43507d
- subpackages:
- - golang.org\x\sys\unix
-devImports: []
diff --git a/glide.yaml b/glide.yaml
deleted file mode 100644
index 69008a9a..00000000
--- a/glide.yaml
+++ /dev/null
@@ -1,59 +0,0 @@
-package: github.com/kataras/iris
-homepage: https://iris-go.com
-license: MIT
-owners:
-- name: Gerasimos Maropoulos
- homepage: https://about.me/makismaropoulos
-import:
-- package: github.com/Joker/jade
- subpackages:
- - github.com\Joker\jade
-- package: github.com/aymerick/raymond
- subpackages:
- - github.com\aymerick\raymond
-- package: github.com/eknkc/amber
- subpackages:
- - github.com\eknkc\amber
-- package: github.com/fatih/color
- subpackages:
- - github.com\fatih\color
-- package: github.com/flosch/pongo2
- subpackages:
- - github.com\flosch\pongo2
-- package: github.com/garyburd/redigo
- subpackages:
- - github.com\garyburd\redigo\redis
-- package: github.com/imdario/mergo
- subpackages:
- - github.com\imdario\mergo
-- package: github.com/iris-contrib/errors
- subpackages:
- - github.com\iris-contrib\errors
-- package: github.com/iris-contrib/formBinder
- subpackages:
- - github.com\iris-contrib\formBinder
-- package: github.com/iris-contrib/websocket
- subpackages:
- - github.com\iris-contrib\websocket
-- package: github.com/kataras/cli
- subpackages:
- - github.com\kataras\cli
-- package: github.com/kataras/rizla
- subpackages:
- - github.com\kataras\rizla\rizla
-- package: github.com/klauspost/compress
- subpackages:
- - github.com\klauspost\compress\gzip
-- package: github.com/mattn/go-colorable
- subpackages:
- - github.com\mattn\go-colorable
-- package: github.com/microcosm-cc/bluemonday
- subpackages:
- - github.com\microcosm-cc\bluemonday
-- package: github.com/russross/blackfriday
- subpackages:
- - github.com\russross\blackfriday
-- package: github.com/valyala/fasthttp
- subpackages:
- - github.com\valyala\fasthttp
- - github.com\valyala\fasthttp\fasthttpadaptor
diff --git a/http.go b/http.go
index ef9fc3b2..689c3f66 100644
--- a/http.go
+++ b/http.go
@@ -13,9 +13,9 @@ import (
"sync"
"time"
- "github.com/iris-contrib/errors"
"github.com/iris-contrib/letsencrypt"
"github.com/iris-contrib/logger"
+ "github.com/kataras/go-errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
@@ -358,7 +358,7 @@ func (s *Server) Hostname() string {
func (s *Server) listen() error {
if s.IsListening() {
- return errServerAlreadyStarted.Return()
+ return errServerAlreadyStarted
}
listener, err := net.Listen("tcp4", s.Config.ListeningAddr)
@@ -383,7 +383,7 @@ func (s *Server) listenUNIX() error {
listener, err := net.Listen("unix", addr)
if err != nil {
- return errServerPortAlreadyUsed.Return()
+ return errServerPortAlreadyUsed
}
if err = os.Chmod(addr, mode); err != nil {
@@ -423,11 +423,11 @@ func (s *Server) serve(l net.Listener) error {
// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
func (s *Server) Open(h fasthttp.RequestHandler) error {
if h == nil {
- return errServerHandlerMissing.Return()
+ return errServerHandlerMissing
}
if s.IsListening() {
- return errServerAlreadyStarted.Return()
+ return errServerAlreadyStarted
}
s.Server.MaxRequestBodySize = s.Config.MaxRequestBodySize
@@ -466,7 +466,7 @@ func (s *Server) Open(h fasthttp.RequestHandler) error {
// Close terminates the server
func (s *Server) Close() (err error) {
if !s.IsListening() {
- return errServerIsClosed.Return()
+ return errServerIsClosed
}
err = s.listener.Close()
diff --git a/iris.go b/iris.go
index ec5db64e..20adbc69 100644
--- a/iris.go
+++ b/iris.go
@@ -68,7 +68,6 @@ import (
"sync"
"github.com/gavv/httpexpect"
- "github.com/iris-contrib/errors"
"github.com/iris-contrib/logger"
"github.com/iris-contrib/response/data"
"github.com/iris-contrib/response/json"
@@ -77,6 +76,7 @@ import (
"github.com/iris-contrib/response/text"
"github.com/iris-contrib/response/xml"
"github.com/iris-contrib/template/html"
+ "github.com/kataras/go-errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
@@ -85,7 +85,7 @@ import (
const (
// Version of the iris
- Version = "4.1.1"
+ Version = "4.1.2"
banner = ` _____ _
|_ _| (_)
@@ -101,7 +101,7 @@ var (
Config *config.Iris
Logger *logger.Logger
Plugins PluginContainer
- Websocket WebsocketServer
+ Websocket *WebsocketServer
// Look ssh.go for this field's configuration
// example: https://github.com/iris-contrib/examples/blob/master/ssh/main.go
SSH *SSHServer
@@ -183,7 +183,7 @@ type (
// configuration by instance.Logger.Config
Logger *logger.Logger
Plugins PluginContainer
- Websocket WebsocketServer
+ Websocket *WebsocketServer
SSH *SSHServer
Available chan bool
// this is setted once when .Tester(t) is called
@@ -1283,7 +1283,7 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
typ := reflect.ValueOf(restAPI).Type()
contextField, found := typ.FieldByName("Context")
if !found {
- panic(errAPIContextNotFound.Return())
+ panic(errAPIContextNotFound)
}
// check & register the Get(),Post(),Put(),Delete() and so on
diff --git a/plugin.go b/plugin.go
index e21e4125..fd56d0a4 100644
--- a/plugin.go
+++ b/plugin.go
@@ -3,7 +3,7 @@ package iris
import (
"sync"
- "github.com/iris-contrib/errors"
+ "github.com/kataras/go-errors"
"github.com/iris-contrib/logger"
"github.com/kataras/iris/utils"
@@ -275,12 +275,12 @@ func (p *pluginContainer) Reset() {
// This doesn't calls the PreClose method
func (p *pluginContainer) Remove(pluginName string) error {
if p.activatedPlugins == nil {
- return errPluginRemoveNoPlugins.Return()
+ return errPluginRemoveNoPlugins
}
if pluginName == "" {
//return error: cannot delete an unamed plugin
- return errPluginRemoveEmptyName.Return()
+ return errPluginRemoveEmptyName
}
indexToRemove := -1
@@ -290,7 +290,7 @@ func (p *pluginContainer) Remove(pluginName string) error {
}
}
if indexToRemove == -1 { //if index stills -1 then no plugin was found with this name, just return an error. it is not a critical error.
- return errPluginRemoveNotFound.Return()
+ return errPluginRemoveNotFound
}
p.activatedPlugins = append(p.activatedPlugins[:indexToRemove], p.activatedPlugins[indexToRemove+1:]...)
diff --git a/response.go b/response.go
index 72e30f70..0dc2c0df 100644
--- a/response.go
+++ b/response.go
@@ -3,7 +3,7 @@ package iris
import (
"strings"
- "github.com/iris-contrib/errors"
+ "github.com/kataras/go-errors"
"github.com/valyala/fasthttp"
)
@@ -123,7 +123,7 @@ func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map
if r == nil {
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
- return errNoResponseEngineFound.Return()
+ return errNoResponseEngineFound
}
var finalResult []byte
@@ -168,7 +168,7 @@ func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map
func (r *responseEngineMap) toString(obj interface{}, options ...map[string]interface{}) (string, error) {
if r == nil {
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
- return "", errNoResponseEngineFound.Return()
+ return "", errNoResponseEngineFound
}
var finalResult []byte
for i, n := 0, len(r.values); i < n; i++ {
diff --git a/ssh.go b/ssh.go
index cb678fd7..ac029eed 100644
--- a/ssh.go
+++ b/ssh.go
@@ -43,10 +43,10 @@ import (
"text/template"
"time"
- "github.com/iris-contrib/errors"
"github.com/iris-contrib/logger"
"github.com/kardianos/osext"
"github.com/kardianos/service"
+ "github.com/kataras/go-errors"
"github.com/kataras/iris/utils"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
diff --git a/template.go b/template.go
index 25d4723e..fb7e970a 100644
--- a/template.go
+++ b/template.go
@@ -5,7 +5,7 @@ import (
"path/filepath"
- "github.com/iris-contrib/errors"
+ "github.com/kataras/go-errors"
"github.com/kataras/iris/utils"
)
@@ -157,7 +157,7 @@ func (t *templateEngineWrapper) load() error {
} else if t.location.directory != "" {
t.LoadDirectory(t.location.directory, t.location.extension)
} else {
- return errMissingDirectoryOrAssets.Return()
+ return errMissingDirectoryOrAssets
}
return nil
}
diff --git a/utils/errors.go b/utils/errors.go
index 20003d55..cac31d20 100644
--- a/utils/errors.go
+++ b/utils/errors.go
@@ -1,7 +1,7 @@
package utils
import (
- "github.com/iris-contrib/errors"
+ "github.com/kataras/go-errors"
)
var (
diff --git a/utils/file.go b/utils/file.go
index 28748a45..b2e23d28 100644
--- a/utils/file.go
+++ b/utils/file.go
@@ -166,7 +166,7 @@ func Unzip(archive string, target string) (string, error) {
// RemoveFile removes a file or directory and returns an error, if any
func RemoveFile(filePath string) error {
- return ErrFileRemove.With(os.RemoveAll(filePath))
+ return ErrFileRemove.Format(os.RemoveAll(filePath))
}
// Install is just the flow of: downloadZip -> unzip -> removeFile(zippedFile)
diff --git a/websocket.go b/websocket.go
index a41fc4ed..e66c588b 100644
--- a/websocket.go
+++ b/websocket.go
@@ -1,18 +1,10 @@
package iris
import (
- "bytes"
- "encoding/json"
- "fmt"
- "strconv"
- "strings"
- "sync"
- "time"
-
"github.com/iris-contrib/logger"
- "github.com/iris-contrib/websocket"
+ irisWebsocket "github.com/iris-contrib/websocket"
+ "github.com/kataras/go-websocket"
"github.com/kataras/iris/config"
- "github.com/kataras/iris/utils"
)
// ---------------------------------------------------------------------------------------------------------
@@ -21,20 +13,39 @@ import (
// Global functions in order to be able to use unlimitted number of websocket servers on each iris station--
// ---------------------------------------------------------------------------------------------------------
+// Note I keep this code only to no change the front-end API, we could only use the go-websocket and set our custom upgrader
+
// NewWebsocketServer creates a websocket server and returns it
-func NewWebsocketServer(c *config.Websocket) WebsocketServer {
- return newWebsocketServer(c)
+func NewWebsocketServer(c *config.Websocket) *WebsocketServer {
+ wsConfig := websocket.Config{
+ WriteTimeout: c.WriteTimeout,
+ PongTimeout: c.PongTimeout,
+ PingPeriod: c.PingPeriod,
+ MaxMessageSize: c.MaxMessageSize,
+ BinaryMessages: c.BinaryMessages,
+ ReadBufferSize: c.ReadBufferSize,
+ WriteBufferSize: c.WriteBufferSize,
+ }
+
+ wsServer := websocket.New(wsConfig)
+
+ upgrader := irisWebsocket.Custom(wsServer.HandleConnection, c.ReadBufferSize, c.WriteBufferSize, false)
+
+ srv := &WebsocketServer{Server: wsServer, Config: c, upgrader: upgrader}
+
+ return srv
}
// RegisterWebsocketServer registers the handlers for the websocket server
// it's a bridge between station and websocket server
-func RegisterWebsocketServer(station FrameworkAPI, server WebsocketServer, logger *logger.Logger) {
- c := server.Config()
+func RegisterWebsocketServer(station FrameworkAPI, server *WebsocketServer, logger *logger.Logger) {
+ c := server.Config
if c.Endpoint == "" {
return
}
websocketHandler := func(ctx *Context) {
+
if err := server.Upgrade(ctx); err != nil {
if ctx.framework.Config.IsDevelopment {
logger.Printf("Websocket error while trying to Upgrade the connection. Trace: %s", err.Error())
@@ -62,1167 +73,53 @@ func RegisterWebsocketServer(station FrameworkAPI, server WebsocketServer, logge
// check if client side already exists
if station.Lookup(clientSideLookupName) == nil {
// serve the client side on domain:port/iris-ws.js
- station.StaticContent("/iris-ws.js", "application/json", websocketClientSource)(clientSideLookupName)
+ station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
}
+ // run the ws server
+ server.Serve()
}
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// --------------------------------WebsocketServer implementation-----------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
+// conversionals
+const (
+ // All is the string which the Emmiter use to send a message to all
+ All = websocket.All
+ // NotMe is the string which the Emmiter use to send a message to all except this websocket.Connection
+ NotMe = websocket.NotMe
+ // Broadcast is the string which the Emmiter use to send a message to all except this websocket.Connection, same as 'NotMe'
+ Broadcast = websocket.Broadcast
+)
type (
- // WebsocketConnectionFunc is the callback which fires when a client/websocketConnection is connected to the websocketServer.
- // Receives one parameter which is the WebsocketConnection
- WebsocketConnectionFunc func(WebsocketConnection)
- // WebsocketRooms is just a map with key a string and value slice of string
- WebsocketRooms map[string][]string
-
- // websocketRoomPayload is used as payload from the websocketConnection to the websocketServer
- websocketRoomPayload struct {
- roomName string
- websocketConnectionID string
- }
-
- // payloads, websocketConnection -> websocketServer
- websocketMessagePayload struct {
- from string
- to string
- data []byte
- }
-
- // WebsocketServer is the websocket server, listens on the config's port, the critical part is the event OnConnection
- WebsocketServer interface {
- Config() *config.Websocket
- Upgrade(ctx *Context) error
- OnConnection(cb WebsocketConnectionFunc)
- }
-
- websocketServer struct {
- config *config.Websocket
- upgrader websocket.Upgrader
- put chan *websocketConnection
- free chan *websocketConnection
- websocketConnections map[string]*websocketConnection
- join chan websocketRoomPayload
- leave chan websocketRoomPayload
- rooms WebsocketRooms // by default a websocketConnection is joined to a room which has the websocketConnection id as its name
- mu sync.Mutex // for rooms
- messages chan websocketMessagePayload
- onConnectionListeners []WebsocketConnectionFunc
- //websocketConnectionPool *sync.Pool // sadly I can't make this because the websocket websocketConnection is live until is closed.
+ // WebsocketServer is the iris websocket server, expose the websocket.Server
+ // the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
+ WebsocketServer struct {
+ websocket.Server
+ Config *config.Websocket
+ upgrader irisWebsocket.Upgrader
}
)
-var _ WebsocketServer = &websocketServer{}
-
-// websocketServer implementation
-
-// newWebsocketServer creates a websocket websocketServer and returns it
-func newWebsocketServer(c *config.Websocket) *websocketServer {
- s := &websocketServer{
- config: c,
- put: make(chan *websocketConnection),
- free: make(chan *websocketConnection),
- websocketConnections: make(map[string]*websocketConnection),
- join: make(chan websocketRoomPayload, 1), // buffered because join can be called immediately on websocketConnection connected
- leave: make(chan websocketRoomPayload),
- rooms: make(WebsocketRooms),
- messages: make(chan websocketMessagePayload, 1), // buffered because messages can be sent/received immediately on websocketConnection connected
- onConnectionListeners: make([]WebsocketConnectionFunc, 0),
- }
-
- s.upgrader = websocket.Custom(s.handleWebsocketConnection, s.config.ReadBufferSize, s.config.WriteBufferSize, false)
- go s.serve() // start the websocketServer automatically
- return s
-}
-
-func (s *websocketServer) Config() *config.Websocket {
- return s.config
-}
-
-func (s *websocketServer) Upgrade(ctx *Context) error {
+// 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-Websocket-Protocol).
+//
+// If the upgrade fails, then Upgrade replies to the client with an HTTP error
+// response.
+func (s *WebsocketServer) Upgrade(ctx *Context) error {
return s.upgrader.Upgrade(ctx)
}
-func (s *websocketServer) handleWebsocketConnection(websocketConn *websocket.Conn) {
- c := newWebsocketConnection(websocketConn, s)
- s.put <- c
- go c.writer()
- c.reader()
+// WebsocketConnection is the front-end API that you will use to communicate with the client side
+type WebsocketConnection interface {
+ websocket.Connection
}
-func (s *websocketServer) OnConnection(cb WebsocketConnectionFunc) {
- s.onConnectionListeners = append(s.onConnectionListeners, cb)
-}
-
-func (s *websocketServer) joinRoom(roomName string, connID string) {
- s.mu.Lock()
- if s.rooms[roomName] == nil {
- s.rooms[roomName] = make([]string, 0)
- }
- s.rooms[roomName] = append(s.rooms[roomName], connID)
- s.mu.Unlock()
-}
-
-func (s *websocketServer) leaveRoom(roomName string, connID string) {
- s.mu.Lock()
- if s.rooms[roomName] != nil {
- for i := range s.rooms[roomName] {
- if s.rooms[roomName][i] == connID {
- s.rooms[roomName][i] = s.rooms[roomName][len(s.rooms[roomName])-1]
- s.rooms[roomName] = s.rooms[roomName][:len(s.rooms[roomName])-1]
- break
- }
- }
- if len(s.rooms[roomName]) == 0 { // if room is empty then delete it
- delete(s.rooms, roomName)
- }
- }
-
- s.mu.Unlock()
-}
-
-func (s *websocketServer) serve() {
- for {
- select {
- case c := <-s.put: // websocketConnection connected
- s.websocketConnections[c.id] = c
- // make and join a room with the websocketConnection's id
- s.rooms[c.id] = make([]string, 0)
- s.rooms[c.id] = []string{c.id}
- for i := range s.onConnectionListeners {
- s.onConnectionListeners[i](c)
- }
- case c := <-s.free: // websocketConnection closed
- if _, found := s.websocketConnections[c.id]; found {
- // leave from all rooms
- for roomName := range s.rooms {
- s.leaveRoom(roomName, c.id)
- }
- delete(s.websocketConnections, c.id)
- close(c.send)
- c.fireDisconnect()
-
- }
- case join := <-s.join:
- s.joinRoom(join.roomName, join.websocketConnectionID)
- case leave := <-s.leave:
- if _, found := s.websocketConnections[leave.websocketConnectionID]; found {
- s.leaveRoom(leave.roomName, leave.websocketConnectionID)
- }
- case msg := <-s.messages: // message received from the websocketConnection
- if msg.to != All && msg.to != NotMe && s.rooms[msg.to] != nil {
- // it suppose to send the message to a room
- for _, websocketConnectionIDInsideRoom := range s.rooms[msg.to] {
- if c, connected := s.websocketConnections[websocketConnectionIDInsideRoom]; connected {
- c.send <- msg.data //here we send it without need to continue below
- } else {
- // the websocketConnection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE:
- s.leaveRoom(c.id, msg.to)
- }
- }
-
- } else { // it suppose to send the message to all opened websocketConnections or to all except the sender
- for connID, c := range s.websocketConnections {
- if msg.to != All { // if it's not suppose to send to all websocketConnections (including itself)
- if msg.to == NotMe && msg.from == connID { // if broadcast to other websocketConnections except this
- continue //here we do the opossite of previous block, just skip this websocketConnection when it's suppose to send the message to all websocketConnections except the sender
- }
- }
- select {
- case s.websocketConnections[connID].send <- msg.data: //send the message back to the websocketConnection in order to send it to the client
- default:
- close(c.send)
- delete(s.websocketConnections, connID)
- c.fireDisconnect()
-
- }
-
- }
- }
-
- }
-
- }
-}
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// --------------------------------WebsocketEmmiter implementation----------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-const (
- // All is the string which the WebsocketEmmiter use to send a message to all
- All = ""
- // NotMe is the string which the WebsocketEmmiter use to send a message to all except this websocketConnection
- NotMe = ";iris;to;all;except;me;"
- // Broadcast is the string which the WebsocketEmmiter use to send a message to all except this websocketConnection, same as 'NotMe'
- Broadcast = NotMe
-)
-
-type (
- // WebsocketEmmiter is the message/or/event manager
- WebsocketEmmiter interface {
- // EmitMessage sends a native websocket message
- EmitMessage([]byte) error
- // Emit sends a message on a particular event
- Emit(string, interface{}) error
- }
-
- websocketEmmiter struct {
- conn *websocketConnection
- to string
- }
-)
-
-var _ WebsocketEmmiter = &websocketEmmiter{}
-
-func newWebsocketEmmiter(c *websocketConnection, to string) *websocketEmmiter {
- return &websocketEmmiter{conn: c, to: to}
-}
-
-func (e *websocketEmmiter) EmitMessage(nativeMessage []byte) error {
- mp := websocketMessagePayload{e.conn.id, e.to, nativeMessage}
- e.conn.websocketServer.messages <- mp
- return nil
-}
-
-func (e *websocketEmmiter) Emit(event string, data interface{}) error {
- message, err := websocketMessageSerialize(event, data)
- if err != nil {
- return err
- }
- e.EmitMessage([]byte(message))
- return nil
-}
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// --------------------------------WebsocketWebsocketConnection implementation-------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-type (
- // WebsocketDisconnectFunc is the callback which fires when a client/websocketConnection closed
- WebsocketDisconnectFunc func()
- // WebsocketErrorFunc is the callback which fires when an error happens
- WebsocketErrorFunc (func(string))
- // WebsocketNativeMessageFunc is the callback for native websocket messages, receives one []byte parameter which is the raw client's message
- WebsocketNativeMessageFunc func([]byte)
- // WebsocketMessageFunc is the second argument to the WebsocketEmmiter's Emit functions.
- // A callback which should receives one parameter of type string, int, bool or any valid JSON/Go struct
- WebsocketMessageFunc interface{}
- // WebsocketConnection is the client
- WebsocketConnection interface {
- // WebsocketEmmiter implements EmitMessage & Emit
- WebsocketEmmiter
- // ID returns the websocketConnection's identifier
- ID() string
- // OnDisconnect registers a callback which fires when this websocketConnection is closed by an error or manual
- OnDisconnect(WebsocketDisconnectFunc)
- // OnError registers a callback which fires when this websocketConnection occurs an error
- OnError(WebsocketErrorFunc)
- // EmitError can be used to send a custom error message to the websocketConnection
- //
- // It does nothing more than firing the OnError listeners. It doesn't sends anything to the client.
- EmitError(errorMessage string)
- // To defines where websocketServer should send a message
- // returns an emmiter to send messages
- To(string) WebsocketEmmiter
- // OnMessage registers a callback which fires when native websocket message received
- OnMessage(WebsocketNativeMessageFunc)
- // On registers a callback to a particular event which fires when a message to this event received
- On(string, WebsocketMessageFunc)
- // Join join a websocketConnection to a room, it doesn't check if websocketConnection is already there, so care
- Join(string)
- // Leave removes a websocketConnection from a room
- Leave(string)
- // 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
- }
-
- websocketConnection struct {
- underline *websocket.Conn
- messageType int
- id string
- send chan []byte
- onDisconnectListeners []WebsocketDisconnectFunc
- onErrorListeners []WebsocketErrorFunc
- onNativeMessageListeners []WebsocketNativeMessageFunc
- onEventListeners map[string][]WebsocketMessageFunc
- // these were maden for performance only
- self WebsocketEmmiter // pre-defined emmiter than sends message to its self client
- broadcast WebsocketEmmiter // pre-defined emmiter that sends message to all except this
- all WebsocketEmmiter // pre-defined emmiter which sends message to all clients
-
- websocketServer *websocketServer
- }
-)
-
-var _ WebsocketConnection = &websocketConnection{}
-
-func newWebsocketConnection(websocketConn *websocket.Conn, s *websocketServer) *websocketConnection {
- c := &websocketConnection{
- id: utils.RandomString(64),
- messageType: websocket.TextMessage,
- underline: websocketConn,
- send: make(chan []byte, 256),
- onDisconnectListeners: make([]WebsocketDisconnectFunc, 0),
- onErrorListeners: make([]WebsocketErrorFunc, 0),
- onNativeMessageListeners: make([]WebsocketNativeMessageFunc, 0),
- onEventListeners: make(map[string][]WebsocketMessageFunc, 0),
- websocketServer: s,
- }
-
- // set the messageType to binary if configuration says to
- if s.config.BinaryMessages {
- c.messageType = websocket.BinaryMessage
- }
-
- c.self = newWebsocketEmmiter(c, c.id)
- c.broadcast = newWebsocketEmmiter(c, NotMe)
- c.all = newWebsocketEmmiter(c, All)
-
- return c
-}
-
-func (c *websocketConnection) write(websocketMessageType int, data []byte) error {
- c.underline.SetWriteDeadline(time.Now().Add(c.websocketServer.config.WriteTimeout))
- return c.underline.WriteMessage(websocketMessageType, data)
-}
-
-func (c *websocketConnection) writer() {
- ticker := time.NewTicker(c.websocketServer.config.PingPeriod)
- defer func() {
- ticker.Stop()
- c.Disconnect()
- }()
-
- for {
- select {
- case msg, ok := <-c.send:
- if !ok {
- defer func() {
-
- // FIX FOR: https://github.com/kataras/iris/issues/175
- // AS I TESTED ON TRIDENT ENGINE (INTERNET EXPLORER/SAFARI):
- // NAVIGATE TO SITE, CLOSE THE TAB, NOTHING HAPPENS
- // CLOSE THE WHOLE BROWSER, THEN THE c.conn is NOT NILL BUT ALL ITS FUNCTIONS PANICS, MEANS THAT IS THE STRUCT IS NOT NIL BUT THE WRITER/READER ARE NIL
- // THE ONLY SOLUTION IS TO RECOVER HERE AT ANY PANIC
- // THE FRAMETYPE = 8, c.closeSend = true
- // NOTE THAT THE CLIENT IS NOT DISCONNECTED UNTIL THE WHOLE WINDOW BROWSER CLOSED, this is engine's bug.
- //
- if err := recover(); err != nil {
- ticker.Stop()
- c.Disconnect()
- }
- }()
- c.write(websocket.CloseMessage, []byte{})
- return
- }
-
- c.underline.SetWriteDeadline(time.Now().Add(c.websocketServer.config.WriteTimeout))
-
- res, err := c.underline.NextWriter(c.messageType)
- if err != nil {
- return
- }
- res.Write(msg)
-
- n := len(c.send)
- for i := 0; i < n; i++ {
- res.Write(<-c.send)
- }
-
- if err := res.Close(); err != nil {
- return
- }
-
- case <-ticker.C:
- if err := c.write(websocket.PingMessage, []byte{}); err != nil {
- return
- }
- }
- }
-}
-
-func (c *websocketConnection) reader() {
- defer func() {
- c.Disconnect()
- }()
- conn := c.underline
-
- conn.SetReadLimit(c.websocketServer.config.MaxMessageSize)
- conn.SetReadDeadline(time.Now().Add(c.websocketServer.config.PongTimeout))
- conn.SetPongHandler(func(s string) error {
- conn.SetReadDeadline(time.Now().Add(c.websocketServer.config.PongTimeout))
- return nil
+// OnConnection this is the main event you, as developer, will work with each of the websocket connections
+func (s *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
+ s.Server.OnConnection(func(c websocket.Connection) {
+ connectionListener(c)
})
-
- for {
- if _, data, err := conn.ReadMessage(); err != nil {
- if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
- c.EmitError(err.Error())
- }
- break
- } else {
- c.messageReceived(data)
- }
-
- }
}
-
-// messageReceived checks the incoming message and fire the nativeMessage listeners or the event listeners (iris-ws custom message)
-func (c *websocketConnection) messageReceived(data []byte) {
-
- if bytes.HasPrefix(data, websocketMessagePrefixBytes) {
- customData := string(data)
- //it's a custom iris-ws message
- receivedEvt := getWebsocketCustomEvent(customData)
- listeners := c.onEventListeners[receivedEvt]
- if listeners == nil { // if not listeners for this event exit from here
- return
- }
- customMessage, err := websocketMessageDeserialize(receivedEvt, customData)
- 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 websocketServer 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 *websocketConnection) ID() string {
- return c.id
-}
-
-func (c *websocketConnection) fireDisconnect() {
- for i := range c.onDisconnectListeners {
- c.onDisconnectListeners[i]()
- }
-}
-
-func (c *websocketConnection) OnDisconnect(cb WebsocketDisconnectFunc) {
- c.onDisconnectListeners = append(c.onDisconnectListeners, cb)
-}
-
-func (c *websocketConnection) OnError(cb WebsocketErrorFunc) {
- c.onErrorListeners = append(c.onErrorListeners, cb)
-}
-
-func (c *websocketConnection) EmitError(errorMessage string) {
- for _, cb := range c.onErrorListeners {
- cb(errorMessage)
- }
-}
-
-func (c *websocketConnection) To(to string) WebsocketEmmiter {
- if to == NotMe { // if send to all except me, then return the pre-defined emmiter, and so on
- return c.broadcast
- } else if to == All {
- return c.all
- } else if to == c.id {
- return c.self
- }
- // is an emmiter to another client/websocketConnection
- return newWebsocketEmmiter(c, to)
-}
-
-func (c *websocketConnection) EmitMessage(nativeMessage []byte) error {
- return c.self.EmitMessage(nativeMessage)
-}
-
-func (c *websocketConnection) Emit(event string, message interface{}) error {
- return c.self.Emit(event, message)
-}
-
-func (c *websocketConnection) OnMessage(cb WebsocketNativeMessageFunc) {
- c.onNativeMessageListeners = append(c.onNativeMessageListeners, cb)
-}
-
-func (c *websocketConnection) On(event string, cb WebsocketMessageFunc) {
- if c.onEventListeners[event] == nil {
- c.onEventListeners[event] = make([]WebsocketMessageFunc, 0)
- }
-
- c.onEventListeners[event] = append(c.onEventListeners[event], cb)
-}
-
-func (c *websocketConnection) Join(roomName string) {
- payload := websocketRoomPayload{roomName, c.id}
- c.websocketServer.join <- payload
-}
-
-func (c *websocketConnection) Leave(roomName string) {
- payload := websocketRoomPayload{roomName, c.id}
- c.websocketServer.leave <- payload
-}
-
-func (c *websocketConnection) Disconnect() error {
- c.websocketServer.free <- c // leaves from all rooms, fires the disconnect listeners and finally remove from conn list
- return c.underline.Close()
-}
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// -----------------websocket messages and de/serialization implementation--------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-/*
-serializer, [de]websocketMessageSerialize the messages from the client to the websocketServer and from the websocketServer to the client
-*/
-
-// The same values are exists on client side also
-const (
- websocketStringMessageType websocketMessageType = iota
- websocketIntMessageType
- websocketBoolMessageType
- websocketBytesMessageType
- websocketJSONMessageType
-)
-
-const (
- websocketMessagePrefix = "iris-websocket-message:"
- websocketMessageSeparator = ";"
- websocketMessagePrefixLen = len(websocketMessagePrefix)
- websocketMessageSeparatorLen = len(websocketMessageSeparator)
- websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1
- websocketMessagePrefixIdx = websocketMessagePrefixLen - 1
- websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1
-)
-
-var (
- websocketMessageSeparatorByte = websocketMessageSeparator[0]
- websocketMessageBuffer = utils.NewBufferPool(256)
- websocketMessagePrefixBytes = []byte(websocketMessagePrefix)
-)
-
-type (
- websocketMessageType uint8
-)
-
-func (m websocketMessageType) String() string {
- return strconv.Itoa(int(m))
-}
-
-func (m websocketMessageType) Name() string {
- if m == websocketStringMessageType {
- return "string"
- } else if m == websocketIntMessageType {
- return "int"
- } else if m == websocketBoolMessageType {
- return "bool"
- } else if m == websocketBytesMessageType {
- return "[]byte"
- } else if m == websocketJSONMessageType {
- return "json"
- }
-
- return "Invalid(" + m.String() + ")"
-
-}
-
-// 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 websocketMessageSerialize(event string, data interface{}) (string, error) {
- var msgType websocketMessageType
- var dataMessage string
-
- if s, ok := data.(string); ok {
- msgType = websocketStringMessageType
- dataMessage = s
- } else if i, ok := data.(int); ok {
- msgType = websocketIntMessageType
- dataMessage = strconv.Itoa(i)
- } else if b, ok := data.(bool); ok {
- msgType = websocketBoolMessageType
- dataMessage = strconv.FormatBool(b)
- } else if by, ok := data.([]byte); ok {
- msgType = websocketBytesMessageType
- dataMessage = string(by)
- } else {
- //we suppose is json
- res, err := json.Marshal(data)
- if err != nil {
- return "", err
- }
- msgType = websocketJSONMessageType
- dataMessage = string(res)
- }
-
- b := websocketMessageBuffer.Get()
- b.WriteString(websocketMessagePrefix)
- b.WriteString(event)
- b.WriteString(websocketMessageSeparator)
- b.WriteString(msgType.String())
- b.WriteString(websocketMessageSeparator)
- b.WriteString(dataMessage)
- dataMessage = b.String()
- websocketMessageBuffer.Put(b)
-
- return dataMessage, nil
-
-}
-
-// websocketMessageDeserialize 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 websocketMessageDeserialize(event string, websocketMessage string) (message interface{}, err error) {
- t, formaterr := strconv.Atoi(websocketMessage[websocketMessagePrefixAndSepIdx+len(event)+1 : websocketMessagePrefixAndSepIdx+len(event)+2]) // in order to iris-websocket-message;user;-> 4
- if formaterr != nil {
- return nil, formaterr
- }
- _type := websocketMessageType(t)
- _message := websocketMessage[websocketMessagePrefixAndSepIdx+len(event)+3:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
-
- if _type == websocketStringMessageType {
- message = string(_message)
- } else if _type == websocketIntMessageType {
- message, err = strconv.Atoi(_message)
- } else if _type == websocketBoolMessageType {
- message, err = strconv.ParseBool(_message)
- } else if _type == websocketBytesMessageType {
- message = []byte(_message)
- } else if _type == websocketJSONMessageType {
- err = json.Unmarshal([]byte(_message), message)
- } else {
- return nil, fmt.Errorf("Type %s is invalid for message: %s", _type.Name(), websocketMessage)
- }
-
- return
-}
-
-// getWebsocketCustomEvent return empty string when the websocketMessage is native message
-func getWebsocketCustomEvent(websocketMessage string) string {
- if len(websocketMessage) < websocketMessagePrefixAndSepIdx {
- return ""
- }
- s := websocketMessage[websocketMessagePrefixAndSepIdx:]
- evt := s[:strings.IndexByte(s, websocketMessageSeparatorByte)]
- return evt
-}
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// ----------------Client side websocket javascript source code ------------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-var websocketClientSource = []byte(`var websocketStringMessageType = 0;
-var websocketIntMessageType = 1;
-var websocketBoolMessageType = 2;
-// bytes is missing here for reasons I will explain somewhen
-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) {
- try {
- JSON.parse(obj);
- }
- catch (e) {
- return false;
- }
- return true;
- };
- //
- // 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 {
- console.log("Invalid");
- }
- 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 iris-ws 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 iris-ws 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;
-}());
-`)
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-// ----------------Client side websocket commented typescript source code --------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-/*
-const websocketStringMessageType = 0;
-const websocketIntMessageType = 1;
-const websocketBoolMessageType = 2;
-// bytes is missing here for reasons I will explain somewhen
-const websocketJSONMessageType = 4;
-
-const websocketMessagePrefix = "iris-websocket-message:";
-const websocketMessageSeparator = ";";
-
-const websocketMessagePrefixLen = websocketMessagePrefix.length;
-var websocketMessageSeparatorLen = websocketMessageSeparator.length;
-var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
-var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
-var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
-
-type onConnectFunc = () => void;
-type onWebsocketDisconnectFunc = () => void;
-type onWebsocketNativeMessageFunc = (websocketMessage: string) => void;
-type onMessageFunc = (message: any) => void;
-
-class Ws {
- private conn: WebSocket;
- private isReady: boolean;
-
- // events listeners
-
- private connectListeners: onConnectFunc[] = [];
- private disconnectListeners: onWebsocketDisconnectFunc[] = [];
- private nativeMessageListeners: onWebsocketNativeMessageFunc[] = [];
- private messageListeners: { [event: string]: onMessageFunc[] } = {};
-
- //
-
- constructor(endpoint: string, protocols?: string[]) {
- if (!window["WebSocket"]) {
- return;
- }
-
- if (endpoint.indexOf("ws") == -1) {
- endpoint = "ws://" + endpoint;
- }
- if (protocols != null && protocols.length > 0) {
- this.conn = new WebSocket(endpoint, protocols);
- } else {
- this.conn = new WebSocket(endpoint);
- }
-
- this.conn.onopen = ((evt: Event): any => {
- this.fireConnect();
- this.isReady = true;
- return null;
- });
-
- this.conn.onclose = ((evt: Event): any => {
- this.fireDisconnect();
- return null;
- });
-
- this.conn.onmessage = ((evt: MessageEvent) => {
- this.messageReceivedFromConn(evt);
- });
- }
-
- //utils
-
- private isNumber(obj: any): boolean {
- return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
- }
-
- private isString(obj: any): boolean {
- return Object.prototype.toString.call(obj) == "[object String]";
- }
-
- private isBoolean(obj: any): boolean {
- return typeof obj === 'boolean' ||
- (typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
- }
-
- private isJSON(obj: any): boolean {
- try {
- JSON.parse(obj);
- } catch (e) {
- return false;
- }
- return true;
- }
-
- //
-
- // 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 {
- console.log("Invalid");
- }
-
- 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 iris-ws 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 iris-ws 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};
-*/