github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/v2/websocket/transport.go (about) 1 package websocket 2 3 import ( 4 "context" 5 "crypto/tls" 6 "encoding/json" 7 "fmt" 8 "github.com/op/go-logging" 9 "net" 10 "net/http" 11 "sync" 12 "time" 13 14 "github.com/gorilla/websocket" 15 ) 16 17 // size of channel that the websocket writer 18 // routine pulls from 19 const WS_WRITE_CAPACITY = 5000 20 // size of channel that the websocket reader 21 // routine pushes websocket updates into 22 const WS_READ_CAPACITY = 10 23 // seconds to wait in between re-sending 24 // the keep alive ping 25 const KEEP_ALIVE_TIMEOUT = 10 26 27 func newWs(baseURL string, logTransport bool, log *logging.Logger) *ws { 28 return &ws{ 29 BaseURL: baseURL, 30 downstream: make(chan []byte, WS_READ_CAPACITY), 31 quit: make(chan error), 32 kill: make(chan interface{}), 33 logTransport: logTransport, 34 log: log, 35 lock: &sync.RWMutex{}, 36 createTime: time.Now(), 37 writeChan: make(chan []byte, WS_WRITE_CAPACITY), 38 } 39 } 40 41 type ws struct { 42 ws *websocket.Conn 43 lock *sync.RWMutex 44 BaseURL string 45 TLSSkipVerify bool 46 downstream chan []byte 47 logTransport bool 48 log *logging.Logger 49 createTime time.Time 50 writeChan chan []byte 51 52 kill chan interface{} // signal to routines to kill 53 quit chan error // signal to parent with error, if applicable 54 } 55 56 func (w *ws) Connect() error { 57 if w.ws != nil { 58 return nil // no op 59 } 60 var d = websocket.Dialer{ 61 Subprotocols: []string{"p1", "p2"}, 62 ReadBufferSize: 1024, 63 WriteBufferSize: 1024, 64 Proxy: http.ProxyFromEnvironment, 65 HandshakeTimeout: time.Second * 10, 66 } 67 68 d.TLSClientConfig = &tls.Config{InsecureSkipVerify: w.TLSSkipVerify} 69 70 w.log.Infof("connecting ws to %s", w.BaseURL) 71 ws, resp, err := d.Dial(w.BaseURL, nil) 72 if err != nil { 73 if err == websocket.ErrBadHandshake { 74 w.log.Errorf("bad handshake: status code %d", resp.StatusCode) 75 } 76 return err 77 } 78 w.ws = ws 79 go w.listenWriteChannel() 80 go w.listenWs() 81 // Gorilla/go dont natively support keep alive pinging 82 // so we need to keep sending a message down the channel to stop 83 // tcp killing the connection 84 go w.keepAlivePinger() 85 return nil 86 } 87 88 func (w *ws) keepAlivePinger() { 89 for { 90 pingTimer := time.After(time.Second * KEEP_ALIVE_TIMEOUT) 91 select { 92 case <-w.kill: 93 return 94 case <-pingTimer: 95 w.writeChan <- []byte("ping") 96 } 97 } 98 } 99 100 // Send marshals the given interface and then sends it to the API. This method 101 // can block so specify a context with timeout if you don't want to wait for too 102 // long. 103 func (w *ws) Send(ctx context.Context, msg interface{}) error { 104 bs, err := json.Marshal(msg) 105 if err != nil { 106 return err 107 } 108 109 select { 110 case <- ctx.Done(): 111 return ctx.Err() 112 case <- w.kill: // ws closed 113 return fmt.Errorf("websocket connection closed") 114 default: 115 } 116 w.log.Debug("ws->srv: %s", string(bs)) 117 // push request into writer channel 118 w.writeChan <- bs 119 return nil 120 } 121 122 func (w *ws) Done() <-chan error { 123 return w.quit 124 } 125 126 // listen for write requests and perform them 127 func (w *ws) listenWriteChannel() { 128 for { 129 select { 130 case <-w.kill: // ws closed 131 return 132 case message := <- w.writeChan: 133 wsWriter, err := w.ws.NextWriter(websocket.TextMessage) 134 if err != nil { 135 w.log.Error("Unable to provision ws connection writer: ", err) 136 w.stop(err) 137 return 138 } 139 _, err = wsWriter.Write(message) 140 if err != nil { 141 w.log.Error("Unable to write to ws: ", err) 142 w.stop(err) 143 return 144 } 145 if err := wsWriter.Close(); err != nil { 146 w.log.Error("Unable to close ws connection writer: ", err) 147 w.stop(err) 148 return 149 } 150 } 151 } 152 } 153 154 // listen on ws & fwd to listen() 155 func (w *ws) listenWs() { 156 for { 157 if w.ws == nil { 158 return 159 } 160 select { 161 case <-w.kill: // ws connection ended 162 return 163 default: 164 _, msg, err := w.ws.ReadMessage() 165 if err != nil { 166 if cl, ok := err.(*websocket.CloseError); ok { 167 w.log.Errorf("close error code: %d", cl.Code) 168 } 169 // a read during normal shutdown results in an OpError: op on closed connection 170 if _, ok := err.(*net.OpError); ok { 171 // general read error on a closed network connection, OK 172 return 173 } 174 175 w.stop(err) 176 return 177 } 178 w.log.Debugf("srv->ws: %s", string(msg)) 179 w.lock.RLock() 180 if w.downstream == nil { 181 w.lock.RUnlock() 182 return 183 } 184 w.downstream <- msg 185 w.lock.RUnlock() 186 } 187 } 188 } 189 190 func (w *ws) Listen() <-chan []byte { 191 return w.downstream 192 } 193 194 func (w *ws) stop(err error) { 195 w.lock.Lock() 196 defer w.lock.Unlock() 197 if w.ws != nil { 198 close(w.kill) 199 w.quit <- err // pass error back 200 close(w.quit) // signal to parent listeners 201 close(w.downstream) 202 w.downstream = nil 203 if err := w.ws.Close(); err != nil { 204 w.log.Error(fmt.Errorf("error closing websocket: %s", err)) 205 } 206 w.ws = nil 207 } 208 } 209 210 // Close the websocket connection 211 func (w *ws) Close() { 212 w.stop(fmt.Errorf("transport connection Close called")) 213 }