mirror of
https://github.com/kataras/iris.git
synced 2025-12-20 19:37:03 +00:00
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
This commit is contained in:
35
HISTORY.md
35
HISTORY.md
@@ -2,6 +2,41 @@
|
|||||||
|
|
||||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||||
|
|
||||||
|
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
|
||||||
|
|
||||||
|
**New**
|
||||||
|
|
||||||
|
A **Response Engine** gives you the freedom to create/change the render/response writer for
|
||||||
|
|
||||||
|
- `context.JSON`
|
||||||
|
- `context.JSONP`
|
||||||
|
- `context.XML`
|
||||||
|
- `context.Text`
|
||||||
|
- `context.Markdown`
|
||||||
|
- `context.Data`
|
||||||
|
- `context.Render("my_custom_type",mystructOrData{}, iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
|
||||||
|
- `context.MarkdownString`
|
||||||
|
- `iris.ResponseString(...)`
|
||||||
|
|
||||||
|
|
||||||
|
**Fix**
|
||||||
|
- https://github.com/kataras/iris/issues/294
|
||||||
|
|
||||||
|
**Small changes**
|
||||||
|
|
||||||
|
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` & `iris.Config.Render.Template.Charset`, but you can override it at runtime by passinth a map `iris.RenderOptions` on the `context.Render` call .
|
||||||
|
- `iris.Config.IsDevelopment` , before alpha.1 was `iris.Config.Render.Template.IsDevelopment`
|
||||||
|
|
||||||
|
|
||||||
|
**Websockets changes**
|
||||||
|
|
||||||
|
No need to import the `github.com/kataras/iris/websocket` to use the `Connection` iteral, the websocket moved inside `kataras/iris` , now all exported variables' names have the prefix of `Websocket`, so the old `websocket.Connection` is now `iris.WebsocketConnection`.
|
||||||
|
|
||||||
|
|
||||||
|
Generally, no other changes on the 'frontend API', for response engines examples and how you can register your own to add more features on existing response engines or replace them, look [here](https://github.com/iris-contrib/response).
|
||||||
|
|
||||||
|
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be updated soon.
|
||||||
|
|
||||||
## 4.0.0-alpha.1 -> 4.0.0-alpha.2
|
## 4.0.0-alpha.1 -> 4.0.0-alpha.2
|
||||||
|
|
||||||
**Sessions were re-written **
|
**Sessions were re-written **
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
|||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v4.0.0-alpha.2**
|
Current: **v4.0.0-alpha.3**
|
||||||
|
|
||||||
> Iris is an active project
|
> Iris is an active project
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ Todo
|
|||||||
- [x] Refactor & extend view engine, separate the engines from the main code base, easier for the community to create new view engines
|
- [x] Refactor & extend view engine, separate the engines from the main code base, easier for the community to create new view engines
|
||||||
- [x] Refactor & extend sessions, split the different databases functionality to the iris-contrib
|
- [x] Refactor & extend sessions, split the different databases functionality to the iris-contrib
|
||||||
- [ ] Refactor & extends the rest render engine in order to be able to developer to use their own implemention for rendering restful types, like, for example a custom JSON implementation using no-standard go package for encode/decode
|
- [ ] Refactor & extends the rest render engine in order to be able to developer to use their own implemention for rendering restful types, like, for example a custom JSON implementation using no-standard go package for encode/decode
|
||||||
- [ ] Move the iris/websocket package's source code inside iris/websocket.go one file, to be easier to use by users without import a new package
|
- [x] Move the iris/websocket package's source code inside iris/websocket.go one file, to be easier to use by users without import a new package
|
||||||
- [ ] configs package should be removed after all these, we will not need big configurations because of different packages splitted & moved to the iris-contrib, we will keep interfaces and all required things inside kataras/iris.go.
|
- [ ] configs package should be removed after all these, we will not need big configurations because of different packages splitted & moved to the iris-contrib, we will keep interfaces and all required things inside kataras/iris.go.
|
||||||
- [ ] Implement all [opened community's feature requests](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22)
|
- [ ] Implement all [opened community's feature requests](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22)
|
||||||
- [ ] Extend i18n middleware for easier and better internalization support
|
- [ ] Extend i18n middleware for easier and better internalization support
|
||||||
@@ -192,7 +192,7 @@ License can be found [here](LICENSE).
|
|||||||
[Travis]: http://travis-ci.org/kataras/iris
|
[Travis]: http://travis-ci.org/kataras/iris
|
||||||
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
||||||
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
||||||
[Release Widget]: https://img.shields.io/badge/release-v4.0.0--alpha.2-blue.svg?style=flat-square
|
[Release Widget]: https://img.shields.io/badge/release-v4.0.0--alpha.3-blue.svg?style=flat-square
|
||||||
[Release]: https://github.com/kataras/iris/releases
|
[Release]: https://github.com/kataras/iris/releases
|
||||||
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
||||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
[Chat]: https://kataras.rocket.chat/channel/iris
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// Charset character encoding for various rendering
|
|
||||||
Charset = "UTF-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// TimeFormat default time format for any kind of datetime parsing
|
// TimeFormat default time format for any kind of datetime parsing
|
||||||
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/iris-contrib/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default values for base Iris conf
|
// Default values for base Iris conf
|
||||||
const (
|
const (
|
||||||
DefaultDisablePathCorrection = false
|
DefaultDisablePathCorrection = false
|
||||||
DefaultDisablePathEscape = false
|
DefaultDisablePathEscape = false
|
||||||
|
DefaultCharset = "UTF-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -89,12 +89,14 @@ type (
|
|||||||
// default is false
|
// default is false
|
||||||
IsDevelopment bool
|
IsDevelopment bool
|
||||||
|
|
||||||
|
// Charset character encoding for various rendering
|
||||||
|
// used for templates and the rest of the responses
|
||||||
|
// defaults to "UTF-8"
|
||||||
|
Charset string
|
||||||
|
|
||||||
// Sessions contains the configs for sessions
|
// Sessions contains the configs for sessions
|
||||||
Sessions Sessions
|
Sessions Sessions
|
||||||
|
|
||||||
// Rest contains the configs for rest render configuration
|
|
||||||
Rest rest.Config
|
|
||||||
|
|
||||||
// Websocket contains the configs for Websocket's server integration
|
// Websocket contains the configs for Websocket's server integration
|
||||||
Websocket *Websocket
|
Websocket *Websocket
|
||||||
|
|
||||||
@@ -111,9 +113,9 @@ func Default() Iris {
|
|||||||
DisableBanner: false,
|
DisableBanner: false,
|
||||||
DisableTemplateEngines: false,
|
DisableTemplateEngines: false,
|
||||||
IsDevelopment: false,
|
IsDevelopment: false,
|
||||||
|
Charset: DefaultCharset,
|
||||||
ProfilePath: "",
|
ProfilePath: "",
|
||||||
Sessions: DefaultSessions(),
|
Sessions: DefaultSessions(),
|
||||||
Rest: rest.DefaultConfig(),
|
|
||||||
Websocket: DefaultWebsocket(),
|
Websocket: DefaultWebsocket(),
|
||||||
Tester: DefaultTester(),
|
Tester: DefaultTester(),
|
||||||
}
|
}
|
||||||
|
|||||||
72
context.go
72
context.go
@@ -39,8 +39,19 @@ const (
|
|||||||
contentLength = "Content-Length"
|
contentLength = "Content-Length"
|
||||||
// ContentHTML is the string of text/html response headers
|
// ContentHTML is the string of text/html response headers
|
||||||
contentHTML = "text/html"
|
contentHTML = "text/html"
|
||||||
// ContentBINARY is the string of application/octet-stream response headers
|
// ContentBinary header value for binary data.
|
||||||
contentBINARY = "application/octet-stream"
|
contentBinary = "application/octet-stream"
|
||||||
|
// ContentJSON header value for JSON data.
|
||||||
|
contentJSON = "application/json"
|
||||||
|
// ContentJSONP header value for JSONP data.
|
||||||
|
contentJSONP = "application/javascript"
|
||||||
|
// ContentText header value for Text data.
|
||||||
|
contentText = "text/plain"
|
||||||
|
// ContentXML header value for XML data.
|
||||||
|
contentXML = "text/xml"
|
||||||
|
|
||||||
|
// contentMarkdown custom key/content type, the real is the text/html
|
||||||
|
contentMarkdown = "text/markdown"
|
||||||
|
|
||||||
// LastModified "Last-Modified"
|
// LastModified "Last-Modified"
|
||||||
lastModified = "Last-Modified"
|
lastModified = "Last-Modified"
|
||||||
@@ -461,26 +472,29 @@ func (ctx *Context) Write(format string, a ...interface{}) {
|
|||||||
ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
|
ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML writes html string with a http status
|
// Gzip accepts bytes, which are compressed to gzip format and sent to the client
|
||||||
func (ctx *Context) HTML(httpStatus int, htmlContents string) {
|
func (ctx *Context) Gzip(b []byte, status int) {
|
||||||
ctx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset)
|
ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
|
||||||
ctx.RequestCtx.SetStatusCode(httpStatus)
|
gzipWriter := ctx.framework.gzipWriterPool.Get().(*gzip.Writer)
|
||||||
ctx.RequestCtx.WriteString(htmlContents)
|
gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
|
||||||
|
gzipWriter.Write(b)
|
||||||
|
gzipWriter.Close()
|
||||||
|
ctx.framework.gzipWriterPool.Put(gzipWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data writes out the raw bytes as binary data.
|
// RenderWithStatus builds up the response from the specified template or a response engine.
|
||||||
func (ctx *Context) Data(status int, v []byte) error {
|
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
|
||||||
return ctx.framework.rest.Data(ctx.RequestCtx, status, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderWithStatus builds up the response from the specified template and bindings.
|
|
||||||
// Note: parameter layout has meaning only when using the iris.HTMLTemplate
|
|
||||||
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error {
|
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error {
|
||||||
ctx.SetStatusCode(status)
|
ctx.SetStatusCode(status)
|
||||||
return ctx.framework.templates.GetBy(name).Execute(ctx, name, binding, options...)
|
if strings.IndexByte(name, '.') > 0 { //we have template
|
||||||
|
return ctx.framework.templates.getBy(name).execute(ctx, name, binding, options...)
|
||||||
|
}
|
||||||
|
return ctx.framework.responses.getBy(name).render(ctx, binding, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render same as .RenderWithStatus but with status to iris.StatusOK (200)
|
// Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists
|
||||||
|
// builds up the response from the specified template or a response engine.
|
||||||
|
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
|
||||||
func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error {
|
func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error {
|
||||||
errCode := ctx.RequestCtx.Response.StatusCode()
|
errCode := ctx.RequestCtx.Response.StatusCode()
|
||||||
if errCode <= 0 {
|
if errCode <= 0 {
|
||||||
@@ -490,6 +504,8 @@ func (ctx *Context) Render(name string, binding interface{}, options ...map[stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
|
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
|
||||||
|
// builds up the response from the specified template or a response engine.
|
||||||
|
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
|
||||||
func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) {
|
func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) {
|
||||||
if err := ctx.Render(name, binding, options...); err != nil {
|
if err := ctx.Render(name, binding, options...); err != nil {
|
||||||
ctx.Panic()
|
ctx.Panic()
|
||||||
@@ -503,29 +519,43 @@ func (ctx *Context) TemplateString(name string, binding interface{}, options ...
|
|||||||
return ctx.framework.TemplateString(name, binding, options...)
|
return ctx.framework.TemplateString(name, binding, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML writes html string with a http status
|
||||||
|
func (ctx *Context) HTML(status int, htmlContents string) {
|
||||||
|
if err := ctx.RenderWithStatus(status, contentHTML, htmlContents); err != nil {
|
||||||
|
ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset)
|
||||||
|
ctx.RequestCtx.SetStatusCode(status)
|
||||||
|
ctx.RequestCtx.WriteString(htmlContents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data writes out the raw bytes as binary data.
|
||||||
|
func (ctx *Context) Data(status int, v []byte) error {
|
||||||
|
return ctx.RenderWithStatus(status, contentBinary, v)
|
||||||
|
}
|
||||||
|
|
||||||
// JSON marshals the given interface object and writes the JSON response.
|
// JSON marshals the given interface object and writes the JSON response.
|
||||||
func (ctx *Context) JSON(status int, v interface{}) error {
|
func (ctx *Context) JSON(status int, v interface{}) error {
|
||||||
return ctx.framework.rest.JSON(ctx.RequestCtx, status, v)
|
return ctx.RenderWithStatus(status, contentJSON, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONP marshals the given interface object and writes the JSON response.
|
// JSONP marshals the given interface object and writes the JSON response.
|
||||||
func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
|
func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
|
||||||
return ctx.framework.rest.JSONP(ctx.RequestCtx, status, callback, v)
|
return ctx.RenderWithStatus(status, contentJSONP, v, map[string]interface{}{"callback": callback})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text writes out a string as plain text.
|
// Text writes out a string as plain text.
|
||||||
func (ctx *Context) Text(status int, v string) error {
|
func (ctx *Context) Text(status int, v string) error {
|
||||||
return ctx.framework.rest.Text(ctx.RequestCtx, status, v)
|
return ctx.RenderWithStatus(status, contentText, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XML marshals the given interface object and writes the XML response.
|
// XML marshals the given interface object and writes the XML response.
|
||||||
func (ctx *Context) XML(status int, v interface{}) error {
|
func (ctx *Context) XML(status int, v interface{}) error {
|
||||||
return ctx.framework.rest.XML(ctx.RequestCtx, status, v)
|
return ctx.RenderWithStatus(status, contentXML, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
|
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
|
||||||
func (ctx *Context) MarkdownString(markdownText string) string {
|
func (ctx *Context) MarkdownString(markdownText string) string {
|
||||||
return ctx.framework.rest.Markdown([]byte(markdownText))
|
return ctx.framework.ResponseString(contentMarkdown, markdownText)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Markdown parses and renders to the client a particular (dynamic) markdown string
|
// Markdown parses and renders to the client a particular (dynamic) markdown string
|
||||||
|
|||||||
2
http.go
2
http.go
@@ -641,7 +641,7 @@ func joinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
|
|||||||
|
|
||||||
func profileMiddleware(debugPath string) Middleware {
|
func profileMiddleware(debugPath string) Middleware {
|
||||||
htmlMiddleware := HandlerFunc(func(ctx *Context) {
|
htmlMiddleware := HandlerFunc(func(ctx *Context) {
|
||||||
ctx.SetContentType(contentHTML + "; charset=" + config.Charset)
|
ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
})
|
})
|
||||||
indexHandler := ToHandlerFunc(pprof.Index)
|
indexHandler := ToHandlerFunc(pprof.Index)
|
||||||
|
|||||||
160
iris.go
160
iris.go
@@ -45,13 +45,14 @@
|
|||||||
//
|
//
|
||||||
// -----------------------------DOCUMENTATION----------------------------
|
// -----------------------------DOCUMENTATION----------------------------
|
||||||
// ----------------------------_______________---------------------------
|
// ----------------------------_______________---------------------------
|
||||||
// For middleware, templates, sessions, websockets, mails, subdomains,
|
// For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
|
||||||
// dynamic subdomains, routes, party of subdomains & routes and much more
|
// dynamic subdomains, routes, party of subdomains & routes and much more
|
||||||
// visit https://www.gitbook.com/book/kataras/iris/details
|
// visit https://www.gitbook.com/book/kataras/iris/details
|
||||||
package iris // import "github.com/kataras/iris"
|
package iris // import "github.com/kataras/iris"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -61,23 +62,29 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/gzip"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gavv/httpexpect"
|
"github.com/gavv/httpexpect"
|
||||||
"github.com/iris-contrib/errors"
|
"github.com/iris-contrib/errors"
|
||||||
"github.com/iris-contrib/logger"
|
"github.com/iris-contrib/logger"
|
||||||
"github.com/iris-contrib/rest"
|
"github.com/iris-contrib/response/data"
|
||||||
|
"github.com/iris-contrib/response/json"
|
||||||
|
"github.com/iris-contrib/response/jsonp"
|
||||||
|
"github.com/iris-contrib/response/markdown"
|
||||||
|
"github.com/iris-contrib/response/text"
|
||||||
|
"github.com/iris-contrib/response/xml"
|
||||||
"github.com/iris-contrib/template/html"
|
"github.com/iris-contrib/template/html"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/utils"
|
"github.com/kataras/iris/utils"
|
||||||
"github.com/kataras/iris/websocket"
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version of the iris
|
// Version of the iris
|
||||||
Version = "4.0.0-alpha.2"
|
Version = "4.0.0-alpha.3"
|
||||||
|
|
||||||
banner = ` _____ _
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
@@ -93,7 +100,7 @@ var (
|
|||||||
Config *config.Iris
|
Config *config.Iris
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Plugins PluginContainer
|
Plugins PluginContainer
|
||||||
Websocket websocket.Server
|
Websocket WebsocketServer
|
||||||
Servers *ServerList
|
Servers *ServerList
|
||||||
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
||||||
// never fires false, if the .Close called then the channel is re-allocating.
|
// never fires false, if the .Close called then the channel is re-allocating.
|
||||||
@@ -151,6 +158,7 @@ type (
|
|||||||
Path(string, ...interface{}) string
|
Path(string, ...interface{}) string
|
||||||
URL(string, ...interface{}) string
|
URL(string, ...interface{}) string
|
||||||
TemplateString(string, interface{}, ...map[string]interface{}) string
|
TemplateString(string, interface{}, ...map[string]interface{}) string
|
||||||
|
ResponseString(string, interface{}, ...map[string]interface{}) string
|
||||||
Tester(t *testing.T) *httpexpect.Expect
|
Tester(t *testing.T) *httpexpect.Expect
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,18 +167,18 @@ type (
|
|||||||
// Implements the FrameworkAPI
|
// Implements the FrameworkAPI
|
||||||
Framework struct {
|
Framework struct {
|
||||||
*muxAPI
|
*muxAPI
|
||||||
rest *rest.Render
|
Config *config.Iris
|
||||||
|
gzipWriterPool sync.Pool // used for several methods, usually inside context
|
||||||
sessions *sessionsManager
|
sessions *sessionsManager
|
||||||
templates *TemplateEngines
|
responses *responseEngines
|
||||||
|
templates *templateEngines
|
||||||
// fields which are useful to the user/dev
|
// fields which are useful to the user/dev
|
||||||
// the last added server is the main server
|
// the last added server is the main server
|
||||||
Servers *ServerList
|
Servers *ServerList
|
||||||
Config *config.Iris
|
|
||||||
// configuration by instance.Logger.Config
|
// configuration by instance.Logger.Config
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Plugins PluginContainer
|
Plugins PluginContainer
|
||||||
Websocket websocket.Server
|
Websocket WebsocketServer
|
||||||
Available chan bool
|
Available chan bool
|
||||||
// this is setted once when .Tester(t) is called
|
// this is setted once when .Tester(t) is called
|
||||||
testFramework *httpexpect.Expect
|
testFramework *httpexpect.Expect
|
||||||
@@ -188,7 +196,14 @@ func New(cfg ...config.Iris) *Framework {
|
|||||||
|
|
||||||
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
|
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
|
||||||
// some things never change :)
|
// some things never change :)
|
||||||
s := &Framework{Config: &c, Available: make(chan bool)}
|
s := &Framework{
|
||||||
|
Config: &c,
|
||||||
|
gzipWriterPool: sync.Pool{New: func() interface{} {
|
||||||
|
return &gzip.Writer{}
|
||||||
|
}},
|
||||||
|
responses: &responseEngines{},
|
||||||
|
Available: make(chan bool),
|
||||||
|
}
|
||||||
{
|
{
|
||||||
///NOTE: set all with s.Config pointer
|
///NOTE: set all with s.Config pointer
|
||||||
// set the Logger
|
// set the Logger
|
||||||
@@ -196,17 +211,17 @@ func New(cfg ...config.Iris) *Framework {
|
|||||||
// set the plugin container
|
// set the plugin container
|
||||||
s.Plugins = &pluginContainer{logger: s.Logger}
|
s.Plugins = &pluginContainer{logger: s.Logger}
|
||||||
// set the templates
|
// set the templates
|
||||||
s.templates = &TemplateEngines{
|
s.templates = &templateEngines{
|
||||||
Helpers: map[string]interface{}{
|
helpers: map[string]interface{}{
|
||||||
"url": s.URL,
|
"url": s.URL,
|
||||||
"urlpath": s.Path,
|
"urlpath": s.Path,
|
||||||
},
|
},
|
||||||
Engines: make([]*TemplateEngineWrapper, 0),
|
engines: make([]*templateEngineWrapper, 0),
|
||||||
}
|
}
|
||||||
//set the session manager
|
//set the session manager
|
||||||
s.sessions = newSessionsManager(c.Sessions)
|
s.sessions = newSessionsManager(c.Sessions)
|
||||||
// set the websocket server
|
// set the websocket server
|
||||||
s.Websocket = websocket.NewServer(s.Config.Websocket)
|
s.Websocket = NewWebsocketServer(s.Config.Websocket)
|
||||||
// set the servemux, which will provide us the public API also, with its context pool
|
// set the servemux, which will provide us the public API also, with its context pool
|
||||||
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
|
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
|
||||||
mux.onLookup = s.Plugins.DoPreLookup
|
mux.onLookup = s.Plugins.DoPreLookup
|
||||||
@@ -220,22 +235,46 @@ func New(cfg ...config.Iris) *Framework {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Framework) initialize() {
|
func (s *Framework) initialize() {
|
||||||
// set the rest
|
// prepare the response engines, if no response engines setted for the default content-types
|
||||||
s.rest = rest.New(s.Config.Rest)
|
// then add them
|
||||||
|
|
||||||
|
for _, ctype := range defaultResponseKeys {
|
||||||
|
if rengine := s.responses.getBy(ctype); rengine == nil {
|
||||||
|
// if not exists
|
||||||
|
switch ctype {
|
||||||
|
case contentText:
|
||||||
|
s.UseResponse(text.New(), ctype)
|
||||||
|
case contentBinary:
|
||||||
|
s.UseResponse(data.New(), ctype)
|
||||||
|
case contentJSON:
|
||||||
|
s.UseResponse(json.New(), ctype)
|
||||||
|
case contentJSONP:
|
||||||
|
s.UseResponse(jsonp.New(), ctype)
|
||||||
|
case contentXML:
|
||||||
|
s.UseResponse(xml.New(), ctype)
|
||||||
|
case contentMarkdown:
|
||||||
|
s.UseResponse(markdown.New(), ctype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prepare the templates if enabled
|
// prepare the templates if enabled
|
||||||
if !s.Config.DisableTemplateEngines {
|
if !s.Config.DisableTemplateEngines {
|
||||||
if err := s.templates.LoadAll(); err != nil {
|
|
||||||
s.Logger.Panic(err) // panic on templates loading before listening if we have an error.
|
|
||||||
}
|
|
||||||
// check and prepare the templates
|
// check and prepare the templates
|
||||||
if len(s.templates.Engines) == 0 { // no template engine is registered, let's use the default
|
if len(s.templates.engines) == 0 { // no template engine is registered, let's use the default
|
||||||
s.UseTemplate(html.New())
|
s.UseTemplate(html.New())
|
||||||
}
|
}
|
||||||
s.templates.Reload = s.Config.IsDevelopment
|
|
||||||
|
if err := s.templates.loadAll(); err != nil {
|
||||||
|
s.Logger.Panic(err) // panic on templates loading before listening if we have an error.
|
||||||
|
}
|
||||||
|
|
||||||
|
s.templates.reload = s.Config.IsDevelopment
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen to websocket connections
|
// listen to websocket connections
|
||||||
websocket.RegisterServer(s, s.Websocket, s.Logger)
|
RegisterWebsocketServer(s, s.Websocket, s.Logger)
|
||||||
|
|
||||||
// prepare the mux & the server
|
// prepare the mux & the server
|
||||||
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
||||||
@@ -486,6 +525,40 @@ func (s *Framework) UseSessionDB(db SessionDatabase) {
|
|||||||
s.sessions.provider.registerDatabase(db)
|
s.sessions.provider.registerDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
|
||||||
|
// the gzip and charset are automatically supported by Iris, by passing the iris.RenderOptions{} map on the context.Render
|
||||||
|
// context.Render renders this response or a template engine if no response engine with the 'key' found
|
||||||
|
// with these engines you can inject the context.JSON,Text,Data,JSONP,XML also
|
||||||
|
// to do that just register with UseResponse(myEngine,"application/json") and so on
|
||||||
|
// look at the https://github.com/iris-contrib/response for examples
|
||||||
|
//
|
||||||
|
// if more than one respone engine with the same key/content type exists then the results will be appended to the final request's body
|
||||||
|
// this allows the developer to be able to create 'middleware' responses engines
|
||||||
|
//
|
||||||
|
// Note: if you pass an engine which contains a dot('.') as key, then the engine will not be registered.
|
||||||
|
// you don't have to import and use github.com/iris-contrib/json, jsonp, xml, data, text, markdown
|
||||||
|
// because iris uses these by default if no other response engine is registered for these content types
|
||||||
|
func UseResponse(e ResponseEngine, forContentTypesOrKeys ...string) {
|
||||||
|
Default.UseResponse(e, forContentTypesOrKeys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
|
||||||
|
// the gzip and charset are automatically supported by Iris, by passing the iris.RenderOptions{} map on the context.Render
|
||||||
|
// context.Render renders this response or a template engine if no response engine with the 'key' found
|
||||||
|
// with these engines you can inject the context.JSON,Text,Data,JSONP,XML also
|
||||||
|
// to do that just register with UseResponse(myEngine,"application/json") and so on
|
||||||
|
// look at the https://github.com/iris-contrib/response for examples
|
||||||
|
//
|
||||||
|
// if more than one respone engine with the same key/content type exists then the results will be appended to the final request's body
|
||||||
|
// this allows the developer to be able to create 'middleware' responses engines
|
||||||
|
//
|
||||||
|
// Note: if you pass an engine which contains a dot('.') as key, then the engine will not be registered.
|
||||||
|
// you don't have to import and use github.com/iris-contrib/json, jsonp, xml, data, text, markdown
|
||||||
|
// because iris uses these by default if no other response engine is registered for these content types
|
||||||
|
func (s *Framework) UseResponse(e ResponseEngine, forContentTypesOrKeys ...string) {
|
||||||
|
s.responses.add(e, forContentTypesOrKeys...)
|
||||||
|
}
|
||||||
|
|
||||||
// UseTemplate adds a template engine to the iris view system
|
// UseTemplate adds a template engine to the iris view system
|
||||||
// it does not build/load them yet
|
// it does not build/load them yet
|
||||||
func UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
func UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
||||||
@@ -495,7 +568,7 @@ func UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
|||||||
// UseTemplate adds a template engine to the iris view system
|
// UseTemplate adds a template engine to the iris view system
|
||||||
// it does not build/load them yet
|
// it does not build/load them yet
|
||||||
func (s *Framework) UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
func (s *Framework) UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
||||||
return s.templates.Add(e)
|
return s.templates.add(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseGlobal registers Handler middleware to the beginning, prepends them instead of append
|
// UseGlobal registers Handler middleware to the beginning, prepends them instead of append
|
||||||
@@ -716,6 +789,25 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcquireGzip prepares a gzip writer and returns it
|
||||||
|
//
|
||||||
|
// Note that: each iris station has its own pool
|
||||||
|
// see ReleaseGzip
|
||||||
|
func (s *Framework) AcquireGzip(w io.Writer) *gzip.Writer {
|
||||||
|
gzipWriter := s.gzipWriterPool.Get().(*gzip.Writer)
|
||||||
|
gzipWriter.Reset(w)
|
||||||
|
return gzipWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseGzip called when flush/close and put the gzip writer back to the pool
|
||||||
|
//
|
||||||
|
// Note that: each iris station has its own pool
|
||||||
|
// see AcquireGzip
|
||||||
|
func (s *Framework) ReleaseGzip(gzipWriter *gzip.Writer) {
|
||||||
|
gzipWriter.Close()
|
||||||
|
s.gzipWriterPool.Put(gzipWriter)
|
||||||
|
}
|
||||||
|
|
||||||
// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails
|
// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails
|
||||||
// returns empty string on error
|
// returns empty string on error
|
||||||
func TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string {
|
func TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string {
|
||||||
@@ -728,7 +820,25 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{},
|
|||||||
if s.Config.DisableTemplateEngines {
|
if s.Config.DisableTemplateEngines {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
res, err := s.templates.GetBy(templateFile).ExecuteToString(templateFile, pageContext, options...)
|
res, err := s.templates.getBy(templateFile).executeToString(templateFile, pageContext, options...)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseString returns the string of a response engine,
|
||||||
|
// does not render it to the client
|
||||||
|
// returns empty string on error
|
||||||
|
func ResponseString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
|
||||||
|
return Default.ResponseString(keyOrContentType, obj, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseString returns the string of a response engine,
|
||||||
|
// does not render it to the client
|
||||||
|
// returns empty string on error
|
||||||
|
func (s *Framework) ResponseString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
|
||||||
|
res, err := s.responses.getBy(keyOrContentType).toString(obj, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
220
response.go
Normal file
220
response.go
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
package iris
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/iris-contrib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// notes for me:
|
||||||
|
// edw an kai kalh idea alla den 9a borw na exw ta defaults mesa sto iris
|
||||||
|
// kai 9a prepei na to metaferw auto sto context i sto utils kai pragmatika den leei
|
||||||
|
// na kanoun import auto gia na kanoun to response engine, ara na prospa9isw kapws aliws mesw context.IContext mono
|
||||||
|
// ektos an metaferw ta defaults mesa sto iris
|
||||||
|
// alla an to kanw auto 9a prepei na vrw tropo na kanoun configuration ta defaults
|
||||||
|
// kai to idio prepei na kanw kai sto template engine html tote...
|
||||||
|
// diladi na kanw prwta to render tou real engine kai meta na parw
|
||||||
|
// ta contents tou body kai na ta kanw gzip ? na kanw resetbody kai na ta ksanasteilw?
|
||||||
|
// alla auto einai argh methodos kai gia ton poutso dn m aresei
|
||||||
|
// kai ola auta gia na mh valw ena property parapanw? 9a valw ...
|
||||||
|
// 9a einai writer,headers,object,options... anagastika.
|
||||||
|
|
||||||
|
/* notes for me:
|
||||||
|
english 'final' thoughs' results now:
|
||||||
|
|
||||||
|
the response engine will be registered with its content type
|
||||||
|
for example: iris.UseResponse("application/json", the engine or func here, optionalOptions...)
|
||||||
|
if more than one response engines registered for the same content type
|
||||||
|
then all the content results will be sent to the client
|
||||||
|
there will be available a system something like middleware but for the response engines
|
||||||
|
this will be useful when one response engine's job is only to add more content to the existing/parent response engine's results .
|
||||||
|
for example when you want to add a standard json object like a last_fetch { "year":...,"month":...,"day":...} for all json responses.
|
||||||
|
The engine will not have access to context or something like that.
|
||||||
|
The default engines will registered when no other engine with the same content type
|
||||||
|
already registered, like I did with template engines, so if someone wants to make a 'middleware' for the default response engines
|
||||||
|
must register them explicit and after register his/her response engine too, for that reason
|
||||||
|
the default engines will not be located inside iris but inside iris-contrib (like the default,and others, template engine),
|
||||||
|
the reason is to be easier to the user/dev to remember what import path should use when he/she wants to edit something,
|
||||||
|
for templates it's 'iris-contrib/template', for response engines will be 'iris-contrib/response'.
|
||||||
|
The body content will return as []byte, al/mong with an error if something bad happened.
|
||||||
|
Now you may ask why not set the header inside from response engine? because to do that we could have one of these four downsides:
|
||||||
|
1.to have access to context.IContext or *Context(if *Context then default engines should live here in iris repo)
|
||||||
|
and if we have context.IContext or *Context we will not be able to set a fast built'n gzip option,
|
||||||
|
because we would copy the contents to the gzip writer, and after copy these contents back to the response body's writer
|
||||||
|
but with an io.Writer as parameter we can simple change this writer to gzip writer and continue to the response engine after.
|
||||||
|
2. we could make something like ResponseWriter struct { io.Writer,Header *fasthttp.ResponseHeader}
|
||||||
|
inside iris repo(then the default response engines should exists in the iris repo and configuration will depends on the iris' configs )
|
||||||
|
or inside context/ folder inside iris repo, then the user/dev should import this path to
|
||||||
|
do his/her response engine, and I want simple things as usual, also we would make a pool for this response writer and create new if not available exist,
|
||||||
|
and this is performarnce downs witch I dissalow on Iris whne no need.
|
||||||
|
3. to have 4 parameters, the writer, the headers(again the user should import the fasthttp to do his/her response engine and I want simple things, as I told before),
|
||||||
|
the object and the optional parameters
|
||||||
|
4. one more function to implement like 'ContentType() string', but if we select this we lose the functionality for ResponseEngine created as simple function,
|
||||||
|
and the biggest issue will be that one response engine must explicit exists for one content type, the user/dev will not be available (to easly)
|
||||||
|
to set the content type for the engine.
|
||||||
|
these were the reasons I decide to set the content type by the frontend iris API itself and not taken by the response engine.
|
||||||
|
|
||||||
|
The Response will have two parameters (one required only) interface{], ...options}, and two return values([]byte,error)
|
||||||
|
The (first) parameter will be an interface{}, for json a json struct, for xml an xml struct, for binary data .([]byte) and so on
|
||||||
|
There will be available a second optional parameter, map of options, the "gzip" option will be built'n implemented by iris
|
||||||
|
so the response engines no need to manually add gzip support(same with template engines).
|
||||||
|
The Charset will be added to the headers automatically, for the previous example of json and the default charset which is UTF-8
|
||||||
|
the end "Content-Type" header content will be: "application/json; charset=UTF-8"
|
||||||
|
if the registered content type is not a $content/type then the text/plain will be sent to the client.
|
||||||
|
|
||||||
|
OR WAIT, some engines maybe want to set the content type or other headers dynamically or render a response depends on cookies or some other existence headers
|
||||||
|
on that situtions it will be impossible with this implementation I explained before, so...
|
||||||
|
access to context.IContext and return the []byte, in order to be able to add the built'n gzip support
|
||||||
|
the dev/user will have to make this import no no no we stick to the previous though, because
|
||||||
|
if the user wants to check all that he/she can just use a middleware with .Use/.UseFunc
|
||||||
|
this is not a middleware implementation, this is a custom content rendering, let's stick to that.
|
||||||
|
|
||||||
|
Ok I did that and I realized that template and response engines, final method structure (string,interface{},options...) is the same
|
||||||
|
so I make the ctx.Render/RenderWithStatus to work with both engines, so the developer can use any type of response engine and render it with ease.
|
||||||
|
Maybe at the future I could have one file 'render.go' which will contain the template engines and response engines, we will see, these all are unique so excuse me if something goes wrong xD
|
||||||
|
|
||||||
|
That's all. Hope some one (other than me) will understand the english here...
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ResponseEngine is the interface which all response engines should implement to send responses
|
||||||
|
// ResponseEngine(s) can be registered with,for example: iris.UseResponse(json.New(), "application/json")
|
||||||
|
ResponseEngine interface {
|
||||||
|
Response(interface{}, ...map[string]interface{}) ([]byte, error)
|
||||||
|
}
|
||||||
|
// ResponseEngineFunc is the alternative way to implement a ResponseEngine using a simple function
|
||||||
|
ResponseEngineFunc func(interface{}, ...map[string]interface{}) ([]byte, error)
|
||||||
|
|
||||||
|
// responseEngineMap is a wrapper with key (content type or name) values(engines) for the registered response engine
|
||||||
|
// it contains all response engines for a specific contentType and two functions, render and toString
|
||||||
|
// these will be used by the iris' context and iris' ResponseString, yes like TemplateToString
|
||||||
|
// it's an internal struct, no need to be exported and return that on registration,
|
||||||
|
// because the two top funcs will be easier to use by the user/dev for multiple engines
|
||||||
|
responseEngineMap struct {
|
||||||
|
values []ResponseEngine
|
||||||
|
// this is used in order to the wrapper to be gettable by the responseEngines iteral,
|
||||||
|
// if key is not a $content/type then the text/plain will be sent to the client
|
||||||
|
key string
|
||||||
|
contentType string // it's not the full content type with charset
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// markdown is custom type, used inside iris to initialize the defaults response engines if no other engine registered with these keys
|
||||||
|
defaultResponseKeys = [...]string{contentText, contentXML, contentBinary, contentJSON, contentJSONP, contentMarkdown}
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNoResponseEngineFound = errors.New("No response engine found")
|
||||||
|
|
||||||
|
// Response returns a response to the client(request's body content)
|
||||||
|
func (r ResponseEngineFunc) Response(obj interface{}, options ...map[string]interface{}) ([]byte, error) {
|
||||||
|
return r(obj, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// on context: Send(contentType string, obj interface{}, ...options)
|
||||||
|
|
||||||
|
func (r *responseEngineMap) add(engine ResponseEngine) {
|
||||||
|
r.values = append(r.values, engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the gzip and charset options are built'n with iris
|
||||||
|
func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map[string]interface{}) error {
|
||||||
|
|
||||||
|
if r == nil {
|
||||||
|
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
|
||||||
|
return errNoResponseEngineFound.Return()
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalResult []byte
|
||||||
|
|
||||||
|
for i, n := 0, len(r.values); i < n; i++ {
|
||||||
|
result, err := r.values[i].Response(obj, options...)
|
||||||
|
if err != nil { // fail on first the first error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
finalResult = append(finalResult, result...)
|
||||||
|
}
|
||||||
|
|
||||||
|
gzipEnabled := false
|
||||||
|
charset := ctx.framework.Config.Charset
|
||||||
|
if len(options) > 0 {
|
||||||
|
gzipEnabled = getGzipOption(options[0]) // located to the template.go below the RenderOptions
|
||||||
|
if chs := getCharsetOption(options[0]); chs != "" {
|
||||||
|
charset = chs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetContentType(r.contentType + "; charset=" + charset)
|
||||||
|
|
||||||
|
if gzipEnabled {
|
||||||
|
ctx.Response.Header.Add("Content-Encoding", "gzip")
|
||||||
|
gzipWriter := ctx.framework.AcquireGzip(ctx.Response.BodyWriter())
|
||||||
|
defer ctx.framework.ReleaseGzip(gzipWriter)
|
||||||
|
_, err := gzipWriter.Write(finalResult)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.Response.SetBody(finalResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseEngineMap) toString(obj interface{}, options ...map[string]interface{}) (string, error) {
|
||||||
|
if r == nil {
|
||||||
|
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
|
||||||
|
return "", errNoResponseEngineFound.Return()
|
||||||
|
}
|
||||||
|
var finalResult []byte
|
||||||
|
for i, n := 0, len(r.values); i < n; i++ {
|
||||||
|
result, err := r.values[i].Response(obj, options...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
finalResult = append(finalResult, result...)
|
||||||
|
}
|
||||||
|
return string(finalResult), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseEngines struct {
|
||||||
|
engines []*responseEngineMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// add accepts a simple response engine with its content type or key, key should not contains a dot('.').
|
||||||
|
func (r *responseEngines) add(engine ResponseEngine, forContentTypesOrKeys ...string) {
|
||||||
|
if r.engines == nil {
|
||||||
|
r.engines = make([]*responseEngineMap, 0)
|
||||||
|
}
|
||||||
|
for _, key := range forContentTypesOrKeys {
|
||||||
|
if strings.IndexByte(key, '.') != -1 { // the dot is not allowed as key
|
||||||
|
continue // skip this engine
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCtypeAndKey := contentText
|
||||||
|
if len(key) == 0 {
|
||||||
|
//if empty key, then set it to text/plain
|
||||||
|
key = defaultCtypeAndKey
|
||||||
|
}
|
||||||
|
|
||||||
|
engineMap := r.getBy(key)
|
||||||
|
if engineMap == nil {
|
||||||
|
|
||||||
|
ctype := defaultCtypeAndKey
|
||||||
|
if strings.IndexByte(key, slashByte) != -1 { // pure check, but developer should know the content types at least.
|
||||||
|
// we have 'valid' content type
|
||||||
|
ctype = key
|
||||||
|
}
|
||||||
|
engineMap = &responseEngineMap{values: make([]ResponseEngine, 0), key: key, contentType: ctype}
|
||||||
|
r.engines = append(r.engines, engineMap)
|
||||||
|
}
|
||||||
|
engineMap.add(engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseEngines) getBy(key string) *responseEngineMap {
|
||||||
|
for i, n := 0, len(r.engines); i < n; i++ {
|
||||||
|
return r.engines[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
107
template.go
107
template.go
@@ -1,11 +1,9 @@
|
|||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/iris-contrib/errors"
|
"github.com/iris-contrib/errors"
|
||||||
"github.com/kataras/iris/utils"
|
"github.com/kataras/iris/utils"
|
||||||
@@ -17,14 +15,6 @@ var (
|
|||||||
// DefaultTemplateDirectory the default directory if empty setted
|
// DefaultTemplateDirectory the default directory if empty setted
|
||||||
DefaultTemplateDirectory = "." + utils.PathSeparator + "templates"
|
DefaultTemplateDirectory = "." + utils.PathSeparator + "templates"
|
||||||
)
|
)
|
||||||
var (
|
|
||||||
// ContentTypeHTML the content type header for rendering
|
|
||||||
// this can be changed
|
|
||||||
ContentTypeHTML = "text/html"
|
|
||||||
// Charset the charset header for rendering
|
|
||||||
// this can be changed
|
|
||||||
Charset = "UTF-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
||||||
@@ -81,6 +71,22 @@ func (t TemplateFuncs) IsFree(key string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getGzipOption(options map[string]interface{}) bool {
|
||||||
|
gzipOpt := options["gzip"] // we only need that, so don't create new map to keep the options.
|
||||||
|
if b, isBool := gzipOpt.(bool); isBool {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCharsetOption(options map[string]interface{}) string {
|
||||||
|
charsetOpt := options["charset"]
|
||||||
|
if s, isString := charsetOpt.(string); isString {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return "" // we return empty in order to set the default charset if not founded.
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// TemplateEngineLocation contains the funcs to set the location for the templates by directory or by binary
|
// TemplateEngineLocation contains the funcs to set the location for the templates by directory or by binary
|
||||||
TemplateEngineLocation struct {
|
TemplateEngineLocation struct {
|
||||||
@@ -119,14 +125,12 @@ func (t *TemplateEngineLocation) isBinary() bool {
|
|||||||
return t.assetFn != nil && t.namesFn != nil
|
return t.assetFn != nil && t.namesFn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateEngineWrapper is the wrapper of a template engine
|
// templateEngineWrapper is the wrapper of a template engine
|
||||||
type TemplateEngineWrapper struct {
|
type templateEngineWrapper struct {
|
||||||
TemplateEngine
|
TemplateEngine
|
||||||
location *TemplateEngineLocation
|
location *TemplateEngineLocation
|
||||||
buffer *utils.BufferPool
|
buffer *utils.BufferPool
|
||||||
gzipWriterPool sync.Pool
|
|
||||||
reload bool
|
reload bool
|
||||||
combiledContentType string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -134,7 +138,7 @@ var (
|
|||||||
errNoTemplateEngineForExt = errors.New("No template engine found to manage '%s' extensions")
|
errNoTemplateEngineForExt = errors.New("No template engine found to manage '%s' extensions")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *TemplateEngineWrapper) load() error {
|
func (t *templateEngineWrapper) load() error {
|
||||||
if t.location.isBinary() {
|
if t.location.isBinary() {
|
||||||
t.LoadAssets(t.location.directory, t.location.extension, t.location.assetFn, t.location.namesFn)
|
t.LoadAssets(t.location.directory, t.location.extension, t.location.assetFn, t.location.namesFn)
|
||||||
} else if t.location.directory != "" {
|
} else if t.location.directory != "" {
|
||||||
@@ -145,13 +149,14 @@ func (t *TemplateEngineWrapper) load() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute execute a template and write its result to the context's body
|
// execute execute a template and write its result to the context's body
|
||||||
// options are the optional runtime options can be passed by user and catched by the template engine when render
|
// options are the optional runtime options can be passed by user and catched by the template engine when render
|
||||||
// an example of this is the "layout"
|
// an example of this is the "layout"
|
||||||
// note that gzip option is an iris dynamic option which exists for all template engines
|
// note that gzip option is an iris dynamic option which exists for all template engines
|
||||||
func (t *TemplateEngineWrapper) Execute(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) {
|
// the gzip and charset options are built'n with iris
|
||||||
|
func (t *templateEngineWrapper) execute(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
//file extension, but no template engine registered, this caused by context, and TemplateEngines. GetBy
|
//file extension, but no template engine registered, this caused by context, and templateEngines. getBy
|
||||||
return errNoTemplateEngineForExt.Format(filepath.Ext(filename))
|
return errNoTemplateEngineForExt.Format(filepath.Ext(filename))
|
||||||
}
|
}
|
||||||
if t.reload {
|
if t.reload {
|
||||||
@@ -162,10 +167,11 @@ func (t *TemplateEngineWrapper) Execute(ctx *Context, filename string, binding i
|
|||||||
|
|
||||||
// we do all these because we don't want to initialize a new map for each execution...
|
// we do all these because we don't want to initialize a new map for each execution...
|
||||||
gzipEnabled := false
|
gzipEnabled := false
|
||||||
|
charset := ctx.framework.Config.Charset
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
gzipOpt := options[0]["gzip"] // we only need that, so don't create new map to keep the options.
|
gzipEnabled = getGzipOption(options[0])
|
||||||
if b, isBool := gzipOpt.(bool); isBool {
|
if chs := getCharsetOption(options[0]); chs != "" {
|
||||||
gzipEnabled = b
|
charset = chs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,26 +184,25 @@ func (t *TemplateEngineWrapper) Execute(ctx *Context, filename string, binding i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.SetContentType(contentHTML + "; charset=" + charset)
|
||||||
|
|
||||||
var out io.Writer
|
var out io.Writer
|
||||||
if gzipEnabled {
|
if gzipEnabled {
|
||||||
ctx.Response.Header.Add("Content-Encoding", "gzip")
|
ctx.Response.Header.Add("Content-Encoding", "gzip")
|
||||||
gzipWriter := t.gzipWriterPool.Get().(*gzip.Writer)
|
gzipWriter := ctx.framework.AcquireGzip(ctx.Response.BodyWriter())
|
||||||
gzipWriter.Reset(ctx.Response.BodyWriter())
|
defer ctx.framework.ReleaseGzip(gzipWriter)
|
||||||
defer gzipWriter.Close()
|
|
||||||
defer t.gzipWriterPool.Put(gzipWriter)
|
|
||||||
out = gzipWriter
|
out = gzipWriter
|
||||||
} else {
|
} else {
|
||||||
out = ctx.Response.BodyWriter()
|
out = ctx.Response.BodyWriter()
|
||||||
}
|
}
|
||||||
ctx.SetHeader("Content-Type", t.combiledContentType)
|
|
||||||
|
|
||||||
return t.ExecuteWriter(out, filename, binding, options...)
|
return t.ExecuteWriter(out, filename, binding, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteToString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
|
// executeToString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
|
||||||
func (t *TemplateEngineWrapper) ExecuteToString(filename string, binding interface{}, opt ...map[string]interface{}) (result string, err error) {
|
func (t *templateEngineWrapper) executeToString(filename string, binding interface{}, opt ...map[string]interface{}) (result string, err error) {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
//file extension, but no template engine registered, this caused by context, and TemplateEngines. GetBy
|
//file extension, but no template engine registered, this caused by context, and templateEngines. getBy
|
||||||
return "", errNoTemplateEngineForExt.Format(filepath.Ext(filename))
|
return "", errNoTemplateEngineForExt.Format(filepath.Ext(filename))
|
||||||
}
|
}
|
||||||
if t.reload {
|
if t.reload {
|
||||||
@@ -215,18 +220,18 @@ func (t *TemplateEngineWrapper) ExecuteToString(filename string, binding interfa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateEngines is the container and manager of the template engines
|
// templateEngines is the container and manager of the template engines
|
||||||
type TemplateEngines struct {
|
type templateEngines struct {
|
||||||
Helpers map[string]interface{}
|
helpers map[string]interface{}
|
||||||
Engines []*TemplateEngineWrapper
|
engines []*templateEngineWrapper
|
||||||
Reload bool
|
reload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBy receives a filename, gets its extension and returns the template engine responsible for that file extension
|
// getBy receives a filename, gets its extension and returns the template engine responsible for that file extension
|
||||||
func (t *TemplateEngines) GetBy(filename string) *TemplateEngineWrapper {
|
func (t *templateEngines) getBy(filename string) *templateEngineWrapper {
|
||||||
extension := filepath.Ext(filename)
|
extension := filepath.Ext(filename)
|
||||||
for i, n := 0, len(t.Engines); i < n; i++ {
|
for i, n := 0, len(t.engines); i < n; i++ {
|
||||||
e := t.Engines[i]
|
e := t.engines[i]
|
||||||
|
|
||||||
if e.location.extension == extension {
|
if e.location.extension == extension {
|
||||||
return e
|
return e
|
||||||
@@ -235,38 +240,34 @@ func (t *TemplateEngines) GetBy(filename string) *TemplateEngineWrapper {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds but not loads a template engine
|
// add adds but not loads a template engine
|
||||||
func (t *TemplateEngines) Add(e TemplateEngine) *TemplateEngineLocation {
|
func (t *templateEngines) add(e TemplateEngine) *TemplateEngineLocation {
|
||||||
location := &TemplateEngineLocation{}
|
location := &TemplateEngineLocation{}
|
||||||
// add the iris helper funcs
|
// add the iris helper funcs
|
||||||
if funcer, ok := e.(TemplateEngineFuncs); ok {
|
if funcer, ok := e.(TemplateEngineFuncs); ok {
|
||||||
if funcer.Funcs() != nil {
|
if funcer.Funcs() != nil {
|
||||||
for k, v := range t.Helpers {
|
for k, v := range t.helpers {
|
||||||
funcer.Funcs()[k] = v
|
funcer.Funcs()[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplEngine := &TemplateEngineWrapper{
|
tmplEngine := &templateEngineWrapper{
|
||||||
TemplateEngine: e,
|
TemplateEngine: e,
|
||||||
location: location,
|
location: location,
|
||||||
buffer: utils.NewBufferPool(20),
|
buffer: utils.NewBufferPool(8),
|
||||||
gzipWriterPool: sync.Pool{New: func() interface{} {
|
reload: t.reload,
|
||||||
return &gzip.Writer{}
|
|
||||||
}},
|
|
||||||
reload: t.Reload,
|
|
||||||
combiledContentType: ContentTypeHTML + "; " + Charset,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Engines = append(t.Engines, tmplEngine)
|
t.engines = append(t.engines, tmplEngine)
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAll loads all templates using all template engines, returns the first error
|
// loadAll loads all templates using all template engines, returns the first error
|
||||||
// called on iris' initialize
|
// called on iris' initialize
|
||||||
func (t *TemplateEngines) LoadAll() error {
|
func (t *templateEngines) loadAll() error {
|
||||||
for i, n := 0, len(t.Engines); i < n; i++ {
|
for i, n := 0, len(t.engines); i < n; i++ {
|
||||||
e := t.Engines[i]
|
e := t.engines[i]
|
||||||
if e.location.directory == "" {
|
if e.location.directory == "" {
|
||||||
e.location.directory = DefaultTemplateDirectory // the defualt dir ./templates
|
e.location.directory = DefaultTemplateDirectory // the defualt dir ./templates
|
||||||
}
|
}
|
||||||
|
|||||||
1207
websocket.go
Normal file
1207
websocket.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
|||||||
# Package information
|
|
||||||
|
|
||||||
This package is new and unique, if you notice a bug or issue [post it here](https://github.com/kataras/iris/issues).
|
|
||||||
|
|
||||||
# How to use
|
|
||||||
|
|
||||||
[E-Book section](https://kataras.gitbooks.io/iris/content/package-websocket.html)
|
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
On **OSX + Safari**, we had an issue which is **fixed** now. BUT by the browser's Engine Design the socket is not closed until the whole browser window is closed,
|
|
||||||
so the **connection.OnDisconnect** event will fire when the user closes the **window browser**, **not just the browser's tab**.
|
|
||||||
|
|
||||||
- Relative issue: https://github.com/kataras/iris/issues/175
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
const stringMessageType = 0;
|
|
||||||
const intMessageType = 1;
|
|
||||||
const boolMessageType = 2;
|
|
||||||
// bytes is missing here for reasons I will explain somewhen
|
|
||||||
const jsonMessageType = 4;
|
|
||||||
|
|
||||||
const prefix = "iris-websocket-message:";
|
|
||||||
const separator = ";";
|
|
||||||
|
|
||||||
const prefixLen = prefix.length;
|
|
||||||
var separatorLen = separator.length;
|
|
||||||
var prefixAndSepIdx = prefixLen + separatorLen - 1;
|
|
||||||
var prefixIdx = prefixLen - 1;
|
|
||||||
var separatorIdx = separatorLen - 1;
|
|
||||||
|
|
||||||
type onConnectFunc = () => void;
|
|
||||||
type onDisconnectFunc = () => void;
|
|
||||||
type onNativeMessageFunc = (websocketMessage: string) => void;
|
|
||||||
type onMessageFunc = (message: any) => void;
|
|
||||||
|
|
||||||
class Ws {
|
|
||||||
private conn: WebSocket;
|
|
||||||
private isReady: boolean;
|
|
||||||
|
|
||||||
// events listeners
|
|
||||||
|
|
||||||
private connectListeners: onConnectFunc[] = [];
|
|
||||||
private disconnectListeners: onDisconnectFunc[] = [];
|
|
||||||
private nativeMessageListeners: onNativeMessageFunc[] = [];
|
|
||||||
private messageListeners: { [event: string]: onMessageFunc[] } = {};
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
constructor(endpoint: string, protocols?: string[]) {
|
|
||||||
if (!window["WebSocket"]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endpoint.indexOf("ws") == -1) {
|
|
||||||
endpoint = "ws://" + endpoint;
|
|
||||||
}
|
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
|
||||||
} else {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conn.onopen = ((evt: Event): any => {
|
|
||||||
this.fireConnect();
|
|
||||||
this.isReady = true;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conn.onclose = ((evt: Event): any => {
|
|
||||||
this.fireDisconnect();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conn.onmessage = ((evt: MessageEvent) => {
|
|
||||||
this.messageReceivedFromConn(evt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//utils
|
|
||||||
|
|
||||||
private isNumber(obj: any): boolean {
|
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isString(obj: any): boolean {
|
|
||||||
return Object.prototype.toString.call(obj) == "[object String]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private isBoolean(obj: any): boolean {
|
|
||||||
return typeof obj === 'boolean' ||
|
|
||||||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
|
|
||||||
}
|
|
||||||
|
|
||||||
private isJSON(obj: any): boolean {
|
|
||||||
try {
|
|
||||||
JSON.parse(obj);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// messages
|
|
||||||
private _msg(event: string, messageType: number, dataMessage: string): string {
|
|
||||||
|
|
||||||
return prefix + event + separator + String(messageType) + separator + dataMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private encodeMessage(event: string, data: any): string {
|
|
||||||
let m = "";
|
|
||||||
let t = 0;
|
|
||||||
if (this.isNumber(data)) {
|
|
||||||
t = intMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isBoolean(data)) {
|
|
||||||
t = boolMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isString(data)) {
|
|
||||||
t = stringMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
} else if (this.isJSON(data)) {
|
|
||||||
//propably json-object
|
|
||||||
t = jsonMessageType;
|
|
||||||
m = JSON.stringify(data);
|
|
||||||
} else {
|
|
||||||
console.log("Invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._msg(event, t, m);
|
|
||||||
}
|
|
||||||
|
|
||||||
private decodeMessage<T>(event: string, websocketMessage: string): T | any {
|
|
||||||
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
|
|
||||||
let skipLen = prefixLen + separatorLen + event.length + 2;
|
|
||||||
if (websocketMessage.length < skipLen + 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let messageType = parseInt(websocketMessage.charAt(skipLen - 2));
|
|
||||||
let theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
|
|
||||||
if (messageType == intMessageType) {
|
|
||||||
return parseInt(theMessage);
|
|
||||||
} else if (messageType == boolMessageType) {
|
|
||||||
return Boolean(theMessage);
|
|
||||||
} else if (messageType == stringMessageType) {
|
|
||||||
return theMessage;
|
|
||||||
} else if (messageType == jsonMessageType) {
|
|
||||||
return JSON.parse(theMessage);
|
|
||||||
} else {
|
|
||||||
return null; // invalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCustomEvent(websocketMessage: string): string {
|
|
||||||
if (websocketMessage.length < prefixAndSepIdx) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
let s = websocketMessage.substring(prefixAndSepIdx, websocketMessage.length);
|
|
||||||
let evt = s.substring(0, s.indexOf(separator));
|
|
||||||
|
|
||||||
return evt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCustomMessage(event: string, websocketMessage: string): string {
|
|
||||||
let eventIdx = websocketMessage.indexOf(event + separator);
|
|
||||||
let s = websocketMessage.substring(eventIdx + event.length + separator.length+2, websocketMessage.length);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Ws Events
|
|
||||||
|
|
||||||
// messageReceivedFromConn this is the func which decides
|
|
||||||
// if it's a native websocket message or a custom iris-ws message
|
|
||||||
// if native message then calls the fireNativeMessage
|
|
||||||
// else calls the fireMessage
|
|
||||||
//
|
|
||||||
// remember Iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
|
|
||||||
private messageReceivedFromConn(evt: MessageEvent): void {
|
|
||||||
//check if iris-ws message
|
|
||||||
let message = <string>evt.data;
|
|
||||||
if (message.indexOf(prefix) != -1) {
|
|
||||||
let event = this.getCustomEvent(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: onDisconnectFunc): void {
|
|
||||||
this.disconnectListeners.push(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireDisconnect(): void {
|
|
||||||
for (let i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnMessage(cb: onNativeMessageFunc): void {
|
|
||||||
this.nativeMessageListeners.push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireNativeMessage(websocketMessage: string): void {
|
|
||||||
for (let i = 0; i < this.nativeMessageListeners.length; i++) {
|
|
||||||
this.nativeMessageListeners[i](websocketMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
On(event: string, cb: onMessageFunc): void {
|
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
|
||||||
this.messageListeners[event] = [];
|
|
||||||
}
|
|
||||||
this.messageListeners[event].push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireMessage(event: string, message: any): void {
|
|
||||||
for (let key in this.messageListeners) {
|
|
||||||
if (this.messageListeners.hasOwnProperty(key)) {
|
|
||||||
if (key == event) {
|
|
||||||
for (let i = 0; i < this.messageListeners[key].length; i++) {
|
|
||||||
this.messageListeners[key][i](message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Ws Actions
|
|
||||||
|
|
||||||
Disconnect(): void {
|
|
||||||
this.conn.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
EmitMessage(websocketMessage: string): void {
|
|
||||||
this.conn.send(websocketMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit sends an iris-custom websocket message
|
|
||||||
Emit(event: string, data: any): void {
|
|
||||||
let messageStr = this.encodeMessage(event, data);
|
|
||||||
this.EmitMessage(messageStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// node-modules export {Ws};
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/iris-contrib/websocket"
|
|
||||||
"github.com/kataras/iris/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 client
|
|
||||||
Connection interface {
|
|
||||||
// Emitter implements EmitMessage & Emit
|
|
||||||
Emitter
|
|
||||||
// ID returns the connection's identifier
|
|
||||||
ID() string
|
|
||||||
// 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 emmiter 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
connection struct {
|
|
||||||
underline *websocket.Conn
|
|
||||||
id string
|
|
||||||
send chan []byte
|
|
||||||
onDisconnectListeners []DisconnectFunc
|
|
||||||
onErrorListeners []ErrorFunc
|
|
||||||
onNativeMessageListeners []NativeMessageFunc
|
|
||||||
onEventListeners map[string][]MessageFunc
|
|
||||||
// these were maden for performance only
|
|
||||||
self Emitter // pre-defined emmiter than sends message to its self client
|
|
||||||
broadcast Emitter // pre-defined emmiter that sends message to all except this
|
|
||||||
all Emitter // pre-defined emmiter which sends message to all clients
|
|
||||||
|
|
||||||
server *server
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Connection = &connection{}
|
|
||||||
|
|
||||||
// connection implementation
|
|
||||||
|
|
||||||
func newConnection(websocketConn *websocket.Conn, s *server) *connection {
|
|
||||||
c := &connection{
|
|
||||||
id: utils.RandomString(64),
|
|
||||||
underline: websocketConn,
|
|
||||||
send: make(chan []byte, 256),
|
|
||||||
onDisconnectListeners: make([]DisconnectFunc, 0),
|
|
||||||
onErrorListeners: make([]ErrorFunc, 0),
|
|
||||||
onNativeMessageListeners: make([]NativeMessageFunc, 0),
|
|
||||||
onEventListeners: make(map[string][]MessageFunc, 0),
|
|
||||||
server: s,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.self = newEmitter(c, c.id)
|
|
||||||
c.broadcast = newEmitter(c, NotMe)
|
|
||||||
c.all = newEmitter(c, All)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connection) write(messageType int, data []byte) error {
|
|
||||||
c.underline.SetWriteDeadline(time.Now().Add(c.server.config.WriteTimeout))
|
|
||||||
return c.underline.WriteMessage(messageType, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connection) writer() {
|
|
||||||
ticker := time.NewTicker(c.server.config.PingPeriod)
|
|
||||||
defer func() {
|
|
||||||
ticker.Stop()
|
|
||||||
c.underline.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg, ok := <-c.send:
|
|
||||||
if !ok {
|
|
||||||
defer func() {
|
|
||||||
|
|
||||||
// FIX FOR: https://github.com/kataras/iris/issues/175
|
|
||||||
// AS I TESTED ON TRIDENT ENGINE (INTERNET EXPLORER/SAFARI):
|
|
||||||
// NAVIGATE TO SITE, CLOSE THE TAB, NOTHING HAPPENS
|
|
||||||
// CLOSE THE WHOLE BROWSER, THEN THE c.conn is NOT NILL BUT ALL ITS FUNCTIONS PANICS, MEANS THAT IS THE STRUCT IS NOT NIL BUT THE WRITER/READER ARE NIL
|
|
||||||
// THE ONLY SOLUTION IS TO RECOVER HERE AT ANY PANIC
|
|
||||||
// THE FRAMETYPE = 8, c.closeSend = true
|
|
||||||
// NOTE THAT THE CLIENT IS NOT DISCONNECTED UNTIL THE WHOLE WINDOW BROWSER CLOSED, this is engine's bug.
|
|
||||||
//
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
ticker.Stop()
|
|
||||||
c.server.free <- c
|
|
||||||
c.underline.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
c.write(websocket.CloseMessage, []byte{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.underline.SetWriteDeadline(time.Now().Add(c.server.config.WriteTimeout))
|
|
||||||
res, err := c.underline.NextWriter(websocket.TextMessage)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.Write(msg)
|
|
||||||
|
|
||||||
n := len(c.send)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
res.Write(<-c.send)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := res.Close(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if err := c.write(websocket.TextMessage, msg); err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
case <-ticker.C:
|
|
||||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connection) reader() {
|
|
||||||
defer func() {
|
|
||||||
c.server.free <- c
|
|
||||||
c.underline.Close()
|
|
||||||
}()
|
|
||||||
conn := c.underline
|
|
||||||
|
|
||||||
conn.SetReadLimit(c.server.config.MaxMessageSize)
|
|
||||||
conn.SetReadDeadline(time.Now().Add(c.server.config.PongTimeout))
|
|
||||||
conn.SetPongHandler(func(s string) error {
|
|
||||||
conn.SetReadDeadline(time.Now().Add(c.server.config.PongTimeout))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
for {
|
|
||||||
if _, data, err := conn.ReadMessage(); err != nil {
|
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
|
||||||
c.EmitError(err.Error())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
c.messageReceived(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// messageReceived checks the incoming message and fire the nativeMessage listeners or the event listeners (iris-ws custom message)
|
|
||||||
func (c *connection) messageReceived(data []byte) {
|
|
||||||
|
|
||||||
if bytes.HasPrefix(data, prefixBytes) {
|
|
||||||
customData := string(data)
|
|
||||||
//it's a custom iris-ws message
|
|
||||||
receivedEvt := getCustomEvent(customData)
|
|
||||||
listeners := c.onEventListeners[receivedEvt]
|
|
||||||
if listeners == nil { // if not listeners for this event exit from here
|
|
||||||
return
|
|
||||||
}
|
|
||||||
customMessage, err := deserialize(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) 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 == NotMe { // if send to all except me, then return the pre-defined emmiter, and so on
|
|
||||||
return c.broadcast
|
|
||||||
} else if to == All {
|
|
||||||
return c.all
|
|
||||||
} else if to == c.id {
|
|
||||||
return c.self
|
|
||||||
}
|
|
||||||
// is an emmiter to another client/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) {
|
|
||||||
payload := roomPayload{roomName, c.id}
|
|
||||||
c.server.join <- payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connection) Leave(roomName string) {
|
|
||||||
payload := roomPayload{roomName, c.id}
|
|
||||||
c.server.leave <- payload
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package websocket
|
|
||||||
|
|
||||||
const (
|
|
||||||
// All is the string which the Emitter use to send a message to all
|
|
||||||
All = ""
|
|
||||||
// NotMe is the string which the Emitter use to send a message to all except this connection
|
|
||||||
NotMe = ";iris;to;all;except;me;"
|
|
||||||
// Broadcast is the string which the Emitter use to send a message to all except this connection, same as 'NotMe'
|
|
||||||
Broadcast = NotMe
|
|
||||||
)
|
|
||||||
|
|
||||||
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{}
|
|
||||||
|
|
||||||
// emitter implementation
|
|
||||||
|
|
||||||
func newEmitter(c *connection, to string) *emitter {
|
|
||||||
return &emitter{conn: c, to: to}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emitter) EmitMessage(nativeMessage []byte) error {
|
|
||||||
mp := messagePayload{e.conn.id, e.to, nativeMessage}
|
|
||||||
e.conn.server.messages <- mp
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emitter) Emit(event string, data interface{}) error {
|
|
||||||
message, err := serialize(event, data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.EmitMessage([]byte(message))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
serializer, [de]serialize the messages from the client to the server and from the server to the client
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The same values are exists on client side also
|
|
||||||
const (
|
|
||||||
stringMessageType messageType = iota
|
|
||||||
intMessageType
|
|
||||||
boolMessageType
|
|
||||||
bytesMessageType
|
|
||||||
jsonMessageType
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
prefix = "iris-websocket-message:"
|
|
||||||
separator = ";"
|
|
||||||
prefixLen = len(prefix)
|
|
||||||
separatorLen = len(separator)
|
|
||||||
prefixAndSepIdx = prefixLen + separatorLen - 1
|
|
||||||
prefixIdx = prefixLen - 1
|
|
||||||
separatorIdx = separatorLen - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
separatorByte = separator[0]
|
|
||||||
buf = utils.NewBufferPool(256)
|
|
||||||
prefixBytes = []byte(prefix)
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
messageType uint8
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m messageType) String() string {
|
|
||||||
return strconv.Itoa(int(m))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m messageType) Name() string {
|
|
||||||
if m == stringMessageType {
|
|
||||||
return "string"
|
|
||||||
} else if m == intMessageType {
|
|
||||||
return "int"
|
|
||||||
} else if m == boolMessageType {
|
|
||||||
return "bool"
|
|
||||||
} else if m == bytesMessageType {
|
|
||||||
return "[]byte"
|
|
||||||
} else if m == jsonMessageType {
|
|
||||||
return "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Invalid(" + m.String() + ")"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize serializes a custom websocket message from server to be delivered to the client
|
|
||||||
// returns the string form of the message
|
|
||||||
// Supported data types are: string, int, bool, bytes and JSON.
|
|
||||||
func serialize(event string, data interface{}) (string, error) {
|
|
||||||
var msgType messageType
|
|
||||||
var dataMessage string
|
|
||||||
|
|
||||||
if s, ok := data.(string); ok {
|
|
||||||
msgType = stringMessageType
|
|
||||||
dataMessage = s
|
|
||||||
} else if i, ok := data.(int); ok {
|
|
||||||
msgType = intMessageType
|
|
||||||
dataMessage = strconv.Itoa(i)
|
|
||||||
} else if b, ok := data.(bool); ok {
|
|
||||||
msgType = boolMessageType
|
|
||||||
dataMessage = strconv.FormatBool(b)
|
|
||||||
} else if by, ok := data.([]byte); ok {
|
|
||||||
msgType = bytesMessageType
|
|
||||||
dataMessage = string(by)
|
|
||||||
} else {
|
|
||||||
//we suppose is json
|
|
||||||
res, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
msgType = jsonMessageType
|
|
||||||
dataMessage = string(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := buf.Get()
|
|
||||||
b.WriteString(prefix)
|
|
||||||
b.WriteString(event)
|
|
||||||
b.WriteString(separator)
|
|
||||||
b.WriteString(msgType.String())
|
|
||||||
b.WriteString(separator)
|
|
||||||
b.WriteString(dataMessage)
|
|
||||||
dataMessage = b.String()
|
|
||||||
buf.Put(b)
|
|
||||||
|
|
||||||
return dataMessage, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// deserialize deserializes a custom websocket message from the client
|
|
||||||
// ex: iris-websocket-message;chat;4;themarshaledstringfromajsonstruct will return 'hello' as string
|
|
||||||
// Supported data types are: string, int, bool, bytes and JSON.
|
|
||||||
func deserialize(event string, websocketMessage string) (message interface{}, err error) {
|
|
||||||
t, formaterr := strconv.Atoi(websocketMessage[prefixAndSepIdx+len(event)+1 : prefixAndSepIdx+len(event)+2]) // in order to iris-websocket-message;user;-> 4
|
|
||||||
if formaterr != nil {
|
|
||||||
return nil, formaterr
|
|
||||||
}
|
|
||||||
_type := messageType(t)
|
|
||||||
_message := websocketMessage[prefixAndSepIdx+len(event)+3:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
|
|
||||||
|
|
||||||
if _type == stringMessageType {
|
|
||||||
message = string(_message)
|
|
||||||
} else if _type == intMessageType {
|
|
||||||
message, err = strconv.Atoi(_message)
|
|
||||||
} else if _type == boolMessageType {
|
|
||||||
message, err = strconv.ParseBool(_message)
|
|
||||||
} else if _type == bytesMessageType {
|
|
||||||
message = []byte(_message)
|
|
||||||
} else if _type == jsonMessageType {
|
|
||||||
err = json.Unmarshal([]byte(_message), message)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Type %s is invalid for message: %s", _type.Name(), websocketMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCustomEvent return empty string when the websocketMessage is native message
|
|
||||||
func getCustomEvent(websocketMessage string) string {
|
|
||||||
if len(websocketMessage) < prefixAndSepIdx {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
s := websocketMessage[prefixAndSepIdx:]
|
|
||||||
evt := s[:strings.IndexByte(s, separatorByte)]
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/iris-contrib/websocket"
|
|
||||||
"github.com/kataras/iris/config"
|
|
||||||
"github.com/kataras/iris/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
// Rooms is just a map with key a string and value slice of string
|
|
||||||
Rooms map[string][]string
|
|
||||||
// Server is the websocket server
|
|
||||||
Server interface {
|
|
||||||
// Upgrade upgrades the client in order websocket works
|
|
||||||
Upgrade(context.IContext) error
|
|
||||||
// OnConnection registers a callback which fires when a connection/client is connected to the server
|
|
||||||
OnConnection(ConnectionFunc)
|
|
||||||
// Config returns a pointer to server's configs
|
|
||||||
Config() *config.Websocket
|
|
||||||
}
|
|
||||||
|
|
||||||
// roomPayload is used as payload from the connection to the server
|
|
||||||
roomPayload struct {
|
|
||||||
roomName string
|
|
||||||
connectionID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// payloads, connection -> server
|
|
||||||
messagePayload struct {
|
|
||||||
from string
|
|
||||||
to string
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
server struct {
|
|
||||||
config *config.Websocket
|
|
||||||
upgrader websocket.Upgrader
|
|
||||||
put chan *connection
|
|
||||||
free chan *connection
|
|
||||||
connections map[string]*connection
|
|
||||||
join chan roomPayload
|
|
||||||
leave chan roomPayload
|
|
||||||
rooms Rooms // by default a connection is joined to a room which has the connection id as its name
|
|
||||||
mu sync.Mutex // for rooms
|
|
||||||
messages chan messagePayload
|
|
||||||
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
|
|
||||||
|
|
||||||
// newServer creates a websocket server and returns it
|
|
||||||
func newServer(c *config.Websocket) *server {
|
|
||||||
s := &server{
|
|
||||||
config: c,
|
|
||||||
put: make(chan *connection),
|
|
||||||
free: make(chan *connection),
|
|
||||||
connections: make(map[string]*connection),
|
|
||||||
join: make(chan roomPayload, 1), // buffered because join can be called immediately on connection connected
|
|
||||||
leave: make(chan roomPayload),
|
|
||||||
rooms: make(Rooms),
|
|
||||||
messages: make(chan messagePayload, 1), // buffered because messages can be sent/received immediately on connection connected
|
|
||||||
onConnectionListeners: make([]ConnectionFunc, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.upgrader = websocket.Custom(s.handleConnection, s.config.ReadBufferSize, s.config.WriteBufferSize, false)
|
|
||||||
go s.serve() // start the server automatically
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Config() *config.Websocket {
|
|
||||||
return s.config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Upgrade(ctx context.IContext) error {
|
|
||||||
return s.upgrader.Upgrade(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) handleConnection(websocketConn *websocket.Conn) {
|
|
||||||
c := newConnection(websocketConn, s)
|
|
||||||
s.put <- c
|
|
||||||
go c.writer()
|
|
||||||
c.reader()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) OnConnection(cb ConnectionFunc) {
|
|
||||||
s.onConnectionListeners = append(s.onConnectionListeners, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) joinRoom(roomName string, connID string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
if s.rooms[roomName] == nil {
|
|
||||||
s.rooms[roomName] = make([]string, 0)
|
|
||||||
}
|
|
||||||
s.rooms[roomName] = append(s.rooms[roomName], connID)
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) leaveRoom(roomName string, connID string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
if s.rooms[roomName] != nil {
|
|
||||||
for i := range s.rooms[roomName] {
|
|
||||||
if s.rooms[roomName][i] == connID {
|
|
||||||
s.rooms[roomName][i] = s.rooms[roomName][len(s.rooms[roomName])-1]
|
|
||||||
s.rooms[roomName] = s.rooms[roomName][:len(s.rooms[roomName])-1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(s.rooms[roomName]) == 0 { // if room is empty then delete it
|
|
||||||
delete(s.rooms, roomName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) serve() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case c := <-s.put: // connection connected
|
|
||||||
s.connections[c.id] = c
|
|
||||||
// make and join a room with the connection's id
|
|
||||||
s.rooms[c.id] = make([]string, 0)
|
|
||||||
s.rooms[c.id] = []string{c.id}
|
|
||||||
for i := range s.onConnectionListeners {
|
|
||||||
s.onConnectionListeners[i](c)
|
|
||||||
}
|
|
||||||
case c := <-s.free: // connection closed
|
|
||||||
if _, found := s.connections[c.id]; found {
|
|
||||||
// leave from all rooms
|
|
||||||
for roomName := range s.rooms {
|
|
||||||
s.leaveRoom(roomName, c.id)
|
|
||||||
}
|
|
||||||
delete(s.connections, c.id)
|
|
||||||
close(c.send)
|
|
||||||
c.fireDisconnect()
|
|
||||||
|
|
||||||
}
|
|
||||||
case join := <-s.join:
|
|
||||||
s.joinRoom(join.roomName, join.connectionID)
|
|
||||||
case leave := <-s.leave:
|
|
||||||
s.leaveRoom(leave.roomName, leave.connectionID)
|
|
||||||
case msg := <-s.messages: // message received from the connection
|
|
||||||
if msg.to != All && msg.to != NotMe && s.rooms[msg.to] != nil {
|
|
||||||
// it suppose to send the message to a room
|
|
||||||
for _, connectionIDInsideRoom := range s.rooms[msg.to] {
|
|
||||||
if c, connected := s.connections[connectionIDInsideRoom]; connected {
|
|
||||||
c.send <- msg.data //here we send it without need to continue below
|
|
||||||
} else {
|
|
||||||
// the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE:
|
|
||||||
s.leaveRoom(c.id, msg.to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // it suppose to send the message to all opened connections or to all except the sender
|
|
||||||
for connID, c := range s.connections {
|
|
||||||
if msg.to != All { // if it's not suppose to send to all connections (including itself)
|
|
||||||
if msg.to == NotMe && msg.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case s.connections[connID].send <- msg.data: //send the message back to the connection in order to send it to the client
|
|
||||||
default:
|
|
||||||
close(c.send)
|
|
||||||
delete(s.connections, connID)
|
|
||||||
c.fireDisconnect()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/iris-contrib/logger"
|
|
||||||
"github.com/kataras/iris/config"
|
|
||||||
"github.com/kataras/iris/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// to avoid the import cycle to /kataras/iris. The ws package is used inside iris' station configuration
|
|
||||||
// inside Iris' configuration like kataras/iris/sessions, kataras/iris/render/rest, kataras/iris/render/template, kataras/iris/server and so on.
|
|
||||||
type irisStation interface {
|
|
||||||
H_(string, string, func(context.IContext)) func(string)
|
|
||||||
StaticContent(string, string, []byte) func(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// New returns a new running websocket server, registers this to the iris station
|
|
||||||
//
|
|
||||||
// Note that:
|
|
||||||
// This is not usable for you, unless you need more than one websocket server,
|
|
||||||
// because iris' station already has one which you can configure and start
|
|
||||||
//
|
|
||||||
// This is deprecated after rc-1, now we create the server and after register it
|
|
||||||
// because I want to be able to call the Websocket via a property and no via func before iris.Listen.
|
|
||||||
func New(station irisStation, c *config.Websocket, logger *logger.Logger) Server {
|
|
||||||
if c.Endpoint == "" {
|
|
||||||
//station.Logger().Panicf("Websockets - config's Endpoint is empty, you have to set it in order to enable and start the websocket server!!. Refer to the docs if you can't figure out.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
server := newServer(c)
|
|
||||||
RegisterServer(station, server, logger)
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewServer creates a websocket server and returns it
|
|
||||||
func NewServer(c *config.Websocket) Server {
|
|
||||||
return newServer(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterServer registers the handlers for the websocket server
|
|
||||||
// it's a bridge between station and websocket server
|
|
||||||
func RegisterServer(station irisStation, server Server, logger *logger.Logger) {
|
|
||||||
c := server.Config()
|
|
||||||
if c.Endpoint == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
websocketHandler := func(ctx context.IContext) {
|
|
||||||
if err := server.Upgrade(ctx); err != nil {
|
|
||||||
logger.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Headers != nil && len(c.Headers) > 0 { // only for performance matter just re-create the websocketHandler if we have headers to set
|
|
||||||
websocketHandler = func(ctx context.IContext) {
|
|
||||||
for k, v := range c.Headers {
|
|
||||||
ctx.SetHeader(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := server.Upgrade(ctx); err != nil {
|
|
||||||
logger.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
station.H_("GET", c.Endpoint, websocketHandler)
|
|
||||||
// serve the client side on domain:port/iris-ws.js
|
|
||||||
station.StaticContent("/iris-ws.js", "application/json", clientSource)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientSource = []byte(`var stringMessageType = 0;
|
|
||||||
var intMessageType = 1;
|
|
||||||
var boolMessageType = 2;
|
|
||||||
// bytes is missing here for reasons I will explain somewhen
|
|
||||||
var jsonMessageType = 4;
|
|
||||||
var prefix = "iris-websocket-message:";
|
|
||||||
var separator = ";";
|
|
||||||
var prefixLen = prefix.length;
|
|
||||||
var separatorLen = separator.length;
|
|
||||||
var prefixAndSepIdx = prefixLen + separatorLen - 1;
|
|
||||||
var prefixIdx = prefixLen - 1;
|
|
||||||
var separatorIdx = separatorLen - 1;
|
|
||||||
var Ws = (function () {
|
|
||||||
//
|
|
||||||
function Ws(endpoint, protocols) {
|
|
||||||
var _this = this;
|
|
||||||
// events listeners
|
|
||||||
this.connectListeners = [];
|
|
||||||
this.disconnectListeners = [];
|
|
||||||
this.nativeMessageListeners = [];
|
|
||||||
this.messageListeners = {};
|
|
||||||
if (!window["WebSocket"]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (endpoint.indexOf("ws") == -1) {
|
|
||||||
endpoint = "ws://" + endpoint;
|
|
||||||
}
|
|
||||||
if (protocols != null && protocols.length > 0) {
|
|
||||||
this.conn = new WebSocket(endpoint, protocols);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.conn = new WebSocket(endpoint);
|
|
||||||
}
|
|
||||||
this.conn.onopen = (function (evt) {
|
|
||||||
_this.fireConnect();
|
|
||||||
_this.isReady = true;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onclose = (function (evt) {
|
|
||||||
_this.fireDisconnect();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
this.conn.onmessage = (function (evt) {
|
|
||||||
_this.messageReceivedFromConn(evt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//utils
|
|
||||||
Ws.prototype.isNumber = function (obj) {
|
|
||||||
return !isNaN(obj - 0) && obj !== null && obj !== "" && obj !== false;
|
|
||||||
};
|
|
||||||
Ws.prototype.isString = function (obj) {
|
|
||||||
return Object.prototype.toString.call(obj) == "[object String]";
|
|
||||||
};
|
|
||||||
Ws.prototype.isBoolean = function (obj) {
|
|
||||||
return typeof obj === 'boolean' ||
|
|
||||||
(typeof obj === 'object' && typeof obj.valueOf() === 'boolean');
|
|
||||||
};
|
|
||||||
Ws.prototype.isJSON = function (obj) {
|
|
||||||
try {
|
|
||||||
JSON.parse(obj);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// messages
|
|
||||||
Ws.prototype._msg = function (event, messageType, dataMessage) {
|
|
||||||
return prefix + event + separator + String(messageType) + separator + dataMessage;
|
|
||||||
};
|
|
||||||
Ws.prototype.encodeMessage = function (event, data) {
|
|
||||||
var m = "";
|
|
||||||
var t = 0;
|
|
||||||
if (this.isNumber(data)) {
|
|
||||||
t = intMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isBoolean(data)) {
|
|
||||||
t = boolMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isString(data)) {
|
|
||||||
t = stringMessageType;
|
|
||||||
m = data.toString();
|
|
||||||
}
|
|
||||||
else if (this.isJSON(data)) {
|
|
||||||
//propably json-object
|
|
||||||
t = jsonMessageType;
|
|
||||||
m = JSON.stringify(data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid");
|
|
||||||
}
|
|
||||||
return this._msg(event, t, m);
|
|
||||||
};
|
|
||||||
Ws.prototype.decodeMessage = function (event, websocketMessage) {
|
|
||||||
//iris-websocket-message;user;4;themarshaledstringfromajsonstruct
|
|
||||||
var skipLen = prefixLen + separatorLen + event.length + 2;
|
|
||||||
if (websocketMessage.length < skipLen + 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var messageType = parseInt(websocketMessage.charAt(skipLen - 2));
|
|
||||||
var theMessage = websocketMessage.substring(skipLen, websocketMessage.length);
|
|
||||||
if (messageType == intMessageType) {
|
|
||||||
return parseInt(theMessage);
|
|
||||||
}
|
|
||||||
else if (messageType == boolMessageType) {
|
|
||||||
return Boolean(theMessage);
|
|
||||||
}
|
|
||||||
else if (messageType == stringMessageType) {
|
|
||||||
return theMessage;
|
|
||||||
}
|
|
||||||
else if (messageType == jsonMessageType) {
|
|
||||||
return JSON.parse(theMessage);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null; // invalid
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.getCustomEvent = function (websocketMessage) {
|
|
||||||
if (websocketMessage.length < prefixAndSepIdx) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
var s = websocketMessage.substring(prefixAndSepIdx, websocketMessage.length);
|
|
||||||
var evt = s.substring(0, s.indexOf(separator));
|
|
||||||
return evt;
|
|
||||||
};
|
|
||||||
Ws.prototype.getCustomMessage = function (event, websocketMessage) {
|
|
||||||
var eventIdx = websocketMessage.indexOf(event + separator);
|
|
||||||
var s = websocketMessage.substring(eventIdx + event.length + separator.length + 2, websocketMessage.length);
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Events
|
|
||||||
// messageReceivedFromConn this is the func which decides
|
|
||||||
// if it's a native websocket message or a custom iris-ws message
|
|
||||||
// if native message then calls the fireNativeMessage
|
|
||||||
// else calls the fireMessage
|
|
||||||
//
|
|
||||||
// remember Iris gives you the freedom of native websocket messages if you don't want to use this client side at all.
|
|
||||||
Ws.prototype.messageReceivedFromConn = function (evt) {
|
|
||||||
//check if iris-ws message
|
|
||||||
var message = evt.data;
|
|
||||||
if (message.indexOf(prefix) != -1) {
|
|
||||||
var event_1 = this.getCustomEvent(message);
|
|
||||||
if (event_1 != "") {
|
|
||||||
// it's a custom message
|
|
||||||
this.fireMessage(event_1, this.getCustomMessage(event_1, message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// it's a native websocket message
|
|
||||||
this.fireNativeMessage(message);
|
|
||||||
};
|
|
||||||
Ws.prototype.OnConnect = function (fn) {
|
|
||||||
if (this.isReady) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
this.connectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireConnect = function () {
|
|
||||||
for (var i = 0; i < this.connectListeners.length; i++) {
|
|
||||||
this.connectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnDisconnect = function (fn) {
|
|
||||||
this.disconnectListeners.push(fn);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireDisconnect = function () {
|
|
||||||
for (var i = 0; i < this.disconnectListeners.length; i++) {
|
|
||||||
this.disconnectListeners[i]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.OnMessage = function (cb) {
|
|
||||||
this.nativeMessageListeners.push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireNativeMessage = function (websocketMessage) {
|
|
||||||
for (var i = 0; i < this.nativeMessageListeners.length; i++) {
|
|
||||||
this.nativeMessageListeners[i](websocketMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ws.prototype.On = function (event, cb) {
|
|
||||||
if (this.messageListeners[event] == null || this.messageListeners[event] == undefined) {
|
|
||||||
this.messageListeners[event] = [];
|
|
||||||
}
|
|
||||||
this.messageListeners[event].push(cb);
|
|
||||||
};
|
|
||||||
Ws.prototype.fireMessage = function (event, message) {
|
|
||||||
for (var key in this.messageListeners) {
|
|
||||||
if (this.messageListeners.hasOwnProperty(key)) {
|
|
||||||
if (key == event) {
|
|
||||||
for (var i = 0; i < this.messageListeners[key].length; i++) {
|
|
||||||
this.messageListeners[key][i](message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Ws Actions
|
|
||||||
Ws.prototype.Disconnect = function () {
|
|
||||||
this.conn.close();
|
|
||||||
};
|
|
||||||
// EmitMessage sends a native websocket message
|
|
||||||
Ws.prototype.EmitMessage = function (websocketMessage) {
|
|
||||||
this.conn.send(websocketMessage);
|
|
||||||
};
|
|
||||||
// Emit sends an iris-custom websocket message
|
|
||||||
Ws.prototype.Emit = function (event, data) {
|
|
||||||
var messageStr = this.encodeMessage(event, data);
|
|
||||||
this.EmitMessage(messageStr);
|
|
||||||
};
|
|
||||||
return Ws;
|
|
||||||
}());
|
|
||||||
`)
|
|
||||||
Reference in New Issue
Block a user