-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathclient.go
143 lines (128 loc) · 4.3 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"encoding/json"
"log"
"time"
"github.com/gorilla/websocket"
)
// ClientList is a map used to help manage a map of clients
type ClientList map[*Client]bool
// Client is a websocket client, basically a frontend visitor
type Client struct {
// the websocket connection
connection *websocket.Conn
// manager is the manager used to manage the client
manager *Manager
// egress is used to avoid concurrent writes on the WebSocket
egress chan Event
// chatroom is used to know what room user is in
chatroom string
}
var (
// pongWait is how long we will await a pong response from client
pongWait = 10 * time.Second
// pingInterval has to be less than pongWait, We cant multiply by 0.9 to get 90% of time
// Because that can make decimals, so instead *9 / 10 to get 90%
// The reason why it has to be less than PingRequency is becuase otherwise it will send a new Ping before getting response
pingInterval = (pongWait * 9) / 10
)
// NewClient is used to initialize a new Client with all required values initialized
func NewClient(conn *websocket.Conn, manager *Manager) *Client {
return &Client{
connection: conn,
manager: manager,
egress: make(chan Event),
}
}
// readMessages will start the client to read messages and handle them
// appropriatly.
// This is suppose to be ran as a goroutine
func (c *Client) readMessages() {
defer func() {
// Graceful Close the Connection once this
// function is done
c.manager.removeClient(c)
}()
// Set Max Size of Messages in Bytes
c.connection.SetReadLimit(512)
// Configure Wait time for Pong response, use Current time + pongWait
// This has to be done here to set the first initial timer.
if err := c.connection.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
log.Println(err)
return
}
// Configure how to handle Pong responses
c.connection.SetPongHandler(c.pongHandler)
// Loop Forever
for {
// ReadMessage is used to read the next message in queue
// in the connection
_, payload, err := c.connection.ReadMessage()
if err != nil {
// If Connection is closed, we will Recieve an error here
// We only want to log Strange errors, but simple Disconnection
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error reading message: %v", err)
}
break // Break the loop to close conn & Cleanup
}
// Marshal incoming data into a Event struct
var request Event
if err := json.Unmarshal(payload, &request); err != nil {
log.Printf("error marshalling message: %v", err)
break // Breaking the connection here might be harsh xD
}
// Route the Event
if err := c.manager.routeEvent(request, c); err != nil {
log.Println("Error handeling Message: ", err)
}
}
}
// pongHandler is used to handle PongMessages for the Client
func (c *Client) pongHandler(pongMsg string) error {
// Current time + Pong Wait time
log.Println("pong")
return c.connection.SetReadDeadline(time.Now().Add(pongWait))
}
// writeMessages is a process that listens for new messages to output to the Client
func (c *Client) writeMessages() {
// Create a ticker that triggers a ping at given interval
ticker := time.NewTicker(pingInterval)
defer func() {
ticker.Stop()
// Graceful close if this triggers a closing
c.manager.removeClient(c)
}()
for {
select {
case message, ok := <-c.egress:
// Ok will be false Incase the egress channel is closed
if !ok {
// Manager has closed this connection channel, so communicate that to frontend
if err := c.connection.WriteMessage(websocket.CloseMessage, nil); err != nil {
// Log that the connection is closed and the reason
log.Println("connection closed: ", err)
}
// Return to close the goroutine
return
}
data, err := json.Marshal(message)
if err != nil {
log.Println(err)
return // closes the connection, should we really
}
// Write a Regular text message to the connection
if err := c.connection.WriteMessage(websocket.TextMessage, data); err != nil {
log.Println(err)
}
log.Println("sent message")
case <-ticker.C:
log.Println("ping")
// Send the Ping
if err := c.connection.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
log.Println("writemsg: ", err)
return // return to break this goroutine triggeing cleanup
}
}
}
}