From 1d49188da9a181a4ded9da42e9b7cffec796a7cc Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Thu, 1 Sep 2016 06:01:53 +0300 Subject: [PATCH] Developers can ignore this update. Use of go-websocket and go-errors. Zero front-end changes. No real improvements. --- HISTORY.md | 7 + README.md | 21 +- context.go | 17 +- glide.lock | 118 ----- glide.yaml | 59 --- http.go | 12 +- iris.go | 10 +- plugin.go | 8 +- response.go | 6 +- ssh.go | 2 +- template.go | 4 +- utils/errors.go | 2 +- utils/file.go | 2 +- websocket.go | 1221 +++-------------------------------------------- 14 files changed, 117 insertions(+), 1372 deletions(-) delete mode 100644 glide.lock delete mode 100644 glide.yaml 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 @@ License -Releases +Releases Practical Guide/Docs
@@ -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}; -*/