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