diff --git a/HISTORY.md b/HISTORY.md
index 88d5db59..f7f25ff3 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -37,6 +37,7 @@ Changes:
- Remove `context.RenderTemplateSource` you should make a new template file and use the `iris.Render` to specify an `io.Writer` like `bytes.Buffer`
- Remove `plugins`, replaced with more pluggable echosystem that I designed from zero on this release, named `Policy` [Adaptors](https://github.com/kataras/iris/tree/master/adaptors) (all plugins have been converted, fixed and improvement, except the iriscontrol).
- `context.Log(string,...interface{})` -> `context.Log(iris.LogMode, string)`
+- Remove `.Config.Websocket` , replaced with the `kataras/iris/adaptors/websocket.Config` adaptor.
- https://github.com/iris-contrib/plugin -> https://github.com/iris-contrib/adaptors
@@ -859,18 +860,208 @@ editors worked before but I couldn't let some developers without support.
### Websockets
-There are many internal improvements to the [websocket server](https://github.com/kataras/go-websocket), and it's
-operating slighty faster.
+There are many internal improvements to the websocket server, it
+operates slighty faster to.
-The kataras/go-websocket library, which `app.OnConnection` is refering to, will not be changed, its API will still remain.
-I am not putting anything new there (I doubt if any bug is available to fix, it's very simple and it just works).
-I started the kataras/go-websocket back then because I wanted a simple and fast websocket server for
-the fasthttp iris' version and back then no one did that before.
-Now(after v6) iris is compatible with any net/http websocket library that already created by third-parties.
+Websocket is an Adaptor too and you can edit more configuration fields than before.
+No Write and Read timeout by default, you have to set the fields if you want to enable timeout.
-If the iris' websocket feature does not cover your app's needs, you can simple use any other
-library for websockets, like the Golang's compatible to `socket.io`, example:
+Below you'll see the before and the after, keep note that the static and templates didn't changed, so I am not putting the whole
+html and javascript sources here, you can run the full examples from [here](https://github.com/kataras/iris/tree/6.2/adaptors/websocket/_examples).
+
+**BEFORE:***
+
+```go
+
+package main
+
+import (
+ "fmt" // optional
+
+ "github.com/kataras/iris"
+)
+
+type clientPage struct {
+ Title string
+ Host string
+}
+
+func main() {
+ iris.StaticWeb("/js", "./static/js")
+
+ iris.Get("/", func(ctx *iris.Context) {
+ ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
+ })
+
+ // the path which the websocket client should listen/registed to ->
+ iris.Config.Websocket.Endpoint = "/my_endpoint"
+ // by-default all origins are accepted, you can change this behavior by setting:
+ // iris.Config.Websocket.CheckOrigin
+
+ var myChatRoom = "room1"
+ iris.Websocket.OnConnection(func(c iris.WebsocketConnection) {
+ // Request returns the (upgraded) *http.Request of this connection
+ // avoid using it, you normally don't need it,
+ // websocket has everything you need to authenticate the user BUT if it's necessary
+ // then you use it to receive user information, for example: from headers.
+
+ // httpRequest := c.Request()
+ // fmt.Printf("Headers for the connection with ID: %s\n\n", c.ID())
+ // for k, v := range httpRequest.Header {
+ // fmt.Printf("%s = '%s'\n", k, strings.Join(v, ", "))
+ // }
+
+ // join to a room (optional)
+ c.Join(myChatRoom)
+
+ c.On("chat", func(message string) {
+ if message == "leave" {
+ c.Leave(myChatRoom)
+ c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
+ c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
+ return
+ }
+ // to all except this connection ->
+ // c.To(iris.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
+ // to all connected clients: c.To(iris.All)
+
+ // to the client itself ->
+ //c.Emit("chat", "Message from myself: "+message)
+
+ //send the message to the whole room,
+ //all connections are inside this room will receive this message
+ c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
+ })
+
+ // or create a new leave event
+ // c.On("leave", func() {
+ // c.Leave(myChatRoom)
+ // })
+
+ c.OnDisconnect(func() {
+ fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
+
+ })
+ })
+
+ iris.Listen(":8080")
+}
+
+
+
+```
+
+
+**AFTER**
+```go
+package main
+
+import (
+ "fmt" // optional
+
+ "gopkg.in/kataras/iris.v6"
+ "gopkg.in/kataras/iris.v6/adaptors/httprouter"
+ "gopkg.in/kataras/iris.v6/adaptors/view"
+ "gopkg.in/kataras/iris.v6/adaptors/websocket"
+)
+
+type clientPage struct {
+ Title string
+ Host string
+}
+
+func main() {
+ app := iris.New()
+ app.Adapt(iris.DevLogger()) // enable all (error) logs
+ app.Adapt(httprouter.New()) // select the httprouter as the servemux
+ app.Adapt(view.HTML("./templates", ".html")) // select the html engine to serve templates
+
+ ws := websocket.New(websocket.Config{
+ // the path which the websocket client should listen/registed to,
+ Endpoint: "/my_endpoint",
+ // the client-side javascript static file path
+ // which will be served by Iris.
+ // default is /iris-ws.js
+ // if you change that you have to change the bottom of templates/client.html
+ // script tag:
+ ClientSourcePath: "/iris-ws.js",
+ //
+ // Set the timeouts, 0 means no timeout
+ // websocket has more configuration, go to ../../config.go for more:
+ // WriteTimeout: 0,
+ // ReadTimeout: 0,
+ // by-default all origins are accepted, you can change this behavior by setting:
+ // CheckOrigin: (r *http.Request ) bool {},
+ //
+ //
+ // IDGenerator used to create (and later on, set)
+ // an ID for each incoming websocket connections (clients).
+ // The request is an argument which you can use to generate the ID (from headers for example).
+ // If empty then the ID is generated by DefaultIDGenerator: randomString(64):
+ // IDGenerator func(ctx *iris.Context) string {},
+ })
+
+ app.Adapt(ws) // adapt the websocket server, you can adapt more than one with different Endpoint
+
+ app.StaticWeb("/js", "./static/js") // serve our custom javascript code
+
+ app.Get("/", func(ctx *iris.Context) {
+ ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
+ })
+
+ var myChatRoom = "room1"
+
+ ws.OnConnection(func(c websocket.Connection) {
+ // Context returns the (upgraded) *iris.Context of this connection
+ // avoid using it, you normally don't need it,
+ // websocket has everything you need to authenticate the user BUT if it's necessary
+ // then you use it to receive user information, for example: from headers.
+
+ // ctx := c.Context()
+
+ // join to a room (optional)
+ c.Join(myChatRoom)
+
+ c.On("chat", func(message string) {
+ if message == "leave" {
+ c.Leave(myChatRoom)
+ c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
+ c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
+ return
+ }
+ // to all except this connection ->
+ // c.To(websocket.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
+ // to all connected clients: c.To(websocket.All)
+
+ // to the client itself ->
+ //c.Emit("chat", "Message from myself: "+message)
+
+ //send the message to the whole room,
+ //all connections are inside this room will receive this message
+ c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
+ })
+
+ // or create a new leave event
+ // c.On("leave", func() {
+ // c.Leave(myChatRoom)
+ // })
+
+ c.OnDisconnect(func() {
+ fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
+ })
+ })
+
+ app.Listen(":8080")
+}
+
+```
+
+
+
+
+If the iris' websocket feature does not cover your app's needs, you can simply use any other
+library for websockets that you used to use, like the Golang's compatible to `socket.io`, simple example:
```go
package main
diff --git a/adaptors/httprouter/httprouter.go b/adaptors/httprouter/httprouter.go
index 11802ee3..bb030490 100644
--- a/adaptors/httprouter/httprouter.go
+++ b/adaptors/httprouter/httprouter.go
@@ -68,30 +68,30 @@ type (
var (
errMuxEntryConflictsWildcard = errors.New(`
- Router: '%s' in new path '%s'
+ httprouter: '%s' in new path '%s'
conflicts with existing wildcarded route with path: '%s'
in existing prefix of'%s' `)
errMuxEntryMiddlewareAlreadyExists = errors.New(`
- Router: Middleware were already registered for the path: '%s'`)
+ httprouter: Middleware were already registered for the path: '%s'`)
errMuxEntryInvalidWildcard = errors.New(`
- Router: More than one wildcard found in the path part: '%s' in route's path: '%s'`)
+ httprouter: More than one wildcard found in the path part: '%s' in route's path: '%s'`)
errMuxEntryConflictsExistingWildcard = errors.New(`
- Router: Wildcard for route path: '%s' conflicts with existing children in route path: '%s'`)
+ httprouter: Wildcard for route path: '%s' conflicts with existing children in route path: '%s'`)
errMuxEntryWildcardUnnamed = errors.New(`
- Router: Unnamed wildcard found in path: '%s'`)
+ httprouter: Unnamed wildcard found in path: '%s'`)
errMuxEntryWildcardInvalidPlace = errors.New(`
- Router: Wildcard is only allowed at the end of the path, in the route path: '%s'`)
+ httprouter: Wildcard is only allowed at the end of the path, in the route path: '%s'`)
errMuxEntryWildcardConflictsMiddleware = errors.New(`
- Router: Wildcard conflicts with existing middleware for the route path: '%s'`)
+ httprouter: Wildcard conflicts with existing middleware for the route path: '%s'`)
errMuxEntryWildcardMissingSlash = errors.New(`
- Router: No slash(/) were found before wildcard in the route path: '%s'`)
+ httprouter: No slash(/) were found before wildcard in the route path: '%s'`)
)
// getParamsLen returns the parameters length from a given path
@@ -575,7 +575,7 @@ func New() iris.Policies {
// while ProdMode means that the iris should not continue running
// by-default it panics on these errors, but to make sure let's introduce the fatalErr to stop visiting
fatalErr = true
- logger(iris.ProdMode, "fatal error on httprouter build adaptor: "+err.Error())
+ logger(iris.ProdMode, err.Error())
return
}
diff --git a/adaptors/view/LICENSE b/adaptors/view/LICENSE
new file mode 100644
index 00000000..2935ad5d
--- /dev/null
+++ b/adaptors/view/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/adaptors/websocket/LICENSE b/adaptors/websocket/LICENSE
new file mode 100644
index 00000000..cbd1a2ca
--- /dev/null
+++ b/adaptors/websocket/LICENSE
@@ -0,0 +1,44 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/adaptors/websocket/_examples/websocket/main.go b/adaptors/websocket/_examples/websocket/main.go
new file mode 100644
index 00000000..be69bb08
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket/main.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "fmt" // optional
+
+ "gopkg.in/kataras/iris.v6"
+ "gopkg.in/kataras/iris.v6/adaptors/httprouter"
+ "gopkg.in/kataras/iris.v6/adaptors/view"
+ "gopkg.in/kataras/iris.v6/adaptors/websocket"
+)
+
+type clientPage struct {
+ Title string
+ Host string
+}
+
+func main() {
+ app := iris.New()
+ app.Adapt(iris.DevLogger()) // enable all (error) logs
+ app.Adapt(httprouter.New()) // select the httprouter as the servemux
+ app.Adapt(view.HTML("./templates", ".html")) // select the html engine to serve templates
+
+ ws := websocket.New(websocket.Config{
+ // the path which the websocket client should listen/registed to,
+ Endpoint: "/my_endpoint",
+ // the client-side javascript static file path
+ // which will be served by Iris.
+ // default is /iris-ws.js
+ // if you change that you have to change the bottom of templates/client.html
+ // script tag:
+ ClientSourcePath: "/iris-ws.js",
+ //
+ // Set the timeouts, 0 means no timeout
+ // websocket has more configuration, go to ../../config.go for more:
+ // WriteTimeout: 0,
+ // ReadTimeout: 0,
+ // by-default all origins are accepted, you can change this behavior by setting:
+ // CheckOrigin: (r *http.Request ) bool {},
+ //
+ //
+ // IDGenerator used to create (and later on, set)
+ // an ID for each incoming websocket connections (clients).
+ // The request is an argument which you can use to generate the ID (from headers for example).
+ // If empty then the ID is generated by DefaultIDGenerator: randomString(64):
+ // IDGenerator func(ctx *iris.Context) string {},
+ })
+
+ app.Adapt(ws) // adapt the websocket server, you can adapt more than one with different Endpoint
+
+ app.StaticWeb("/js", "./static/js") // serve our custom javascript code
+
+ app.Get("/", func(ctx *iris.Context) {
+ ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
+ })
+
+ var myChatRoom = "room1"
+
+ ws.OnConnection(func(c websocket.Connection) {
+ // Context returns the (upgraded) *iris.Context of this connection
+ // avoid using it, you normally don't need it,
+ // websocket has everything you need to authenticate the user BUT if it's necessary
+ // then you use it to receive user information, for example: from headers.
+
+ // ctx := c.Context()
+
+ // join to a room (optional)
+ c.Join(myChatRoom)
+
+ c.On("chat", func(message string) {
+ if message == "leave" {
+ c.Leave(myChatRoom)
+ c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
+ c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
+ return
+ }
+ // to all except this connection ->
+ // c.To(websocket.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
+ // to all connected clients: c.To(websocket.All)
+
+ // to the client itself ->
+ //c.Emit("chat", "Message from myself: "+message)
+
+ //send the message to the whole room,
+ //all connections are inside this room will receive this message
+ c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
+ })
+
+ // or create a new leave event
+ // c.On("leave", func() {
+ // c.Leave(myChatRoom)
+ // })
+
+ c.OnDisconnect(func() {
+ fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
+ })
+ })
+
+ app.Listen(":8080")
+}
diff --git a/adaptors/websocket/_examples/websocket/static/js/chat.js b/adaptors/websocket/_examples/websocket/static/js/chat.js
new file mode 100644
index 00000000..920a2050
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket/static/js/chat.js
@@ -0,0 +1,38 @@
+var messageTxt;
+var messages;
+
+$(function () {
+
+ messageTxt = $("#messageTxt");
+ messages = $("#messages");
+
+
+ w = new Ws("ws://" + HOST + "/my_endpoint");
+ w.OnConnect(function () {
+ console.log("Websocket connection established");
+ });
+
+ w.OnDisconnect(function () {
+ appendMessage($("
+
+
+
+
+
+
+
+
+
+
+
diff --git a/adaptors/websocket/_examples/websocket_connectionlist/main.go b/adaptors/websocket/_examples/websocket_connectionlist/main.go
new file mode 100644
index 00000000..1127a944
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket_connectionlist/main.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gopkg.in/kataras/iris.v6"
+ "gopkg.in/kataras/iris.v6/adaptors/httprouter"
+ "gopkg.in/kataras/iris.v6/adaptors/view"
+ "gopkg.in/kataras/iris.v6/adaptors/websocket"
+)
+
+type clientPage struct {
+ Title string
+ Host string
+}
+
+func main() {
+ app := iris.New()
+ app.Adapt(iris.DevLogger()) // enable all (error) logs
+ app.Adapt(httprouter.New()) // select the httprouter as the servemux
+ app.Adapt(view.HTML("./templates", ".html")) // select the html engine to serve templates
+
+ ws := websocket.New(websocket.Config{
+ // the path which the websocket client should listen/registed to,
+ Endpoint: "/my_endpoint",
+ // the client-side javascript static file path
+ // which will be served by Iris.
+ // default is /iris-ws.js
+ // if you change that you have to change the bottom of templates/client.html
+ // script tag:
+ ClientSourcePath: "/iris-ws.js",
+ //
+ // Set the timeouts, 0 means no timeout
+ // websocket has more configuration, go to ../../config.go for more:
+ // WriteTimeout: 0,
+ // ReadTimeout: 0,
+ // by-default all origins are accepted, you can change this behavior by setting:
+ // CheckOrigin: (r *http.Request ) bool {},
+ //
+ //
+ // IDGenerator used to create (and later on, set)
+ // an ID for each incoming websocket connections (clients).
+ // The request is an argument which you can use to generate the ID (from headers for example).
+ // If empty then the ID is generated by DefaultIDGenerator: randomString(64):
+ // IDGenerator func(ctx *iris.Context) string {},
+ })
+
+ app.Adapt(ws) // adapt the websocket server, you can adapt more than one with different Endpoint
+
+ app.StaticWeb("/js", "./static/js") // serve our custom javascript code
+
+ app.Get("/", func(ctx *iris.Context) {
+ ctx.Render("client.html", clientPage{"Client Page", ctx.ServerHost()})
+ })
+
+ Conn := make(map[websocket.Connection]bool)
+ var myChatRoom = "room1"
+ var mutex = new(sync.Mutex)
+
+ ws.OnConnection(func(c websocket.Connection) {
+ c.Join(myChatRoom)
+ mutex.Lock()
+ Conn[c] = true
+ mutex.Unlock()
+ c.On("chat", func(message string) {
+ if message == "leave" {
+ c.Leave(myChatRoom)
+ c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
+ c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
+ return
+ }
+ })
+ c.OnDisconnect(func() {
+ mutex.Lock()
+ delete(Conn, c)
+ mutex.Unlock()
+ fmt.Printf("\nConnection with ID: %s has been disconnected!\n", c.ID())
+ })
+ })
+
+ var delay = 1 * time.Second
+ go func() {
+ i := 0
+ for {
+ mutex.Lock()
+ broadcast(Conn, fmt.Sprintf("aaaa %d\n", i))
+ mutex.Unlock()
+ time.Sleep(delay)
+ i++
+ }
+ }()
+
+ go func() {
+ i := 0
+ for {
+ mutex.Lock()
+ broadcast(Conn, fmt.Sprintf("aaaa2 %d\n", i))
+ mutex.Unlock()
+ time.Sleep(delay)
+ i++
+ }
+ }()
+
+ app.Listen(":8080")
+}
+
+func broadcast(Conn map[websocket.Connection]bool, message string) {
+ for k := range Conn {
+ k.To("room1").Emit("chat", message)
+ }
+}
diff --git a/adaptors/websocket/_examples/websocket_connectionlist/static/js/chat.js b/adaptors/websocket/_examples/websocket_connectionlist/static/js/chat.js
new file mode 100644
index 00000000..920a2050
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket_connectionlist/static/js/chat.js
@@ -0,0 +1,38 @@
+var messageTxt;
+var messages;
+
+$(function () {
+
+ messageTxt = $("#messageTxt");
+ messages = $("#messages");
+
+
+ w = new Ws("ws://" + HOST + "/my_endpoint");
+ w.OnConnect(function () {
+ console.log("Websocket connection established");
+ });
+
+ w.OnDisconnect(function () {
+ appendMessage($("
+
+
+
+
+
+
+
+
diff --git a/adaptors/websocket/client.go b/adaptors/websocket/client.go
new file mode 100644
index 00000000..08bf8f2b
--- /dev/null
+++ b/adaptors/websocket/client.go
@@ -0,0 +1,219 @@
+package websocket
+
+// ------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------
+// ----------------Client side websocket javascript source which is typescript compiled
+// ------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------
+
+// ClientSource the client-side javascript raw source code
+var ClientSource = []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) {
+ return typeof obj === 'object';
+ };
+ //
+ // messages
+ Ws.prototype._msg = function (event, websocketMessageType, dataMessage) {
+ return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
+ };
+ Ws.prototype.encodeMessage = function (event, data) {
+ var m = "";
+ var t = 0;
+ if (this.isNumber(data)) {
+ t = websocketIntMessageType;
+ m = data.toString();
+ }
+ else if (this.isBoolean(data)) {
+ t = websocketBoolMessageType;
+ m = data.toString();
+ }
+ else if (this.isString(data)) {
+ t = websocketStringMessageType;
+ m = data.toString();
+ }
+ else if (this.isJSON(data)) {
+ //propably json-object
+ t = websocketJSONMessageType;
+ m = JSON.stringify(data);
+ }
+ else {
+ console.log("Invalid");
+ }
+ return this._msg(event, t, m);
+ };
+ Ws.prototype.decodeMessage = function (event, websocketMessage) {
+ //q-websocket-message;user;4;themarshaledstringfromajsonstruct
+ var skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
+ if (websocketMessage.length < skipLen + 1) {
+ return null;
+ }
+ var websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
+ var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
+ if (websocketMessageType == websocketIntMessageType) {
+ return parseInt(theMessage);
+ }
+ else if (websocketMessageType == websocketBoolMessageType) {
+ return Boolean(theMessage);
+ }
+ else if (websocketMessageType == websocketStringMessageType) {
+ return theMessage;
+ }
+ else if (websocketMessageType == websocketJSONMessageType) {
+ return JSON.parse(theMessage);
+ }
+ else {
+ return null; // invalid
+ }
+ };
+ Ws.prototype.getWebsocketCustomEvent = function (websocketMessage) {
+ if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
+ return "";
+ }
+ var s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
+ var evt = s.substring(0, s.indexOf(websocketMessageSeparator));
+ return evt;
+ };
+ Ws.prototype.getCustomMessage = function (event, websocketMessage) {
+ var eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
+ var s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
+ return s;
+ };
+ //
+ // Ws Events
+ // messageReceivedFromConn this is the func which decides
+ // if it's a native websocket message or a custom qws message
+ // if native message then calls the fireNativeMessage
+ // else calls the fireMessage
+ //
+ // remember q gives you the freedom of native websocket messages if you don't want to use this client side at all.
+ Ws.prototype.messageReceivedFromConn = function (evt) {
+ //check if qws message
+ var message = evt.data;
+ if (message.indexOf(websocketMessagePrefix) != -1) {
+ var event_1 = this.getWebsocketCustomEvent(message);
+ if (event_1 != "") {
+ // it's a custom message
+ this.fireMessage(event_1, this.getCustomMessage(event_1, message));
+ return;
+ }
+ }
+ // it's a native websocket message
+ this.fireNativeMessage(message);
+ };
+ Ws.prototype.OnConnect = function (fn) {
+ if (this.isReady) {
+ fn();
+ }
+ this.connectListeners.push(fn);
+ };
+ Ws.prototype.fireConnect = function () {
+ for (var i = 0; i < this.connectListeners.length; i++) {
+ this.connectListeners[i]();
+ }
+ };
+ Ws.prototype.OnDisconnect = function (fn) {
+ this.disconnectListeners.push(fn);
+ };
+ Ws.prototype.fireDisconnect = function () {
+ for (var i = 0; i < this.disconnectListeners.length; i++) {
+ this.disconnectListeners[i]();
+ }
+ };
+ Ws.prototype.OnMessage = function (cb) {
+ this.nativeMessageListeners.push(cb);
+ };
+ Ws.prototype.fireNativeMessage = function (websocketMessage) {
+ for (var i = 0; i < this.nativeMessageListeners.length; i++) {
+ this.nativeMessageListeners[i](websocketMessage);
+ }
+ };
+ Ws.prototype.On = function (event, cb) {
+ if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
+ this.messageListeners[event] = [];
+ }
+ this.messageListeners[event].push(cb);
+ };
+ Ws.prototype.fireMessage = function (event, message) {
+ for (var key in this.messageListeners) {
+ if (this.messageListeners.hasOwnProperty(key)) {
+ if (key == event) {
+ for (var i = 0; i < this.messageListeners[key].length; i++) {
+ this.messageListeners[key][i](message);
+ }
+ }
+ }
+ }
+ };
+ //
+ // Ws Actions
+ Ws.prototype.Disconnect = function () {
+ this.conn.close();
+ };
+ // EmitMessage sends a native websocket message
+ Ws.prototype.EmitMessage = function (websocketMessage) {
+ this.conn.send(websocketMessage);
+ };
+ // Emit sends an q-custom websocket message
+ Ws.prototype.Emit = function (event, data) {
+ var messageStr = this.encodeMessage(event, data);
+ this.EmitMessage(messageStr);
+ };
+ return Ws;
+}());
+`)
diff --git a/adaptors/websocket/client.ts b/adaptors/websocket/client.ts
new file mode 100644
index 00000000..865808af
--- /dev/null
+++ b/adaptors/websocket/client.ts
@@ -0,0 +1,261 @@
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// ----------------Client side websocket commented typescript source code --------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+// export to client.go:clientSource []byte
+
+const websocketStringMessageType = 0;
+const websocketIntMessageType = 1;
+const websocketBoolMessageType = 2;
+// bytes is missing here for reasons I will explain somewhen
+const websocketJSONMessageType = 4;
+
+const websocketMessagePrefix = "iris-websocket-message:";
+const websocketMessageSeparator = ";";
+
+const websocketMessagePrefixLen = websocketMessagePrefix.length;
+var websocketMessageSeparatorLen = websocketMessageSeparator.length;
+var websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1;
+var websocketMessagePrefixIdx = websocketMessagePrefixLen - 1;
+var websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1;
+
+type onConnectFunc = () => void;
+type onWebsocketDisconnectFunc = () => void;
+type onWebsocketNativeMessageFunc = (websocketMessage: string) => void;
+type onMessageFunc = (message: any) => void;
+
+class Ws {
+ private conn: WebSocket;
+ private isReady: boolean;
+
+ // events listeners
+
+ private connectListeners: onConnectFunc[] = [];
+ private disconnectListeners: onWebsocketDisconnectFunc[] = [];
+ private nativeMessageListeners: onWebsocketNativeMessageFunc[] = [];
+ private messageListeners: { [event: string]: onMessageFunc[] } = {};
+
+ //
+
+ constructor(endpoint: string, protocols?: string[]) {
+ if (!window["WebSocket"]) {
+ return;
+ }
+
+ if (endpoint.indexOf("ws") == -1) {
+ endpoint = "ws://" + endpoint;
+ }
+ if (protocols != null && protocols.length > 0) {
+ this.conn = new WebSocket(endpoint, protocols);
+ } else {
+ this.conn = new WebSocket(endpoint);
+ }
+
+ this.conn.onopen = ((evt: Event): any => {
+ this.fireConnect();
+ this.isReady = true;
+ return null;
+ });
+
+ this.conn.onclose = ((evt: Event): any => {
+ this.fireDisconnect();
+ return null;
+ });
+
+ this.conn.onmessage = ((evt: MessageEvent) => {
+ this.messageReceivedFromConn(evt);
+ });
+ }
+
+ //utils
+
+ private isNumber(obj: any): boolean {
+ return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
+ }
+
+ private isString(obj: any): boolean {
+ return Object.prototype.toString.call(obj) == "[object String]";
+ }
+
+ private isBoolean(obj: any): boolean {
+ return typeof obj === 'boolean' ||
+ (typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
+ }
+
+ private isJSON(obj: any): boolean {
+ return typeof obj === 'object';
+ }
+
+ //
+
+ // messages
+ private _msg(event: string, websocketMessageType: number, dataMessage: string): string {
+
+ return websocketMessagePrefix + event + websocketMessageSeparator + String(websocketMessageType) + websocketMessageSeparator + dataMessage;
+ }
+
+ private encodeMessage(event: string, data: any): string {
+ let m = "";
+ let t = 0;
+ if (this.isNumber(data)) {
+ t = websocketIntMessageType;
+ m = data.toString();
+ } else if (this.isBoolean(data)) {
+ t = websocketBoolMessageType;
+ m = data.toString();
+ } else if (this.isString(data)) {
+ t = websocketStringMessageType;
+ m = data.toString();
+ } else if (this.isJSON(data)) {
+ //propably json-object
+ t = websocketJSONMessageType;
+ m = JSON.stringify(data);
+ } else {
+ console.log("Invalid");
+ }
+
+ return this._msg(event, t, m);
+ }
+
+ private decodeMessage(event: string, websocketMessage: string): T | any {
+ //q-websocket-message;user;4;themarshaledstringfromajsonstruct
+ let skipLen = websocketMessagePrefixLen + websocketMessageSeparatorLen + event.length + 2;
+ if (websocketMessage.length < skipLen + 1) {
+ return null;
+ }
+ let websocketMessageType = parseInt(websocketMessage.charAt(skipLen - 2));
+ let theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
+ if (websocketMessageType == websocketIntMessageType) {
+ return parseInt(theMessage);
+ } else if (websocketMessageType == websocketBoolMessageType) {
+ return Boolean(theMessage);
+ } else if (websocketMessageType == websocketStringMessageType) {
+ return theMessage;
+ } else if (websocketMessageType == websocketJSONMessageType) {
+ return JSON.parse(theMessage);
+ } else {
+ return null; // invalid
+ }
+ }
+
+ private getWebsocketCustomEvent(websocketMessage: string): string {
+ if (websocketMessage.length < websocketMessagePrefixAndSepIdx) {
+ return "";
+ }
+ let s = websocketMessage.substring(websocketMessagePrefixAndSepIdx, websocketMessage.length);
+ let evt = s.substring(0, s.indexOf(websocketMessageSeparator));
+
+ return evt;
+ }
+
+ private getCustomMessage(event: string, websocketMessage: string): string {
+ let eventIdx = websocketMessage.indexOf(event + websocketMessageSeparator);
+ let s = websocketMessage.substring(eventIdx + event.length + websocketMessageSeparator.length + 2, websocketMessage.length);
+ return s;
+ }
+
+ //
+
+ // Ws Events
+
+ // messageReceivedFromConn this is the func which decides
+ // if it's a native websocket message or a custom qws message
+ // if native message then calls the fireNativeMessage
+ // else calls the fireMessage
+ //
+ // remember q gives you the freedom of native websocket messages if you don't want to use this client side at all.
+ private messageReceivedFromConn(evt: MessageEvent): void {
+ //check if qws message
+ let message = evt.data;
+ if (message.indexOf(websocketMessagePrefix) != -1) {
+ let event = this.getWebsocketCustomEvent(message);
+ if (event != "") {
+ // it's a custom message
+ this.fireMessage(event, this.getCustomMessage(event, message));
+ return;
+ }
+ }
+
+ // it's a native websocket message
+ this.fireNativeMessage(message);
+ }
+
+ OnConnect(fn: onConnectFunc): void {
+ if (this.isReady) {
+ fn();
+ }
+ this.connectListeners.push(fn);
+ }
+
+ fireConnect(): void {
+ for (let i = 0; i < this.connectListeners.length; i++) {
+ this.connectListeners[i]();
+ }
+ }
+
+ OnDisconnect(fn: onWebsocketDisconnectFunc): void {
+ this.disconnectListeners.push(fn);
+ }
+
+ fireDisconnect(): void {
+ for (let i = 0; i < this.disconnectListeners.length; i++) {
+ this.disconnectListeners[i]();
+ }
+ }
+
+ OnMessage(cb: onWebsocketNativeMessageFunc): void {
+ this.nativeMessageListeners.push(cb);
+ }
+
+ fireNativeMessage(websocketMessage: string): void {
+ for (let i = 0; i < this.nativeMessageListeners.length; i++) {
+ this.nativeMessageListeners[i](websocketMessage);
+ }
+ }
+
+ On(event: string, cb: onMessageFunc): void {
+ if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
+ this.messageListeners[event] = [];
+ }
+ this.messageListeners[event].push(cb);
+ }
+
+ fireMessage(event: string, message: any): void {
+ for (let key in this.messageListeners) {
+ if (this.messageListeners.hasOwnProperty(key)) {
+ if (key == event) {
+ for (let i = 0; i < this.messageListeners[key].length; i++) {
+ this.messageListeners[key][i](message);
+ }
+ }
+ }
+ }
+ }
+
+
+ //
+
+ // Ws Actions
+
+ Disconnect(): void {
+ this.conn.close();
+ }
+
+ // EmitMessage sends a native websocket message
+ EmitMessage(websocketMessage: string): void {
+ this.conn.send(websocketMessage);
+ }
+
+ // Emit sends an q-custom websocket message
+ Emit(event: string, data: any): void {
+ let messageStr = this.encodeMessage(event, data);
+ this.EmitMessage(messageStr);
+ }
+
+ //
+
+}
+
+// node-modules export {Ws};
diff --git a/adaptors/websocket/config.go b/adaptors/websocket/config.go
new file mode 100644
index 00000000..2d942ecb
--- /dev/null
+++ b/adaptors/websocket/config.go
@@ -0,0 +1,136 @@
+package websocket
+
+import (
+ "net/http"
+ "time"
+
+ "gopkg.in/kataras/iris.v6"
+)
+
+const (
+ // DefaultWebsocketWriteTimeout 0, no timeout
+ DefaultWebsocketWriteTimeout = 0
+ // DefaultWebsocketReadTimeout 0, no timeout
+ DefaultWebsocketReadTimeout = 0
+ // DefaultWebsocketPongTimeout 60 * time.Second
+ DefaultWebsocketPongTimeout = 60 * time.Second
+ // DefaultWebsocketPingPeriod (DefaultPongTimeout * 9) / 10
+ DefaultWebsocketPingPeriod = (DefaultWebsocketPongTimeout * 9) / 10
+ // DefaultWebsocketMaxMessageSize 1024
+ DefaultWebsocketMaxMessageSize = 1024
+ // DefaultWebsocketReadBufferSize 4096
+ DefaultWebsocketReadBufferSize = 4096
+ // DefaultWebsocketWriterBufferSize 4096
+ DefaultWebsocketWriterBufferSize = 4096
+ // DefaultClientSourcePath "/iris-ws.js"
+ DefaultClientSourcePath = "/iris-ws.js"
+)
+
+var (
+ // DefaultIDGenerator returns the result of 64
+ // random combined characters as the id of a new connection.
+ // Used when config.IDGenerator is nil
+ DefaultIDGenerator = func(*iris.Context) string { return randomString(64) }
+)
+
+// Config the websocket server configuration
+// all of these are optional.
+type Config struct {
+ // Endpoint is the path which the websocket server will listen for clients/connections
+ // Default value is empty string, if you don't set it the Websocket server is disabled.
+ Endpoint string
+ // ClientSourcePath is is the path which the client-side
+ // will be auto-served when the server adapted to an Iris station.
+ // Default value is "/iris-ws.js"
+ ClientSourcePath string
+ Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
+ CheckOrigin func(r *http.Request) bool
+ // WriteTimeout time allowed to write a message to the connection.
+ // 0 means no timeout.
+ // Default value is 0
+ WriteTimeout time.Duration
+ // ReadTimeout time allowed to read a message from the connection.
+ // 0 means no timeout.
+ // Default value is 0
+ ReadTimeout time.Duration
+ // PongTimeout allowed to read the next pong message from the connection.
+ // Default value is 60 * time.Second
+ PongTimeout time.Duration
+ // PingPeriod send ping messages to the connection with this period. Must be less than PongTimeout.
+ // Default value is 60 *time.Second
+ PingPeriod time.Duration
+ // MaxMessageSize max message size allowed from connection.
+ // Default value is 1024
+ MaxMessageSize int64
+ // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text
+ // compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication.
+ // defaults to false
+ BinaryMessages bool
+ // ReadBufferSize is the buffer size for the underline reader
+ // Default value is 4096
+ ReadBufferSize int
+ // WriteBufferSize is the buffer size for the underline writer
+ // Default value is 4096
+ WriteBufferSize int
+ // IDGenerator used to create (and later on, set)
+ // an ID for each incoming websocket connections (clients).
+ // The request is an argument which you can use to generate the ID (from headers for example).
+ // If empty then the ID is generated by DefaultIDGenerator: randomString(64)
+ IDGenerator func(ctx *iris.Context) string
+}
+
+// Validate validates the configuration
+func (c Config) Validate() Config {
+
+ if c.ClientSourcePath == "" {
+ c.ClientSourcePath = DefaultClientSourcePath
+ }
+
+ // 0 means no timeout.
+ if c.WriteTimeout < 0 {
+ c.WriteTimeout = DefaultWebsocketWriteTimeout
+ }
+
+ if c.ReadTimeout < 0 {
+ c.ReadTimeout = DefaultWebsocketReadTimeout
+ }
+
+ if c.PongTimeout < 0 {
+ c.PongTimeout = DefaultWebsocketPongTimeout
+ }
+
+ if c.PingPeriod <= 0 {
+ c.PingPeriod = DefaultWebsocketPingPeriod
+ }
+
+ if c.MaxMessageSize <= 0 {
+ c.MaxMessageSize = DefaultWebsocketMaxMessageSize
+ }
+
+ if c.ReadBufferSize <= 0 {
+ c.ReadBufferSize = DefaultWebsocketReadBufferSize
+ }
+
+ if c.WriteBufferSize <= 0 {
+ c.WriteBufferSize = DefaultWebsocketWriterBufferSize
+ }
+
+ if c.Error == nil {
+ c.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
+ //empty
+ }
+ }
+
+ if c.CheckOrigin == nil {
+ c.CheckOrigin = func(r *http.Request) bool {
+ // allow all connections by default
+ return true
+ }
+ }
+
+ if c.IDGenerator == nil {
+ c.IDGenerator = DefaultIDGenerator
+ }
+
+ return c
+}
diff --git a/adaptors/websocket/connection.go b/adaptors/websocket/connection.go
new file mode 100644
index 00000000..1434211f
--- /dev/null
+++ b/adaptors/websocket/connection.go
@@ -0,0 +1,382 @@
+package websocket
+
+import (
+ "bytes"
+ "io"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "gopkg.in/kataras/iris.v6"
+)
+
+// UnderlineConnection is used for compatible with fasthttp and net/http underline websocket libraries
+// we only need ~8 funcs from websocket.Conn so:
+type UnderlineConnection interface {
+ // SetWriteDeadline sets the write deadline on the underlying network
+ // connection. After a write has timed out, the websocket state is corrupt and
+ // all future writes will return an error. A zero value for t means writes will
+ // not time out.
+ SetWriteDeadline(t time.Time) error
+ // SetReadDeadline sets the read deadline on the underlying network connection.
+ // After a read has timed out, the websocket connection state is corrupt and
+ // all future reads will return an error. A zero value for t means reads will
+ // not time out.
+ SetReadDeadline(t time.Time) error
+ // SetReadLimit sets the maximum size for a message read from the peer. If a
+ // message exceeds the limit, the connection sends a close frame to the peer
+ // and returns ErrReadLimit to the application.
+ SetReadLimit(limit int64)
+ // SetPongHandler sets the handler for pong messages received from the peer.
+ // The appData argument to h is the PONG frame application data. The default
+ // pong handler does nothing.
+ SetPongHandler(h func(appData string) error)
+ // SetPingHandler sets the handler for ping messages received from the peer.
+ // The appData argument to h is the PING frame application data. The default
+ // ping handler sends a pong to the peer.
+ SetPingHandler(h func(appData string) error)
+ // WriteControl writes a control message with the given deadline. The allowed
+ // message types are CloseMessage, PingMessage and PongMessage.
+ WriteControl(messageType int, data []byte, deadline time.Time) error
+ // WriteMessage is a helper method for getting a writer using NextWriter,
+ // writing the message and closing the writer.
+ WriteMessage(messageType int, data []byte) error
+ // ReadMessage is a helper method for getting a reader using NextReader and
+ // reading from that reader to a buffer.
+ ReadMessage() (messageType int, p []byte, err error)
+ // NextWriter returns a writer for the next message to send. The writer's Close
+ // method flushes the complete message to the network.
+ //
+ // There can be at most one open writer on a connection. NextWriter closes the
+ // previous writer if the application has not already done so.
+ NextWriter(messageType int) (io.WriteCloser, error)
+ // Close closes the underlying network connection without sending or waiting for a close frame.
+ Close() error
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------Connection implementation-----------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+type (
+ // DisconnectFunc is the callback which fires when a client/connection closed
+ DisconnectFunc func()
+ // ErrorFunc is the callback which fires when an error happens
+ ErrorFunc (func(string))
+ // NativeMessageFunc is the callback for native websocket messages, receives one []byte parameter which is the raw client's message
+ NativeMessageFunc func([]byte)
+ // MessageFunc is the second argument to the Emitter's Emit functions.
+ // A callback which should receives one parameter of type string, int, bool or any valid JSON/Go struct
+ MessageFunc interface{}
+ // Connection is the front-end API that you will use to communicate with the client side
+ Connection interface {
+ // Emitter implements EmitMessage & Emit
+ Emitter
+ // ID returns the connection's identifier
+ ID() string
+
+ // Context returns the (upgraded) *iris.Context of this connection
+ // avoid using it, you normally don't need it,
+ // websocket has everything you need to authenticate the user BUT if it's necessary
+ // then you use it to receive user information, for example: from headers
+ Context() *iris.Context
+
+ // OnDisconnect registers a callback which fires when this connection is closed by an error or manual
+ OnDisconnect(DisconnectFunc)
+ // OnError registers a callback which fires when this connection occurs an error
+ OnError(ErrorFunc)
+ // EmitError can be used to send a custom error message to the connection
+ //
+ // It does nothing more than firing the OnError listeners. It doesn't sends anything to the client.
+ EmitError(errorMessage string)
+ // To defines where server should send a message
+ // returns an emitter to send messages
+ To(string) Emitter
+ // OnMessage registers a callback which fires when native websocket message received
+ OnMessage(NativeMessageFunc)
+ // On registers a callback to a particular event which fires when a message to this event received
+ On(string, MessageFunc)
+ // Join join a connection to a room, it doesn't check if connection is already there, so care
+ Join(string)
+ // Leave removes a connection 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
+ }
+
+ connection struct {
+ underline UnderlineConnection
+ id string
+ messageType int
+ pinger *time.Ticker
+ disconnected bool
+ onDisconnectListeners []DisconnectFunc
+ onErrorListeners []ErrorFunc
+ onNativeMessageListeners []NativeMessageFunc
+ onEventListeners map[string][]MessageFunc
+ // these were maden for performance only
+ self Emitter // pre-defined emitter than sends message to its self client
+ broadcast Emitter // pre-defined emitter that sends message to all except this
+ all Emitter // pre-defined emitter which sends message to all clients
+
+ // access to the Context, use with causion, you can't use response writer as you imagine.
+ ctx *iris.Context
+ server *server
+ // #119 , websocket writers are not protected by locks inside the gorilla's websocket code
+ // so we must protect them otherwise we're getting concurrent connection error on multi writers in the same time.
+ writerMu sync.Mutex
+ // same exists for reader look here: https://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages
+ // but we only use one reader in one goroutine, so we are safe.
+ // readerMu sync.Mutex
+ }
+)
+
+var _ Connection = &connection{}
+
+func newConnection(s *server, ctx *iris.Context, underlineConn UnderlineConnection, id string) *connection {
+ c := &connection{
+ underline: underlineConn,
+ id: id,
+ messageType: websocket.TextMessage,
+ onDisconnectListeners: make([]DisconnectFunc, 0),
+ onErrorListeners: make([]ErrorFunc, 0),
+ onNativeMessageListeners: make([]NativeMessageFunc, 0),
+ onEventListeners: make(map[string][]MessageFunc, 0),
+ ctx: ctx,
+ server: s,
+ }
+
+ if s.config.BinaryMessages {
+ c.messageType = websocket.BinaryMessage
+ }
+
+ c.self = newEmitter(c, c.id)
+ c.broadcast = newEmitter(c, Broadcast)
+ c.all = newEmitter(c, All)
+
+ return c
+}
+
+// write writes a raw websocket message with a specific type to the client
+// used by ping messages and any CloseMessage types.
+func (c *connection) write(websocketMessageType int, data []byte) {
+ // for any-case the app tries to write from different goroutines,
+ // we must protect them because they're reporting that as bug...
+ c.writerMu.Lock()
+ if writeTimeout := c.server.config.WriteTimeout; writeTimeout > 0 {
+ // set the write deadline based on the configuration
+ c.underline.SetWriteDeadline(time.Now().Add(writeTimeout))
+ }
+
+ // .WriteMessage same as NextWriter and close (flush)
+ err := c.underline.WriteMessage(websocketMessageType, data)
+ c.writerMu.Unlock()
+ if err != nil {
+ // if failed then the connection is off, fire the disconnect
+ c.Disconnect()
+ }
+}
+
+// writeDefault is the same as write but the message type is the configured by c.messageType
+// if BinaryMessages is enabled then it's raw []byte as you expected to work with protobufs
+func (c *connection) writeDefault(data []byte) {
+ c.write(c.messageType, data)
+}
+
+const (
+ // WriteWait is 1 second at the internal implementation,
+ // same as here but this can be changed at the future*
+ WriteWait = 1 * time.Second
+)
+
+func (c *connection) startPinger() {
+
+ // this is the default internal handler, we just change the writeWait because of the actions we must do before
+ // the server sends the ping-pong.
+
+ pingHandler := func(message string) error {
+ err := c.underline.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(WriteWait))
+ if err == websocket.ErrCloseSent {
+ return nil
+ } else if e, ok := err.(net.Error); ok && e.Temporary() {
+ return nil
+ }
+ return err
+ }
+
+ c.underline.SetPingHandler(pingHandler)
+
+ // start a new timer ticker based on the configuration
+ c.pinger = time.NewTicker(c.server.config.PingPeriod)
+
+ go func() {
+ for {
+ // wait for each tick
+ <-c.pinger.C
+ // try to ping the client, if failed then it disconnects
+ c.write(websocket.PingMessage, []byte{})
+ }
+ }()
+}
+
+func (c *connection) startReader() {
+ conn := c.underline
+ hasReadTimeout := c.server.config.ReadTimeout > 0
+
+ conn.SetReadLimit(c.server.config.MaxMessageSize)
+ conn.SetPongHandler(func(s string) error {
+ if hasReadTimeout {
+ conn.SetReadDeadline(time.Now().Add(c.server.config.ReadTimeout))
+ }
+
+ return nil
+ })
+
+ defer func() {
+ c.Disconnect()
+ }()
+
+ for {
+ if hasReadTimeout {
+ // set the read deadline based on the configuration
+ conn.SetReadDeadline(time.Now().Add(c.server.config.ReadTimeout))
+ }
+
+ _, data, err := conn.ReadMessage()
+ if 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 (ws custom message)
+func (c *connection) messageReceived(data []byte) {
+
+ if bytes.HasPrefix(data, websocketMessagePrefixBytes) {
+ customData := string(data)
+ //it's a custom 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 server side waiting for string but client side sent an int, just convert this int to a string
+ fnString(strconv.Itoa(msgInt))
+ }
+
+ } else if fnInt, ok := listeners[i].(func(int)); ok {
+ fnInt(customMessage.(int))
+ } else if fnBool, ok := listeners[i].(func(bool)); ok {
+ fnBool(customMessage.(bool))
+ } else if fnBytes, ok := listeners[i].(func([]byte)); ok {
+ fnBytes(customMessage.([]byte))
+ } else {
+ listeners[i].(func(interface{}))(customMessage)
+ }
+
+ }
+ } else {
+ // it's native websocket message
+ for i := range c.onNativeMessageListeners {
+ c.onNativeMessageListeners[i](data)
+ }
+ }
+
+}
+
+func (c *connection) ID() string {
+ return c.id
+}
+
+func (c *connection) Context() *iris.Context {
+ return c.ctx
+}
+
+func (c *connection) fireDisconnect() {
+ for i := range c.onDisconnectListeners {
+ c.onDisconnectListeners[i]()
+ }
+}
+
+func (c *connection) OnDisconnect(cb DisconnectFunc) {
+ c.onDisconnectListeners = append(c.onDisconnectListeners, cb)
+}
+
+func (c *connection) OnError(cb ErrorFunc) {
+ c.onErrorListeners = append(c.onErrorListeners, cb)
+}
+
+func (c *connection) EmitError(errorMessage string) {
+ for _, cb := range c.onErrorListeners {
+ cb(errorMessage)
+ }
+}
+
+func (c *connection) To(to string) Emitter {
+ if to == Broadcast { // if send to all except me, then return the pre-defined emitter, and so on
+ return c.broadcast
+ } else if to == All {
+ return c.all
+ } else if to == c.id {
+ return c.self
+ }
+ // is an emitter to another client/connection
+ return newEmitter(c, to)
+}
+
+func (c *connection) EmitMessage(nativeMessage []byte) error {
+ return c.self.EmitMessage(nativeMessage)
+}
+
+func (c *connection) Emit(event string, message interface{}) error {
+ return c.self.Emit(event, message)
+}
+
+func (c *connection) OnMessage(cb NativeMessageFunc) {
+ c.onNativeMessageListeners = append(c.onNativeMessageListeners, cb)
+}
+
+func (c *connection) On(event string, cb MessageFunc) {
+ if c.onEventListeners[event] == nil {
+ c.onEventListeners[event] = make([]MessageFunc, 0)
+ }
+
+ c.onEventListeners[event] = append(c.onEventListeners[event], cb)
+}
+
+func (c *connection) Join(roomName string) {
+ c.server.Join(roomName, c.id)
+}
+
+func (c *connection) Leave(roomName string) {
+ c.server.Leave(roomName, c.id)
+}
+
+func (c *connection) Disconnect() error {
+ return c.server.Disconnect(c.ID())
+}
diff --git a/adaptors/websocket/emitter.go b/adaptors/websocket/emitter.go
new file mode 100644
index 00000000..6a68de6f
--- /dev/null
+++ b/adaptors/websocket/emitter.go
@@ -0,0 +1,49 @@
+package websocket
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Emitter implementation-------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+const (
+ // All is the string which the Emitter use to send a message to all
+ All = ""
+ // Broadcast is the string which the Emitter use to send a message to all except this connection
+ Broadcast = ";gowebsocket;to;all;except;me;"
+)
+
+type (
+ // Emitter is the message/or/event manager
+ Emitter interface {
+ // EmitMessage sends a native websocket message
+ EmitMessage([]byte) error
+ // Emit sends a message on a particular event
+ Emit(string, interface{}) error
+ }
+
+ emitter struct {
+ conn *connection
+ to string
+ }
+)
+
+var _ Emitter = &emitter{}
+
+func newEmitter(c *connection, to string) *emitter {
+ return &emitter{conn: c, to: to}
+}
+
+func (e *emitter) EmitMessage(nativeMessage []byte) error {
+ e.conn.server.emitMessage(e.conn.id, e.to, nativeMessage)
+ return nil
+}
+
+func (e *emitter) Emit(event string, data interface{}) error {
+ message, err := websocketMessageSerialize(event, data)
+ if err != nil {
+ return err
+ }
+ e.EmitMessage([]byte(message))
+ return nil
+}
diff --git a/adaptors/websocket/message.go b/adaptors/websocket/message.go
new file mode 100644
index 00000000..f7ac748e
--- /dev/null
+++ b/adaptors/websocket/message.go
@@ -0,0 +1,188 @@
+package websocket
+
+import (
+ "encoding/json"
+ "github.com/kataras/go-errors"
+ "github.com/valyala/bytebufferpool"
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------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 = bytebufferpool.Pool{}
+ 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
+
+}
+
+var errInvalidTypeMessage = errors.New("Type %s is invalid for message: %s")
+
+// 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, errInvalidTypeMessage.Format(_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
+}
+
+const (
+ letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+ b[i] = letterBytes[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+
+ return b
+}
+
+// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm
+func randomString(n int) string {
+ return string(random(n))
+}
diff --git a/adaptors/websocket/server.go b/adaptors/websocket/server.go
new file mode 100644
index 00000000..300c1a11
--- /dev/null
+++ b/adaptors/websocket/server.go
@@ -0,0 +1,388 @@
+package websocket
+
+import (
+ "sync"
+
+ "github.com/gorilla/websocket"
+ "gopkg.in/kataras/iris.v6"
+)
+
+// Server is the websocket server,
+// listens on the config's port, the critical part is the event OnConnection
+type Server interface {
+ // Adapt implements the iris' adaptor, it adapts the websocket server to an Iris station.
+ // see websocket.go
+ Adapt(frame *iris.Policies)
+
+ // Handler returns the iris.HandlerFunc
+ // which is setted to the 'Websocket Endpoint path',
+ // the client should target to this handler's developer's custom path
+ // ex: iris.Default.Any("/myendpoint", mywebsocket.Handler())
+ Handler() iris.HandlerFunc
+
+ // OnConnection this is the main event you, as developer, will work with each of the websocket connections
+ OnConnection(cb ConnectionFunc)
+
+ /*
+ connection actions, same as the connection's method,
+ but these methods accept the connection ID,
+ which is useful when the developer maps
+ this id with a database field (using config.IDGenerator).
+ */
+
+ // IsConnected returns true if the connection with that ID is connected to the server
+ // useful when you have defined a custom connection id generator (based on a database)
+ // and you want to check if that connection is already connected (on multiple tabs)
+ IsConnected(connID string) bool
+
+ // Join joins a websocket client to a room,
+ // first parameter is the room name and the second the connection.ID()
+ //
+ // You can use connection.Join("room name") instead.
+ Join(roomName string, connID string)
+
+ // LeaveAll kicks out a connection from ALL of its joined rooms
+ LeaveAll(connID string)
+
+ // Leave leaves a websocket client from a room,
+ // first parameter is the room name and the second the connection.ID()
+ //
+ // You can use connection.Leave("room name") instead.
+ Leave(roomName string, connID string)
+
+ // Disconnect force-disconnects a websocket connection
+ // based on its connection.ID()
+ // What it does?
+ // 1. remove the connection from the list
+ // 2. leave from all joined rooms
+ // 3. fire the disconnect callbacks, if any
+ // 4. close the underline connection and return its error, if any.
+ //
+ // You can use the connection.Disconnect() instead.
+ Disconnect(connID string) error
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Connection key-based list----------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+type connectionKV struct {
+ key string // the connection ID
+ value *connection
+}
+
+type connections []connectionKV
+
+func (cs *connections) add(key string, value *connection) {
+ args := *cs
+ n := len(args)
+ // check if already id/key exist, if yes replace the conn
+ for i := 0; i < n; i++ {
+ kv := &args[i]
+ if kv.key == key {
+ kv.value = value
+ return
+ }
+ }
+
+ c := cap(args)
+ // make the connections slice bigger and put the conn
+ if c > n {
+ args = args[:n+1]
+ kv := &args[n]
+ kv.key = key
+ kv.value = value
+ *cs = args
+ return
+ }
+ // append to the connections slice and put the conn
+ kv := connectionKV{}
+ kv.key = key
+ kv.value = value
+ *cs = append(args, kv)
+}
+
+func (cs *connections) get(key string) *connection {
+ args := *cs
+ n := len(args)
+ for i := 0; i < n; i++ {
+ kv := &args[i]
+ if kv.key == key {
+ return kv.value
+ }
+ }
+ return nil
+}
+
+// returns the connection which removed and a bool value of found or not
+// the connection is useful to fire the disconnect events, we use that form in order to
+// make work things faster without the need of get-remove, just -remove should do the job.
+func (cs *connections) remove(key string) (*connection, bool) {
+ args := *cs
+ n := len(args)
+ for i := 0; i < n; i++ {
+ kv := &args[i]
+ if kv.key == key {
+ conn := kv.value
+ // we found the index,
+ // let's remove the item by appending to the temp and
+ // after set the pointer of the slice to this temp args
+ args = append(args[:i], args[i+1:]...)
+ *cs = args
+ return conn, true
+ }
+ }
+ return nil, false
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Server implementation--------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+type (
+ // ConnectionFunc is the callback which fires when a client/connection is connected to the server.
+ // Receives one parameter which is the Connection
+ ConnectionFunc func(Connection)
+
+ // websocketRoomPayload is used as payload from the connection to the server
+ websocketRoomPayload struct {
+ roomName string
+ connectionID string
+ }
+
+ // payloads, connection -> server
+ websocketMessagePayload struct {
+ from string
+ to string
+ data []byte
+ }
+
+ server struct {
+ config Config
+ connections connections
+ rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
+ mu sync.Mutex // for rooms
+ onConnectionListeners []ConnectionFunc
+ //connectionPool *sync.Pool // sadly I can't make this because the websocket connection is live until is closed.
+ }
+)
+
+var _ Server = &server{}
+
+// server implementation
+
+func (s *server) Handler() iris.HandlerFunc {
+ // build the upgrader once
+ c := s.config
+
+ upgrader := websocket.Upgrader{ReadBufferSize: c.ReadBufferSize, WriteBufferSize: c.WriteBufferSize, Error: c.Error, CheckOrigin: c.CheckOrigin}
+ return func(ctx *iris.Context) {
+ // Upgrade upgrades the HTTP server connection to the WebSocket protocol.
+ //
+ // The responseHeader is included in the response to the client's upgrade
+ // request. Use the responseHeader to specify cookies (Set-Cookie) and the
+ // application negotiated subprotocol (Sec--Protocol).
+ //
+ // If the upgrade fails, then Upgrade replies to the client with an HTTP error
+ // response.
+ conn, err := upgrader.Upgrade(ctx.ResponseWriter, ctx.Request, ctx.ResponseWriter.Header())
+ if err != nil {
+ ctx.Log(iris.DevMode, "websocket error: "+err.Error())
+ ctx.EmitError(iris.StatusServiceUnavailable)
+ return
+ }
+ s.handleConnection(ctx, conn)
+ }
+}
+
+// handleConnection creates & starts to listening to a new connection
+func (s *server) handleConnection(ctx *iris.Context, websocketConn UnderlineConnection) {
+ // use the config's id generator (or the default) to create a websocket client/connection id
+ cid := s.config.IDGenerator(ctx)
+ // create the new connection
+ c := newConnection(s, ctx, websocketConn, cid)
+ // add the connection to the server's list
+ s.connections.add(cid, c)
+
+ // join to itself
+ s.Join(c.ID(), c.ID())
+
+ // NOTE TO ME: fire these first BEFORE startReader and startPinger
+ // in order to set the events and any messages to send
+ // the startPinger will send the OK to the client and only
+ // then the client is able to send and receive from server
+ // when all things are ready and only then. DO NOT change this order.
+
+ // fire the on connection event callbacks, if any
+ for i := range s.onConnectionListeners {
+ s.onConnectionListeners[i](c)
+ }
+
+ // start the ping
+ c.startPinger()
+
+ // start the messages reader
+ c.startReader()
+}
+
+/* Notes:
+ We use the id as the signature of the connection because with the custom IDGenerator
+ the developer can share this ID with a database field, so we want to give the oportunnity to handle
+ his/her websocket connections without even use the connection itself.
+
+ Another question may be:
+ Q: Why you use server as the main actioner for all of the connection actions?
+ For example the server.Disconnect(connID) manages the connection internal fields, is this code-style correct?
+ A: It's the correct code-style for these type of applications and libraries, server manages all, the connnection's functions
+ should just do some internal checks (if needed) and push the action to its parent, which is the server, the server is able to
+ remove a connection, the rooms of its connected and all these things, so in order to not split the logic, we have the main logic
+ here, in the server, and let the connection with some exported functions whose exists for the per-connection action user's code-style.
+
+ Ok my english are s** I can feel it, but these comments are mostly for me.
+*/
+
+// OnConnection this is the main event you, as developer, will work with each of the websocket connections
+func (s *server) OnConnection(cb ConnectionFunc) {
+ s.onConnectionListeners = append(s.onConnectionListeners, cb)
+}
+
+// IsConnected returns true if the connection with that ID is connected to the server
+// useful when you have defined a custom connection id generator (based on a database)
+// and you want to check if that connection is already connected (on multiple tabs)
+func (s *server) IsConnected(connID string) bool {
+ c := s.connections.get(connID)
+ return c != nil
+}
+
+// Join joins a websocket client to a room,
+// first parameter is the room name and the second the connection.ID()
+//
+// You can use connection.Join("room name") instead.
+func (s *server) Join(roomName string, connID string) {
+ s.mu.Lock()
+ s.join(roomName, connID)
+ s.mu.Unlock()
+}
+
+// join used internally, no locks used.
+func (s *server) join(roomName string, connID string) {
+ if s.rooms[roomName] == nil {
+ s.rooms[roomName] = make([]string, 0)
+ }
+ s.rooms[roomName] = append(s.rooms[roomName], connID)
+}
+
+// LeaveAll kicks out a connection from ALL of its joined rooms
+func (s *server) LeaveAll(connID string) {
+ s.mu.Lock()
+ for name, connectionIDs := range s.rooms {
+ for i := range connectionIDs {
+ if connectionIDs[i] == connID {
+ // the connection is inside this room, lets remove it
+ s.rooms[name][i] = s.rooms[name][len(s.rooms[name])-1]
+ s.rooms[name] = s.rooms[name][:len(s.rooms[name])-1]
+ }
+ }
+ }
+ s.mu.Unlock()
+}
+
+// Leave leaves a websocket client from a room,
+// first parameter is the room name and the second the connection.ID()
+//
+// You can use connection.Leave("room name") instead.
+func (s *server) Leave(roomName string, connID string) {
+ s.mu.Lock()
+ s.leave(roomName, connID)
+ s.mu.Unlock()
+}
+
+// leave used internally, no locks used.
+func (s *server) leave(roomName string, connID string) {
+ ///THINK: we could add locks to its room but we still use the lock for the whole rooms or we can just do what we do with connections
+ // I will think about it on the next revision, so far we use the locks only for rooms so we are ok...
+ if s.rooms[roomName] != nil {
+ for i := range s.rooms[roomName] {
+ if s.rooms[roomName][i] == connID {
+ s.rooms[roomName][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)
+ }
+ }
+}
+
+// emitMessage is the main 'router' of the messages coming from the connection
+// this is the main function which writes the RAW websocket messages to the client.
+// It sends them(messages) to the correct room (self, broadcast or to specific client)
+//
+// You don't have to use this generic method, exists only for extreme
+// apps which you have an external goroutine with a list of custom connection list.
+//
+// You SHOULD use connection.EmitMessage/Emit/To().Emit/EmitMessage instead.
+// let's keep it unexported for the best.
+func (s *server) emitMessage(from, to string, data []byte) {
+ if to != All && to != Broadcast && s.rooms[to] != nil {
+ // it suppose to send the message to a specific room/or a user inside its own room
+ for _, connectionIDInsideRoom := range s.rooms[to] {
+ if c := s.connections.get(connectionIDInsideRoom); c != nil {
+ c.writeDefault(data) //send the message to the client(s)
+ } else {
+ // the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE:
+ cid := connectionIDInsideRoom
+ if c != nil {
+ cid = c.id
+ }
+ s.Leave(cid, to)
+ }
+ }
+ } else {
+ // it suppose to send the message to all opened connections or to all except the sender
+ for _, cKV := range s.connections {
+ connID := cKV.key
+ if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
+ if to == Broadcast && from == connID { // if broadcast to other connections except this
+ continue //here we do the opossite of previous block,
+ // just skip this connection when it's suppose to send the message to all connections except the sender
+ }
+
+ }
+ // send to the client(s) when the top validators passed
+ cKV.value.writeDefault(data)
+ }
+ }
+}
+
+// Disconnect force-disconnects a websocket connection based on its connection.ID()
+// What it does?
+// 1. remove the connection from the list
+// 2. leave from all joined rooms
+// 3. fire the disconnect callbacks, if any
+// 4. close the underline connection and return its error, if any.
+//
+// You can use the connection.Disconnect() instead.
+func (s *server) Disconnect(connID string) (err error) {
+ // remove the connection from the list
+ if c, ok := s.connections.remove(connID); ok {
+ if !c.disconnected {
+ c.disconnected = true
+ // stop the ping timer
+ c.pinger.Stop()
+ // leave from all joined rooms
+ s.LeaveAll(connID)
+ // fire the disconnect callbacks, if any
+ c.fireDisconnect()
+ // close the underline connection and return its error, if any.
+ err = c.underline.Close()
+ }
+ }
+
+ return
+}
diff --git a/adaptors/websocket/websocket.go b/adaptors/websocket/websocket.go
new file mode 100644
index 00000000..9e92ef54
--- /dev/null
+++ b/adaptors/websocket/websocket.go
@@ -0,0 +1,62 @@
+// Package websocket provides an easy way to setup server and client side rich websocket experience for Iris
+package websocket
+
+import (
+ "strings"
+
+ "gopkg.in/kataras/iris.v6"
+)
+
+// New returns a new websocket server policy adaptor.
+func New(cfg Config) Server {
+ return &server{
+ config: cfg.Validate(),
+ rooms: make(map[string][]string, 0),
+ onConnectionListeners: make([]ConnectionFunc, 0),
+ }
+}
+
+func fixPath(s string) string {
+ if s == "" {
+ return ""
+ }
+
+ if s[0] != '/' {
+ s = "/" + s
+ }
+
+ s = strings.Replace(s, "//", "/", -1)
+ return s
+}
+
+// Adapt implements the iris' adaptor, it adapts the websocket server to an Iris station.
+func (s *server) Adapt(frame *iris.Policies) {
+ // bind the server's Handler to Iris at Boot state
+ evt := iris.EventPolicy{
+ Boot: func(f *iris.Framework) {
+ wsPath := fixPath(s.config.Endpoint)
+ if wsPath == "" {
+ f.Log(iris.DevMode, "websocket's configuration field 'Endpoint' cannot be empty, websocket server stops")
+ return
+ }
+
+ wsClientSidePath := fixPath(s.config.ClientSourcePath)
+ if wsClientSidePath == "" {
+ f.Log(iris.DevMode, "websocket's configuration field 'ClientSourcePath' cannot be empty, websocket server stops")
+ return
+ }
+
+ // set the routing for client-side source (javascript) (optional)
+ clientSideLookupName := "iris-websocket-client-side"
+ wsHandler := s.Handler()
+ f.Get(wsPath, wsHandler)
+ // check if client side doesn't already exists
+ if f.Routes().Lookup(clientSideLookupName) == nil {
+ // serve the client side on domain:port/iris-ws.js
+ f.StaticContent(wsClientSidePath, "application/javascript", ClientSource).ChangeName(clientSideLookupName)
+ }
+ },
+ }
+
+ evt.Adapt(frame)
+}
diff --git a/configuration.go b/configuration.go
index 1858104d..a7005d97 100644
--- a/configuration.go
+++ b/configuration.go
@@ -4,7 +4,6 @@ import (
"crypto/tls"
"net"
"net/http"
- "net/url"
"strconv"
"time"
@@ -17,11 +16,12 @@ type (
// OptionSetter sets a configuration field to the main configuration
// used to help developers to write less and configure only what they really want and nothing else
// example:
- // iris.New(iris.Configuration{Sessions:iris.SessionConfiguration{Cookie:"mysessionid"}, Websocket: iris.WebsocketConfiguration{Endpoint:"/my_endpoint"}})
+ // iris.New(iris.Configuration{Charset: "UTF-8", Gzip:true})
// now can be done also by using iris.Option$FIELD:
- // iris.New(irisOptionSessionsCookie("mycookieid"),iris.OptionWebsocketEndpoint("my_endpoint"))
+ // iris.New(iris.OptionCharset("UTF-8"), iris.OptionGzip(true))
// benefits:
- // 1. user/dev have no worries what option to pass, he/she can just press iris.Option and all options should be shown to her/his editor's autocomplete-popup window
+ // 1. dev has no worries what option to pass,
+ // he/she can just press iris.Option and all options should be shown to her/his editor's autocomplete-popup window
// 2. can be passed with any order
// 3. Can override previous configuration
OptionSetter interface {
@@ -177,9 +177,6 @@ type Configuration struct {
// Sessions contains the configs for sessions
Sessions SessionsConfiguration
- // Websocket contains the configs for Websocket's server integration
- Websocket WebsocketConfiguration
-
// Other are the custom, dynamic options, can be empty
// this fill used only by you to set any app's options you want
// for each of an Iris instance
@@ -443,7 +440,6 @@ func DefaultConfiguration() Configuration {
Charset: DefaultCharset,
Gzip: false,
Sessions: DefaultSessionsConfiguration(),
- Websocket: DefaultWebsocketConfiguration(),
Other: options.Options{},
}
}
@@ -529,184 +525,6 @@ func DefaultSessionsConfiguration() SessionsConfiguration {
}
}
-// WebsocketConfiguration the config contains options for the Websocket main config field
-type WebsocketConfiguration struct {
- // WriteTimeout time allowed to write a message to the connection.
- // Default value is 15 * time.Second
- WriteTimeout time.Duration
- // PongTimeout allowed to read the next pong message from the connection
- // Default value is 60 * time.Second
- PongTimeout time.Duration
- // PingPeriod send ping messages to the connection with this period. Must be less than PongTimeout
- // Default value is (PongTimeout * 9) / 10
- PingPeriod time.Duration
- // MaxMessageSize max message size allowed from connection
- // Default value is 1024
- MaxMessageSize int64
- // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text
- // see https://github.com/kataras/iris/issues/387#issuecomment-243006022 for more
- // Defaults to false
- BinaryMessages bool
- // Endpoint is the path which the websocket server will listen for clients/connections
- // Default value is empty string, if you don't set it the Websocket server is disabled.
- Endpoint string
- // ReadBufferSize is the buffer size for the underline reader
- ReadBufferSize int
- // WriteBufferSize is the buffer size for the underline writer
- WriteBufferSize int
- // Error specifies the function for generating HTTP error responses.
- //
- // The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status))
- Error func(ctx *Context, status int, reason error)
- // CheckOrigin returns true if the request Origin header is acceptable. If
- // CheckOrigin is nil, the host in the Origin header must not be set or
- // must match the host of the request.
- //
- // The default behavior is to allow all origins
- // you can change this behavior by setting the iris.Default.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
- CheckOrigin func(r *http.Request) bool
- // IDGenerator used to create (and later on, set)
- // an ID for each incoming websocket connections (clients).
- // If empty then the ID is generated by the result of 64
- // random combined characters
- IDGenerator func(r *http.Request) string
-}
-
-var (
- // OptionWebsocketWriteTimeout time allowed to write a message to the connection.
- // Default value is 15 * time.Second
- OptionWebsocketWriteTimeout = func(val time.Duration) OptionSet {
- return func(c *Configuration) {
- c.Websocket.WriteTimeout = val
- }
- }
- // OptionWebsocketPongTimeout allowed to read the next pong message from the connection
- // Default value is 60 * time.Second
- OptionWebsocketPongTimeout = func(val time.Duration) OptionSet {
- return func(c *Configuration) {
- c.Websocket.PongTimeout = val
- }
- }
- // OptionWebsocketPingPeriod send ping messages to the connection with this period. Must be less than PongTimeout
- // Default value is (PongTimeout * 9) / 10
- OptionWebsocketPingPeriod = func(val time.Duration) OptionSet {
- return func(c *Configuration) {
- c.Websocket.PingPeriod = val
- }
- }
- // OptionWebsocketMaxMessageSize max message size allowed from connection
- // Default value is 1024
- OptionWebsocketMaxMessageSize = func(val int64) OptionSet {
- return func(c *Configuration) {
- c.Websocket.MaxMessageSize = val
- }
- }
- // OptionWebsocketBinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text
- // see https://github.com/kataras/iris/issues/387#issuecomment-243006022 for more
- // Defaults to false
- OptionWebsocketBinaryMessages = func(val bool) OptionSet {
- return func(c *Configuration) {
- c.Websocket.BinaryMessages = val
- }
- }
- // OptionWebsocketEndpoint is the path which the websocket server will listen for clients/connections
- // Default value is empty string, if you don't set it the Websocket server is disabled.
- OptionWebsocketEndpoint = func(val string) OptionSet {
- return func(c *Configuration) {
- c.Websocket.Endpoint = val
- }
- }
- // OptionWebsocketReadBufferSize is the buffer size for the underline reader
- OptionWebsocketReadBufferSize = func(val int) OptionSet {
- return func(c *Configuration) {
- c.Websocket.ReadBufferSize = val
- }
- }
- // OptionWebsocketWriteBufferSize is the buffer size for the underline writer
- OptionWebsocketWriteBufferSize = func(val int) OptionSet {
- return func(c *Configuration) {
- c.Websocket.WriteBufferSize = val
- }
- }
-
- // OptionWebsocketError specifies the function for generating HTTP error responses.
- OptionWebsocketError = func(val func(*Context, int, error)) OptionSet {
- return func(c *Configuration) {
- c.Websocket.Error = val
- }
- }
- // OptionWebsocketCheckOrigin returns true if the request Origin header is acceptable. If
- // CheckOrigin is nil, the host in the Origin header must not be set or
- // must match the host of the request.
- OptionWebsocketCheckOrigin = func(val func(*http.Request) bool) OptionSet {
- return func(c *Configuration) {
- c.Websocket.CheckOrigin = val
- }
- }
-
- // OptionWebsocketIDGenerator used to create (and later on, set)
- // an ID for each incoming websocket connections (clients).
- // If empty then the ID is generated by the result of 64
- // random combined characters
- OptionWebsocketIDGenerator = func(val func(*http.Request) string) OptionSet {
- return func(c *Configuration) {
- c.Websocket.IDGenerator = val
- }
- }
-)
-
-const (
- // DefaultWebsocketWriteTimeout 15 * time.Second
- DefaultWebsocketWriteTimeout = 15 * time.Second
- // DefaultWebsocketPongTimeout 60 * time.Second
- DefaultWebsocketPongTimeout = 60 * time.Second
- // DefaultWebsocketPingPeriod (DefaultPongTimeout * 9) / 10
- DefaultWebsocketPingPeriod = (DefaultWebsocketPongTimeout * 9) / 10
- // DefaultWebsocketMaxMessageSize 1024
- DefaultWebsocketMaxMessageSize = 1024
-)
-
-var (
- // DefaultWebsocketError is the default method to manage the handshake websocket errors
- DefaultWebsocketError = func(ctx *Context, status int, reason error) {
- ctx.Set("WsError", reason)
- ctx.EmitError(status)
- }
- // DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server
- // you can change this behavior by setting the iris.Default.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
- DefaultWebsocketCheckOrigin = func(r *http.Request) bool {
- return true
- }
- // WebsocketCheckSameOrigin returns true if the origin is not set or is equal to the request host
- WebsocketCheckSameOrigin = func(r *http.Request) bool {
- origin := r.Header.Get("origin")
- if len(origin) == 0 {
- return true
- }
- u, err := url.Parse(origin)
- if err != nil {
- return false
- }
- return u.Host == r.Host
- }
-)
-
-// DefaultWebsocketConfiguration returns the default config for iris-ws websocket package
-func DefaultWebsocketConfiguration() WebsocketConfiguration {
- return WebsocketConfiguration{
- WriteTimeout: DefaultWebsocketWriteTimeout,
- PongTimeout: DefaultWebsocketPongTimeout,
- PingPeriod: DefaultWebsocketPingPeriod,
- MaxMessageSize: DefaultWebsocketMaxMessageSize,
- BinaryMessages: false,
- ReadBufferSize: 4096,
- WriteBufferSize: 4096,
- Endpoint: "",
- // use the kataras/go-websocket default
- IDGenerator: nil,
- }
-}
-
// Default values for base Server conf
const (
// DefaultServerHostname returns the default hostname which is 0.0.0.0
diff --git a/doc.go b/doc.go
index 08edcb91..9159e898 100644
--- a/doc.go
+++ b/doc.go
@@ -513,11 +513,11 @@ Example code:
You should have a basic idea of the framework by now, we just scratched the surface.
If you enjoy what you just saw and want to learn more, please follow the below links:
- - examples: https://github.com/iris-contrib/examples
- - book: https://docs.iris-go.com
- - adaptors: https://github.com/kataras/iris/tree/v6/adaptors
- - middleware: https://github.com/kataras/iris/tree/v6/middleware & https://github.com/iris-contrib/middleware
- - godocs: https://godoc.org/github.com/kataras/iris
+- examples: https://github.com/iris-contrib/examples
+- book: https://docs.iris-go.com
+- adaptors: https://github.com/kataras/iris/tree/v6/adaptors
+- middleware: https://github.com/kataras/iris/tree/v6/middleware & https://github.com/iris-contrib/middleware
+- godocs: https://godoc.org/github.com/kataras/iris
*/
diff --git a/iris.go b/iris.go
index c0817bef..0055d552 100644
--- a/iris.go
+++ b/iris.go
@@ -70,10 +70,9 @@ type Framework struct {
ln net.Listener
closedManually bool
- once sync.Once
- Config *Configuration
- sessions sessions.Sessions
- Websocket *WebsocketServer
+ once sync.Once
+ Config *Configuration
+ sessions sessions.Sessions
}
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags)
@@ -253,19 +252,6 @@ func New(setters ...OptionSetter) *Framework {
}})
}
- {
- // +------------------------------------------------------------+
- // | Module Name: Websocket |
- // | On Init: Attach a new websocket server. |
- // | It starts on first callback registration |
- // +------------------------------------------------------------+
-
- // in order to be able to call $instance.Websocket.OnConnection.
- // The whole server's configuration will be
- // initialized on the first OnConnection registration (no runtime)
- s.Websocket = NewWebsocketServer(s)
- }
-
{
// +------------------------------------------------------------+
// | Module Name: Router |
diff --git a/middleware/basicauth/LICENSE b/middleware/basicauth/LICENSE
new file mode 100644
index 00000000..2935ad5d
--- /dev/null
+++ b/middleware/basicauth/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/middleware/i18n/LICENSE b/middleware/i18n/LICENSE
index d0978209..eb4883c1 100644
--- a/middleware/i18n/LICENSE
+++ b/middleware/i18n/LICENSE
@@ -1,3 +1,25 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
diff --git a/middleware/logger/LICENSE b/middleware/logger/LICENSE
new file mode 100644
index 00000000..2935ad5d
--- /dev/null
+++ b/middleware/logger/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/middleware/recover/LICENSE b/middleware/recover/LICENSE
new file mode 100644
index 00000000..2935ad5d
--- /dev/null
+++ b/middleware/recover/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 Gerasimos Maropoulos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/websocket.go b/websocket.go
deleted file mode 100644
index cb30ca0b..00000000
--- a/websocket.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package iris
-
-import (
- "net/http"
- "sync"
-
- "github.com/kataras/go-websocket"
-)
-
-// conversionals
-const (
- // All is the string which the Emitter use to send a message to all
- All = websocket.All
- // NotMe is the string which the Emitter use to send a message to all except this websocket.Connection
- NotMe = websocket.NotMe
- // Broadcast is the string which the Emitter use to send a message to all except this websocket.Connection, same as 'NotMe'
- Broadcast = websocket.Broadcast
-)
-
-// 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
-
-type (
- // 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
- station *Framework
- once sync.Once
- // Config:
- // if endpoint is not empty then this configuration is used instead of the station's
- // useful when the user/dev wants more than one websocket server inside one iris instance.
- Config WebsocketConfiguration
- }
-)
-
-// NewWebsocketServer returns a new empty unitialized websocket server
-// it runs on first OnConnection
-func NewWebsocketServer(station *Framework) *WebsocketServer {
- return &WebsocketServer{station: station, Server: websocket.New(), Config: station.Config.Websocket}
-}
-
-// NewWebsocketServer creates the client side source route and the route path Endpoint with the correct Handler
-// receives the websocket configuration and the iris station
-// and returns the websocket server which can be attached to more than one iris station (if needed)
-func (ws *WebsocketServer) init() {
-
- if ws.Config.Endpoint == "" {
- ws.Config = ws.station.Config.Websocket
- }
-
- c := ws.Config
-
- if c.Endpoint == "" {
- return
- }
-
- if c.CheckOrigin == nil {
- c.CheckOrigin = DefaultWebsocketCheckOrigin
- }
-
- if c.Error == nil {
- c.Error = DefaultWebsocketError
- }
- // set the underline websocket server's configuration
- ws.Server.Set(websocket.Config{
- WriteTimeout: c.WriteTimeout,
- PongTimeout: c.PongTimeout,
- PingPeriod: c.PingPeriod,
- MaxMessageSize: c.MaxMessageSize,
- BinaryMessages: c.BinaryMessages,
- ReadBufferSize: c.ReadBufferSize,
- WriteBufferSize: c.WriteBufferSize,
- Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
- ws.station.Context.Run(w, r, func(ctx *Context) {
- c.Error(ctx, status, reason)
- })
- },
- CheckOrigin: c.CheckOrigin,
- IDGenerator: c.IDGenerator,
- })
-
- // set the routing for client-side source (javascript) (optional)
- clientSideLookupName := "iris-websocket-client-side"
- ws.station.Get(c.Endpoint, ToHandler(ws.Server.Handler()))
- // check if client side already exists
- if ws.station.Routes().Lookup(clientSideLookupName) == nil {
- // serve the client side on domain:port/iris-ws.js
- ws.station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource).ChangeName(clientSideLookupName)
- }
-}
-
-// WebsocketConnection is the front-end API that you will use to communicate with the client side
-type WebsocketConnection interface {
- websocket.Connection
-}
-
-// OnConnection this is the main event you, as developer, will work with each of the websocket connections
-func (ws *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
- ws.once.Do(ws.init)
-
- ws.Server.OnConnection(func(c websocket.Connection) {
- connectionListener(c)
- })
-}