├── .gitignore ├── LICENSE ├── README.md ├── TODO ├── attachment.go ├── buglist ├── caller.go ├── client.go ├── client_conn.go ├── example ├── main.go └── test.go ├── ioutil.go ├── message_reader.go ├── parser.go └── trim_writer.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of go-socket.io-client nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-socket.io-client 2 | 3 | go-socket.io-client is an client implementation of [socket.io](http://socket.io) in golang, which is a realtime application framework. 4 | 5 | It is compatible with latest implementation of socket.io in node.js, and supports namespace. 6 | 7 | * It is base on [googollee/go-socket.io](https://github.com/googollee/go-socket.io) and [googollee/go-engine.io](https://github.com/googollee/go-engine.io) 8 | 9 | ## Install 10 | 11 | Install the package with: 12 | 13 | ```bash 14 | go get github.com/zhouhui8915/go-socket.io-client 15 | ``` 16 | 17 | Import it with: 18 | 19 | ```go 20 | import "github.com/zhouhui8915/go-socket.io-client" 21 | ``` 22 | 23 | and use `socketio_client` as the package name inside the code. 24 | 25 | ## Example 26 | 27 | Please check the example folder for details. 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "bufio" 34 | "github.com/zhouhui8915/go-socket.io-client" 35 | "log" 36 | "os" 37 | ) 38 | 39 | func main() { 40 | 41 | opts := &socketio_client.Options{ 42 | Transport: "websocket", 43 | Query: make(map[string]string), 44 | } 45 | opts.Query["user"] = "user" 46 | opts.Query["pwd"] = "pass" 47 | uri := "http://192.168.1.70:9090/socket.io/" 48 | 49 | client, err := socketio_client.NewClient(uri, opts) 50 | if err != nil { 51 | log.Printf("NewClient error:%v\n", err) 52 | return 53 | } 54 | 55 | client.On("error", func() { 56 | log.Printf("on error\n") 57 | }) 58 | client.On("connection", func() { 59 | log.Printf("on connect\n") 60 | }) 61 | client.On("message", func(msg string) { 62 | log.Printf("on message:%v\n", msg) 63 | }) 64 | client.On("disconnection", func() { 65 | log.Printf("on disconnect\n") 66 | }) 67 | 68 | reader := bufio.NewReader(os.Stdin) 69 | for { 70 | data, _, _ := reader.ReadLine() 71 | command := string(data) 72 | client.Emit("message", command) 73 | log.Printf("send message:%v\n", command) 74 | } 75 | } 76 | ``` 77 | 78 | ## License 79 | 80 | The 3-clause BSD License - see LICENSE for more details 81 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1.connect timeout -------------------------------------------------------------------------------- /attachment.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | ) 10 | 11 | // Attachment is an attachment handler used in emit args. All attachments will send as binary in transport layer. When use attachment, make sure use as pointer. 12 | // 13 | // For example: 14 | // 15 | // type Arg struct { 16 | // Title string `json:"title"` 17 | // File *Attachment `json:"file"` 18 | // } 19 | // 20 | // f, _ := os.Open("./some_file") 21 | // arg := Arg{ 22 | // Title: "some_file", 23 | // File: &Attachment{ 24 | // Data: f, 25 | // } 26 | // } 27 | // 28 | // socket.Emit("send file", arg) 29 | // socket.On("get file", func(so Socket, arg Arg) { 30 | // b, _ := ioutil.ReadAll(arg.File.Data) 31 | // }) 32 | type Attachment struct { 33 | 34 | // Data is the ReadWriter of the attachment data. 35 | Data io.ReadWriter 36 | num int 37 | } 38 | 39 | func encodeAttachments(v interface{}) []io.Reader { 40 | index := 0 41 | return encodeAttachmentValue(reflect.ValueOf(v), &index) 42 | } 43 | 44 | func encodeAttachmentValue(v reflect.Value, index *int) []io.Reader { 45 | v = reflect.Indirect(v) 46 | ret := []io.Reader{} 47 | if !v.IsValid() { 48 | return ret 49 | } 50 | switch v.Kind() { 51 | case reflect.Struct: 52 | if v.Type().Name() == "Attachment" { 53 | a, ok := v.Addr().Interface().(*Attachment) 54 | if !ok { 55 | panic("can't convert") 56 | } 57 | a.num = *index 58 | ret = append(ret, a.Data) 59 | (*index)++ 60 | return ret 61 | } 62 | for i, n := 0, v.NumField(); i < n; i++ { 63 | var r []io.Reader 64 | r = encodeAttachmentValue(v.Field(i), index) 65 | ret = append(ret, r...) 66 | } 67 | case reflect.Map: 68 | if v.IsNil() { 69 | return ret 70 | } 71 | for _, key := range v.MapKeys() { 72 | var r []io.Reader 73 | r = encodeAttachmentValue(v.MapIndex(key), index) 74 | ret = append(ret, r...) 75 | } 76 | case reflect.Slice: 77 | if v.IsNil() { 78 | return ret 79 | } 80 | fallthrough 81 | case reflect.Array: 82 | for i, n := 0, v.Len(); i < n; i++ { 83 | var r []io.Reader 84 | r = encodeAttachmentValue(v.Index(i), index) 85 | ret = append(ret, r...) 86 | } 87 | case reflect.Interface: 88 | ret = encodeAttachmentValue(reflect.ValueOf(v.Interface()), index) 89 | } 90 | return ret 91 | } 92 | 93 | func decodeAttachments(v interface{}, binary [][]byte) error { 94 | return decodeAttachmentValue(reflect.ValueOf(v), binary) 95 | } 96 | 97 | func decodeAttachmentValue(v reflect.Value, binary [][]byte) error { 98 | v = reflect.Indirect(v) 99 | if !v.IsValid() { 100 | return fmt.Errorf("invalid value") 101 | } 102 | switch v.Kind() { 103 | case reflect.Struct: 104 | if v.Type().Name() == "Attachment" { 105 | a, ok := v.Addr().Interface().(*Attachment) 106 | if !ok { 107 | panic("can't convert") 108 | } 109 | if a.num >= len(binary) || a.num < 0 { 110 | return fmt.Errorf("out of range") 111 | } 112 | if a.Data == nil { 113 | a.Data = bytes.NewBuffer(nil) 114 | } 115 | for b := binary[a.num]; len(b) > 0; { 116 | n, err := a.Data.Write(b) 117 | if err != nil { 118 | return err 119 | } 120 | b = b[n:] 121 | } 122 | return nil 123 | } 124 | for i, n := 0, v.NumField(); i < n; i++ { 125 | if err := decodeAttachmentValue(v.Field(i), binary); err != nil { 126 | return err 127 | } 128 | } 129 | case reflect.Map: 130 | if v.IsNil() { 131 | return nil 132 | } 133 | for _, key := range v.MapKeys() { 134 | if err := decodeAttachmentValue(v.MapIndex(key), binary); err != nil { 135 | return err 136 | } 137 | } 138 | case reflect.Slice: 139 | if v.IsNil() { 140 | return nil 141 | } 142 | fallthrough 143 | case reflect.Array: 144 | for i, n := 0, v.Len(); i < n; i++ { 145 | if err := decodeAttachmentValue(v.Index(i), binary); err != nil { 146 | return err 147 | } 148 | } 149 | case reflect.Interface: 150 | if err := decodeAttachmentValue(reflect.ValueOf(v.Interface()), binary); err != nil { 151 | return err 152 | } 153 | } 154 | return nil 155 | } 156 | 157 | func (a Attachment) MarshalJSON() ([]byte, error) { 158 | return []byte(fmt.Sprintf("{\"_placeholder\":true,\"num\":%d}", a.num)), nil 159 | } 160 | 161 | func (a *Attachment) UnmarshalJSON(b []byte) error { 162 | var v struct { 163 | Num int `json:"num"` 164 | } 165 | if err := json.Unmarshal(b, &v); err != nil { 166 | return err 167 | } 168 | a.num = v.Num 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /buglist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hesh915/go-socket.io-client/83ee73793ba4ec64250ecd10639dc203663a1cb8/buglist -------------------------------------------------------------------------------- /caller.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | ) 9 | 10 | type caller struct { 11 | sync.RWMutex 12 | Func reflect.Value 13 | Args []reflect.Type 14 | } 15 | 16 | func newCaller(f interface{}) (*caller, error) { 17 | fv := reflect.ValueOf(f) 18 | if fv.Kind() != reflect.Func { 19 | return nil, fmt.Errorf("f is not func") 20 | } 21 | ft := fv.Type() 22 | if ft.NumIn() == 0 { 23 | return &caller{ 24 | Func: fv, 25 | }, nil 26 | } 27 | args := make([]reflect.Type, ft.NumIn()) 28 | for i, n := 0, ft.NumIn(); i < n; i++ { 29 | args[i] = ft.In(i) 30 | } 31 | 32 | return &caller{ 33 | Func: fv, 34 | Args: args, 35 | }, nil 36 | } 37 | 38 | func (c *caller) GetArgs() []interface{} { 39 | c.RLock() 40 | defer c.RUnlock() 41 | 42 | ret := make([]interface{}, len(c.Args)) 43 | for i, argT := range c.Args { 44 | if argT.Kind() == reflect.Ptr { 45 | argT = argT.Elem() 46 | } 47 | v := reflect.New(argT) 48 | ret[i] = v.Interface() 49 | } 50 | return ret 51 | } 52 | 53 | func (c *caller) Call(args []interface{}) []reflect.Value { 54 | c.RLock() 55 | defer c.RUnlock() 56 | var a []reflect.Value 57 | diff := 0 58 | 59 | a = make([]reflect.Value, len(args)) 60 | for i, arg := range args { 61 | v := reflect.ValueOf(arg) 62 | if c.Args[i].Kind() != reflect.Ptr { 63 | if v.IsValid() { 64 | v = v.Elem() 65 | } else { 66 | v = reflect.Zero(c.Args[i]) 67 | } 68 | } 69 | a[i+diff] = v 70 | } 71 | 72 | if len(args) != len(c.Args) { 73 | return []reflect.Value{reflect.ValueOf([]interface{}{}), reflect.ValueOf(errors.New("Arguments do not match"))} 74 | } 75 | 76 | return c.Func.Call(a) 77 | } 78 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | "reflect" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | type Options struct { 12 | Transport string //protocol name string,websocket polling... 13 | Query map[string]string //url的附加的参数 14 | Header map[string][]string 15 | } 16 | 17 | type Client struct { 18 | opts *Options 19 | 20 | conn *clientConn 21 | 22 | eventsLock sync.RWMutex 23 | events map[string]*caller 24 | acks map[int]*caller 25 | id int 26 | namespace string 27 | } 28 | 29 | func NewClient(uri string, opts *Options) (client *Client, err error) { 30 | 31 | url, err := url.Parse(uri) 32 | if err != nil { 33 | return 34 | } 35 | url.Path = path.Join("/socket.io", url.Path) 36 | url.Path = url.EscapedPath() 37 | if strings.HasSuffix(url.Path, "socket.io") { 38 | url.Path += "/" 39 | } 40 | q := url.Query() 41 | for k, v := range opts.Query { 42 | q.Set(k, v) 43 | } 44 | url.RawQuery = q.Encode() 45 | 46 | socket, err := newClientConn(opts, url) 47 | if err != nil { 48 | return 49 | } 50 | 51 | client = &Client{ 52 | opts: opts, 53 | conn: socket, 54 | 55 | events: make(map[string]*caller), 56 | acks: make(map[int]*caller), 57 | } 58 | 59 | go client.readLoop() 60 | 61 | return 62 | } 63 | 64 | func (client *Client) On(message string, f interface{}) (err error) { 65 | c, err := newCaller(f) 66 | if err != nil { 67 | return 68 | } 69 | client.eventsLock.Lock() 70 | client.events[message] = c 71 | client.eventsLock.Unlock() 72 | return 73 | } 74 | 75 | func (client *Client) Emit(message string, args ...interface{}) (err error) { 76 | var c *caller 77 | if l := len(args); l > 0 { 78 | fv := reflect.ValueOf(args[l-1]) 79 | if fv.Kind() == reflect.Func { 80 | var err error 81 | c, err = newCaller(args[l-1]) 82 | if err != nil { 83 | return err 84 | } 85 | args = args[:l-1] 86 | } 87 | } 88 | args = append([]interface{}{message}, args...) 89 | if c != nil { 90 | id, err := client.sendId(args) 91 | if err != nil { 92 | return err 93 | } 94 | client.eventsLock.Lock() 95 | client.acks[id] = c 96 | client.eventsLock.Unlock() 97 | return nil 98 | } 99 | return client.send(args) 100 | } 101 | 102 | func (client *Client) sendConnect() error { 103 | packet := packet{ 104 | Type: _CONNECT, 105 | Id: -1, 106 | NSP: client.namespace, 107 | } 108 | encoder := newEncoder(client.conn) 109 | return encoder.Encode(packet) 110 | } 111 | 112 | func (client *Client) sendId(args []interface{}) (int, error) { 113 | client.eventsLock.Lock() 114 | packet := packet{ 115 | Type: _EVENT, 116 | Id: client.id, 117 | NSP: client.namespace, 118 | Data: args, 119 | } 120 | client.id++ 121 | if client.id < 0 { 122 | client.id = 0 123 | } 124 | client.eventsLock.Unlock() 125 | 126 | encoder := newEncoder(client.conn) 127 | err := encoder.Encode(packet) 128 | if err != nil { 129 | return -1, nil 130 | } 131 | return packet.Id, nil 132 | } 133 | 134 | func (client *Client) send(args []interface{}) error { 135 | packet := packet{ 136 | Type: _EVENT, 137 | Id: -1, 138 | NSP: client.namespace, 139 | Data: args, 140 | } 141 | encoder := newEncoder(client.conn) 142 | return encoder.Encode(packet) 143 | } 144 | 145 | func (client *Client) onPacket(decoder *decoder, packet *packet) ([]interface{}, error) { 146 | var message string 147 | switch packet.Type { 148 | case _CONNECT: 149 | message = "connection" 150 | case _DISCONNECT: 151 | message = "disconnection" 152 | case _ERROR: 153 | message = "error" 154 | case _ACK: 155 | fallthrough 156 | case _BINARY_ACK: 157 | return nil, client.onAck(packet.Id, decoder, packet) 158 | default: 159 | message = decoder.Message() 160 | } 161 | client.eventsLock.RLock() 162 | c, ok := client.events[message] 163 | client.eventsLock.RUnlock() 164 | if !ok { 165 | // If the message is not recognized by the server, the decoder.currentCloser 166 | // needs to be closed otherwise the server will be stuck until the e 167 | decoder.Close() 168 | return nil, nil 169 | } 170 | args := c.GetArgs() 171 | olen := len(args) 172 | if decoder != nil && olen > 0 { 173 | packet.Data = &args 174 | if err := decoder.DecodeData(packet); err != nil { 175 | return nil, err 176 | } 177 | } 178 | for i := len(args); i < olen; i++ { 179 | args = append(args, nil) 180 | } 181 | 182 | retV := c.Call(args) 183 | if len(retV) == 0 { 184 | return nil, nil 185 | } 186 | 187 | var err error 188 | if last, ok := retV[len(retV)-1].Interface().(error); ok { 189 | err = last 190 | retV = retV[0 : len(retV)-1] 191 | } 192 | ret := make([]interface{}, len(retV)) 193 | for i, v := range retV { 194 | ret[i] = v.Interface() 195 | } 196 | return ret, err 197 | } 198 | 199 | func (client *Client) onAck(id int, decoder *decoder, packet *packet) error { 200 | client.eventsLock.RLock() 201 | c, ok := client.acks[id] 202 | client.eventsLock.RUnlock() 203 | if !ok { 204 | return nil 205 | } 206 | delete(client.acks, id) 207 | 208 | args := c.GetArgs() 209 | packet.Data = &args 210 | if err := decoder.DecodeData(packet); err != nil { 211 | return err 212 | } 213 | c.Call(args) 214 | return nil 215 | } 216 | 217 | func (client *Client) readLoop() error { 218 | defer func() { 219 | p := packet{ 220 | Type: _DISCONNECT, 221 | Id: -1, 222 | } 223 | client.onPacket(nil, &p) 224 | }() 225 | 226 | for { 227 | decoder := newDecoder(client.conn) 228 | var p packet 229 | if err := decoder.Decode(&p); err != nil { 230 | return err 231 | } 232 | ret, err := client.onPacket(decoder, &p) 233 | if err != nil { 234 | return err 235 | } 236 | switch p.Type { 237 | case _CONNECT: 238 | client.namespace = p.NSP 239 | // !!!下面这个不能有,否则会有死循环 240 | //client.sendConnect() 241 | case _BINARY_EVENT: 242 | fallthrough 243 | case _EVENT: 244 | if p.Id >= 0 { 245 | p := packet{ 246 | Type: _ACK, 247 | Id: p.Id, 248 | NSP: client.namespace, 249 | Data: ret, 250 | } 251 | encoder := newEncoder(client.conn) 252 | if err := encoder.Encode(p); err != nil { 253 | return err 254 | } 255 | } 256 | case _DISCONNECT: 257 | return nil 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /client_conn.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/zhouhui8915/engine.io-go/message" 8 | "github.com/zhouhui8915/engine.io-go/parser" 9 | "github.com/zhouhui8915/engine.io-go/polling" 10 | "github.com/zhouhui8915/engine.io-go/transport" 11 | "github.com/zhouhui8915/engine.io-go/websocket" 12 | "io" 13 | "net/http" 14 | "net/url" 15 | "strings" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | var InvalidError = errors.New("invalid transport") 21 | 22 | var transports = []string{"polling", "websocket"} 23 | 24 | var creaters map[string]transport.Creater 25 | 26 | func init() { 27 | creaters = make(map[string]transport.Creater) 28 | 29 | for _, t := range transports { 30 | switch t { 31 | case "polling": 32 | creaters[t] = polling.Creater 33 | case "websocket": 34 | creaters[t] = websocket.Creater 35 | } 36 | } 37 | } 38 | 39 | type MessageType message.MessageType 40 | 41 | const ( 42 | MessageBinary MessageType = MessageType(message.MessageBinary) 43 | MessageText MessageType = MessageType(message.MessageText) 44 | ) 45 | 46 | type state int 47 | 48 | const ( 49 | stateUnknow state = iota 50 | stateNormal 51 | stateUpgrading 52 | stateClosing 53 | stateClosed 54 | ) 55 | 56 | type clientConn struct { 57 | id string 58 | options *Options 59 | url *url.URL 60 | request *http.Request 61 | writerLocker sync.Mutex 62 | transportLocker sync.RWMutex 63 | currentName string 64 | current transport.Client 65 | upgradingName string 66 | upgrading transport.Client 67 | state state 68 | stateLocker sync.RWMutex 69 | readerChan chan *connReader 70 | pingTimeout time.Duration 71 | pingInterval time.Duration 72 | pingChan chan bool 73 | } 74 | 75 | func newClientConn(opts *Options, u *url.URL) (client *clientConn, err error) { 76 | if opts.Transport == "" { 77 | opts.Transport = "websocket" 78 | } 79 | 80 | _, exists := creaters[opts.Transport] 81 | if !exists { 82 | return nil, InvalidError 83 | } 84 | 85 | client = &clientConn{ 86 | url: u, 87 | options: opts, 88 | state: stateNormal, 89 | pingTimeout: 60000 * time.Millisecond, 90 | pingInterval: 25000 * time.Millisecond, 91 | pingChan: make(chan bool), 92 | readerChan: make(chan *connReader), 93 | } 94 | 95 | err = client.onOpen() 96 | if err != nil { 97 | return 98 | } 99 | 100 | go client.pingLoop() 101 | go client.readLoop() 102 | 103 | return 104 | } 105 | 106 | func (c *clientConn) Id() string { 107 | return c.id 108 | } 109 | 110 | func (c *clientConn) Request() *http.Request { 111 | return c.request 112 | } 113 | 114 | func (c *clientConn) NextReader() (MessageType, io.ReadCloser, error) { 115 | if c.getState() == stateClosed { 116 | return MessageBinary, nil, io.EOF 117 | } 118 | ret := <-c.readerChan 119 | if ret == nil { 120 | return MessageBinary, nil, io.EOF 121 | } 122 | return MessageType(ret.MessageType()), ret, nil 123 | } 124 | 125 | func (c *clientConn) NextWriter(t MessageType) (io.WriteCloser, error) { 126 | switch c.getState() { 127 | case stateUpgrading: 128 | for i := 0; i < 30; i++ { 129 | time.Sleep(50 * time.Millisecond) 130 | if c.getState() != stateUpgrading { 131 | break 132 | } 133 | } 134 | if c.getState() == stateUpgrading { 135 | return nil, fmt.Errorf("upgrading") 136 | } 137 | case stateNormal: 138 | default: 139 | return nil, io.EOF 140 | } 141 | c.writerLocker.Lock() 142 | ret, err := c.getCurrent().NextWriter(message.MessageType(t), parser.MESSAGE) 143 | if err != nil { 144 | c.writerLocker.Unlock() 145 | return ret, err 146 | } 147 | writer := newConnWriter(ret, &c.writerLocker) 148 | return writer, err 149 | } 150 | 151 | func (c *clientConn) Close() error { 152 | if c.getState() != stateNormal && c.getState() != stateUpgrading { 153 | return nil 154 | } 155 | if c.upgrading != nil { 156 | c.upgrading.Close() 157 | } 158 | c.writerLocker.Lock() 159 | if w, err := c.getCurrent().NextWriter(message.MessageText, parser.CLOSE); err == nil { 160 | writer := newConnWriter(w, &c.writerLocker) 161 | writer.Close() 162 | } else { 163 | c.writerLocker.Unlock() 164 | } 165 | if err := c.getCurrent().Close(); err != nil { 166 | return err 167 | } 168 | c.setState(stateClosing) 169 | return nil 170 | } 171 | 172 | func (c *clientConn) OnPacket(r *parser.PacketDecoder) { 173 | if s := c.getState(); s != stateNormal && s != stateUpgrading { 174 | return 175 | } 176 | switch r.Type() { 177 | case parser.OPEN: 178 | case parser.CLOSE: 179 | c.getCurrent().Close() 180 | case parser.PING: 181 | t := c.getCurrent() 182 | u := c.getUpgrade() 183 | newWriter := t.NextWriter 184 | c.writerLocker.Lock() 185 | if u != nil { 186 | if w, _ := t.NextWriter(message.MessageText, parser.NOOP); w != nil { 187 | w.Close() 188 | } 189 | newWriter = u.NextWriter 190 | } 191 | if w, _ := newWriter(message.MessageText, parser.PONG); w != nil { 192 | io.Copy(w, r) 193 | w.Close() 194 | } 195 | c.writerLocker.Unlock() 196 | fallthrough 197 | case parser.PONG: 198 | c.pingChan <- true 199 | if c.getState() == stateUpgrading { 200 | p := make([]byte, 64) 201 | _, err := r.Read(p) 202 | if err == nil && strings.Contains(string(p), "probe") { 203 | c.writerLocker.Lock() 204 | w, _ := c.getUpgrade().NextWriter(message.MessageText, parser.UPGRADE) 205 | if w != nil { 206 | io.Copy(w, r) 207 | w.Close() 208 | } 209 | c.writerLocker.Unlock() 210 | 211 | c.upgraded() 212 | //fmt.Println("probe") 213 | 214 | /* 215 | w, _ = c.getCurrent().NextWriter(message.MessageText, parser.MESSAGE) 216 | if w != nil { 217 | w.Write([]byte("2[\"message\",\"testtesttesttesttesttest\"]")) 218 | w.Close() 219 | } 220 | */ 221 | } 222 | } 223 | case parser.MESSAGE: 224 | closeChan := make(chan struct{}) 225 | c.readerChan <- newConnReader(r, closeChan) 226 | <-closeChan 227 | close(closeChan) 228 | r.Close() 229 | case parser.UPGRADE: 230 | c.upgraded() 231 | case parser.NOOP: 232 | } 233 | } 234 | 235 | func (c *clientConn) OnClose(server transport.Client) { 236 | if t := c.getUpgrade(); server == t { 237 | c.setUpgrading("", nil) 238 | t.Close() 239 | return 240 | } 241 | t := c.getCurrent() 242 | if server != t { 243 | return 244 | } 245 | t.Close() 246 | if t := c.getUpgrade(); t != nil { 247 | t.Close() 248 | c.setUpgrading("", nil) 249 | } 250 | c.setState(stateClosed) 251 | close(c.readerChan) 252 | close(c.pingChan) 253 | } 254 | 255 | func (c *clientConn) onOpen() error { 256 | 257 | var err error 258 | c.request, err = http.NewRequest("GET", c.url.String(), nil) 259 | if err != nil { 260 | return err 261 | } 262 | 263 | creater, exists := creaters["polling"] 264 | if !exists { 265 | return InvalidError 266 | } 267 | 268 | q := c.request.URL.Query() 269 | q.Set("transport", "polling") 270 | c.request.URL.RawQuery = q.Encode() 271 | if (c.options.Header != nil) { 272 | c.request.Header = c.options.Header 273 | } 274 | 275 | transport, err := creater.Client(c.request) 276 | if err != nil { 277 | return err 278 | } 279 | c.setCurrent("polling", transport) 280 | 281 | pack, err := c.getCurrent().NextReader() 282 | if err != nil { 283 | return err 284 | } 285 | 286 | p := make([]byte, 4096) 287 | l, err := pack.Read(p) 288 | if err != nil { 289 | return err 290 | } 291 | //fmt.Println(string(p)) 292 | 293 | type connectionInfo struct { 294 | Sid string `json:"sid"` 295 | Upgrades []string `json:"upgrades"` 296 | PingInterval time.Duration `json:"pingInterval"` 297 | PingTimeout time.Duration `json:"pingTimeout"` 298 | } 299 | 300 | var msg connectionInfo 301 | err = json.Unmarshal(p[:l], &msg) 302 | if err != nil { 303 | return err 304 | } 305 | msg.PingInterval *= 1000 * 1000 306 | msg.PingTimeout *= 1000 * 1000 307 | 308 | //fmt.Println(msg) 309 | 310 | c.pingInterval = msg.PingInterval 311 | c.pingTimeout = msg.PingTimeout 312 | c.id = msg.Sid 313 | 314 | c.getCurrent().Close() 315 | 316 | q.Set("sid", c.id) 317 | c.request.URL.RawQuery = q.Encode() 318 | 319 | transport, err = creater.Client(c.request) 320 | if err != nil { 321 | return err 322 | } 323 | c.setCurrent("polling", transport) 324 | 325 | pack, err = c.getCurrent().NextReader() 326 | if err != nil { 327 | return err 328 | } 329 | 330 | p2 := make([]byte, 4096) 331 | l, err = pack.Read(p2) 332 | if err != nil { 333 | return err 334 | } 335 | //fmt.Println(string(p2)) 336 | 337 | if c.options.Transport == "polling" { 338 | //over 339 | } else if c.options.Transport == "websocket" { 340 | //upgrade 341 | creater, exists = creaters["websocket"] 342 | if !exists { 343 | return InvalidError 344 | } 345 | 346 | if c.request.URL.Scheme == "https" { 347 | c.request.URL.Scheme = "wss" 348 | } else { 349 | c.request.URL.Scheme = "ws" 350 | } 351 | q.Set("sid", c.id) 352 | q.Set("transport", "websocket") 353 | c.request.URL.RawQuery = q.Encode() 354 | 355 | transport, err = creater.Client(c.request) 356 | if err != nil { 357 | return err 358 | } 359 | c.setUpgrading("websocket", transport) 360 | 361 | w, err := c.getUpgrade().NextWriter(message.MessageText, parser.PING) 362 | if err != nil { 363 | return err 364 | } 365 | w.Write([]byte("probe")) 366 | w.Close() 367 | } else { 368 | return InvalidError 369 | } 370 | 371 | //fmt.Println("end") 372 | 373 | return nil 374 | } 375 | 376 | func (c *clientConn) getCurrent() transport.Client { 377 | c.transportLocker.RLock() 378 | defer c.transportLocker.RUnlock() 379 | 380 | return c.current 381 | } 382 | 383 | func (c *clientConn) getUpgrade() transport.Client { 384 | c.transportLocker.RLock() 385 | defer c.transportLocker.RUnlock() 386 | 387 | return c.upgrading 388 | } 389 | 390 | func (c *clientConn) setCurrent(name string, s transport.Client) { 391 | c.transportLocker.Lock() 392 | defer c.transportLocker.Unlock() 393 | 394 | c.currentName = name 395 | c.current = s 396 | } 397 | 398 | func (c *clientConn) setUpgrading(name string, s transport.Client) { 399 | c.transportLocker.Lock() 400 | defer c.transportLocker.Unlock() 401 | 402 | c.upgradingName = name 403 | c.upgrading = s 404 | c.setState(stateUpgrading) 405 | } 406 | 407 | func (c *clientConn) upgraded() { 408 | c.transportLocker.Lock() 409 | 410 | current := c.current 411 | c.current = c.upgrading 412 | c.currentName = c.upgradingName 413 | c.upgrading = nil 414 | c.upgradingName = "" 415 | 416 | c.transportLocker.Unlock() 417 | 418 | current.Close() 419 | c.setState(stateNormal) 420 | } 421 | 422 | func (c *clientConn) getState() state { 423 | c.stateLocker.RLock() 424 | defer c.stateLocker.RUnlock() 425 | return c.state 426 | } 427 | 428 | func (c *clientConn) setState(state state) { 429 | c.stateLocker.Lock() 430 | defer c.stateLocker.Unlock() 431 | c.state = state 432 | } 433 | 434 | func (c *clientConn) pingLoop() { 435 | lastPing := time.Now() 436 | lastTry := lastPing 437 | for { 438 | now := time.Now() 439 | pingDiff := now.Sub(lastPing) 440 | tryDiff := now.Sub(lastTry) 441 | select { 442 | case ok := <-c.pingChan: 443 | if !ok { 444 | return 445 | } 446 | lastPing = time.Now() 447 | lastTry = lastPing 448 | case <-time.After(c.pingInterval - tryDiff): 449 | c.writerLocker.Lock() 450 | if w, _ := c.getCurrent().NextWriter(message.MessageText, parser.PING); w != nil { 451 | writer := newConnWriter(w, &c.writerLocker) 452 | writer.Close() 453 | } else { 454 | c.writerLocker.Unlock() 455 | } 456 | lastTry = time.Now() 457 | case <-time.After(c.pingTimeout - pingDiff): 458 | c.Close() 459 | return 460 | } 461 | } 462 | } 463 | 464 | func (c *clientConn) readLoop() { 465 | 466 | current := c.getCurrent() 467 | 468 | defer func() { 469 | c.OnClose(current) 470 | }() 471 | 472 | for { 473 | current = c.getCurrent() 474 | if c.getUpgrade() != nil { 475 | current = c.getUpgrade() 476 | } 477 | 478 | pack, err := current.NextReader() 479 | if err != nil { 480 | return 481 | } 482 | c.OnPacket(pack) 483 | pack.Close() 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "github.com/zhouhui8915/go-socket.io-client" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | opts := &socketio_client.Options{ 13 | //Transport:"polling", 14 | Transport:"websocket", 15 | Query: make(map[string]string), 16 | } 17 | opts.Query["user"] = "user" 18 | opts.Query["pwd"] = "pass" 19 | uri := "http://192.168.1.70:9090" 20 | 21 | client, err := socketio_client.NewClient(uri, opts) 22 | if err != nil { 23 | log.Printf("NewClient error:%v\n", err) 24 | return 25 | } 26 | 27 | client.On("error", func() { 28 | log.Printf("on error\n") 29 | }) 30 | client.On("connection", func() { 31 | log.Printf("on connect\n") 32 | }) 33 | client.On("message", func(msg string) { 34 | log.Printf("on message:%v\n", msg) 35 | }) 36 | client.On("disconnection", func() { 37 | log.Printf("on disconnect\n") 38 | }) 39 | 40 | reader := bufio.NewReader(os.Stdin) 41 | for { 42 | data, _, _ := reader.ReadLine() 43 | command := string(data) 44 | client.Emit("message", command) 45 | log.Printf("send message:%v\n", command) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zhouhui8915/go-socket.io-client" 5 | "log" 6 | "bufio" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | 13 | opts := &socketio_client.Options{ 14 | //Transport:"polling", 15 | Transport:"websocket", 16 | Query:make(map[string]string), 17 | } 18 | opts.Query["uid"] = "1" 19 | opts.Query["cid"] = "conf_123" 20 | uri := "http://192.168.1.70:9090" 21 | 22 | client,err := socketio_client.NewClient(uri,opts) 23 | if err != nil { 24 | log.Printf("NewClient error:%v\n",err) 25 | return 26 | } 27 | 28 | client.On("error", func() { 29 | log.Printf("on error\n") 30 | }) 31 | client.On("connection", func() { 32 | log.Printf("on connect\n") 33 | }) 34 | client.On("message", func(msg string) { 35 | log.Printf("on message:%v\n", msg) 36 | }) 37 | client.On("disconnection", func() { 38 | log.Printf("on disconnect\n") 39 | }) 40 | 41 | go func() { 42 | authStr := "{\"uid\":\"" + opts.Query["uid"] + "\",\"cid\":\"" + opts.Query["cid"] + "\"}" 43 | for { 44 | err := client.Emit("authenticate", authStr) 45 | if err != nil { 46 | log.Printf("Emit auth error:%v\n",err) 47 | } 48 | time.Sleep(10 * time.Second) 49 | } 50 | }() 51 | 52 | reader := bufio.NewReader(os.Stdin) 53 | for { 54 | data, _, _ := reader.ReadLine() 55 | command := string(data) 56 | err := client.Emit("message",command) 57 | if err != nil { 58 | log.Printf("Emit message error:%v\n",err) 59 | continue 60 | } 61 | log.Printf("send message:%v\n",command) 62 | } 63 | } -------------------------------------------------------------------------------- /ioutil.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "github.com/zhouhui8915/engine.io-go/parser" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | type connReader struct { 10 | *parser.PacketDecoder 11 | closeChan chan struct{} 12 | } 13 | 14 | func newConnReader(d *parser.PacketDecoder, closeChan chan struct{}) *connReader { 15 | return &connReader{ 16 | PacketDecoder: d, 17 | closeChan: closeChan, 18 | } 19 | } 20 | 21 | func (r *connReader) Close() error { 22 | if r.closeChan == nil { 23 | return nil 24 | } 25 | r.closeChan <- struct{}{} 26 | r.closeChan = nil 27 | return nil 28 | } 29 | 30 | type connWriter struct { 31 | io.WriteCloser 32 | locker *sync.Mutex 33 | } 34 | 35 | func newConnWriter(w io.WriteCloser, locker *sync.Mutex) *connWriter { 36 | return &connWriter{ 37 | WriteCloser: w, 38 | locker: locker, 39 | } 40 | } 41 | 42 | func (w *connWriter) Close() error { 43 | defer func() { 44 | if w.locker != nil { 45 | w.locker.Unlock() 46 | w.locker = nil 47 | } 48 | }() 49 | return w.WriteCloser.Close() 50 | } 51 | 52 | 53 | // add with github.com/googollee/go-socket.io/ioutil.go 54 | 55 | type writerHelper struct { 56 | writer io.Writer 57 | err error 58 | } 59 | 60 | func newWriterHelper(w io.Writer) *writerHelper { 61 | return &writerHelper{ 62 | writer: w, 63 | } 64 | } 65 | 66 | func (h *writerHelper) Write(p []byte) { 67 | if h.err != nil { 68 | return 69 | } 70 | for len(p) > 0 { 71 | n, err := h.writer.Write(p) 72 | if err != nil { 73 | h.err = err 74 | return 75 | } 76 | p = p[n:] 77 | } 78 | } 79 | 80 | func (h *writerHelper) Error() error { 81 | return h.err 82 | } 83 | -------------------------------------------------------------------------------- /message_reader.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "bufio" 5 | ) 6 | 7 | type messageReader struct { 8 | reader *bufio.Reader 9 | message string 10 | firstRead bool 11 | } 12 | 13 | func newMessageReader(bufr *bufio.Reader) (*messageReader, error) { 14 | if _, err := bufr.ReadBytes('"'); err != nil { 15 | return nil, err 16 | } 17 | msg, err := bufr.ReadBytes('"') 18 | if err != nil { 19 | return nil, err 20 | } 21 | for { 22 | b, err := bufr.Peek(1) 23 | if err != nil { 24 | return nil, err 25 | } 26 | if b[0] == ',' { 27 | bufr.ReadByte() 28 | break 29 | } 30 | if b[0] != ' ' { 31 | break 32 | } 33 | bufr.ReadByte() 34 | } 35 | return &messageReader{ 36 | reader: bufr, 37 | message: string(msg[:len(msg)-1]), 38 | firstRead: true, 39 | }, nil 40 | } 41 | 42 | func (r *messageReader) Message() string { 43 | return r.message 44 | } 45 | 46 | func (r *messageReader) Read(b []byte) (int, error) { 47 | if len(b) == 0 { 48 | return 0, nil 49 | } 50 | if r.firstRead { 51 | r.firstRead = false 52 | b[0] = '[' 53 | n, err := r.reader.Read(b[1:]) 54 | if err != nil { 55 | return -1, err 56 | } 57 | return n + 1, err 58 | } 59 | return r.reader.Read(b) 60 | } 61 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "strconv" 11 | ) 12 | 13 | const Protocol = 4 14 | 15 | type packetType int 16 | 17 | const ( 18 | _CONNECT packetType = iota 19 | _DISCONNECT 20 | _EVENT 21 | _ACK 22 | _ERROR 23 | _BINARY_EVENT 24 | _BINARY_ACK 25 | ) 26 | 27 | func (t packetType) String() string { 28 | switch t { 29 | case _CONNECT: 30 | return "connect" 31 | case _DISCONNECT: 32 | return "disconnect" 33 | case _EVENT: 34 | return "event" 35 | case _ACK: 36 | return "ack" 37 | case _ERROR: 38 | return "error" 39 | case _BINARY_EVENT: 40 | return "binary_event" 41 | case _BINARY_ACK: 42 | return "binary_ack" 43 | } 44 | return fmt.Sprintf("unknown(%d)", t) 45 | } 46 | 47 | type frameReader interface { 48 | NextReader() (MessageType, io.ReadCloser, error) 49 | } 50 | 51 | type frameWriter interface { 52 | NextWriter(MessageType) (io.WriteCloser, error) 53 | } 54 | 55 | type packet struct { 56 | Type packetType 57 | NSP string 58 | Id int 59 | Data interface{} 60 | attachNumber int 61 | } 62 | 63 | type encoder struct { 64 | w frameWriter 65 | err error 66 | } 67 | 68 | func newEncoder(w frameWriter) *encoder { 69 | return &encoder{ 70 | w: w, 71 | } 72 | } 73 | 74 | func (e *encoder) Encode(v packet) error { 75 | attachments := encodeAttachments(v.Data) 76 | v.attachNumber = len(attachments) 77 | if v.attachNumber > 0 { 78 | v.Type += _BINARY_EVENT - _EVENT 79 | } 80 | if err := e.encodePacket(v); err != nil { 81 | return err 82 | } 83 | for _, a := range attachments { 84 | if err := e.writeBinary(a); err != nil { 85 | return err 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (e *encoder) encodePacket(v packet) error { 92 | writer, err := e.w.NextWriter(MessageText) 93 | if err != nil { 94 | return err 95 | } 96 | defer writer.Close() 97 | 98 | w := newTrimWriter(writer, "\n") 99 | wh := newWriterHelper(w) 100 | wh.Write([]byte{byte(v.Type) + '0'}) 101 | if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK { 102 | wh.Write([]byte(fmt.Sprintf("%d-", v.attachNumber))) 103 | } 104 | needEnd := false 105 | if v.NSP != "" { 106 | wh.Write([]byte(v.NSP)) 107 | needEnd = true 108 | } 109 | if v.Id >= 0 { 110 | f := "%d" 111 | if needEnd { 112 | f = ",%d" 113 | needEnd = false 114 | } 115 | wh.Write([]byte(fmt.Sprintf(f, v.Id))) 116 | } 117 | if v.Data != nil { 118 | if needEnd { 119 | wh.Write([]byte{','}) 120 | needEnd = false 121 | } 122 | if wh.Error() != nil { 123 | return wh.Error() 124 | } 125 | encoder := json.NewEncoder(w) 126 | return encoder.Encode(v.Data) 127 | } 128 | return wh.Error() 129 | } 130 | 131 | func (e *encoder) writeBinary(r io.Reader) error { 132 | writer, err := e.w.NextWriter(MessageBinary) 133 | if err != nil { 134 | return err 135 | } 136 | defer writer.Close() 137 | 138 | if _, err := io.Copy(writer, r); err != nil { 139 | return err 140 | } 141 | return nil 142 | 143 | } 144 | 145 | type decoder struct { 146 | reader frameReader 147 | message string 148 | current io.Reader 149 | currentCloser io.Closer 150 | } 151 | 152 | func newDecoder(r frameReader) *decoder { 153 | return &decoder{ 154 | reader: r, 155 | } 156 | } 157 | 158 | func (d *decoder) Close() { 159 | if d != nil && d.currentCloser != nil { 160 | d.currentCloser.Close() 161 | d.current = nil 162 | d.currentCloser = nil 163 | } 164 | } 165 | 166 | func (d *decoder) Decode(v *packet) error { 167 | ty, r, err := d.reader.NextReader() 168 | if err != nil { 169 | return err 170 | } 171 | if d.current != nil { 172 | d.Close() 173 | } 174 | defer func() { 175 | if d.current == nil { 176 | r.Close() 177 | } 178 | }() 179 | 180 | if ty != MessageText { 181 | return fmt.Errorf("need text package") 182 | } 183 | reader := bufio.NewReader(r) 184 | 185 | v.Id = -1 186 | 187 | t, err := reader.ReadByte() 188 | if err != nil { 189 | return err 190 | } 191 | v.Type = packetType(t - '0') 192 | 193 | if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK { 194 | num, err := reader.ReadBytes('-') 195 | if err != nil { 196 | return err 197 | } 198 | numLen := len(num) 199 | if numLen == 0 { 200 | return fmt.Errorf("invalid packet") 201 | } 202 | n, err := strconv.ParseInt(string(num[:numLen-1]), 10, 64) 203 | if err != nil { 204 | return fmt.Errorf("invalid packet") 205 | } 206 | v.attachNumber = int(n) 207 | } 208 | 209 | next, err := reader.Peek(1) 210 | if err == io.EOF { 211 | return nil 212 | } 213 | if err != nil { 214 | return err 215 | } 216 | if len(next) == 0 { 217 | return fmt.Errorf("invalid packet") 218 | } 219 | 220 | if next[0] == '/' { 221 | path, err := reader.ReadBytes(',') 222 | if err != nil && err != io.EOF { 223 | return err 224 | } 225 | pathLen := len(path) 226 | if pathLen == 0 { 227 | return fmt.Errorf("invalid packet") 228 | } 229 | if err == nil { 230 | path = path[:pathLen-1] 231 | } 232 | v.NSP = string(path) 233 | if err == io.EOF { 234 | return nil 235 | } 236 | } 237 | 238 | id := bytes.NewBuffer(nil) 239 | finish := false 240 | for { 241 | next, err := reader.Peek(1) 242 | if err == io.EOF { 243 | finish = true 244 | break 245 | } 246 | if err != nil { 247 | return err 248 | } 249 | if '0' <= next[0] && next[0] <= '9' { 250 | if err := id.WriteByte(next[0]); err != nil { 251 | return err 252 | } 253 | } else { 254 | break 255 | } 256 | reader.ReadByte() 257 | } 258 | if id.Len() > 0 { 259 | id, err := strconv.ParseInt(id.String(), 10, 64) 260 | if err != nil { 261 | return err 262 | } 263 | v.Id = int(id) 264 | } 265 | if finish { 266 | return nil 267 | } 268 | 269 | switch v.Type { 270 | case _EVENT: 271 | fallthrough 272 | case _BINARY_EVENT: 273 | msgReader, err := newMessageReader(reader) 274 | if err != nil { 275 | return err 276 | } 277 | d.message = msgReader.Message() 278 | d.current = msgReader 279 | d.currentCloser = r 280 | case _ACK: 281 | fallthrough 282 | case _BINARY_ACK: 283 | d.current = reader 284 | d.currentCloser = r 285 | } 286 | return nil 287 | } 288 | 289 | func (d *decoder) Message() string { 290 | return d.message 291 | } 292 | 293 | func (d *decoder) DecodeData(v *packet) error { 294 | if d.current == nil { 295 | return nil 296 | } 297 | defer func() { 298 | d.Close() 299 | }() 300 | decoder := json.NewDecoder(d.current) 301 | if err := decoder.Decode(v.Data); err != nil { 302 | return err 303 | } 304 | if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK { 305 | binary, err := d.decodeBinary(v.attachNumber) 306 | if err != nil { 307 | return err 308 | } 309 | if err := decodeAttachments(v.Data, binary); err != nil { 310 | return err 311 | } 312 | v.Type -= _BINARY_EVENT - _EVENT 313 | } 314 | return nil 315 | } 316 | 317 | func (d *decoder) decodeBinary(num int) ([][]byte, error) { 318 | ret := make([][]byte, num) 319 | for i := 0; i < num; i++ { 320 | d.currentCloser.Close() 321 | t, r, err := d.reader.NextReader() 322 | if err != nil { 323 | return nil, err 324 | } 325 | d.currentCloser = r 326 | if t == MessageText { 327 | return nil, fmt.Errorf("need binary") 328 | } 329 | b, err := ioutil.ReadAll(r) 330 | if err != nil { 331 | return nil, err 332 | } 333 | ret[i] = b 334 | } 335 | return ret, nil 336 | } 337 | -------------------------------------------------------------------------------- /trim_writer.go: -------------------------------------------------------------------------------- 1 | package socketio_client 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | type trimWriter struct { 9 | trimChars string 10 | trimBuf []byte 11 | output io.Writer 12 | } 13 | 14 | func newTrimWriter(w io.Writer, trimChars string) *trimWriter { 15 | return &trimWriter{ 16 | trimChars: trimChars, 17 | output: w, 18 | } 19 | } 20 | 21 | func (w *trimWriter) Write(p []byte) (int, error) { 22 | out := bytes.TrimRight(p, w.trimChars) 23 | buf := p[len(out):] 24 | var written int 25 | if (len(out) > 0) && (w.trimBuf != nil) { 26 | var err error 27 | if written, err = w.output.Write(w.trimBuf); err != nil { 28 | return 0, err 29 | } 30 | w.trimBuf = nil 31 | } 32 | if w.trimBuf != nil { 33 | w.trimBuf = append(w.trimBuf, buf...) 34 | } else { 35 | w.trimBuf = buf 36 | } 37 | if len(p) == 0 { 38 | return written, nil 39 | } 40 | ret, err := w.output.Write(out) 41 | if err != nil { 42 | return 0, err 43 | } 44 | return written + ret, nil 45 | } 46 | --------------------------------------------------------------------------------