This repository has been archived by the owner on Apr 5, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
/
server.go
294 lines (245 loc) · 7.44 KB
/
server.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
package main
import (
"errors"
"html/template"
"mime"
"net"
"net/http"
"os"
"path/filepath"
"sync"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
const (
errorInvalidSession = iota
errorNotFound = iota
errorNotAllowed = iota
)
var log = MainLogger
// SessionTemplateModel used for templating
type SessionTemplateModel struct {
SessionID string
Salt string
WSPath string
}
// TTYServerConfig is used to configure the tty server before it is started
type TTYServerConfig struct {
WebAddress string
TTYSenderAddress string
ServerURL string
// The TLS Cert and Key can be null, if TLS should not be used
TLSCertFile string
TLSKeyFile string
FrontendPath string
}
// TTYServer represents the instance of a tty server
type TTYServer struct {
httpServer *http.Server
ttySendersListener net.Listener
config TTYServerConfig
activeSessions map[string]*ttyShareSession
activeSessionsRWLock sync.RWMutex
}
func (server *TTYServer) serveContent(w http.ResponseWriter, r *http.Request, name string) {
// If a path to the frontend resources was passed, serve from there, otherwise, serve from the
// builtin bundle
if server.config.FrontendPath == "" {
file, err := Asset(name)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
ctype := mime.TypeByExtension(filepath.Ext(name))
if ctype == "" {
ctype = http.DetectContentType(file)
}
w.Header().Set("Content-Type", ctype)
w.Write(file)
} else {
filePath := server.config.FrontendPath + string(os.PathSeparator) + name
_, err := os.Open(filePath)
if err != nil {
log.Errorf("Couldn't find resource: %s at %s", name, filePath)
w.WriteHeader(http.StatusNotFound)
return
}
log.Debugf("Serving %s from %s", name, filePath)
http.ServeFile(w, r, filePath)
}
}
// NewTTYServer creates a new instance
func NewTTYServer(config TTYServerConfig) (server *TTYServer) {
server = &TTYServer{
config: config,
}
server.httpServer = &http.Server{
Addr: config.WebAddress,
}
routesHandler := mux.NewRouter()
routesHandler.PathPrefix("/static/").Handler(http.StripPrefix("/static/",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.serveContent(w, r, r.URL.Path)
})))
routesHandler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://github.com/elisescu/tty-share", http.StatusMovedPermanently)
})
routesHandler.HandleFunc("/s/{sessionID}", func(w http.ResponseWriter, r *http.Request) {
server.handleSession(w, r)
})
routesHandler.HandleFunc("/ws/{sessionID}", func(w http.ResponseWriter, r *http.Request) {
server.handleWebsocket(w, r)
})
routesHandler.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.serveContent(w, r, "404.html")
})
server.activeSessions = make(map[string]*ttyShareSession)
server.httpServer.Handler = routesHandler
return server
}
func getWSPath(sessionID string) string {
return "/ws/" + sessionID
}
func (server *TTYServer) handleWebsocket(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionID := vars["sessionID"]
defer log.Debug("Finished WS connection for ", sessionID)
// Validate incoming request.
if r.Method != "GET" {
w.WriteHeader(http.StatusForbidden)
return
}
// Upgrade to Websocket mode.
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error("Cannot create the WS connection for session ", sessionID, ". Error: ", err.Error())
return
}
session := getSession(server, sessionID)
if session == nil {
log.Error("WE connection for invalid sessionID: ", sessionID, ". Killing it.")
w.WriteHeader(http.StatusForbidden)
return
}
session.HandleReceiver(newWSConnection(conn))
}
func (server *TTYServer) handleSession(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionID := vars["sessionID"]
log.Debug("Handling web TTYReceiver session: ", sessionID)
session := getSession(server, sessionID)
// No valid session with this ID
if session == nil {
server.serveContent(w, r, "invalid-session.html")
return
}
var t *template.Template
var err error
if server.config.FrontendPath == "" {
templateDta, err := Asset("tty-receiver.in.html")
if err != nil {
panic("Cannot find the tty-receiver html template")
}
t = template.New("tty-receiver.html")
_, err = t.Parse(string(templateDta))
} else {
t, err = template.ParseFiles(server.config.FrontendPath + string(os.PathSeparator) + "tty-receiver.in.html")
}
if err != nil {
panic("Cannot parse the tty-receiver html template")
}
templateModel := SessionTemplateModel{
SessionID: sessionID,
Salt: "salt&pepper",
WSPath: getWSPath(sessionID),
}
err = t.Execute(w, templateModel)
if err != nil {
panic("Cannot execute the tty-receiver html template")
}
}
func addNewSession(server *TTYServer, session *ttyShareSession) {
server.activeSessionsRWLock.Lock()
server.activeSessions[session.GetID()] = session
server.activeSessionsRWLock.Unlock()
}
func removeSession(server *TTYServer, session *ttyShareSession) {
server.activeSessionsRWLock.Lock()
delete(server.activeSessions, session.GetID())
server.activeSessionsRWLock.Unlock()
}
func getSession(server *TTYServer, sessionID string) (session *ttyShareSession) {
// TODO: move this in a better place
server.activeSessionsRWLock.RLock()
session = server.activeSessions[sessionID]
server.activeSessionsRWLock.RUnlock()
return
}
func handleTTYSenderConnection(server *TTYServer, conn net.Conn) {
defer conn.Close()
session := newTTYShareSession(conn, server.config.ServerURL)
if err := session.InitSender(); err != nil {
log.Warnf("Cannot create session with %s. Error: %s", conn.RemoteAddr().String(), err.Error())
return
}
addNewSession(server, session)
session.HandleSenderConnection()
removeSession(server, session)
log.Debug("Finished session ", session.GetID(), ". Removing it.")
}
// Listen starts listening on connections
func (server *TTYServer) Listen() (err error) {
var wg sync.WaitGroup
runTLS := server.config.TLSCertFile != "" && server.config.TLSKeyFile != ""
// Start listening on the frontend side
wg.Add(1)
go func() {
if !runTLS {
err = server.httpServer.ListenAndServe()
} else {
err = server.httpServer.ListenAndServeTLS(server.config.TLSCertFile, server.config.TLSKeyFile)
}
// Just in case we are existing because of an error, close the other listener too
if server.ttySendersListener != nil {
server.ttySendersListener.Close()
}
wg.Done()
}()
// TODO: Add support for listening for connections over TLS
// Listen on connections on the tty sender side
server.ttySendersListener, err = net.Listen("tcp", server.config.TTYSenderAddress)
if err != nil {
log.Error("Cannot create the front server. Error: ", err.Error())
return
}
for {
connection, err := server.ttySendersListener.Accept()
if err == nil {
go handleTTYSenderConnection(server, connection)
} else {
break
}
}
// Close the http side too
if server.httpServer != nil {
server.httpServer.Close()
}
wg.Wait()
log.Debug("Server finished")
return
}
// Stop closes down the server
func (server *TTYServer) Stop() error {
log.Debug("Stopping the server")
err1 := server.httpServer.Close()
err2 := server.ttySendersListener.Close()
if err1 != nil || err2 != nil {
//TODO: do this nicer
return errors.New(err1.Error() + err2.Error())
}
return nil
}