mirror of
https://github.com/kataras/iris.git
synced 2025-12-17 09:57:01 +00:00
Update to version 11.0.1. Feature request implemented: https://github.com/kataras/iris/issues/1113
Former-commit-id: 0ce38dbacc2458fe327fa4401fdde1e69c8aacb0
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// ClientHandler is the handler which serves the javascript client-side
|
||||
// library. It uses a small cache based on the iris/context.StaticCacheDuration.
|
||||
// library. It uses a small cache based on the iris/context.WriteWithExpiration.
|
||||
func ClientHandler() context.Handler {
|
||||
modNow := time.Now()
|
||||
return func(ctx context.Context) {
|
||||
@@ -20,12 +20,12 @@ func ClientHandler() context.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// ClientSource the client-side javascript raw source code
|
||||
// ClientSource the client-side javascript raw source code.
|
||||
var ClientSource = []byte(`var websocketStringMessageType = 0;
|
||||
var websocketIntMessageType = 1;
|
||||
var websocketBoolMessageType = 2;
|
||||
var websocketJSONMessageType = 4;
|
||||
var websocketMessagePrefix = "iris-websocket-message:";
|
||||
var websocketMessagePrefix = "` + DefaultEvtMessageKey + `";
|
||||
var websocketMessageSeparator = ";";
|
||||
var websocketMessagePrefixLen = websocketMessagePrefix.length;
|
||||
var websocketMessageSeparatorLen = websocketMessageSeparator.length;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/iris-contrib/go.uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,15 +25,24 @@ const (
|
||||
DefaultWebsocketReadBufferSize = 4096
|
||||
// DefaultWebsocketWriterBufferSize 4096
|
||||
DefaultWebsocketWriterBufferSize = 4096
|
||||
// DefaultClientSourcePath "/iris-ws.js"
|
||||
DefaultClientSourcePath = "/iris-ws.js"
|
||||
// DefaultEvtMessageKey is the default prefix of the underline websocket events
|
||||
// that are being established under the hoods.
|
||||
//
|
||||
// Defaults to "iris-websocket-message:".
|
||||
// Last character of the prefix should be ':'.
|
||||
DefaultEvtMessageKey = "iris-websocket-message:"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultIDGenerator returns the result of 64
|
||||
// random combined characters as the id of a new connection.
|
||||
// Used when config.IDGenerator is nil
|
||||
DefaultIDGenerator = func(context.Context) string { return randomString(64) }
|
||||
// DefaultIDGenerator returns a random unique for a new connection.
|
||||
// Used when config.IDGenerator is nil.
|
||||
DefaultIDGenerator = func(context.Context) string {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return randomString(64)
|
||||
}
|
||||
return id.String()
|
||||
}
|
||||
)
|
||||
|
||||
// Config the websocket server configuration
|
||||
@@ -38,9 +50,16 @@ var (
|
||||
type Config struct {
|
||||
// IDGenerator used to create (and later on, set)
|
||||
// an ID for each incoming websocket connections (clients).
|
||||
// The request is an argument which you can use to generate the ID (from headers for example).
|
||||
// The request is an input parameter which you can use to generate the ID (from headers for example).
|
||||
// If empty then the ID is generated by DefaultIDGenerator: randomString(64)
|
||||
IDGenerator func(ctx context.Context) string
|
||||
// EvtMessagePrefix is the prefix of the underline websocket events that are being established under the hoods.
|
||||
// This prefix is visible only to the javascript side (code) and it has nothing to do
|
||||
// with the message that the end-user receives.
|
||||
// Do not change it unless it is absolutely necessary.
|
||||
//
|
||||
// If empty then defaults to []byte("iris-websocket-message:").
|
||||
EvtMessagePrefix []byte
|
||||
// Error is the function that will be fired if any client couldn't upgrade the HTTP connection
|
||||
// to a websocket connection, a handshake error.
|
||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||
@@ -135,9 +154,47 @@ func (c Config) Validate() Config {
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.EvtMessagePrefix) == 0 {
|
||||
c.EvtMessagePrefix = []byte(DefaultEvtMessageKey)
|
||||
}
|
||||
|
||||
if c.IDGenerator == nil {
|
||||
c.IDGenerator = DefaultIDGenerator
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
const (
|
||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// random takes a parameter (int) and returns random slice of byte
|
||||
// ex: var randomstrbytes []byte; randomstrbytes = utils.Random(32)
|
||||
func random(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm
|
||||
func randomString(n int) string {
|
||||
return string(random(n))
|
||||
}
|
||||
|
||||
@@ -415,15 +415,15 @@ func (c *connection) startReader() {
|
||||
// messageReceived checks the incoming message and fire the nativeMessage listeners or the event listeners (ws custom message)
|
||||
func (c *connection) messageReceived(data []byte) {
|
||||
|
||||
if bytes.HasPrefix(data, websocketMessagePrefixBytes) {
|
||||
customData := string(data)
|
||||
if bytes.HasPrefix(data, c.server.config.EvtMessagePrefix) {
|
||||
//it's a custom ws message
|
||||
receivedEvt := getWebsocketCustomEvent(customData)
|
||||
listeners := c.onEventListeners[receivedEvt]
|
||||
if listeners == nil { // if not listeners for this event exit from here
|
||||
return
|
||||
receivedEvt := c.server.messageSerializer.getWebsocketCustomEvent(data)
|
||||
listeners, ok := c.onEventListeners[string(receivedEvt)]
|
||||
if !ok || len(listeners) == 0 {
|
||||
return // if not listeners for this event exit from here
|
||||
}
|
||||
customMessage, err := websocketMessageDeserialize(receivedEvt, customData)
|
||||
|
||||
customMessage, err := c.server.messageSerializer.deserialize(receivedEvt, data)
|
||||
if customMessage == nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ func (e *emitter) EmitMessage(nativeMessage []byte) error {
|
||||
}
|
||||
|
||||
func (e *emitter) Emit(event string, data interface{}) error {
|
||||
message, err := websocketMessageSerialize(event, data)
|
||||
message, err := e.conn.server.messageSerializer.serialize(event, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.EmitMessage([]byte(message))
|
||||
e.EmitMessage(message)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,179 +1,181 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
)
|
||||
|
||||
// The same values are exists on client side also
|
||||
const (
|
||||
websocketStringMessageType websocketMessageType = iota
|
||||
websocketIntMessageType
|
||||
websocketBoolMessageType
|
||||
websocketBytesMessageType
|
||||
websocketJSONMessageType
|
||||
)
|
||||
|
||||
const (
|
||||
websocketMessagePrefix = "iris-websocket-message:"
|
||||
websocketMessageSeparator = ";"
|
||||
websocketMessagePrefixLen = len(websocketMessagePrefix)
|
||||
websocketMessageSeparatorLen = len(websocketMessageSeparator)
|
||||
websocketMessagePrefixAndSepIdx = websocketMessagePrefixLen + websocketMessageSeparatorLen - 1
|
||||
websocketMessagePrefixIdx = websocketMessagePrefixLen - 1
|
||||
websocketMessageSeparatorIdx = websocketMessageSeparatorLen - 1
|
||||
)
|
||||
|
||||
var (
|
||||
websocketMessageSeparatorByte = websocketMessageSeparator[0]
|
||||
websocketMessageBuffer = bytebufferpool.Pool{}
|
||||
websocketMessagePrefixBytes = []byte(websocketMessagePrefix)
|
||||
)
|
||||
|
||||
type (
|
||||
websocketMessageType uint8
|
||||
messageType uint8
|
||||
)
|
||||
|
||||
func (m websocketMessageType) String() string {
|
||||
func (m messageType) String() string {
|
||||
return strconv.Itoa(int(m))
|
||||
}
|
||||
|
||||
func (m websocketMessageType) Name() string {
|
||||
if m == websocketStringMessageType {
|
||||
func (m messageType) Name() string {
|
||||
switch m {
|
||||
case messageTypeString:
|
||||
return "string"
|
||||
} else if m == websocketIntMessageType {
|
||||
case messageTypeInt:
|
||||
return "int"
|
||||
} else if m == websocketBoolMessageType {
|
||||
case messageTypeBool:
|
||||
return "bool"
|
||||
} else if m == websocketBytesMessageType {
|
||||
case messageTypeBytes:
|
||||
return "[]byte"
|
||||
} else if m == websocketJSONMessageType {
|
||||
case messageTypeJSON:
|
||||
return "json"
|
||||
default:
|
||||
return "Invalid(" + m.String() + ")"
|
||||
}
|
||||
|
||||
return "Invalid(" + m.String() + ")"
|
||||
|
||||
}
|
||||
|
||||
// The same values are exists on client side too.
|
||||
const (
|
||||
messageTypeString messageType = iota
|
||||
messageTypeInt
|
||||
messageTypeBool
|
||||
messageTypeBytes
|
||||
messageTypeJSON
|
||||
)
|
||||
|
||||
const (
|
||||
messageSeparator = ";"
|
||||
)
|
||||
|
||||
var messageSeparatorByte = messageSeparator[0]
|
||||
|
||||
type messageSerializer struct {
|
||||
prefix []byte
|
||||
|
||||
prefixLen int
|
||||
separatorLen int
|
||||
prefixAndSepIdx int
|
||||
prefixIdx int
|
||||
separatorIdx int
|
||||
|
||||
buf *bytebufferpool.Pool
|
||||
}
|
||||
|
||||
func newMessageSerializer(messagePrefix []byte) *messageSerializer {
|
||||
return &messageSerializer{
|
||||
prefix: messagePrefix,
|
||||
prefixLen: len(messagePrefix),
|
||||
separatorLen: len(messageSeparator),
|
||||
prefixAndSepIdx: len(messagePrefix) + len(messageSeparator) - 1,
|
||||
prefixIdx: len(messagePrefix) - 1,
|
||||
separatorIdx: len(messageSeparator) - 1,
|
||||
|
||||
buf: new(bytebufferpool.Pool),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
boolTrueB = []byte("true")
|
||||
boolFalseB = []byte("false")
|
||||
)
|
||||
|
||||
// websocketMessageSerialize serializes a custom websocket message from websocketServer to be delivered to the client
|
||||
// returns the string form of the message
|
||||
// Supported data types are: string, int, bool, bytes and JSON.
|
||||
func websocketMessageSerialize(event string, data interface{}) (string, error) {
|
||||
var msgType websocketMessageType
|
||||
var dataMessage string
|
||||
func (ms *messageSerializer) serialize(event string, data interface{}) ([]byte, error) {
|
||||
b := ms.buf.Get()
|
||||
b.Write(ms.prefix)
|
||||
b.WriteString(event)
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
|
||||
if s, ok := data.(string); ok {
|
||||
msgType = websocketStringMessageType
|
||||
dataMessage = s
|
||||
} else if i, ok := data.(int); ok {
|
||||
msgType = websocketIntMessageType
|
||||
dataMessage = strconv.Itoa(i)
|
||||
} else if b, ok := data.(bool); ok {
|
||||
msgType = websocketBoolMessageType
|
||||
dataMessage = strconv.FormatBool(b)
|
||||
} else if by, ok := data.([]byte); ok {
|
||||
msgType = websocketBytesMessageType
|
||||
dataMessage = string(by)
|
||||
} else {
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
b.WriteString(messageTypeString.String())
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
b.WriteString(v)
|
||||
case int:
|
||||
b.WriteString(messageTypeInt.String())
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
binary.Write(b, binary.LittleEndian, v)
|
||||
case bool:
|
||||
b.WriteString(messageTypeBool.String())
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
if v {
|
||||
b.Write(boolTrueB)
|
||||
} else {
|
||||
b.Write(boolFalseB)
|
||||
}
|
||||
case []byte:
|
||||
b.WriteString(messageTypeBytes.String())
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
b.Write(v)
|
||||
default:
|
||||
//we suppose is json
|
||||
res, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
msgType = websocketJSONMessageType
|
||||
dataMessage = string(res)
|
||||
b.WriteString(messageTypeJSON.String())
|
||||
b.WriteByte(messageSeparatorByte)
|
||||
b.Write(res)
|
||||
}
|
||||
|
||||
b := websocketMessageBuffer.Get()
|
||||
b.WriteString(websocketMessagePrefix)
|
||||
b.WriteString(event)
|
||||
b.WriteString(websocketMessageSeparator)
|
||||
b.WriteString(msgType.String())
|
||||
b.WriteString(websocketMessageSeparator)
|
||||
b.WriteString(dataMessage)
|
||||
dataMessage = b.String()
|
||||
websocketMessageBuffer.Put(b)
|
||||
|
||||
return dataMessage, nil
|
||||
message := b.Bytes()
|
||||
ms.buf.Put(b)
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
var errInvalidTypeMessage = errors.New("Type %s is invalid for message: %s")
|
||||
|
||||
// websocketMessageDeserialize deserializes a custom websocket message from the client
|
||||
// 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 websocketMessageDeserialize(event string, websocketMessage string) (message interface{}, err error) {
|
||||
t, formaterr := strconv.Atoi(websocketMessage[websocketMessagePrefixAndSepIdx+len(event)+1 : websocketMessagePrefixAndSepIdx+len(event)+2]) // in order to iris-websocket-message;user;-> 4
|
||||
if formaterr != nil {
|
||||
return nil, formaterr
|
||||
}
|
||||
_type := websocketMessageType(t)
|
||||
_message := websocketMessage[websocketMessagePrefixAndSepIdx+len(event)+3:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
|
||||
|
||||
if _type == websocketStringMessageType {
|
||||
message = string(_message)
|
||||
} else if _type == websocketIntMessageType {
|
||||
message, err = strconv.Atoi(_message)
|
||||
} else if _type == websocketBoolMessageType {
|
||||
message, err = strconv.ParseBool(_message)
|
||||
} else if _type == websocketBytesMessageType {
|
||||
message = []byte(_message)
|
||||
} else if _type == websocketJSONMessageType {
|
||||
err = json.Unmarshal([]byte(_message), &message)
|
||||
} else {
|
||||
return nil, errInvalidTypeMessage.Format(_type.Name(), websocketMessage)
|
||||
func (ms *messageSerializer) deserialize(event []byte, websocketMessage []byte) (interface{}, error) {
|
||||
dataStartIdx := ms.prefixAndSepIdx + len(event) + 3
|
||||
if len(websocketMessage) <= dataStartIdx {
|
||||
return nil, errors.New("websocket invalid message: " + string(websocketMessage))
|
||||
}
|
||||
|
||||
return
|
||||
typ, err := strconv.Atoi(string(websocketMessage[ms.prefixAndSepIdx+len(event)+1 : ms.prefixAndSepIdx+len(event)+2])) // in order to iris-websocket-message;user;-> 4
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := websocketMessage[dataStartIdx:] // in order to iris-websocket-message;user;4; -> themarshaledstringfromajsonstruct
|
||||
|
||||
switch messageType(typ) {
|
||||
case messageTypeString:
|
||||
return string(data), nil
|
||||
case messageTypeInt:
|
||||
msg, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
case messageTypeBool:
|
||||
if bytes.Equal(data, boolTrueB) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case messageTypeBytes:
|
||||
return data, nil
|
||||
case messageTypeJSON:
|
||||
var msg interface{}
|
||||
err := json.Unmarshal(data, &msg)
|
||||
return msg, err
|
||||
default:
|
||||
return nil, errInvalidTypeMessage.Format(messageType(typ).Name(), websocketMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// getWebsocketCustomEvent return empty string when the websocketMessage is native message
|
||||
func getWebsocketCustomEvent(websocketMessage string) string {
|
||||
if len(websocketMessage) < websocketMessagePrefixAndSepIdx {
|
||||
return ""
|
||||
func (ms *messageSerializer) getWebsocketCustomEvent(websocketMessage []byte) []byte {
|
||||
if len(websocketMessage) < ms.prefixAndSepIdx {
|
||||
return nil
|
||||
}
|
||||
s := websocketMessage[websocketMessagePrefixAndSepIdx:]
|
||||
evt := s[:strings.IndexByte(s, websocketMessageSeparatorByte)]
|
||||
s := websocketMessage[ms.prefixAndSepIdx:]
|
||||
evt := s[:bytes.IndexByte(s, messageSeparatorByte)]
|
||||
return evt
|
||||
}
|
||||
|
||||
const (
|
||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// random takes a parameter (int) and returns random slice of byte
|
||||
// ex: var randomstrbytes []byte; randomstrbytes = utils.Random(32)
|
||||
func random(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm
|
||||
func randomString(n int) string {
|
||||
return string(random(n))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
@@ -34,7 +35,15 @@ type (
|
||||
//
|
||||
// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
|
||||
Server struct {
|
||||
config Config
|
||||
config Config
|
||||
// ClientSource contains the javascript side code
|
||||
// for the iris websocket communication
|
||||
// based on the configuration's `EvtMessagePrefix`.
|
||||
//
|
||||
// Use a route to serve this file on a specific path, i.e
|
||||
// app.Any("/iris-ws.js", func(ctx iris.Context) { ctx.Write(mywebsocketServer.ClientSource) })
|
||||
ClientSource []byte
|
||||
messageSerializer *messageSerializer
|
||||
connections sync.Map // 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
|
||||
mu sync.RWMutex // for rooms.
|
||||
@@ -53,6 +62,8 @@ func New(cfg Config) *Server {
|
||||
cfg = cfg.Validate()
|
||||
return &Server{
|
||||
config: cfg,
|
||||
ClientSource: bytes.Replace(ClientSource, []byte(DefaultEvtMessageKey), cfg.EvtMessagePrefix, -1),
|
||||
messageSerializer: newMessageSerializer(cfg.EvtMessagePrefix),
|
||||
connections: sync.Map{}, // ready-to-use, this is not necessary.
|
||||
rooms: make(map[string][]string),
|
||||
onConnectionListeners: make([]ConnectionFunc, 0),
|
||||
|
||||
Reference in New Issue
Block a user