github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/main.go (about) 1 // Copyright 2023 IAC. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "os" 22 "os/signal" 23 "sync" 24 "syscall" 25 "time" 26 27 "log" 28 "net/http" 29 "net/http/httputil" 30 "net/url" 31 32 "plugin" 33 "reflect" 34 35 "github.com/gin-gonic/gin" 36 "github.com/google/uuid" 37 configuration "github.com/mdaxf/iac/config" 38 dbconn "github.com/mdaxf/iac/databases" 39 mongodb "github.com/mdaxf/iac/documents" 40 41 "github.com/mdaxf/iac/com" 42 ) 43 44 var wg sync.WaitGroup 45 var router *gin.Engine 46 47 // main is the entry point of the program. 48 // It loads the configuration file, initializes the database connection, and starts the server. 49 // It also loads controllers dynamically and statically based on the configuration file. 50 // The server is started on the port specified in the configuration file. 51 // The server serves static files from the portal directory. 52 // The server also serves static files from the plugins directory. 53 54 func main() { 55 56 Initialized = false 57 startTime := time.Now() 58 defer func() { 59 elapsed := time.Since(startTime) 60 if Initialized { 61 ilog.PerformanceWithDuration("main", elapsed) 62 } 63 }() 64 /* 65 defer func() { 66 if r := recover(); r != nil { 67 if Initialized { 68 ilog.Error(fmt.Sprintf("Panic: %v", r)) 69 } else { 70 log.Fatalf("Panic: %v", r) 71 } 72 } 73 }() */ 74 // Load configuration from the file 75 76 config, err := configuration.LoadConfig() 77 if err != nil { 78 log.Fatalf("Failed to load configuration: %v", err) 79 // ilog.Error("Failed to load configuration: %v", err) 80 } 81 82 configuration.GlobalConfiguration, err = configuration.LoadGlobalConfig() 83 84 if err != nil { 85 log.Fatalf("Failed to load global configuration: %v", err) 86 // ilog.Error("Failed to load global configuration: %v", err) 87 } 88 com.NodeHeartBeats = make(map[string]interface{}) 89 90 com.IACNode = make(map[string]interface{}) 91 appid := uuid.New().String() 92 com.IACNode["Name"] = "iac" 93 com.IACNode["AppID"] = appid 94 com.IACNode["Type"] = "Application Server" 95 com.IACNode["Version"] = "1.0.0" 96 com.IACNode["Description"] = "IAC Application Server" 97 com.IACNode["Status"] = "Started" 98 com.IACNode["StartTime"] = time.Now() 99 data := make(map[string]interface{}) 100 data["Node"] = com.IACNode 101 data["Result"] = make(map[string]interface{}) 102 103 com.NodeHeartBeats[appid] = data 104 105 initialize() 106 107 if dbconn.DB != nil { 108 defer dbconn.DB.Close() 109 } else { 110 //log.Fatalf("Failed to connect to database") 111 ilog.Error("Failed to connect to database") 112 } 113 114 if mongodb.DocDBCon.MongoDBClient != nil { 115 defer mongodb.DocDBCon.MongoDBClient.Disconnect(context.Background()) 116 } else { 117 //log.Fatalf("Failed to connect to database") 118 ilog.Error("Failed to connect to document database") 119 } 120 // Initialize the Gin router 121 122 for _, dbclient := range com.MongoDBClients { 123 if dbclient != nil { 124 defer dbclient.Disconnect(context.Background()) 125 } else { 126 //log.Fatalf("Failed to connect to database") 127 ilog.Error("Failed to connect to the configured document database") 128 } 129 130 } 131 132 if com.IACMessageBusClient != nil { 133 defer com.IACMessageBusClient.Stop() 134 } else { 135 //log.Fatalf("Failed to connect to database") 136 ilog.Error("Failed to connect to the configured message bus") 137 } 138 portal := config.Portal 139 140 router = gin.Default() 141 // Load controllers dynamically based on the configuration file 142 plugincontrollers := make(map[string]interface{}) 143 for _, controllerConfig := range config.PluginControllers { 144 145 jsonString, err := json.Marshal(controllerConfig) 146 if err != nil { 147 148 ilog.Error(fmt.Sprintf("Error marshaling json: %v", err)) 149 return 150 } 151 fmt.Println(string(jsonString)) 152 controllerModule, err := loadpluginControllerModule(controllerConfig.Path) 153 if err != nil { 154 ilog.Error(fmt.Sprintf("Failed to load controller module %s: %v", controllerConfig.Path, err)) 155 } 156 plugincontrollers[controllerConfig.Path] = controllerModule 157 } 158 159 go func() { 160 // Create endpoints dynamically based on the configuration file 161 for _, controllerConfig := range config.PluginControllers { 162 for _, endpointConfig := range controllerConfig.Endpoints { 163 method := endpointConfig.Method 164 path := fmt.Sprintf("/%s%s", controllerConfig.Path, endpointConfig.Path) 165 handler := plugincontrollers[controllerConfig.Path].(map[string]interface{})[endpointConfig.Handler].(func(*gin.Context)) 166 router.Handle(method, path, handler) 167 } 168 } 169 }() 170 // Load controllers statically based on the configuration file 171 ilog.Info("Loading controllers") 172 173 go func() { 174 loadControllers(router, config.Controllers) 175 }() 176 // Start the portals 177 ilog.Info("Starting portals") 178 179 jsonString, err := json.Marshal(config.Portal) 180 if err != nil { 181 ilog.Error(fmt.Sprintf("Error marshaling json: %v", err)) 182 return 183 } 184 fmt.Println(string(jsonString)) 185 186 ilog.Info(fmt.Sprintf("Starting portal on port %d, page:%s, logon: %s", portal.Port, portal.Home, portal.Logon)) 187 188 clientconfig := make(map[string]interface{}) 189 clientconfig["signalrconfig"] = com.SingalRConfig 190 clientconfig["instance"] = com.Instance 191 clientconfig["instanceType"] = com.InstanceType 192 clientconfig["instanceName"] = com.InstanceName 193 194 router.GET("/app/config", func(c *gin.Context) { 195 c.JSON(http.StatusOK, clientconfig) 196 }) 197 198 router.GET("/app/debug", func(c *gin.Context) { 199 headers := c.Request.Header 200 useragent := c.Request.Header.Get("User-Agent") 201 ilog.Debug(fmt.Sprintf("User-Agent: %s, headers: %v", useragent, headers)) 202 debugInfo := map[string]interface{}{ 203 "Route": c.FullPath(), 204 "requestheader": headers, 205 "User-Agent": useragent, 206 "requestbody": c.Request.Body, 207 "responseheader": c.Writer.Header(), 208 "Method": c.Request.Method, 209 } 210 211 c.JSON(http.StatusOK, debugInfo) 212 }) 213 /* 214 router.Use(static.Serve("/portal", static.LocalFile("./portal", true))) 215 router.Use(static.Serve("/portal/scripts", static.LocalFile("./portal/scripts", true)))*/ 216 /* 217 218 */ 219 // Start the server 220 //go router.Run(fmt.Sprintf(":%d", config.Port)) 221 222 server := &http.Server{ 223 Addr: fmt.Sprintf(":%d", config.Port), // Set your desired port 224 Handler: router, 225 ReadTimeout: time.Duration(config.Timeout) * time.Millisecond, // Set read timeout 226 WriteTimeout: time.Duration(2*config.Timeout) * time.Millisecond, // Set write timeout 227 IdleTimeout: time.Duration(3*config.Timeout) * time.Millisecond, // Set idle timeout 228 } 229 230 go func() { 231 if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 232 ilog.Error(fmt.Sprintf("Failed to start server: %v", err)) 233 panic(err) 234 } 235 }() 236 237 ilog.Info(fmt.Sprintf("Started iac endpoint server on port %d, page:%s, logon: %s", portal.Port, portal.Home, portal.Logon)) 238 waitForTerminationSignal() 239 elapsed := time.Since(startTime) 240 ilog.PerformanceWithDuration("main.main", elapsed) 241 242 wg.Wait() 243 244 } 245 246 // loadpluginControllerModule loads a plugin controller module from the specified controllerPath. 247 // It returns the loaded module as an interface{} and an error if any. 248 // The module is loaded from the plugins directory. 249 func loadpluginControllerModule(controllerPath string) (interface{}, error) { 250 251 ilog.Info(fmt.Sprintf("Loading plugin controllers %s", controllerPath)) 252 253 modulePath := fmt.Sprintf("./plugins/%s/%s.so", controllerPath, controllerPath) 254 print(modulePath) 255 module, err := plugin.Open(modulePath) 256 if err != nil { 257 258 return nil, fmt.Errorf("Failed to open controller module %s: %v", modulePath, err) 259 } 260 sym, err := module.Lookup(controllerPath + "Controller") 261 if err != nil { 262 return nil, fmt.Errorf("Failed to lookup symbol in controller module %s: %v", modulePath, err) 263 } 264 return sym, nil 265 } 266 267 // getpluginHandlerFunc is a function that returns a gin.HandlerFunc for loading and executing a plugin handler function. 268 // It takes a module reflect.Value and the name of the handler function as parameters. 269 // The function loads the plugin handler function from the module using reflection and returns a gin.HandlerFunc. 270 // The returned handler function takes a *gin.Context as a parameter and executes the plugin handler function with the context. 271 // If the plugin handler function returns an error, the handler function aborts the request with a 500 Internal Server Error. 272 // If the plugin handler function returns a []byte, the handler function sends the data as a JSON response with a 200 OK status code. 273 // If the plugin handler function does not return an error or []byte, the handler function sends a 200 OK status code. 274 // If the plugin handler function does not exist, the function returns nil. 275 func getpluginHandlerFunc(module reflect.Value, name string) gin.HandlerFunc { 276 277 ilog.Info(fmt.Sprintf("Loading plugin handler function: %s", name)) 278 279 method := module.MethodByName(name) 280 if !method.IsValid() { 281 return nil 282 } 283 284 return func(c *gin.Context) { 285 in := make([]reflect.Value, 1) 286 in[0] = reflect.ValueOf(c) 287 out := method.Call(in) 288 if len(out) > 0 { 289 if err, ok := out[0].Interface().(error); ok { 290 c.AbortWithError(http.StatusInternalServerError, err) 291 return 292 } 293 if data, ok := out[0].Interface().([]byte); ok { 294 c.Data(http.StatusOK, "application/json", data) 295 return 296 } 297 } 298 c.Status(http.StatusOK) 299 } 300 } 301 302 // CORSMiddleware is a middleware function that adds Cross-Origin Resource Sharing (CORS) headers to the HTTP response. 303 // It allows requests from a specified origin and supports various HTTP methods. 304 // The allowOrigin parameter specifies the allowed origin for CORS requests. 305 // This middleware function also handles preflight requests by responding with appropriate headers. 306 307 func CORSMiddleware(allowOrigin string) gin.HandlerFunc { 308 return func(c *gin.Context) { 309 c.Header("Access-Control-Allow-Origin", allowOrigin) 310 c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") 311 // c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, Origin") 312 c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With") 313 314 // ilog.Debug(fmt.Sprintf("CORSMiddleware: %s", allowOrigin)) 315 // ilog.Debug(fmt.Sprintf("CORSMiddleware header: %s", c.Request.Header)) 316 if c.Request.Method == "OPTIONS" { 317 c.AbortWithStatus(204) 318 return 319 } 320 321 c.Next() 322 } 323 } 324 325 // GinMiddleware is a middleware function that sets the specified headers in the HTTP response. 326 // It takes a map of headers as input and returns a gin.HandlerFunc. 327 // The middleware sets the headers in the response using the values provided in the headers map. 328 // If the HTTP request method is OPTIONS, it aborts the request with a status code of 204 (No Content). 329 // After setting the headers, it calls the next handler in the chain. 330 // The next handler can be a controller function or another middleware function. 331 // The next handler can also be a gin.HandlerFunc. 332 func GinMiddleware(headers map[string]interface{}) gin.HandlerFunc { 333 return func(c *gin.Context) { 334 335 // ilog.Debug(fmt.Sprintf("GinMiddleware: %v", headers)) 336 // ilog.Debug(fmt.Sprintf("GinMiddleware header: %s", c.Request.Header)) 337 338 for key, value := range headers { 339 c.Header(key, value.(string)) 340 // c.Writer.Header().Set(key, value.(string)) 341 } 342 343 if c.Request.Method == http.MethodOptions { 344 c.AbortWithStatus(http.StatusNoContent) 345 return 346 } 347 c.Next() 348 } 349 } 350 351 // renderproxy is a function that renders a proxy configuration by creating routes in a gin.Engine instance. 352 // It takes a map of proxy configurations and a pointer to a gin.Engine as parameters. 353 // Each key-value pair in the proxy map represents a route path and its corresponding target URL. 354 // The function iterates over the proxy map and creates a route for each key-value pair in the gin.Engine instance. 355 // When a request matches a route, the function sets up a reverse proxy to forward the request to the target URL. 356 // The function also updates the request URL path based on the "path" parameter in the route. 357 // Note that the ServeHTTP method of the reverse proxy is non-blocking and uses a goroutine under the hood. 358 359 func renderproxy(proxy map[string]interface{}, router *gin.Engine) { 360 ilog.Debug(fmt.Sprintf("renderproxy: %v", proxy)) 361 362 for key, value := range proxy { 363 ilog.Debug(fmt.Sprintf("renderproxy key: %s, value: %s", key, value)) 364 365 nextURL, _ := url.Parse((value).(string)) 366 ilog.Debug(fmt.Sprintf("renderproxy nextURL: %v", nextURL)) 367 368 router.Any(fmt.Sprintf("/%s/*path", key), func(c *gin.Context) { 369 370 ilog.Debug(fmt.Sprintf("renderproxy path: %s, target: %s", c.Request.URL.Path, nextURL)) 371 372 proxy := httputil.NewSingleHostReverseProxy(nextURL) 373 374 // Update the headers to allow for SSL redirection 375 // req := c.Request 376 // req.URL.Host = nextURL.Host 377 // req.URL.Scheme = nextURL.Scheme 378 //req.Header["X-Forwarded-Host"] = req.Header["Host"] 379 380 c.Request.URL.Path = c.Param("path") 381 382 ilog.Debug(fmt.Sprintf("request: %v", c.Request)) 383 // Note that ServeHttp is non blocking and uses a go routine under the hood 384 proxy.ServeHTTP(c.Writer, c.Request) 385 386 }) 387 388 } 389 } 390 391 func waitForTerminationSignal() { 392 c := make(chan os.Signal, 1) 393 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 394 <-c 395 fmt.Println("\nShutting down...") 396 397 time.Sleep(2 * time.Second) // Add any cleanup or graceful shutdown logic here 398 os.Exit(0) 399 }