f0777e3d10b4b227d1a24a70ab8f65e670ea178a
[src/xds/xds-agent.git] / lib / xdsserver / server.go
1 package xdsserver
2
3 import (
4         "net/http"
5
6         "github.com/Sirupsen/logrus"
7         "github.com/gin-gonic/gin"
8         "github.com/googollee/go-socket.io"
9         "github.com/iotbzh/xds-agent/lib/apiv1"
10         "github.com/iotbzh/xds-agent/lib/session"
11         "github.com/iotbzh/xds-agent/lib/xdsconfig"
12 )
13
14 // ServerService .
15 type ServerService struct {
16         router    *gin.Engine
17         api       *apiv1.APIService
18         sIOServer *socketio.Server
19         webApp    *gin.RouterGroup
20         cfg       *xdsconfig.Config
21         sessions  *session.Sessions
22         log       *logrus.Logger
23         stop      chan struct{} // signals intentional stop
24 }
25
26 const indexFilename = "index.html"
27 const cookieMaxAge = "3600"
28
29 // NewServer creates an instance of ServerService
30 func NewServer(conf *xdsconfig.Config, log *logrus.Logger) *ServerService {
31
32         // Setup logging for gin router
33         if log.Level == logrus.DebugLevel {
34                 gin.SetMode(gin.DebugMode)
35         } else {
36                 gin.SetMode(gin.ReleaseMode)
37         }
38
39         // TODO
40         //  - try to bind gin DefaultWriter & DefaultErrorWriter to logrus logger
41         //  - try to fix pb about isTerminal=false when out is in VSC Debug Console
42         //gin.DefaultWriter = ??
43         //gin.DefaultErrorWriter = ??
44
45         // Creates gin router
46         r := gin.New()
47
48         svr := &ServerService{
49                 router:    r,
50                 api:       nil,
51                 sIOServer: nil,
52                 webApp:    nil,
53                 cfg:       conf,
54                 log:       log,
55                 sessions:  nil,
56                 stop:      make(chan struct{}),
57         }
58
59         return svr
60 }
61
62 // Serve starts a new instance of the Web Server
63 func (s *ServerService) Serve() error {
64         var err error
65
66         // Setup middlewares
67         s.router.Use(gin.Logger())
68         s.router.Use(gin.Recovery())
69         s.router.Use(s.middlewareXDSDetails())
70         s.router.Use(s.middlewareCORS())
71
72         // Sessions manager
73         s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge)
74
75         // Create REST API
76         s.api = apiv1.New(s.sessions, s.cfg, s.log, s.router)
77
78         // Websocket routes
79         s.sIOServer, err = socketio.NewServer(nil)
80         if err != nil {
81                 s.log.Fatalln(err)
82         }
83
84         s.router.GET("/socket.io/", s.socketHandler)
85         s.router.POST("/socket.io/", s.socketHandler)
86         /* TODO: do we want to support ws://...  ?
87         s.router.Handle("WS", "/socket.io/", s.socketHandler)
88         s.router.Handle("WSS", "/socket.io/", s.socketHandler)
89         */
90
91         // Serve in the background
92         serveError := make(chan error, 1)
93         go func() {
94                 serveError <- http.ListenAndServe(":"+s.cfg.HTTPPort, s.router)
95         }()
96
97         // Wait for stop, restart or error signals
98         select {
99         case <-s.stop:
100                 // Shutting down permanently
101                 s.sessions.Stop()
102                 s.log.Infoln("shutting down (stop)")
103         case err = <-serveError:
104                 // Error due to listen/serve failure
105                 s.log.Errorln(err)
106         }
107
108         return nil
109 }
110
111 // Stop web server
112 func (s *ServerService) Stop() {
113         close(s.stop)
114 }
115
116 // Add details in Header
117 func (s *ServerService) middlewareXDSDetails() gin.HandlerFunc {
118         return func(c *gin.Context) {
119                 c.Header("XDS-Agent-Version", s.cfg.Version)
120                 c.Header("XDS-API-Version", s.cfg.APIVersion)
121                 c.Next()
122         }
123 }
124
125 // CORS middleware
126 func (s *ServerService) middlewareCORS() gin.HandlerFunc {
127         return func(c *gin.Context) {
128
129                 if c.Request.Method == "OPTIONS" {
130                         c.Header("Access-Control-Allow-Origin", "*")
131                         c.Header("Access-Control-Allow-Headers", "Content-Type")
132                         c.Header("Access-Control-Allow-Methods", "POST, DELETE, GET, PUT")
133                         c.Header("Content-Type", "application/json")
134                         c.Header("Access-Control-Max-Age", cookieMaxAge)
135                         c.AbortWithStatus(204)
136                         return
137                 }
138
139                 c.Next()
140         }
141 }
142
143 // socketHandler is the handler for the "main" websocket connection
144 func (s *ServerService) socketHandler(c *gin.Context) {
145
146         // Retrieve user session
147         sess := s.sessions.Get(c)
148         if sess == nil {
149                 c.JSON(500, gin.H{"error": "Cannot retrieve session"})
150                 return
151         }
152
153         s.sIOServer.On("connection", func(so socketio.Socket) {
154                 s.log.Debugf("WS Connected (SID=%v)", so.Id())
155                 s.sessions.UpdateIOSocket(sess.ID, &so)
156
157                 so.On("disconnection", func() {
158                         s.log.Debugf("WS disconnected (SID=%v)", so.Id())
159                         s.sessions.UpdateIOSocket(sess.ID, nil)
160                 })
161         })
162
163         s.sIOServer.On("error", func(so socketio.Socket, err error) {
164                 s.log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error())
165         })
166
167         s.sIOServer.ServeHTTP(c.Writer, c.Request)
168 }