websocket: replace sync.Map with custom map[string]*connection. Add translate template function example. Fix ctx.HandlerName() does not return the end-dev-defined current route's name, this will give better warnings when using MVC in a wrong way

Former-commit-id: 38fda8a20da9bc7665cdd209b7b367c1337dbd94
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-01-25 23:47:31 +02:00
parent 443776c423
commit 680b5a0923
6 changed files with 59 additions and 61 deletions

View File

@ -59,6 +59,19 @@ func newApp() *iris.Application {
"key2", fromSecondFileValue) "key2", fromSecondFileValue)
}) })
// using in inside your templates:
view := iris.HTML("./templates", ".html")
app.RegisterView(view)
app.Get("/templates", func(ctx iris.Context) {
ctx.View("index.html", iris.Map{
"tr": ctx.Translate,
})
// it will return "hello, iris"
// when {{call .tr "hi" "iris"}}
})
//
return app return app
} }

View File

@ -0,0 +1 @@
{{call .tr "hi" "iris"}}

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/middleware/recover"
"github.com/kataras/iris/sessions" "github.com/kataras/iris/sessions"
"github.com/kataras/iris/mvc" "github.com/kataras/iris/mvc"
@ -11,6 +12,7 @@ import (
func main() { func main() {
app := iris.New() app := iris.New()
app.Use(recover.New())
app.Logger().SetLevel("debug") app.Logger().SetLevel("debug")
mvc.Configure(app.Party("/basic"), basicMVC) mvc.Configure(app.Party("/basic"), basicMVC)

View File

@ -0,0 +1,4 @@
@echo off
REM run.bat 30
start go run main.go server
for /L %%n in (1,1,%1) do start go run main.go client

View File

@ -275,7 +275,8 @@ type Context interface {
// that can be used to share information between handlers and middleware. // that can be used to share information between handlers and middleware.
Values() *memstore.Store Values() *memstore.Store
// Translate is the i18n (localization) middleware's function, // Translate is the i18n (localization) middleware's function,
// it calls the Get("translate") to return the translated value. // it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey())
// to execute the translate function and return the localized text value.
// //
// Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
Translate(format string, args ...interface{}) string Translate(format string, args ...interface{}) string
@ -1180,6 +1181,9 @@ func (ctx *context) Proceed(h Handler) bool {
// HandlerName returns the current handler's name, helpful for debugging. // HandlerName returns the current handler's name, helpful for debugging.
func (ctx *context) HandlerName() string { func (ctx *context) HandlerName() string {
if name := ctx.currentRouteName; name != "" {
return name
}
return HandlerName(ctx.handlers[ctx.currentHandlerIndex]) return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
} }
@ -1380,7 +1384,8 @@ func (ctx *context) Values() *memstore.Store {
} }
// Translate is the i18n (localization) middleware's function, // Translate is the i18n (localization) middleware's function,
// it calls the Get("translate") to return the translated value. // it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey())
// to execute the translate function and return the localized text value.
// //
// Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
func (ctx *context) Translate(format string, args ...interface{}) string { func (ctx *context) Translate(format string, args ...interface{}) string {

View File

@ -44,9 +44,9 @@ type (
// app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) }) // app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) })
ClientSource []byte ClientSource []byte
messageSerializer *messageSerializer messageSerializer *messageSerializer
connections sync.Map // key = the Connection ID. connections map[string]*connection // key = the Connection ID.
rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name rooms map[string][]string // by default a connection is joined to a room which has the connection id as its name
mu sync.RWMutex // for rooms. mu sync.RWMutex // for rooms and connections.
onConnectionListeners []ConnectionFunc onConnectionListeners []ConnectionFunc
//connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed. //connectionPool sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
upgrader websocket.Upgrader upgrader websocket.Upgrader
@ -64,7 +64,7 @@ func New(cfg Config) *Server {
config: cfg, config: cfg,
ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1), ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1),
messageSerializer: newMessageSerializer(cfg.EvtMessagePrefix), messageSerializer: newMessageSerializer(cfg.EvtMessagePrefix),
connections: sync.Map{}, // ready-to-use, this is not necessary. connections: make(map[string]*connection),
rooms: make(map[string][]string), rooms: make(map[string][]string),
onConnectionListeners: make([]ConnectionFunc, 0), onConnectionListeners: make([]ConnectionFunc, 0),
upgrader: websocket.Upgrader{ upgrader: websocket.Upgrader{
@ -133,19 +133,14 @@ func (s *Server) Upgrade(ctx context.Context) Connection {
} }
func (s *Server) addConnection(c *connection) { func (s *Server) addConnection(c *connection) {
s.connections.Store(c.id, c) s.mu.Lock()
s.connections[c.id] = c
s.mu.Unlock()
} }
func (s *Server) getConnection(connID string) (*connection, bool) { func (s *Server) getConnection(connID string) (*connection, bool) {
if cValue, ok := s.connections.Load(connID); ok { c, ok := s.connections[connID]
// this cast is not necessary, return c, ok
// we know that we always save a connection, but for good or worse let it be here.
if conn, ok := cValue.(*connection); ok {
return conn, ok
}
}
return nil, false
} }
// wrapConnection wraps an underline connection to an iris websocket connection. // wrapConnection wraps an underline connection to an iris websocket connection.
@ -290,34 +285,24 @@ func (s *Server) leave(roomName string, connID string) (left bool) {
// GetTotalConnections returns the number of total connections // GetTotalConnections returns the number of total connections
func (s *Server) GetTotalConnections() (n int) { func (s *Server) GetTotalConnections() (n int) {
s.connections.Range(func(k, v interface{}) bool { s.mu.RLock()
n++ n = len(s.connections)
return true s.mu.RUnlock()
})
return n return
} }
// GetConnections returns all connections // GetConnections returns all connections
func (s *Server) GetConnections() []Connection { func (s *Server) GetConnections() []Connection {
// first call of Range to get the total length, we don't want to use append or manually grow the list here for many reasons. s.mu.RLock()
length := s.GetTotalConnections() conns := make([]Connection, len(s.connections))
conns := make([]Connection, length, length)
i := 0 i := 0
// second call of Range. for _, c := range s.connections {
s.connections.Range(func(k, v interface{}) bool { conns[i] = c
conn, ok := v.(*connection)
if !ok {
// if for some reason (should never happen), the value is not stored as *connection
// then stop the iteration and don't continue insertion of the result connections
// in order to avoid any issues while end-dev will try to iterate a nil entry.
return false
}
conns[i] = conn
i++ i++
return true }
})
s.mu.RUnlock()
return conns return conns
} }
@ -339,13 +324,11 @@ func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
if connIDs, found := s.rooms[roomName]; found { if connIDs, found := s.rooms[roomName]; found {
for _, connID := range connIDs { for _, connID := range connIDs {
// existence check is not necessary here. // existence check is not necessary here.
if cValue, ok := s.connections.Load(connID); ok { if conn, ok := s.connections[connID]; ok {
if conn, ok := cValue.(*connection); ok {
conns = append(conns, conn) conns = append(conns, conn)
} }
} }
} }
}
s.mu.RUnlock() s.mu.RUnlock()
@ -382,32 +365,20 @@ func (s *Server) emitMessage(from, to string, data []byte) {
} }
} }
} else { } else {
s.mu.RLock()
// it suppose to send the message to all opened connections or to all except the sender. // it suppose to send the message to all opened connections or to all except the sender.
s.connections.Range(func(k, v interface{}) bool { for _, conn := range s.connections {
connID, ok := k.(string) if to != All && to != conn.id { // if it's not suppose to send to all connections (including itself)
if !ok { if to == Broadcast && from == conn.id { // if broadcast to other connections except this
// should never happen.
return true
}
if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
if to == Broadcast && from == connID { // if broadcast to other connections except this
// here we do the opossite of previous block, // 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. // just skip this connection when it's suppose to send the message to all connections except the sender.
return true continue
}
} }
}
// not necessary cast.
conn, ok := v.(*connection)
if ok {
// send to the client(s) when the top validators passed
conn.writeDefault(data) conn.writeDefault(data)
} }
s.mu.RUnlock()
return ok
})
} }
} }
@ -432,7 +403,9 @@ func (s *Server) Disconnect(connID string) (err error) {
// close the underline connection and return its error, if any. // close the underline connection and return its error, if any.
err = conn.underline.Close() err = conn.underline.Close()
s.connections.Delete(connID) s.mu.Lock()
delete(s.connections, conn.id)
s.mu.Unlock()
} }
return return