summaryrefslogtreecommitdiffstats
path: root/lib/xdsserver/server.go
blob: f0777e3d10b4b227d1a24a70ab8f65e670ea178a (plain)
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
package xdsserver

import (
	"net/http"

	"github.com/Sirupsen/logrus"
	"github.com/gin-gonic/gin"
	"github.com/googollee/go-socket.io"
	"github.com/iotbzh/xds-agent/lib/apiv1"
	"github.com/iotbzh/xds-agent/lib/session"
	"github.com/iotbzh/xds-agent/lib/xdsconfig"
)

// ServerService .
type ServerService struct {
	router    *gin.Engine
	api       *apiv1.APIService
	sIOServer *socketio.Server
	webApp    *gin.RouterGroup
	cfg       *xdsconfig.Config
	sessions  *session.Sessions
	log       *logrus.Logger
	stop      chan struct{} // signals intentional stop
}

const indexFilename = "index.html"
const cookieMaxAge = "3600"

// NewServer creates an instance of ServerService
func NewServer(conf *xdsconfig.Config, log *logrus.Logger) *ServerService {

	// Setup logging for gin router
	if log.Level == logrus.DebugLevel {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}

	// TODO
	//  - try to bind gin DefaultWriter & DefaultErrorWriter to logrus logger
	//  - try to fix pb about isTerminal=false when out is in VSC Debug Console
	//gin.DefaultWriter = ??
	//gin.DefaultErrorWriter = ??

	// Creates gin router
	r := gin.New()

	svr := &ServerService{
		router:    r,
		api:       nil,
		sIOServer: nil,
		webApp:    nil,
		cfg:       conf,
		log:       log,
		sessions:  nil,
		stop:      make(chan struct{}),
	}

	return svr
}

// Serve starts a new instance of the Web Server
func (s *ServerService) Serve() error {
	var err error

	// Setup middlewares
	s.router.Use(gin.Logger())
	s.router.Use(gin.Recovery())
	s.router.Use(s.middlewareXDSDetails())
	s.router.Use(s.middlewareCORS())

	// Sessions manager
	s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge)

	// Create REST API
	s.api = apiv1.New(s.sessions, s.cfg, s.log, s.router)

	// Websocket routes
	s.sIOServer, err = socketio.NewServer(nil)
	if err != nil {
		s.log.Fatalln(err)
	}

	s.router.GET("/socket.io/", s.socketHandler)
	s.router.POST("/socket.io/", s.socketHandler)
	/* TODO: do we want to support ws://...  ?
	s.router.Handle("WS", "/socket.io/", s.socketHandler)
	s.router.Handle("WSS", "/socket.io/", s.socketHandler)
	*/

	// Serve in the background
	serveError := make(chan error, 1)
	go func() {
		serveError <- http.ListenAndServe(":"+s.cfg.HTTPPort, s.router)
	}()

	// Wait for stop, restart or error signals
	select {
	case <-s.stop:
		// Shutting down permanently
		s.sessions.Stop()
		s.log.Infoln("shutting down (stop)")
	case err = <-serveError:
		// Error due to listen/serve failure
		s.log.Errorln(err)
	}

	return nil
}

// Stop web server
func (s *ServerService) Stop() {
	close(s.stop)
}

// Add details in Header
func (s *ServerService) middlewareXDSDetails() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("XDS-Agent-Version", s.cfg.Version)
		c.Header("XDS-API-Version", s.cfg.APIVersion)
		c.Next()
	}
}

// CORS middleware
func (s *ServerService) middlewareCORS() gin.HandlerFunc {
	return func(c *gin.Context) {

		if c.Request.Method == "OPTIONS" {
			c.Header("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Headers", "Content-Type")
			c.Header("Access-Control-Allow-Methods", "POST, DELETE, GET, PUT")
			c.Header("Content-Type", "application/json")
			c.Header("Access-Control-Max-Age", cookieMaxAge)
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}

// socketHandler is the handler for the "main" websocket connection
func (s *ServerService) socketHandler(c *gin.Context) {

	// Retrieve user session
	sess := s.sessions.Get(c)
	if sess == nil {
		c.JSON(500, gin.H{"error": "Cannot retrieve session"})
		return
	}

	s.sIOServer.On("connection", func(so socketio.Socket) {
		s.log.Debugf("WS Connected (SID=%v)", so.Id())
		s.sessions.UpdateIOSocket(sess.ID, &so)

		so.On("disconnection", func() {
			s.log.Debugf("WS disconnected (SID=%v)", so.Id())
			s.sessions.UpdateIOSocket(sess.ID, nil)
		})
	})

	s.sIOServer.On("error", func(so socketio.Socket, err error) {
		s.log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error())
	})

	s.sIOServer.ServeHTTP(c.Writer, c.Request)
}