1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 02:17:05 +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:
Gerasimos Maropoulos
2016-07-18 17:40:42 +03:00
parent 077984bd60
commit 675c0d510c
17 changed files with 1717 additions and 1358 deletions

View File

@@ -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

View File

@@ -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};

View File

@@ -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
}
//

View File

@@ -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
}
//

View File

@@ -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
}

View File

@@ -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()
}
}
}
}
}
}
//

View File

@@ -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;
}());
`)