PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/socket.go

https://github.com/courtf/geobin.io
Go | 195 lines | 134 code | 28 blank | 33 comment | 26 complexity | 5f9c62678faa4fef2decfb4a6b1a620e MD5 | raw file
Possible License(s): Apache-2.0
  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "net/http"
  6. "net/url"
  7. "sync"
  8. "time"
  9. "github.com/gorilla/websocket"
  10. )
  11. const (
  12. // Time allowed to write a message to the peer.
  13. writeWait = 10 * time.Second
  14. // Send pings to peer with this period. Must be less than pongWait.
  15. pingPeriod = 60 * time.Second
  16. )
  17. // S exposes some methods for interacting with a websocket
  18. type Socket interface {
  19. // Submits a payload to the web socket as a text message.
  20. Write([]byte)
  21. // return the provided name
  22. GetName() string
  23. // Close the socket.
  24. Close()
  25. }
  26. // implementation of S
  27. type s struct {
  28. // a string associated with the socket
  29. name string
  30. // the websocket connection
  31. ws *websocket.Conn
  32. // buffered channel of outbound messages
  33. send chan []byte
  34. shutdown chan bool
  35. closed bool
  36. closeLock *sync.Mutex
  37. // event functions
  38. onRead func(messageType int, message []byte)
  39. onClose func(name string)
  40. }
  41. // NewSocket upgrades an existing TCP connection to a websocket connection in response to a client request for a websocket.
  42. // `name` here is just an identifying string for the socket, which will be returned when/if the socket is closed
  43. // by calling a provided function (settable with `SetOnClose()`).
  44. // `or` here is the func that's called when a message is read from the socket. The call is made from a separate routine.
  45. // The message types are defined in RFC 6455, section 11.8.
  46. // `oc` here is the func that's called when the socket is just about to be closed. The call is made from a
  47. // separate routine.
  48. // If you do not care about these callbacks, pass nil instead.
  49. func NewSocket(name string, w http.ResponseWriter, r *http.Request, or func(int, []byte), oc func(string)) (Socket, error) {
  50. ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
  51. if _, ok := err.(websocket.HandshakeError); ok {
  52. http.Error(w, "Not a websocket handshake", http.StatusBadRequest)
  53. return nil, err
  54. } else if err != nil {
  55. http.Error(w, "Error while opening websocket!", http.StatusInternalServerError)
  56. return nil, err
  57. }
  58. return socketSetup(name, ws, or, oc), nil
  59. }
  60. // NewClient creates a client web socket connection to the host running at the provided URL.
  61. // `name` here is just an identifying string for the socket, which will be returned when/if the socket is closed
  62. // by calling a provided function (settable with `SetOnClose()`).
  63. // `or` here is the func that's called when a message is read from the socket. The call is made from a separate routine.
  64. // The message types are defined in RFC 6455, section 11.8.
  65. // `oc` here is the func that's called when the socket is just about to be closed. The call is made from a
  66. // separate routine.
  67. // If you do not care about these callbacks, pass nil instead.
  68. func NewClient(name string, socketURL string, or func(int, []byte), oc func(string)) (Socket, error) {
  69. u, err := url.Parse(socketURL)
  70. if err != nil {
  71. log.Println("Could not parse URL from provided URL string:", socketURL, err)
  72. return nil, err
  73. }
  74. conn, err := net.Dial("tcp", u.Host)
  75. if err != nil {
  76. log.Println("Could not connect to provided host:", u.Host, err)
  77. return nil, err
  78. }
  79. ws, _, err := websocket.NewClient(conn, u, nil, 1024, 1024)
  80. if err != nil {
  81. log.Println("Error while opening websocket:", err)
  82. return nil, err
  83. }
  84. return socketSetup(name, ws, or, oc), nil
  85. }
  86. func socketSetup(name string, ws *websocket.Conn, or func(int, []byte), oc func(string)) Socket {
  87. if or == nil {
  88. or = func(int, []byte) {}
  89. }
  90. if oc == nil {
  91. oc = func(string) {}
  92. }
  93. s := &s{
  94. name: name,
  95. ws: ws,
  96. send: make(chan []byte, 256),
  97. shutdown: make(chan bool),
  98. closed: false,
  99. closeLock: &sync.Mutex{},
  100. onRead: or,
  101. onClose: oc,
  102. }
  103. go s.writePump()
  104. go s.readPump()
  105. return s
  106. }
  107. func (s *s) Write(payload []byte) {
  108. s.send <- payload
  109. }
  110. func (s *s) Close() {
  111. s.closeLock.Lock()
  112. if s.closed {
  113. return
  114. }
  115. s.closed = true
  116. s.closeLock.Unlock()
  117. s.onClose(s.name)
  118. s.ws.Close()
  119. }
  120. func (s *s) GetName() string {
  121. return s.name
  122. }
  123. // readPump pumps messages from the websocket
  124. func (s *s) readPump() {
  125. for {
  126. mt, message, err := s.ws.ReadMessage()
  127. if err != nil {
  128. // This happens anytime a client closes the connection, which can end up with
  129. // chatty logs, so we aren't logging this error currently. If we did, it would look like:
  130. // log.Println("[" + s.name + "]", "Error during socket read:", err)
  131. s.Close()
  132. s.shutdown <- true
  133. return
  134. }
  135. s.onRead(mt, message)
  136. }
  137. }
  138. // writePump pumps messages to the websocket
  139. func (s *s) writePump() {
  140. ticker := time.NewTicker(pingPeriod)
  141. defer func() {
  142. ticker.Stop()
  143. }()
  144. for {
  145. select {
  146. case <-s.shutdown:
  147. return
  148. case message := <-s.send:
  149. if err := s.write(websocket.TextMessage, message); err != nil {
  150. log.Println("["+s.name+"]", "Error during socket write:", err)
  151. s.Close()
  152. return
  153. }
  154. case <-ticker.C:
  155. if err := s.write(websocket.PingMessage, []byte{}); err != nil {
  156. log.Println("["+s.name+"]", "Error during ping for socket:", err)
  157. s.Close()
  158. return
  159. }
  160. }
  161. }
  162. }
  163. // writes a message with the given message type and payload.
  164. func (s *s) write(mt int, payload []byte) error {
  165. s.ws.SetWriteDeadline(time.Now().Add(writeWait))
  166. return s.ws.WriteMessage(mt, payload)
  167. }